我正在接收一个无序的 Int32 值流,并且需要跟踪我收到的不同值的计数。
我的想法是将 Int32 值添加到HashSet<Int32>
. 根据 HashSet 的行为,根本不会添加重复条目。
我是否正确理解集合成员资格基于 GetHashCode() 并且 Int32 的哈希码是数字本身?
有没有一种方法可以提高 CPU 或内存效率?
更新
数据流相当大。简单地使用 Linq 迭代流以获得不同的计数不是我想要的,因为这将涉及第二次迭代流。
我正在接收一个无序的 Int32 值流,并且需要跟踪我收到的不同值的计数。
我的想法是将 Int32 值添加到HashSet<Int32>
. 根据 HashSet 的行为,根本不会添加重复条目。
我是否正确理解集合成员资格基于 GetHashCode() 并且 Int32 的哈希码是数字本身?
有没有一种方法可以提高 CPU 或内存效率?
更新
数据流相当大。简单地使用 Linq 迭代流以获得不同的计数不是我想要的,因为这将涉及第二次迭代流。
假设你有某种IEnumerable<int>
你可以做以下事情:
int count = stream.Distinct().Count();
我是否正确理解集合成员资格基于 GetHashCode()
不完全的。a 中的成员资格HashSet
基于GetHashCode
和 等式检查的组合。通常,两个对象可以具有相同的哈希码但不相等。虽然int
那不可能发生。
并且 Int32 的哈希码是数字本身?
对,那是正确的。
有没有一种方法可以提高 CPU 或内存效率?
如果您知道您的整数将在一个小范围内,您可以使用位图有效地存储您所看到的。例如,如果您有 1,000,000 的范围,您可以将您看到的整数存储在 1,000,000 位中。在索引 n 处设置为 1 表示您已经看到整数 n。下面是一些示例代码,展示了一种实现方式:
void Main()
{
int max = 1000000;
IEnumerable<int> stream = GetStream(max);
int count = DistinctCount(stream, max);
int count2 = stream.Distinct().Count();
Debug.Assert(count == count2);
}
int DistinctCount(IEnumerable<int> stream, int max)
{
int[] seen = new int[max / 32];
foreach (int x in stream)
{
seen[x / 32] |= 1 << (x % 32);
}
int count = 0;
foreach (uint s in seen)
{
uint t = s;
while (t > 0)
{
if (t % 2 == 1) { count++; }
t /= 2;
}
}
return count;
}
IEnumerable<int> GetStream(int max)
{
List<int> stream = new List<int>();
Random random = new Random();
for (int i = 0; i < 2000000; ++i)
{
stream.Add(random.Next(max));
}
return stream;
}
一个想法是,如果您有非常大的数据流(数百万到数十亿),则可以使用Bloom 过滤器。这将使您能够在流式传输数据时确定近似计数,如果您需要精确计数,您可以离线处理它。
一个合理的 C# 实现在这里:http ://bloomfilter.codeplex.com/
不太了解您的领域,但是有一些算法可以使用非常小的内存和处理来计算大集合的基数。
我在我的一个项目中使用 HyperLogLog。我用它来计算数百万个不同的项目,使用低至 8KB 的内存,误差为 1%。
这是一篇描述它的论文:
http://algo.inria.fr/flajolet/Publications/FlFuGaMe07.pdf
我已经用 Java 和 Python 实现了它。Python版本是开源的,算法相当小。看看这个:
https://github.com/juanplopes/hyperloglog.py/blob/master/hyperloglog.py
我假设您以块的形式接收值,一次一个整数到一堆整数。
鉴于此,最简单的可能是最好的,我也会使用哈希。但是我看不到如何使用 HashSet。如果你想要不同值的计数,你只会得到找到的值
Dictionary<int,int> _countHash = new Dictionary<int,int>();
void moreIntsArrived(IEnumerable<int> bunch)
{
foreach(var value in bunch)
{
if (_countHash.ContainsKey(value))
{
_countHash[value] += _countHash[value];
}
else
{
_countHash[value] = 0;
}
}
}
然而,按照汉斯尔曼先生的建议,测量它
如果您的流足够大以停止获取新的唯一值,则在执行 ContainsKey 检查和在未找到密钥时仅处理异常之间可能存在权衡
void moreIntsArrived(IEnumerable<int> bunch)
{
foreach(var value in bunch)
{
try
{
int c = _countHash[value];
_countHash[value] = c + 1;
}
catch(KeyNotFoundException)
{
_countHash[value] = 0;
}
}
}
然后还有 Dictionary::TryGetValue() 方法,但这取决于它内部的作用:-)使用源
我很欣赏其他答案,但发现使用 a 的原始方法HashSet<T>
最适合我的情况。
重新迭代流以获得不同的计数效率不高。