views:

421

answers:

4

I want to sort a list of person say

List<Person> persons=new List<Person>();
persons.Add(new Person("Jon","Bernald",45000.89));
persons.Add(new Person("Mark","Drake",346.89)); 
persons.Add(new Person("Bill","Watts",456.899));

based on

public enum CompareOptions
 {
    ByFirstName,
    ByLastName,
    BySalary
 }

 public enum SortOrder
 {
   Ascending,
   Descending
 }

using lambda expression what is the way to go for sorting?

    public static List<Person> SortPeople(this List<Person> lst, 
   CompareOptions opt1,SortOrder ord)

        {
           lst.Sort((p,op1,op2)=>{ how to apply lambda expression here});
        }
+7  A: 

It looks like you are attempting to call the Sort method on List<T> which takes a Comparison<T> delegate. This will require a bit of work because you first have to define a compatible comparison function.

First step is to write a comparison function based on the CompareOptions value

private static Comparison<Person> Create(CompareOptions opt) {
  switch (opt) {
    case CompareOptions.ByFirstName: (x,y) => x.FirstName.CompareTo(y.FirstName);
    case CompareOptions.ByLastName: (x,y) => x.LastName.CompareTo(y.LastName);
    case CompareOptions.BySalary: (x,y) => x.Salary - y.Salary;
    default: throw new Exception();
  }
}

By default this function will sort in ascending order. If you want it to be descending simply negate the value. So now writing SortPeople can be done by the following

public static List<Person> SortPeople(
   this List<Person> list, 
   CompareOptions opt1,
   SortOrder ord) )
   var original = Create(opt1);
   var comp = original;
   if( ord == SortOrder.Descending ) {
     comp = (x,y) => -(orig(x,y));
   }
   list.Sort(comp);
}

EDIT

Version which is done 100% in a lambda

public static List<Person> SortPeople(
   this List<Person> list, 
   CompareOptions opt1,
   SortOrder ord) )

   list.Sort( (x,y) => {
     int comp = 0;
     switch (opt) {
       case CompareOptions.ByFirstName: comp = x.FirstName.CompareTo(y.FirstName);
       case CompareOptions.ByLastName: comp = x.LastName.CompareTo(y.LastName);
       case CompareOptions.BySalary: comp = x.Salary.CompareTo(y.Salary);
       default: throw new Exception();
     }
     if ( ord == SortOrder.Descending ) {
       comp = -comp;
     }
     return comp;
   });
}
JaredPar
This is nice and clean, but it doesn't really show how to do it in a lambda, which is what the OP asked for.
Reed Copsey
@Reed Copsey I wouldn't recommend doing it in just one lamba, as every comparision will transverse all the ifs, not just one time to choose the correct lambda to use.
Wilhelm
@Reed, true, I updated the answer to include a pure lambda version.
JaredPar
Oh, I agree - as I said, this is very clean (the first, unedited one), but the question was specifically how to do it there...
Reed Copsey
@Jared: I am naive to put this. But you could have comp initialized with int comp = (ord == SortOrder.Descending ? -1 : 1); and inside the switch block, you could write return comp * x.FirstName.CompareTo(y.FirstName); Let me know, if I am wrong somewhere.
shahkalpesh
@Jared: Doesn't this require break; inside the switch? I haven't tried that piece of code.
shahkalpesh
@Jared :Error at comp => x.Salary - y.Salary; instead can we use x.Salary.CompareTo(Y.Salary) ?
+3  A: 

To get this to work in a lambda, the expression needs to form a Comparison<T> signature. This would take 2 "Person" instances. You could do this like:

public static void SortPeople(
    this List<Person> lst, CompareOptions opt1,SortOrder ord)
{
    lst.Sort((left, right) => 
             {
                 int result;
                 // left and right are the two Person instances
                 if (opt1 == CompareOptions.Salary)
                 {
                     result = left.Salary.CompareTo(right.Salary);
                 }
                 else
                 {
                     string compStr1, compStr2;
                     if (opt1 == CompareOptions.FirstName)
                     {
                          compStr1 = left.FirstName;
                          compStr2 = right.FirstName;
                     }
                     else
                     {
                          compStr1 = left.LastName;
                          compStr2 = right.LastName;
                     }
                     result = compStr1.CompareTo(compStr2);
                 }
                 if (ord == SortOrder.Descending)
                     result *= -1;
                 return result;
             });
}
Reed Copsey
The extension method needs to have return type void.
klausbyskov
It does. The "return result" is the Comparsion<T> returning the sorting order. The extension method is just { lst.Sort(...); }
Reed Copsey
No, I'm just saying that the SortPeople method has a return type of List<Person> which it does not return, but I guess you just copied it from his example...
klausbyskov
Oh, yeah, sorry - good catch, fixed. I just copied it from the OP, which had this setup that way, too...
Reed Copsey
+2  A: 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication2
{
     class Program
    {
    static void Main(string[] args)
    {
        List<Person> persons = new List<Person>(); 
        persons.Add(new Person("Jon", "Bernald", 45000.89)); 
        persons.Add(new Person("Mark", "Drake", 346.89)); 
        persons.Add(new Person("Bill", "Watts", 456.899));

        persons.SortPeople(CompareOptions.ByFirstName, SortOrder.Ascending);

        persons.ForEach(p => Console.WriteLine(p.ToString()));

        persons.SortPeople(CompareOptions.ByFirstName, SortOrder.Descending);

        persons.ForEach(p => Console.WriteLine(p.ToString()));

        persons.SortPeople(CompareOptions.ByLastName, SortOrder.Ascending);

        persons.ForEach(p => Console.WriteLine(p.ToString()));

        persons.SortPeople(CompareOptions.ByLastName, SortOrder.Descending);

        persons.ForEach(p => Console.WriteLine(p.ToString()));

        persons.SortPeople(CompareOptions.BySalary, SortOrder.Ascending);

        persons.ForEach(p => Console.WriteLine(p.ToString()));

        persons.SortPeople(CompareOptions.BySalary, SortOrder.Descending);

        persons.ForEach(p => Console.WriteLine(p.ToString()));

        Console.ReadLine();
    }
}

public static class Extensions
{
    public static void SortPeople(this List<Person> lst, CompareOptions opt1,SortOrder ord){
        lst.Sort((Person p1, Person p2) => 
            {
                switch (opt1)
                {
                    case CompareOptions.ByFirstName:
                        return ord == SortOrder.Ascending ? p1.FirstName.CompareTo(p2.FirstName) : p2.FirstName.CompareTo(p1.FirstName);
                    case CompareOptions.ByLastName:
                        return ord == SortOrder.Ascending ? p1.LastName.CompareTo(p2.LastName) : p2.LastName.CompareTo(p1.LastName);
                    case CompareOptions.BySalary:
                        return ord == SortOrder.Ascending ? p1.Salary.CompareTo(p2.Salary) : p2.Salary.CompareTo(p1.Salary);
                    default:
                        return 0;
                }
            });
    }
}

public class Person
{
    public double Salary { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public Person(string first, string last, double salary)
    {
        this.Salary = salary;
        this.FirstName = first;
        this.LastName = last;
    }

    public override string ToString()
    {
        return string.Format("{0} {1} has a salary of {2}", this.FirstName, this.LastName, this.Salary);
    }
}

public enum CompareOptions { ByFirstName, ByLastName, BySalary }
public enum SortOrder { Ascending, Descending }

}

klausbyskov
+4  A: 

Do you really need the enums? I don't think that encapsulating your search logic in a method is much clearer or more DRY than just using linq methods:

persons.OrderBy( p => p.FirstName );
persons.OrderByDescending( p => p.Salary);

etc.

Gabe Moothart