开发者

How can I make this LINQ search method handle more than two terms?

The following search method works fine for up to two terms.

How can I make it dynamic so that it is able to handle any number of search terms?

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace TestContains82343
{
    class Program
    {
        static void Main(string[] args)
        {
            List<string> tasks = new List<string>();
            tasks.Add("Add contract to Customer.");
            tasks.Add("New contract for customer.");
            tasks.Add("Create new contract.");
            tasks.Add("Go through the old contracts.");
            tasks.Add("Attach files to customers.");

            var filteredTasks =开发者_如何学C SearchListWithSearchPhrase(tasks, "contract customer");

            filteredTasks.ForEach(t => Console.WriteLine(t));
            Console.ReadLine();
        }

        public static List<string> SearchListWithSearchPhrase(List<string> tasks, string searchPhrase)
        {
            string[] parts = searchPhrase.Split(new char[] { ' ' });
            List<string> searchTerms = new List<string>();
            foreach (string part in parts)
            {
                searchTerms.Add(part.Trim());
            }

            switch (searchTerms.Count())
            {
                case 1:
                    return (from t in tasks
                            where t.ToUpper().Contains(searchTerms[0].ToUpper()) 
                            select t).ToList();
                case 2:
                    return (from t in tasks
                            where t.ToUpper().Contains(searchTerms[0].ToUpper()) && t.ToUpper().Contains(searchTerms[1].ToUpper())
                            select t).ToList();
                default:
                    return null;
            }

        }
    }
}


How about replacing

switch (searchTerms.Count())
{
    case 1:
        return (from t in tasks
                where t.ToUpper().Contains(searchTerms[0].ToUpper())
                select t).ToList();
    case 2:
        return (from t in tasks
                where t.ToUpper().Contains(searchTerms[0].ToUpper()) && t.ToUpper().Contains(searchTerms[1].ToUpper())
                select t).ToList();
    default:
        return null;
}

By

(from t in tasks
 where searchTerms.All(term => t.ToUpper().Contains(term.ToUpper()))
 select t).ToList();


Just call Where repeatedly... I've changed the handling of searchTerms as well to make this slightly more LINQ-y :)

public static List<string> SearchListWithSearchPhrase
    (List<string> tasks, string searchPhrase)
{
    IEnumerable<string> searchTerms = searchPhrase.Split(' ')
                                                  .Select(x => x.Trim());
    IEnumerable<string> query = tasks;
    foreach (string term in searchTerms)
    {
        // See edit below
        String captured = term;
        query = query.Where(t => t.ToUpper().Contains(captured));
    }
    return query.ToList();
}

You should note that by default, ToUpper() will be culture-sensitive - there are various caveats about case-insensitive matching :( Have a look at this guidance on MSDN for more details. I'm not sure how much support there is for case-insensitive Contains though :(

EDIT: I like konamiman's answer, although it looks like it's splitting somewhat differently to your original code. All is definitely a useful LINQ operator to know about...

Here's how I would write it though:

return tasks.Where(t => searchTerms.All(term => t.ToUpper().Contains(term)))
            .ToList();

(I don't generally use a query expression when it's a single operator applied to the outer query.)

EDIT: Aargh, I can't believe I fell into the captured variable issue :( You need to create a copy of the foreach loop variable as otherwise the closure will always refer to the "current" value of the variable... which will always be the last value by the time ToList is executed :(

EDIT: Note that everything so far is inefficient in terms of uppercasing each task several times. That's probably fine in reality, but you could avoid it by using something like this:

IEnumerable<string> query = tasks.Select
    (t => new { Original = t, Upper = t.ToUpper });
return query.Where(task => searchTerms.All(term => task.Upper.Contains(term)))
            .Select(task => task.Original)
            .ToList();


Can't test code right now, but you could do something similar to this:

from t in tasks
let taskWords=t.ToUpper().Split(new char[] { ' ' });
where searchTerms.All(term => taskWords.Contains(term.ToUpper()))
select t


Replace the switch statement with a for loop :)

    [TestMethod]
    public void TestSearch()
    {
        List<string> tasks = new List<string>
            {
                "Add contract to Customer.",
                "New contract for customer.",
                "Create new contract.",
                "Go through the old contracts.",
                "Attach files to customers."
            };

        var filteredTasks = SearchListWithSearchPhrase(tasks, "contract customer new");

        filteredTasks.ForEach(Console.WriteLine);
    }

    public static List<string> SearchListWithSearchPhrase(List<string> tasks, string searchPhrase)
    {
        var query = tasks.AsEnumerable();

        foreach (var term in searchPhrase.Split(new[] { ' ' }))
        {
            string s = term.Trim();
            query = query.Where(x => x.IndexOf(s, StringComparison.InvariantCultureIgnoreCase) != -1);
        }

        return query.ToList();
    }


why not use a foreach and AddRange() after splitting the terms and saving it into a list.

List<ItemsImLookingFor> items = new List<ItemsImLookingFor>();

// search for terms
foreach(string term in searchTerms)
{
   // add users to list
   items.AddRange(dbOrList(
         item => item.Name.ToLower().Contains(str)).ToList()
   );
}

that should work for any amount of terms.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜