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

时间:2008-09-18 21:45:40

标签: c# arrays string collections performance

假设某个类有一个集合(无论是数组,通用列表,还是其他最快的解决方案),我们称之为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);
}

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

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

9 个答案:

答案 0 :(得分:2)

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

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

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

因此,它真正归结为您要针对该数据执行哪些查询。

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

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

编辑,基于评论

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

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

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

答案 1 :(得分:1)

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

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

答案 2 :(得分:0)

不确定我理解......所有你真正能做的就是优化规则,这是需要最快的部分。你不能在没有投入更多硬件的情况下加速循环。

如果您有多个核心或计算机,则可以并行化。

答案 3 :(得分:0)

我现在没有使用我的Java,但我会考虑以下事项。

您是如何创建列表的?也许你可以通过减少比较时间的方式来创建它。

如果您只是在您的集合中进行直接循环,那么将它存储为数组或链接列表之间不会有太大区别。

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

通过确定哪个比较最有可能是真实的并且首先执行该比较也可以帮助优化您的比较查询。即:如果一般有10%的时间集合成员以查询开头,并且成员以查询结束的时间占30%,那么您可能希望先进行最终比较。

答案 4 :(得分:0)

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

通常,如果您事先知道查询的结构,则可以对集合进行排序(或者如果有多个子句,则为集合构建多个排序索引);如果不这样做,你将无法做到比线性搜索更好。

答案 5 :(得分:0)

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

答案 6 :(得分:0)

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

我们可以建立这样的索引:

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,但是我们必须再次使索引保持最新。不能保证它更快,但它肯定更复杂。

答案 7 :(得分:0)

取决于。是否所有对象都将被加载到内存中?您是否有可能加载的对象的有限限制?您的查询是否必须考虑尚未加载的对象?

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

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

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

答案 8 :(得分:0)

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