对于每一天,我们有大约50,000个数据结构实例(最终可能会变得更大),其中包含以下内容:
DateTime AsOfDate;
int key;
List<int> values; // list of distinct integers
这可能不相关,但列表values
是具有属性的不同整数的列表,对于给定值AsOfDate
,values
与key
的所有值的并集{ {1}}生成一个不同整数的列表。也就是说,同一天在两个不同的values
列表中不会出现整数。
列表通常包含很少的元素(1到5之间),但有时只有50个元素。
鉴于相邻日期,我们正在尝试查找这两个日期key
的值不同的这些对象的实例,但列表values
包含相同的整数。
我们正在使用以下算法。通过
将列表values
转换为字符串
string signature = String.Join("|", values.OrderBy(n => n).ToArray());
然后将signature
哈希到一个整数,排序生成的哈希码列表(每天一个列表),遍历两个列表查找匹配项,然后检查相关键是否不同。 (还要检查相关列表以确保我们没有哈希冲突。)
有更好的方法吗?
答案 0 :(得分:5)
你可能只是散列列表本身,而不是通过String。
除此之外,我认为你的算法几乎是最优的。假设没有哈希冲突,则为O(n log n + m log m),其中n和m是您比较两天中每一天的条目数。 (排序是瓶颈。)
如果您使用插入哈希的存储桶阵列(基本上是哈希表),则可以在O(n + m)中执行此操作。您可以在O(max(n,m))中比较两个存储桶阵列假设长度取决于条目数(以获得合理的负载系数)。
应该可以让库通过使用HashSet.IntersectWith()并编写合适的比较函数来为您执行此操作(看起来您正在使用.NET)。
你不能比O(n + m)做得更好,因为每个条目至少需要访问一次。
编辑:误读,修复。
答案 1 :(得分:4)
除了其他答案之外,您还可以通过在每个List的所有元素中创建一个简单构造的XOR的低成本哈希来加快处理速度。
你不必订购你的清单,而你得到的只是一个int
,它比字符串更容易,更快速地存储。
然后,您只需使用生成的XORed编号作为Hashtable的键,并在插入之前检查密钥是否存在。 如果已有现有密钥,则只对相应的列表进行排序并进行比较。
如果找到匹配项,您仍然需要比较它们,因为使用简单的XOR可能会发生一些碰撞 我认为结果会比重新排序数组并将它们转换为字符串要快得多并且内存占用更少。
如果您拥有自己的List<>
实现,那么您可以在其中构建XOR键的生成,以便在列表上的每个操作中重新计算。
这样可以更快地检查重复列表。
<强>代码强>
以下是实施此操作的第一次尝试。
Dictionary<int, List<List<int>>> checkHash = new Dictionary<int, List<List<int>>>();
public bool CheckDuplicate(List<int> theList) {
bool isIdentical = false;
int xorkey = 0;
foreach (int v in theList) xorkey ^= v;
List<List<int>> existingLists;
checkHash.TryGetValue(xorkey, out existingLists);
if (existingLists != null) {
// Already in the dictionary. Check each stored list
foreach (List<int> li in existingLists) {
isIdentical = (theList.Count == li.Count);
if (isIdentical) {
// Check all elements
foreach (int v in theList) {
if (!li.Contains(v)) {
isIdentical = false;
break;
}
}
}
if (isIdentical) break;
}
}
if (existingLists == null || !isIdentical) {
// never seen this before, add it
List<List<int>> newList = new List<List<int>>();
newList.Add(theList);
checkHash.Add(xorkey, newList);
}
return isIdentical;
}
不是第一眼看到的最优雅或最容易阅读,它更像是'hackey',我甚至不确定它是否比Guffa更优雅的版本表现更好。
它的作用是通过在词典中存储List<int>
的列表来处理XOR键中的冲突。
如果找到重复的密钥,我们会遍历每个先前存储的List,直到找到不匹配为止。
关于代码的好处在于它应该可能在大多数情况下尽可能快,并且在发生冲突时仍然比编译字符串更快。
答案 2 :(得分:2)
为List实现IEqualityComparer,然后您可以将该列表用作字典中的键。
如果对列表进行排序,则可以这么简单:
IntListEqualityComparer : IEqualityComparer<List<int>> {
public int GetHashCode(List<int> list) {
int code = 0;
foreach (int value in list) code ^=value;
return code;
}
public bool Equals(List<int> list1, List<int> list2) {
if (list1.Count != list2.Coount) return false;
for (int i = 0; i < list1.Count; i++) {
if (list1[i] != list2[i]) return false;
}
return true;
}
}
现在您可以创建一个使用IEqualityComparer的字典:
Dictionary<List<int>, YourClass> day1 = new Dictionary<List<int>, YourClass>(new IntListEqualityComparer());
添加词典中第一天的所有项目,然后循环第二天的项目并检查词典中是否存在该键。由于IEqualityComprarer都处理哈希码和比较,因此不会得到任何错误匹配。
您可能想要测试一些计算哈希码的不同方法。示例中的一个有效,但可能无法为您的特定数据提供最佳效率。对字典的哈希代码起作用的唯一要求是,相同的列表总是获得相同的哈希码,因此您可以做任何想要计算的内容。目标是为字典中的键获取尽可能多的不同哈希码,以便每个桶中的项目尽可能少(具有相同的哈希码)。
答案 3 :(得分:0)
订购是否重要?即第1天的[1,2]和第2天的[2,1],它们是否相等? 如果它们是,那么散列可能不会那么好。您可以使用排序的数组/向量来帮助进行比较。
还有什么样的按键?它是否有明确的范围(例如0-63)?您可能能够将它们连接成大整数(可能需要超过64位的精度)和散列,而不是转换为字符串,因为这可能需要一段时间。
答案 4 :(得分:0)
将它放在SQL数据库中可能是值得的。如果您不想拥有完整的DBMS,可以使用sqlite。
这将使唯一性检查和联合以及这些类型的操作非常简单,并且非常有效。如果再次需要,它还可以让您轻松存储信息。
答案 5 :(得分:0)
您是否会考虑总结值列表以获取一个整数,该整数可以用作预先检查不同列表是否包含相同的值集合?
虽然会有更多的碰撞(相同的总和并不一定意味着同一组值),但我认为它可以首先减少大部分所需的比较集。