C# 使用具有字符串属性的对象加快集合搜索

C# 使用具有字符串属性的对象加快集合搜索,c#,performance,linq,C#,Performance,Linq,我有以下几点 public class SearchResult { public string Description { get; set; } public int Year{ get; set; } public int Type { get; set; } } var found = myList.Where(x => x.Description.StartsWith(query)).Take(10).ToList(); 我创建了一个这些列表的列表并将

我有以下几点

public class SearchResult
{
    public string Description { get; set; }
    public int Year{ get; set; }
    public int Type { get; set; }
}
var found = myList.Where(x => x.Description.StartsWith(query)).Take(10).ToList();
我创建了一个这些列表的列表并将其缓存,然后我尝试通过以下内容搜索这个集合(1.2M)记录

public class SearchResult
{
    public string Description { get; set; }
    public int Year{ get; set; }
    public int Type { get; set; }
}
var found = myList.Where(x => x.Description.StartsWith(query)).Take(10).ToList();
这是非常缓慢的我要说的,有没有更好的方法来存储对象列表,并有能力搜索对象的字符串属性

我应该在缓存集合之前对其进行排序吗? 我希望能够以最快的路径在Description属性上执行.StartsWith和.Contains,以获得前10个匹配项


如果我只是更快地进入数据库(我在文本字段上放置了一个索引),我希望通过获得一次结果,将结果粘贴到内存中,然后对内存中的缓存执行所有搜索,而不是每次进入数据库,来提高我的性能。但事实证明,这比使用类似SQL的“{query}%”语句的db调用要慢,字符串比较本身就慢,此外,您必须完全迭代整个列表,以查看是否有匹配项。这永远不会有好的表现,事实上,随着时间的推移,随着新记录添加到源中,情况很可能会变得更糟

这是一篇关于字符串搜索的好文章,适合那些关注速度的人

我建议像您提到的那样,将搜索移动到数据库,并限制返回的行数。虽然这仍然是I/O,但数据库已针对处理这类事情进行了优化。其他一些优势是,你最终不会陷入应用程序崩溃和丢失缓存搜索的陷阱,同样,你可以利用
async/await
,这将使你的应用程序更具响应性

如果您决定仍然要将所有内容放入内存,然后查询对象,那么祝您好运。我唯一的另一个建议是考虑搜索缓存,这样如果最近有人搜索相同的东西,你可以缓存这些结果并立即返回它们。 来自同一位作者,这里是另一个阅读来源-这里他比较了收集字符串的查找速度

首先,这是一个很好的解决方案,而且似乎没有一种有效的方法仅使用LINQ

你需要的是某种帮助。在您的例子中,反向索引的非常简单的实现是
字典
。对于电影片名中的每个单词,本
词典包含所有电影,其中片名包含该单词。要构建此简单的反向索引,可以使用以下代码:

        reverseIndex = new Dictionary<string, List<SearchResult>> ();
        for (int i = 0; i < searchResults.Count; i++) {
            var res = searchResults[i];
            string[] words = res.Description.Split (new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
            foreach (var word in words) {
                if (!reverseIndex.ContainsKey (word))
                    reverseIndex [word] = new List<SearchResult> () { res };
                else if (!reverseIndex[word].Contains(res))
                    reverseIndex [word].Add (res);              
            }
        }
您可以使用简单的:

reverseIndex[query];
它工作得很快。而不是

searchResults.Where(x => x.Description.StartsWith(query));
您可以使用:

reverseIndex[query].Where(s => s.Description.StartsWith(query));
如果查询包含多个单词,可以将其拆分为多个单词,然后为每个单词提取
List
,然后将列表相交


通过这种简单的反向索引实现,您的查询只能包含整个单词。如果要按单词的一部分进行搜索,则需要使用。在C#上可以找到一个可能的实现。请注意,permuterm索引需要大量额外内存。

快速字符串前缀搜索最好使用数据结构。trie的酷之处在于,任何给定节点的所有子代都有一个与该节点关联的字符串的公共前缀。它还可以压缩成一个简单的文件,实现起来可能稍微复杂一些

现在,您正在使用Linq to objects来迭代每次搜索的整个列表(每个
StartsWith
方法都是
O(m)
,其中
m
查询
字符串的长度)。如果使用Linq to SQL,它将被转换为SQL查询,该查询将使用索引执行更高效的查找

具有使用trie自动完成功能的示例实现

(更新)

正如@David在评论中提到的,如果您已经在列表中加载了这些数据(也就是说,如果您仍然需要以这种形式保存这些数据,您可能会这样做),那么trie可能是一种过度杀伤力。在这种情况下,
StartsWith
查询的更好选择是对列表进行排序。这将允许您使用二进制搜索在
O(mlogn)
中获得结果

根据数据是否经常更改,您还可以使用平衡的二叉树来存储数据,以允许快速插入/删除(这基本上就是提供给您的)

但最终,如果您还需要
包含
查询,那么您要么需要在索引上留出更多内存(如所述),要么干脆让您的DMB来做(如所建议)


另一方面,您可能希望尝试使用SQL Server的全文搜索。或者,如果您不想编写SQL,那么可以使用内存缓存。

因为像“query%”一样是可搜索的,它使用索引进行搜索。您也可以创建一些从数据到搜索的树。但是如果你只想做一次的话,那就太慢了。寻找b-树。这是索引的存储方式。
Description
属性包含单个单词或短语?“查询”怎么样?@IgorBendrup它们是电影标题,可能是一个单词或短语。奇怪的是,这些链接甚至没有提到Boyer Moore或Knutt Morris Pratt,这可能比所有其他方法更有效(假设您需要进行多次查找,并且可以在预处理搜索字符串方面节省一些指令).你有具体的例子要分享吗?没有具体的,可以在谷歌上找到实现。我只是想说这个列表还不够完整(例如,我认为作者没有尝试使用编译过的正则表达式)。第二个链接只比较了在集合中搜索精确字符串的内置方式(使用哈希集是很容易的),这不适用于许多需要部分匹配的问题。+1,我没有意识到OP需要
包含
以及
StartsWith
,在这种情况下,您的答案会更加敏感