从字符串成员的条件匹配的集合中查找对象的最快方法

Posted

技术标签:

【中文标题】从字符串成员的条件匹配的集合中查找对象的最快方法【英文标题】:Fastest way to find objects from a collection matched by condition on string member 【发布时间】:2010-09-10 23:19:46 【问题描述】:

假设我有一个特定类的集合(可以是数组、通用列表或任何最快解决此问题的方法),我们称之为ClassFoo

class ClassFoo

    public string word;
    public float score;
    //... etc ...
 

假设集合中将有 50.000 个项目,全部在内存中。 现在我想尽可能快地获取集合中遵守其 bar 成员条件的所有实例,例如:

List<ClassFoo> result = new List<ClassFoo>();
foreach (ClassFoo cf in collection)

    if (cf.word.StartsWith(query) || cf.word.EndsWith(query))
        result.Add(cf);

如何尽快获得结果?我应该考虑一些高级索引技术和数据结构吗?

此问题的应用程序域是一个自动完成程序,它获取查询并作为结果提供一组建议。假设条件没有比这更复杂。还假设会有很多搜索。

【问题讨论】:

【参考方案1】:

由于条件子句可以是“任何东西”的约束,那么您只能扫描整个列表并应用条件。

如果条件子句有限制,那么您可以考虑组织数据以更有效地处理查询。

例如,带有“byFirstLetter”字典的代码示例对“endsWith”查询没有任何帮助。

所以,这实际上归结为您要针对该数据执行哪些查询。

在数据库中,这个问题是“查询优化器”的负担。在典型的数据库中,如果您有一个没有索引的数据库,那么显然每个查询都将是一次表扫描。当您向表中添加索引时,优化器可以使用该数据来制定更复杂的查询计划,以更好地获取数据。这基本上就是您所描述的问题。

一旦您有了更具体的查询类型子集,您就可以更好地决定哪种结构最好。此外,您需要考虑数据量。如果你有一个包含 10 个元素的列表,每个元素都小于 100 字节,那么扫描所有内容可能是你能做的最快的事情,因为你有这么少量的数据。显然这不会扩展到 1M 元素,但即使是聪明的访问技术也会在设置、维护(如索引维护)和内存方面产生成本。

编辑,基于评论

如果它是自动完成器,如果数据是静态的,则对其进行排序并使用二进制搜索。你真的不会比这更快。

如果数据是动态的,则将其存储在平衡树中,然后进行搜索。这实际上是一种二分搜索,它可以让您随机添加数据。

其他任何东西都是对这些概念的一些专业化。

【讨论】:

稍微改变了问题并添加了以下内容:此问题的应用程序域是一个自动完成程序,它获取查询并提供一组建议作为结果。假设条件没有比这更复杂。还假设会有很多搜索。 将:对其进行排序并使用二进制搜索我可以对字符串成员上的集合进行排序。但是我将如何通过它搜索二进制文件?是否有一些标准的 .NET 东西,或者我必须编写一个二叉树结构来悬挂对象? 我不知道 .NET 可以使用哪些数据结构。我知道java有一个二进制搜索算法(我认为在集合中)。这是一个常见且简单的算法——在网上追踪它。自己实现会是很好的体验。【参考方案2】:

var Answers = myList.Where(item => item.bar.StartsWith(query) || item.bar.EndsWith(query));

在我看来这是最简单的,应该执行得相当快。

【讨论】:

【参考方案3】:

不确定我是否理解...您真正能做的就是优化规则,这是需要最快的部分。如果不投入更多硬件,就无法加速循环。

如果您有多个内核或机器,您可以并行化。

【讨论】:

【参考方案4】:

我现在还没有掌握 Java,但我会考虑以下事情。

您是如何创建列表的?也许您可以通过一种减少比较时间的方式创建已经排序的它。

如果您只是在集合中进行直接循环,则将其存储为数组或链表不会有太大区别。

对于存储结果,取决于您收集它们的方式,结构可能会有所不同(但假设 Java 的通用结构是智能的,它不会)。正如我所说,我对我的 Java 没有兴趣,但我认为通用链表会保留一个尾指针。在这种情况下,它不会真正有所作为。对底层数组与链表实现以及它最终如何查看字节码有更多了解的人可能会告诉您使用尾指针附加到链表或插入数组是否更快(我的猜测是数组)。另一方面,如果您想使用数组,则需要知道结果集的大小或牺牲一些存储空间,使其与您正在迭代的整个集合一样大。

通过找出最有可能为真的比较来优化您的比较查询并首先进行比较也可能会有所帮助。即:如果通常 10% 的集合成员以您的查询开始,而 30% 的成员以查询结束,您会希望先进行结束比较。

【讨论】:

【参考方案5】:

对于您的特定示例,对集合进行排序会有所帮助,因为您可以 binarychop 到以查询开头的第一个项目,并在您到达下一个没有的项目时提前终止;您还可以生成一个指向集合项目的指针表,该表按第二个子句的每个字符串的倒序排序。

一般来说,如果你事先知道查询的结构,你可以适当地对你的集合进行排序(或者如果有多个子句的话,为你的集合建立几个排序索引);如果你不这样做,你将无法比线性搜索做得更好。

【讨论】:

【参考方案6】:

如果您填充列表一次然后进行多次查找(数千次或更多),那么您可以创建某种查找字典,该字典将以值开头/结尾映射到它们的实际值。这将是一个快速查找,但会使用更多的内存。如果您没有进行那么多查找或知道您将至少半频繁地重新填充列表,我会使用 CQ 建议的 LINQ 查询。

【讨论】:

根据您的反应更改了问题:此问题的应用程序域是一个自动完成程序,它获取查询并提供一组建议作为结果。假设条件没有比这更复杂。还假设会有很多搜索。【参考方案7】:

您可以创建某种索引,它可能会变得更快。

我们可以这样建立一个索引:

Dictionary<char, List<ClassFoo>> indexByFirstLetter;
foreach (var cf in collection) 
  indexByFirstLetter[cf.bar[0]] = indexByFirstLetter[cf.bar[0]] ?? new List<ClassFoo>();
  indexByFirstLetter[cf.bar[0]].Add(cf);
  indexByFirstLetter[cf.bar[cf.bar.length - 1]] = indexByFirstLetter[cf.bar[cf.bar.Length - 1]] ?? new List<ClassFoo>();
  indexByFirstLetter[cf.bar[cf.bar.Length - 1]].Add(cf);

然后像这样使用它:

foreach (ClasssFoo cf in indexByFirstLetter[query[0]]) 
  if (cf.bar.StartsWith(query) || cf.bar.EndsWith(query))
    result.Add(cf);

现在我们可能不必像在您的示例中那样循环遍历尽可能多的 ClassFoo,但是我们必须再次保持索引是最新的。不能保证它会更快,但肯定会更复杂。

【讨论】:

【参考方案8】:

视情况而定。你所有的对象总是要加载到内存中吗?您是否有可以加载的对象的有限限制?您的查询是否必须考虑尚未加载的对象?

如果集合变大,我肯定会使用索引。

事实上,如果集合可以增长到任意大小并且您不确定是否能够将其全部放入内存中,我会研究 ORM、内存数据库或其他嵌入式数据库。我想到了来自 DevExpress 的用于 ORM 的 XPO 或用于内存数据库的 SQLite.Net。

如果您不想走这么远,请创建一个简单的索引,其中包含映射到类引用的“bar”成员引用。

【讨论】:

是的,这一切都会在内存中。假设大约。 50.000 项。【参考方案9】:

如果可能的标准集固定且很小,您可以为列表中的每个元素分配一个位掩码。位掩码的大小是标准集的大小。当您创建一个元素/将其添加到列表中时,您检查它满足哪些条件,然后在该元素的位掩码中设置相应的位。匹配列表中的元素就像将它们的位掩码与目标位掩码匹配一样容易。更通用的方法是布隆过滤器。

【讨论】:

以上是关于从字符串成员的条件匹配的集合中查找对象的最快方法的主要内容,如果未能解决你的问题,请参考以下文章

Groovy集合遍历 ( 使用集合的 find 方法查找集合元素 | 闭包中使用 == 作为查找匹配条件 | 闭包中使用 is 作为查找匹配条件 | 闭包使用 true 作为条件 | 代码示例 )(代

在对象数组中,查找属性与搜索匹配的对象的索引的最快方法

Groovy集合遍历 ( 使用集合的 findAll 方法查找集合中符合匹配条件的所有元素 | 代码示例 )

Groovy集合遍历 ( 使用集合的 findAll 方法查找集合中符合匹配条件的所有元素 | 代码示例 )

在 C# 中查找两个集合的补集的最快方法

JavaScript中的match方法和search方法