【问题标题】:Most Efficient way to test large number of strings against a large List<string>针对大型 List<string> 测试大量字符串的最有效方法
【发布时间】:2011-07-11 15:50:56
【问题描述】:

我查看了许多其他类似的问题,但给出的方法对于我想要完成的任务来说似乎太慢了,或者正在测试部分匹配,我不需要并且应该更慢。

我有两个用字符串填充的大文件,我需要检查一个列表中的每个字符串,看看它是否与第二个列表中的任何字符串匹配。我不需要检查部分匹配,并且所有内容都应该正确转义。

第二个列表(要删除的字符串)包含 160,000 个字符串。我已将其加载到 List&lt;String&gt; 中,然后读取较大文件的每一行并使用 List&lt;String&gt;.Any(line.contains) 对其进行测试。

即使只有第一个列表的一小部分(40k 字符串),这也需要很长时间,在我快速开发的计算机上可能超过 20 分钟。

这是我的问题

当不需要部分匹配时,是否有更多/什么是最有效的方法来单独比较一个大字符串列表与另一个更大的字符串列表。

【问题讨论】:

  • 听起来像是在做数据库的工作?
  • 不,它只是一个用于清理某些 CSV 的简单应用程序。它不应该花这么长时间来制作,也不会经常使用。

标签: c# string linq performance


【解决方案1】:

不要使用List&lt;string&gt;,而是使用HashSet&lt;string&gt;。它有 O(1) 查找而不是 O(n) 像列表。如果您进行此更改,您应该会看到数量级的加速。

与 LINQ 的 .Any() 扩展方法相比,使用 HashSet.Contains() 也可能会给您带来更好的性能。

【讨论】:

  • 哇,令人难以置信的进步。 40k 字符串文件也需要 20-30 分钟来比较。测试时我不小心使用了更大的近 100 万个字符串列表,大约花了 10 秒。另外,我只需要更改两行即可使其正常工作。
  • “我不小心把整个列表都写了”。
  • “性能稍好”是指O(1) 还是O(N)?因为那时“稍微”不是正确的术语。 :)
  • 这就是我所说的反射技巧(as 是)。至于复杂性,那不是您所拥有的AnyContains 可以很快,是的。
  • @Rotsor:Linq 中的很多扩展方法至少对某些类型是特殊的。
【解决方案2】:

首先,我认为您的逻辑完全错误。将委托给 Contains 传递给 Any 方法将进行部分字符串匹配,并且您已明确声明您只需要完全匹配。

除此之外,您的性能问题是由于列表数据结构的性质造成的;它并非旨在通过“任何”进行有效搜索。

问题在于,“Any”只是进行线性搜索,从列表的开头开始一直遍历,直到找到匹配项。如果列表有 100K 条目,那么每个“miss”将进行 100K 字符串比较,每个“hit”将平均进行 50K 字符串比较。

太可怕了。

您应该做的是将 List 转换为字符串的 HashSet。该集合占用的内存稍多,但非常搜索速度很快。

另一个可能的优化是对其中一个列表进行排序——这是一个 O(n lg n) 操作——然后对排序后的列表进行二进制搜索,这是一个 O(lg n) 操作。

第三种可能的优化是对两个列表进行排序,然后编写一个排序列表比较器。显然,排序列表比较器比未排序列表比较器快得多。您在每个列表中保留一个索引,并仅推进指向“较小”项目的索引。也就是说,如果列表是

A, B, C, D, G, I
B, D, E, H, I

然后您从指向 A 和 B 的索引开始。“A”较小,因此您将第一个索引推进到“B”。现在它们是一样的;你有比赛。推进他们两个。第一个索引是“C”,第二个是“D”。 “C”更小”,所以推进它...


不过,更一般地说,我认为您描述的问题太低了。当你应该问一个关于孔的问题时,我觉得你是在问一个关于钻孔的问题。也许钻头一开始就不是正确的工具。你能告诉我们为什么你要匹配两个大的字符串列表吗?也许有一种更简单的方法可以做你想做的事。

【讨论】:

  • 关于你的第一条评论,我真的不知道该怎么做,我使用的方法是我能找到的最相似场景的最常见解决方案。意识到这还不够,我来到这里提出正确的问题。
  • 你也可以使用基数排序来降低 O(n) 的第二种和第三种方法的排序顺序
【解决方案3】:

使用UnionExcept 运算符来获取两个列表之间的差异和相似之处。

工会: http://msdn.microsoft.com/en-us/library/bb341731.aspx

除外: http://msdn.microsoft.com/en-us/library/system.linq.enumerable.except.aspx

每个函数都返回一个包含结果数据的列表。

【讨论】:

  • 这些应该会增加代码的可读性,但不会加速复杂性,只要容器是列表。
  • @SethO:你能解释一下你的评论吗?为什么不能加速? 序列的实现细节与此事有什么关系?
  • @Eric L:我是说选择数据结构对 OP 的主要关注点——速度最重要。正如您在回复帖子中所建议的那样,更改底层容器(例如,将 List 更改为 Hashset)可以提高 OP 所寻求的效率。 List1.Except(List2) 仍然受到线性复杂度限制,不是吗?
  • @SethO:您的说法是 list1.Except(list2) 是 O(n^2)?你是根据什么得出这个结论的?
  • @SethO:Except的实现是:Set&lt;T&gt; set = new Set&lt;T&gt;(comparer); foreach (T element in second) set.Add(element); foreach (T element in first) if (set.Add(element)) yield return element;——所以,假设set.Add在时间和空间上是O(1),并且序列的大小是m和n,那么该算法在时间上是 O(m+n),在空间上是 O(n)。
【解决方案4】:

我不明白为什么还没有人提到Enumerable.Intersect。这是一个非常有效且非常简单的函数。

【讨论】:

    【解决方案5】:

    您可以将第一个列表的每个元素插入HashSet&lt;string&gt;,然后测试第二个列表的每个元素是否存在于集合中。这只会命中每个项目一次,并且插入和测试应该是 O(1)(除非您的数据集由于某种原因是病态的。)

    【讨论】:

      【解决方案6】:

      字符串是预先知道的,因此排序向量将比哈希映射更快。使用字符串友好的哈希值(例如FNV)对较小文件中的字符串进行哈希处理,并将它们放入

      vector<pair<int, string> >
      

      定义函数以使其在哈希上可排序和可比较,然后sort 向量。

      bool operator < (pair<int, string> const&, pair<int, string> const&) { ... }
      bool operator < (pair<int, string> const&, int) { ... }
      bool operator < (int, pair<int, string> const&) { ... }
      

      现在从较大的文件中读取每个字符串,对其进行哈希处理并使用equal_range 搜索向量。仅比较哈希匹配的完整字符串。 (注意:可能需要额外的魔法才能通过哈希而不是使用虚拟 pair&lt;int, string&gt; 进行搜索。)

      如果输出开始前的较长延迟是可以接受的并且有足够的可用空间,则将两个文件加载到已排序的向量中,使用set_intersection 找到匹配的哈希并比较它们的完整字符串可能会更快。我将把细节作为练习留给读者(:-)。请记住,双方都可能存在哈希冲突。

      【讨论】:

        猜你喜欢
        • 2010-10-04
        • 2012-11-15
        • 1970-01-01
        • 2019-12-27
        • 2010-09-05
        • 1970-01-01
        • 1970-01-01
        • 2018-12-09
        • 2020-02-12
        相关资源
        最近更新 更多