哪种 C# 数据结构允许最有效地在一对字符串中搜索子字符串?

Posted

技术标签:

【中文标题】哪种 C# 数据结构允许最有效地在一对字符串中搜索子字符串?【英文标题】:Which C# data structure allows searching a pair of strings most efficiently for substrings? 【发布时间】:2012-02-18 05:03:01 【问题描述】:

我有一个数据结构,它由成对的值组成,第一个是整数,第二个是字母数字字符串(可能以数字开头):

+--------+-----------------+
| Number | Name            |
+--------+-----------------+
| 15     | APPLES          |
| 16     | APPLE COMPUTER  |
| 17     | ORANGE          |
| 21     | TWENTY-1        |
| 291    | 156TH ELEMENT   |
+--------+-----------------+

这些表格最多可包含 100,000 行。

我想提供一个查找功能,用户可以在其中查找数字(好像它是一个字符串)或字符串的片段。理想情况下,查找将在用户键入时“实时”进行;在每次击键后(或者可能在短暂的延迟 ~250-500 毫秒后),将进行新的搜索以找到最有可能的候选人。所以,例如搜索

1 将返回 15 APPLES16 APPLE COMPUTER17 ORANGE291 156TH ELEMENT 15 将搜索范围缩小到 15 APPLES291 156TH ELEMENT AP 将返回 15 APPLES16 APPLE COMPUTER (理想情况下,但不是必需的)ELEM 将返回 291 156TH ELEMENT

我正在考虑使用两个Dictionary<string, string>s,因为最终将ints 与strings 进行比较——一个将按整数部分索引,另一个按字符串部分索引。

但真正按子字符串搜索不应该使用散列函数,而且使用我认为应该需要的两倍内存似乎很浪费。

最终的问题是,是否有任何性能良好的方法可以同时对两个大列表进行文本搜索以查找子字符串?

如果做不到这一点,SortedDictionary 怎么样?可能会提高性能,但仍然无法解决哈希问题。

考虑过动态创建一个正则表达式,但我认为这会非常糟糕。

我是 C# 新手(来自 Java 世界),所以我还没有研究过 LINQ;这就是答案吗?

编辑 18:21 EST:“名称”字段中的所有字符串都不会超过 12-15 个字符,如果这会影响您的潜在解决方案。

【问题讨论】:

我认为稍微修改一下Knuth–Morris–Pratt algorithm 的实现会很有用。 当您说“高效”时,您的意思是“快速”还是内存最少?通常在这些情况下,您可以用速度换取内存,或者在两者之间找到一些可接受的平衡点。 100k 字符串也是相当静态的,这意味着营业额很少而且会被反复搜索? @EBarr:内存不是一个大问题,但我不想浪费。速度在这里更重要。 那么如果用户输入“comp”,你希望显示“16 APPLE COMPUTER”吗?如果匹配可以在这样的字符串内,则该方法将与您只想匹配表中值的开头的方法完全不同。 +1 表示(不常见)正确使用“comprise” 【参考方案1】:

如果可能,我会避免将所有 100,000 个条目加载到内存中。我会使用数据库或Lucene.Net 来索引这些值。然后使用适当的查询语法来有效地搜索结果。

【讨论】:

我上面概述的只是产品的一小部分,我真的更喜欢最轻量级的解决方案。也就是说,如果我想不出任何其他表现良好的东西,我肯定会考虑使用内存中的 Lucene.net。谢谢!【参考方案2】:

我会考虑使用Trie 数据结构。

如何做到这一点?叶子将代表您的“行”,但您将有“两条路径”到“行”的每个内存实例(一个用于数字,另一个用于名称)。

然后你可以牺牲你的条件:

(ideally, but not required) ELEM will return 291 156TH ELEMENT.

或者为您的行实例提供更多路径。

【讨论】:

有趣;我当然会考虑实现它,看看它的性能如何。我没有在原始帖子中包含这个事实,但我可能可以在程序启动时进行初始树创建;如果这需要一点额外的时间,那肯定不是世界末日。谢谢! 看这里。打我一拳;-) 它比“在内存使用方面的最佳解决方案”更“邪恶”的解决方案。当你实现它时,它会让你像个孩子一样哭泣 :) 正如 Phil 所提到的,Lucene.Net 是一个很好的解决方案,但它确实取决于你的具体用例。 10 万个这样的字符串……大概是 1MB。如果您将它们放在内存中,实际上并没有多少,但是您需要根据请求多次从数据库中提取它们并首先构建一个 trie,然后那是另一回事了。 在打开文件或首次打开应用程序并将其保存在内存中时,可能会构造 trie。用户可能会在一天中多次使用此特定搜索。如果用户超过一定时间不使用搜索,我也可以自己进行某种简单的垃圾收集,并在必要时按需重建 trie。 我认为这个变体将是赢家。我正在编写一个基数树的实现,它将带来树的每个节点上可用的有效负载的好处。有效负载将携带对此字符串表示的对象的引用。这也将允许共享一个共同开头的条目(如“APPLE”和“APPLESAUCE”)指向不同的实体。此外,数字部分和名称部分将有自己的 trie 路径,并且都指向同一个底层对象,因为它们代表同一个对象。谢谢大家!【参考方案3】:

由于您正在搜索单词的开头,因此基于键的集合将不起作用,除非您存储所有可能的单词片段,例如“a”、“ap”、“app”、“appl”、“apple” .

我的建议是将System.Collections.Generic.List<T> 与二分搜索结合使用。您必须提供自己的IComparer<T>,它也可以找到单词的开头。您将使用两种数据结构。

一个List<KeyValuePair<string,int>>持有单个单词或数字作为键和数字作为值。

一个Dictionary<int,string> 持有全名。

你会这样继续:

    将您的句子(全名)拆分为单个单词。

    将它们添加到列表中,单词为键,数字为KeyValuePair 的值。

    将数字作为键和KeyValuePair 的值添加到列表中。

    当列表已满时,对列表进行排序以允许二分查找。

搜索单词的开头:

    BinarySearch 与您的IComparer<T> 结合使用,在列表中搜索。

    您从搜索中获得的索引可能不是第一个适用的索引,因此请返回列表,直到找到第一个匹配的条目。

    使用作为值存储在列表中的数字,使用该数字作为键在字典中查找全名。

【讨论】:

以上是关于哪种 C# 数据结构允许最有效地在一对字符串中搜索子字符串?的主要内容,如果未能解决你的问题,请参考以下文章

iOS 想知道使用 CoreData ManagedObjects 或 NSMutableArrays 哪种搜索方法最有效?

宏宇电商:当下流行的网络推广方式,你知道哪种最有效?

如何最有效地在 SQL Server 中插入/更新几百万行?

使用 C#,将包含二进制数据的字符串转换为字节数组的最有效方法是啥

使用 PHP 在一个字符串中搜索多个标记的最有效方法是啥?

哪种 BouncyCastle API 支持的加密算法对于 C# .NET 中的短字符串加密最快且非常安全?