/// <summary>
/// returns the # of unique characters in a string as a rough
/// measurement of entropy
/// </summary>
public static int Entropy(this string s)
{
var d = new Dictionary<char, bool>();
foreach (char c in s)
if (!d.ContainsKey(c)) d.Add(c, true);
return d.Count();
}
是否有更好/更优雅/更准确的方法来计算字符串的熵?
效率也很好,尽管我们从来没有在大型字符串上调用它,所以它并不是一个大问题。
#1 楼
string name = "lltt";
int uniqueCharacterCount = name.Distinct().Count();
将返回2
评论
\ $ \ begingroup \ $
鉴于Distinct可能使用了HashSet,我认为这是最简洁明了的实现。
\ $ \ endgroup \ $
– ICR
2011-2-20在21:43
\ $ \ begingroup \ $
这是重点。目标是计算字符串的熵,而不是找到一种计算字符的理想方法。计数字符是一种尝试(在OP中):更优雅地计数字符在计算熵方面并不明显更好。
\ $ \ endgroup \ $
– Y牛
15年1月13日在14:37
#2 楼
public static int Entropy(this string s)
{
HashSet<char> chars = new HashSet<char>(s);
return chars.Count;
}
评论
\ $ \ begingroup \ $
我总是惊讶于仅使用正确的数据结构如何获得简单的算法,或者在这种情况下完全消失。我最喜欢的示例是计算离散值的直方图,实际上它只是新的MultiSet(sourceData)。
\ $ \ endgroup \ $
–Jörg W Mittag
2011年2月21日在17:56
\ $ \ begingroup \ $
代表相同字形或不能用单个字符表示的字形的不同字符呢?
\ $ \ endgroup \ $
– dfhwze
19年5月19日在12:30
\ $ \ begingroup \ $
@dfhwze s参数是令牌流。在此答案提供的实现中,每个字符已经是一个标记,因此无需对其进行预处理;在您的情况下,您需要先“标记化”您的输入。 (并且该参数将不是字符串,更像是IEnumerable
\ $ \ endgroup \ $
–加百列
19年5月20日在17:37
#3 楼
我还基于Shannon熵提出了这一点。在信息论中,熵是对与随机变量相关的不确定性的度量。在这种情况下,该术语通常是指香农熵,它以通常以比特为单位量化消息中包含的信息的期望值。
与简单地计算字母相比,它是一种更“形式化”的熵计算方法: >
/// <summary>
/// returns bits of entropy represented in a given string, per
/// http://en.wikipedia.org/wiki/Entropy_(information_theory)
/// </summary>
public static double ShannonEntropy(string s)
{
var map = new Dictionary<char, int>();
foreach (char c in s)
{
if (!map.ContainsKey(c))
map.Add(c, 1);
else
map[c] += 1;
}
double result = 0.0;
int len = s.Length;
foreach (var item in map)
{
var frequency = (double)item.Value / len;
result -= frequency * (Math.Log(frequency) / Math.Log(2));
}
return result;
}
评论
\ $ \ begingroup \ $
这里有一些微妙之处。您所计算的不是字符串的熵,而是字符串中字符的熵。您应该考虑是否为字符串终止符包括一个频率为1的伪字符(一元数具有某些内容),以及是否要乘以字符串的长度。
\ $ \ endgroup \ $
– Peter Taylor
2011-2-22在16:42
\ $ \ begingroup \ $
对不起,刚刚注意到这一点,这等效于我稍后发布的代码。杰夫,这绝对是一个更好的解决方案。我认为,对这个问题最不切实际的答案就是要点。
\ $ \ endgroup \ $
– BlueRaja-Danny Pflughoeft
2011-2-24在2:03
\ $ \ begingroup \ $
在这里,我们看到了频率数据结构很有用的另一种情况。 var map = new FrequencyTable
\ $ \ endgroup \ $
– ICR
2011-2-27 14:14
\ $ \ begingroup \ $
如果您不使用键,那么进行foreach(map.Values中的var值)是否更清晰?
\ $ \ endgroup \ $
– ICR
2011-2-27在14:15
\ $ \ begingroup \ $
并不是说这会是一件大事,但我会将Math.Log(2)计算从循环中移出。
\ $ \ endgroup \ $
– Jesse C. Slicer
2011年11月21日在19:15
#4 楼
从理论上讲,您只能从给定模型的角度衡量熵。例如,PI位数分布均匀,但实际上熵高吗?完全没有,因为可以将无限序列压缩到一个计算所有数字的小程序中。但是,我想向您建议一些可以构成一个非常简单但实用的模型的东西。比较相同的字符在某种程度上就是这样,但是一般来说是建立一个频率表并检查分布。给定一个长度为N的字符串,我应该期望多少个A字符平均来说,给定我的模型(可以是英语分布,也可以是自然分布)?
那“ abcdefg”呢?这里没有重复,但这根本不是随机的。
所以这里想要的是也取一阶导数,并检查一阶导数的分布。从第一个字符减去第二个字符,从第二个字符减去第三位字符,因此在我们的示例字符串中,它变成:“ abcdefg” => 1,1,1,1,1,1,1
现在aobut“ ababab” ...?由于导数为1,-1,1,-1,...,这似乎具有更好的分布。因此,您实际上想要的是取绝对值。
长字符串
如果字符串足够长,则毫无脑子的方法是:尝试对其进行压缩,然后计算压缩输出与输入之间的比率。
评论
\ $ \ begingroup \ $
棘手的... asdfghjkl;也是很烂的弦
\ $ \ endgroup \ $
– Sam Saffron
2011-2-20在22:17
\ $ \ begingroup \ $
@Sam:一阶导数测试实际上会将您的字符串标记为低熵。当然,这里您要更改模型,也就是说,根据键盘上字符的位置,这也是一个好的模型。当然,您也可以将其添加到混合中。
\ $ \ endgroup \ $
– Antirez
2011-2-20在22:19
\ $ \ begingroup \ $
非常有趣的方法。请记住,我们的熵测试主要针对真正短的字符串。这是结合其他一些算法使用的经典示例(stackoverflow.com/review/…)
\ $ \ endgroup \ $
– Sam Saffron
2011-02-20 22:24
\ $ \ begingroup \ $
您不能通过查看字符串来判断它是否是随机产生的(abc)。如果从均等分布中选择3个字符,则abc,aaa,zzz,zur和apk的机会均等。当然,在您的示例中,您是有意而非随机地选择了abcdef,但这并不能证明随机生成器不可能形成它。
\ $ \ endgroup \ $
–用户未知
2011-2-21在11:59
#5 楼
实际计算熵如何?同样,尚不清楚字符级熵是否会有所帮助,但这是可行的。它使用我的母语C ++,但是可以肯定的是,您可以使用Array而不是std :: vector将其转换为Java。float CharacterEntropy(const char *str) {
std::vector<unsigned> counts(256);
for (const char *i = str; *i; ++i)
++counts[static_cast<unsigned char>(*i)];
unsigned int total = 0;
for (unsigned i = 0; i < 256; ++i)
total += counts[i];
float total_float = static_cast<float>(total);
float ret = 0.0;
for (unsigned i = 0; i < 256; ++i) {
float p = static_cast<float>(counts[i]) / total_float;
ret -= p * logf(p);
}
return p * M_LN2;
}
评论
\ $ \ begingroup \ $
注意0 * log(0)-> 0
\ $ \ endgroup \ $
–尼尔G
2011-2-21在0:25
\ $ \ begingroup \ $
不是Java-我猜是C#。在Java中,它是“字符串”而不是“字符串”。 :)
\ $ \ endgroup \ $
–用户未知
2011-2-21在13:18
#6 楼
与zngu的答案类似,我认为比计算字符数更好的是计算消息的字符熵:public double CalculateEntropy(string entropyString)
{
Dictionary<char, int> characterCounts = new Dictionary<char, int>();
foreach(char c in entropyString.ToLower())
{
if(c == ' ') continue;
int currentCount;
characterCounts.TryGetValue(c, out currentCount);
characterCounts[c] = currentCount + 1;
}
IEnumerable<double> characterEntropies =
from c in characterCounts.Keys
let frequency = (double)characterCounts[c]/entropyString.Length
select -1*frequency*Math.Log(frequency);
return characterEntropies.Sum();
}
以下是一些测试:
private void CalculateEntropyTest(object sender, EventArgs e)
{
string[] testStrings = {
"Hello world!",
"This is a typical english sentence containing all the letters of the english language - The quick brown fox jumped over the lazy dogs",
String.Join("", "This is a typical english sentence containing all the letters of the english language - The quick brown fox jumped over the lazy dogs".ToCharArray().OrderBy(o => o).Select(o => o.ToString()).ToArray()),
"Won't this work too?\nstring name = \"lltt\";\nint uniqueCharacterCount = name.Distinct().Count();\nwill return 2",
"Pull the entropy finding source from any compression algotithm, i.e. Huffman",
"float CharacterEntropy(const char *str) {\n std::vector<unsigned> counts(256);\n for (const char *i = str; *i; ++i)\n ++counts[static_cast<unsigned char>(*i)];\n unsigned int total = 0;\n for (unsigned i = 0; i < 256; ++i)\n total += counts[i];\n float total_float = static_cast<float>(total);\n float ret = 0.0;\n for (unsigned i = 0; i < 256; ++i) {\n float p = static_cast<float>(counts[i]) / total_float;\n ret -= p * logf(p);\n }\n return p * M_LN2;\n}",
"~~~~~~No.~~~~~~",
"asdasdasdasdasdasd",
"abcdefghijklmnopqrstuvwxyz",
"Fuuuuuuu-------",
};
foreach(string str in testStrings)
{
Console.WriteLine("{0}\nEntropy: {1:0.000}\n", str, CalculateEntropy(str));
}
}
结果:
你好,世界!
这是一个典型的英语句子,包含所有英语字母-敏捷的棕色狐狸跳过了懒狗
熵:2.593
-TTaaaaaaabccccddeeeeeeeeeeeeeeeeeeffgggggghhhhhhhiiiiiiiijk
lllllllmnnnnnnnnnooooooppqrrrssys
llbrlllmnnnnnnnnooooooppqrrrssys
llt />也不行吗?
字符串名称=“ lltt”;
int uniqueCharacterCount = name.Distinct()。Count();
将返回2
熵: 2.838
从任何压缩算法中提取熵查找源,即霍夫曼
熵:2.641
float CharacterEntropy(const char * str){
std :: vector counts(256);
(const char * i = s tr; *一世; ++ i)
++ counts [static_cast(* i)];
无符号整数总计= 0;
for(无符号i = 0; i <256; ++ i)
total + = counts [i];
float total_float = static_cast(total);
float ret = 0.0;
for(无符号i = 0; i <256; ++ i) {
float p = static_cast(counts [i])/ total_float;
ret-= p * logf(p);
}
return p * M_LN2;
}
熵:2.866
~~~~~~ No. ~~~~~~
熵:0.720
asdasdasdasdasdasd
熵:1.099
abcdefghijklmnopqrstuvwxyz
熵:3.258
Fuuuuuuu -------
熵:0.892
实际上,我认为最好进行一些频率分析,但是我对代码中使用的符号的频率一无所知。确定它的最佳位置是stackoverflow数据转储-在2年内完成下载后,我将不得不与您联系。
#7 楼
我不明白傻瓜的意思。您从未出现过将其设置为false的情况,因此我们可以改用
List<T>
。此方法应等效且更快:/// <summary>
/// returns the # of unique characters in a string as a rough
/// measurement of entropy
/// </summary>
public static int Entropy(this string s)
{
var hs = new HashSet<char>();
foreach (char c in s)
hs.Add(c);
return hs.Count();
}
评论
\ $ \ begingroup \ $
尽管我同意使用HashSet比使用Dictionary更清楚,而只是忽略其值,但我看不出有什么理由会更快。
\ $ \ endgroup \ $
–sepp2k
2011-2-20在21:45
#8 楼
为什么不将给定字符串中的唯一字符数除以该字符串中的字符总数。这样可以更准确地度量熵。例如,按照您的公式,一个5个字符的字符串的熵为3应该很好,但是一个8个字符的字符串的熵为3是可以的。很穷。但是,您的公式无法区分两个结果。鉴于上述公式可以提供更准确的度量。
#9 楼
我认为antirez提出熵方法需要模型是正确的。因此,假设我们在说英语,然后检查字符串的字符分布以及它与“平均值”对齐的紧密程度,很可能表明该文本大部分为英语。但这是您要实现的目标吗?可能有很多东西是代码或伪代码。压缩是个好主意,但这会为随机文本提供最高的熵-高熵不好吗?较低的熵表示可能有很多重复,也许是冗长,但是人们可以用轻率的单词写出很长的句子,而传递的信息很少(例如此评论)。#10 楼
我只是一起鞭打了这个算法,所以我不知道这有多好。我担心如果在很长的字符串上使用它会导致溢出异常。此算法的关键概念:
第一次遇到字符时,则将最大值添加到未归一化的熵总计中。 “最大值”是字符串的长度。
如果再次遇到一个字符,则我们计算该事件与最后一次事件之间的位置数,然后减去该字符出现的总次数在字符串中。然后,我们将该值添加到未归一化的熵总计中。
public static int Entropy(this string s)
{
int entropy = 0;
var mapOfIndexByChar = new Dictionary<char, CharEntropyInfo>();
int index = 0;
foreach (char c in s)
{
CharEntropyInfo charEntropyInfo;
if (mapOfIndexByChar.TryGetValue(c, out charEntropyInfo))
{
// If this character has occurred previously, then only add the number of characters from
// the last occurrence to this occurrence, and subtract the number of previous occurrences.
// Many repeated characters can actually result in the entropy total being negative.
entropy += ((index - charEntropyInfo.LastIndex) - charEntropyInfo.Occurrences);
// update the last index and number of occurrences of this character
mapOfIndexByChar[c] = new CharEntropyInfo(index, charEntropyInfo.Occurrences + 1);
}
else
{
// each newly found character adds the maximum possible value to the entropy total
entropy += s.Length;
// record the first index of this character
mapOfIndexByChar.Add(c, new CharEntropyInfo(index, 1));
}
}
// divide the entropy total by the length of the string to "normalize" the result
return entropy / s.Length;
}
struct CharEntropyInfo
{
int _LastIndex;
int _Occurrences;
public int LastIndex
{
get { return _LastIndex; }
}
public int Occurrences
{
get { return _Occurrences; }
}
public CharEntropyInfo(int lastIndex, int occurrences)
{
_LastIndex = lastIndex;
_Occurrences = occurrences;
}
}
快速测试:
var inputs = new[]{
"Hi there!",
"Hi there, bob!",
"ababababababababababababab",
@"We're calculating entropy of a string a few places in Stack Overflow as a signifier of low quality.
I whipped up this simple method which counts unique characters in a string, but it is quite literally the first thing that popped into my head. It's the ""dumbest thing that works""."
};
foreach (string s in inputs)
{
System.Console.WriteLine("{1}: \"{0}\"", s, s.Entropy());
}
结果熵值:
7:“你好!”
10:“你好,鲍勃!”
25:“我们正在计算熵字符串...“
#11 楼
您可能可以将其扩展为二元语法和三元语法,以得到诸如“ sdsdsdsdsdsdsdsdsdsdsdsd”之类的内容(尽管您也可以理解)。垃圾邮件过滤器之类的贝叶斯方法是否适合您想要实现的目标?评论
\ $ \ begingroup \ $
一阶导数也将很容易抓住这一点
\ $ \ endgroup \ $
– Antirez
2011-2-20在22:04
#12 楼
我将假定这是英语(因为这就是我们所做的一切)。保留HashSet<string>
停用词(英语中不传达含义的最常见单词),将字符串标记为单词,并计算不是停用词的单词数量,会更好吗? >#13 楼
我会尝试对每个字符进行计数,并验证它与英语字母的正常频率大致匹配。 (在足够大的输入下)比计算字母的数量可能更精确。如果按字母的出现次数对字母进行排序,则从统计学上讲,您应该得到类似
ETAONRISHDLFCMUGYPWBVKXJQZ
的东西。您可以使用此字符串和字母之间的编辑距离(按外观顺序排序)来粗略地度量熵。 (如果这样做,我建议您从计数中排除代码片段...)评论
\ $ \ begingroup \ $
作为第二次减少唯一字符的原始计数,我建议计算每个唯一字符的计数方差。这样,您就不会偏向英语和代码,而只要求某些字符的出现频率比其他字符少。
\ $ \ endgroup \ $
– David Harkness
2011-02-20 23:29
#14 楼
我已经看到许多答案,建议计算不同字符的数量。但是请注意,这仅适用于16位字符!C#中的字符是UTF-16代码单元。扩展的unicode字符存储在多个C#字符中。 CharUnicodeInfo.GetUnicodeCategory允许我们检测C#字符表示真实字符还是它是扩展的unicode字符或组合字符(UnicodeCategory.Surrogate)的一部分。
测试(伪)熵:
public static void Main()
{
var value = "\U00020B20";
// yields 2, even though \U00020B20 represents a single unicode-character '𠬠'
var entropyTest = value.Distinct().Count();
}
为了计算字符(不是C#字符),我们需要增强算法。我正在使用一个名为Grapheme的类来完成技巧。此类可以检测扩展的Unicode字符和变音符号。
测试熵:
public static void Main()
{
var grapheme = Grapheme.Parse("\U00020B20");
// yields 1, as \U00020B20 represents a single unicode-character '𠬠'.
var entropyTest = grapheme.Select(x => x.Glyph).Distinct().Count();
// yields 2, as \U00020B20 is stored in 2 C# characters.
var codeUnits = grapheme.Single().CodeUnits.Length;
}
最后的注释:
测试字符串的熵不是没有上下文的。根据所使用的字体,某些字符或组合字符会产生相同的字形。因此,熵只能在字体的上下文中计算。 Grapheme类没有考虑到这一点,因为不同的字体会呈现不同的熵。据说Grapheme类是上下文无关的。
(A)两个不同的字符可能具有完全相同的字形(homoglyph)
(B)组合的字符可能具有与另一个字符相同的标志符号
示例:
A:\ u0061和\ u0430都以某些字体表示字母“ a”
B: Å既是字符\ u00C5,又是带有点字符的组合字符
A
附录:字形
public class Grapheme
{
private char[] _codeUnits;
private Grapheme[] _diacritics;
private string _glyph;
public Grapheme(string glyph) {
Guard.NotNull(glyph, "glyph");
_glyph = StringInfo.GetNextTextElement(glyph);
Guard.Condition(_glyph.Length != glyph.Length, "glyph", "Invalid glyph specified");
var codeUnits = new List<char>();
var diacritics = new List<Grapheme>();
var buffer = _glyph;
if (buffer.Length > 0) {
var cu0 = CharUnicodeInfo.GetUnicodeCategory(buffer[0]);
switch (cu0) {
case UnicodeCategory.Surrogate:
codeUnits.AddRange(buffer.Take(2));
buffer = buffer.Substring(2);
break;
default:
codeUnits.Add(buffer[0]);
buffer = buffer.Substring(1);
break;
}
diacritics.AddRange(Parse(buffer));
}
_codeUnits = codeUnits.ToArray();
_diacritics = diacritics.ToArray();
if (_codeUnits.Length == 2) {
Guard.Condition(!char.IsSurrogatePair(new string(_codeUnits), 0),
"glyph", "Invalid surrogate pair specified");
}
}
public static Grapheme[] Parse(string value) {
Guard.NotNull(value, "value");
return StringInfo.ParseCombiningCharacters(value).Select(i
=> new Grapheme(StringInfo.GetNextTextElement(value, i))).ToArray();
}
public static int[] ParseIndices(string value) {
Guard.NotNull(value, "value");
return StringInfo.ParseCombiningCharacters(value).ToArray();
}
public static Grapheme ParseNext(string value, int index) {
return new Grapheme(StringInfo.GetNextTextElement(value, index));
}
public static Grapheme ParseNext(string value) {
return ParseNext(value, 0);
}
public char[] CodeUnits {
get {
return _codeUnits;
}
}
public Grapheme[] Diacritics {
get {
return _diacritics;
}
}
public string Glyph {
get {
return _glyph;
}
}
public Grapheme[] Flatten() {
return new[] { this }.Concat(_diacritics.SelectMany(x => x.Flatten())).ToArray();
}
public Grapheme Normalize() {
return new Grapheme(_glyph.Normalize());
}
public Grapheme Normalize(NormalizationForm form) {
return new Grapheme(_glyph.Normalize(form));
}
public override bool Equals(object obj) {
if (obj is Grapheme) {
return string.Equals(((Grapheme)obj)._glyph, _glyph);
}
return false;
}
public override int GetHashCode() {
return _glyph.GetHashCode();
}
public override string ToString() {
return _glyph;
}
}
评论
en.wikipedia.org/wiki/Entropy_(information_theory)您的问题使我想起了我20年前读过的Dobbs博士的文章。幸运的是,它可以在线使用。它包括简单的.c代码drdobbs.com/security/184408492
杰夫,请告诉我,您不是在尝试使用此代码,以使其更难以发布“是”之类的简短注释。通过阻止用户添加点或破折号...
我不知道您想使用它做什么,但是估计数据熵的一种方法是压缩它,并取结果的长度。数据的长度是熵的上限。压缩机程序越好-估算值就越好。
从技术上讲,已知字符串没有熵。产生弦的过程具有熵。您正在做的是假设一个进程空间,估计哪个进程产生了此字符串,并给出了该进程的熵。