views:

67

answers:

3

Consider the need to search a list of Customer by both first and last names. The desire is to have the results list sorted by the Customer with the most matches in the search terms.

FirstName      LastName
----------     ---------
Foo            Laurie
Bar            Jackson
Jackson        Bro
Laurie         Foo
Jackson        Laurie
string[] searchTerms = new string[] {"Jackson", "Laurie"};

//want to find those customers with first, last or BOTH names in the searchTerms
var matchingCusts = Customers
                       .Where(m => searchTerms.Contains(m.FirstName)
                               ||  searchTerms.Contains(m.LastName))
                       .ToList();

/* Want to sort for those results with BOTH FirstName and LastName 
   matching in the search terms. Those that match on both First and Last
   should  be at the top of the results, the rest who match on 
   one property should be below.
*/

 return matchingCusts.OrderBy(m=>m); 

Desired Sort:

Jackson        Laurie  (matches on both properties)
Foo            Laurie
Bar            Jackson
Jackson        Bro
Laurie         Foo

How can I achieve this desired functionality with LINQ and OrderBy / OrderByDescending?

+5  A: 

Use Select to project a "match evaluation" with the customer, and then order by that:

class Program
{
    static void Main(string[] args)
    {
        var Customers = new Customer[]
        {
            new Customer { FirstName = "Foo", LastName = "Laurie" },
            new Customer { FirstName = "Bar", LastName = "Jackson" },
            new Customer { FirstName = "Jackson", LastName = "Bro" },
            new Customer { FirstName = "Laurie", LastName = "Foo" },
            new Customer { FirstName = "Jackson", LastName = "Laurie" },
        };

        string[] searchTerms = new string[] { "Jackson", "Laurie" };

        //want to find those customers with first, last or BOTH names in the searchTerms 
        var matchingCusts = Customers
                               .Where(m => searchTerms.Contains(m.FirstName)
                                       || searchTerms.Contains(m.LastName))
                               .ToList();

        var result = matchingCusts.Select(x => new
            {
                Customer = x,
                MatchEvaluation = (searchTerms.Contains(x.FirstName) ? 1 : 0) + (searchTerms.Contains(x.LastName) ? 1 : 0),
            })
            .OrderByDescending(x => x.MatchEvaluation)
            .Select(x => x.Customer);

        foreach (var c in result)
        {
            Console.WriteLine(c.FirstName + " " + c.LastName);
        }

        Console.ReadKey();
    }

    public sealed class Customer
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }
}
Stephen Cleary
Note: the only reason I'm calling `ToList` is to pull the results from the DB, so the match evaluation and sorting are done using LINQ to Objects. If there is no DB, then the `ToList` is unnecessary.
Stephen Cleary
A: 

You can make your own IEqualityComparer<Customer> to pass into the orderby functions.

Make the constructor take the list of searchTerms and then prioritize containment over alphabetic.

Then Do .SortBy(c => c.LastName, new MyEqualityComparer(searchTerms)).ThenBy(c => c.FirstName, new MyEqualityComparer(searchTerms))

Aren
I think you mean `IComparer<Customer>` rather than `IEqualityComparer<Customer>`.
Stephen Cleary
+2  A: 

Since I totally missed the original question:

One liner :)

var matchingCusts = Customers
    .Where(m => searchTerms.Contains(m.FirstName) ||
                searchTerms.Contains(m.LastName))
    .Select(c => new
    {
        Customer = c,
        Sort = ((searchTerms.Contains(c.FirstName) ? 0 : 1) + 
                (searchTerms.Contains(c.LastName) ? 0 : 1))
    })
    .OrderBy(o => o.Sort)
    .ThenBy(o1 => o1.Customer.LastName)
    .ThenBy(o2 => o2.Customer.FirstName)
    .Select(r => r.Customer);
Kelsey