使用 LINQ 位置/获取返回最相关的搜索结果

本文关键字:搜索结果 返回 LINQ 位置 获取 使用 | 更新日期: 2023-09-27 18:36:48

我有一个List<Locations>,将被过滤以产生一组与搜索词相关的结果。

目前,我通过以下过滤来尝试这些"搜索结果":

return locations.Where(o => o.Name.Contains(term)).Take(10).ToList();

问题

如果我输入"切斯特"

作为搜索词,我将永远不会看到"切斯特"项目,尽管它存在于locations列表中。原因是列表中有 10 个或更多其他项目的名称中包含字符串"Chester"(曼彻斯特、多切斯特等)。

如何使用 LINQ 首先获取以搜索词开头的结果?

到目前为止我得到了什么

    var startsWithList = list.Where(o => o.Name.StartsWith(term)).Take(10).ToList();
    var containsList = list.Where(o => o.Name.StartsWith(term) && !startsWithList.Contains(o)).Take(10 - startsWithList.Count).ToList();
    return startsWithList.AddRange(containsList);

我根本不喜欢上面的代码。我觉得这应该在一个Where中实现,而不是执行两个 Where 和 Take's 并将两个列表结合起来。

使用 LINQ 位置/获取返回最相关的搜索结果

只需在 Take 之前按升序排序,为以 term 开头的项目设置较低的值。

return locations.Where(o => o.Name.Contains(term))
                .OrderBy(m => m.Name.StartsWith(term) ? 0 : 1)
                 //or OrderByDescending(m => m.Name.StartsWith(term))
                .Take(10)
                .ToList();

随着MikeT的改进而改编(在StartsWith之前完全匹配),你可以做

return locations.Where(o => o.Name.Contains(term))
                    .OrderBy(m => m.Name.StartsWith(term) 
                                     ? (m.Name == term ? 0 : 1) 
                                     : 2)
                    .Take(10)
                    .ToList();

我创建了一个新的 github 项目,该项目使用表达式树在任意数量的属性中搜索文本

它还具有一个RankedSearch()方法,该方法返回匹配的项目以及每条记录的命中次数,这意味着您可以执行以下操作:

return locations.RankedSearch(term, l => l.Name)
                .OrderByDescending(x => x.Hits)
                .Take(10)
                .ToList();

如果需要,可以跨多个属性进行搜索

return locations.RankedSearch(term, l => l.Name, l => l.City)

。或多个术语

return locations.RankedSearch(l => l.Name, term, "AnotherTerm" )

。或同时适用于多个属性多个术语

return locations.RankedSearch(new[]{term, "AnotherTerm"}, 
                              l => l.Name, 
                              l => l.City)

查看这篇文章,了解有关生成的SQL和其他用法的更多信息:http://jnye.co/Posts/27/searchextensions-multiple-property-search-support-with-ranking-in-c-sharp

可以将其作为 nuget 包下载到:https://www.nuget.org/packages/NinjaNye.SearchExtensions/

> Raphaël的解决方案将起作用,但如果你说搜索沃里克,你会发现如果沃里克郡也是一个可能的位置,它可能不会把沃里克放在列表的顶部,使用分数你也可以用更多的匹配方法无限扩展它,以及调整分数值来优化你的搜索顺序

return locations.Select(l => New {SearchResult=l, 
                                    Score=(L.Name == Term ?
                                        100 :
                                        l.Name.StartsWith(term) ?
                                            10 :
                                            l.Name.Contains(term) ? 
                                                1 : 
                                                0
                                        )})
                .OrderByDescending(r=>r.Score)
                .Take(10)
                .Select(r => r.SearchResult);

注意我可能会通过制作一个 Match 方法来做到这一点,并在那里而不是像我上面所做的那样在 linq 中执行逻辑,所以它只是

return locations.OrderByDescending(Match).Take(10);

所有解决方案都可以使用,但更好的分数可以更容易地获得如下

return locations.Where(o => o.Name.Contains(term))
            .OrderBy(m => m.Name.IndexOf(term))
            .Take(10)
            .ToList();

因此,包含最接近开头的术语的每个名称都首先显示。

这个呢?

return locations.Where(o => o.Name.Contains(term))
                .OrderBy(m => m.Length)
                .Take(10)
                .ToList();