2013-10-24 3 views
2

У меня есть List<Locations>, который будет отфильтрован, чтобы получить набор результатов, относящихся к поисковому запросу.Возвращение большинства релевантных результатов поиска с использованием LINQ Где/взять

На данный момент, я попробовал эти «результаты поиска» путем фильтрации со следующим:

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

Проблема

Если я войду «Chester» как термин поиска, я буду никогда не см. пункт «Честер», несмотря на то, что он существует в списке locations. Причиной этого является то, что в списке есть 10 или более других элементов, которые содержат строчку «Честер» от их имени (Манчестер, Дорчестер и т. Д.).

Как я могу использовать LINQ, чтобы в первую очередь взять результаты, которые начинаются с поискового запроса?

Что у меня есть So Far

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, в отличие от выполнения двух «Куда» и «Take» и объединения двух списков.

+0

общий путь вы делаете что-то вроде этого с забитым матчем, так что если значение = поиск оценка = 1000, если начинается с, то оценкой = 100, если содержит балл = 10, если частично содержит балл = 1, то вы суммируйте все баллы и порядок по сумме, прежде чем принимать свой результирующий набор, хотя это, вероятно, сложнее, чем вам нужно. – MikeT

+0

Похоже, вы хотите что-то вроде [приблизительного соответствия строк] (https://en.wikipedia.org/wiki/Approximate_string_matching), чтобы узнать, какой из них лучший результат. –

ответ

7

просто закажите по возрастанию до Take, положив более низкое значение для предметов, начинающихся с термина.

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(); 
+0

Это выглядит красиво. Точно, что я был после! +1. Я не знал, что вы можете сделать это с OrderBy. –

+0

Кроме того, если вам действительно не нужно делать ToList, так как это приведет к созданию экземпляра набора данных в этот момент, когда в противном случае, если вы сделаете какие-либо другие запросы, они будут добавлены в запрос данных и не будут выполняться отдельно по данным, особенно важно, если это linqToSQL – MikeT

1

Решение Raphael будет работать, но если вы говорите в поисках Warwick вы можете обнаружить, что она не может поместите Warwick в начало списка, если Warwickshire также является возможным местом, используя оценки, вы также можете расширить это бесконечно с помощью более подходящих методов, а также настроить значения оценки, чтобы уточнить ваш поисковый запрос.

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(r=>Match(r)).Take(10); 
3

Я создал new github project, который использует деревья выражений для поиска текста в любом количестве свойств

Он также имеет RankedSearch() метод, который возвращает элементы совпадающих с числом хитов для каждой записи означает, что вы можете сделать следующее:

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

Если вы хотите, вы можете искать accross нескольких свойств

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) 

Checkout этот пост для получения дополнительной информации о SQL сгенерированного и других использований: http://jnye.co/Posts/27/searchextensions-multiple-property-search-support-with-ranking-in-c-sharp

Вы можете скачать этот как пакет NuGet к : https://www.nuget.org/packages/NinjaNye.SearchExtensions/

+0

Это выглядит на месте. Я очень заинтересован, скачаю его чуть позже. Большое спасибо. –

+0

Привет @DeeMac, спасибо за ваш интерес. Не стесняйтесь взглянуть и выдвигайте любые предложения. Свяжитесь со мной через http://jnye.co или twitter. Все элементы находятся на моей странице [SO profile] (http://stackoverflow.com/users/611288/ninjanye) – NinjaNye

1

Все решения будут работать, но лучший результат может получить mor е легче, как показано ниже

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

в результате каждый имя, которые содержат термин в ближайшие начать, показать первый.

0

Как насчет этого?

return locations.Where(o => o.Name.Contains(term)) 
       .OrderBy(m => m.Length) 
       .Take(10) 
       .ToList(); 
Смежные вопросы