PDA

View Full Version : C# Sorting Lists help


traxxion
31st July 2008, 12:28
Hi :)

Been struggling to solve this for quite a long time now.. Nowhere on the internet can I find a solution for this.

Please find below a hypothetical example of what I have..
I'm sorting a List<> of "Persons" (consists of name, age and city). This is no problem whatsoever for sorting actions that sort only 1 parameter at a time. Example:


public class Person
{
public int age;
public string name;
public string city;

public Person(int age, string name, string city)
{
this.age = age;
this.name = name;
this.city = city;
}
}

static void Main(string[] args)
{
List<Person> lstPersons = newList<Person>();
lstPersons.Add(new Person(37, "Peter", "Amsterdam"));
lstPersons.Add(new Person(24, "René", "Den Bosch"));
lstPersons.Add(new Person(41, "Albert", "Paris"));
lstPersons.Add(new Person(35, "Albert", "Paris"));

Console.WriteLine("Sorting by age");
lstPersons.Sort(delegate(Person p1, Person p2) { return p1.age.CompareTo(p2.age); });
foreach (Person p in lstPersons)
Console.WriteLine("Name: {0} - Age: {1} - City: {2}", p.name, p.age, p.city);

/* OUTPUT:
* Sorting by name
Name: René - Age: 24 - City: Den Bosch
Name: Albert - Age: 35 - City: Paris
Name: Peter - Age: 37 - City: Amsterdam
Name: Albert - Age: 41 - City: Paris
* */

Console.WriteLine(Environment.NewLine);
Console.WriteLine("Sorting by name");
lstPersons.Sort(delegate(Person p1, Person p2) { return p1.name.CompareTo(p2.name); });
foreach (Person p in lstPersons)
Console.WriteLine("Name: {0} - Age: {1} - City: {2}", p.name, p.age, p.city);

/* OUTPUT:
* Sorting by age
Name: Albert - Age: 41 - City: Paris
Name: Albert - Age: 35 - City: Paris
Name: Peter - Age: 37 - City: Amsterdam
Name: René - Age: 24 - City: Den Bosch
* */


Console.ReadLine();
}



Now what I'm looking for is an output like this:


/* OUTPUT SHOULD BE:
* Sort by name, then by age
Name: Albert - Age: 35 - City: Paris
Name: Albert - Age: 41 - City: Paris
Name: Peter - Age: 37 - City: Amsterdam
Name: René - Age: 24 - City: Den Bosch
* */



So sort the list by name first, and after that sort the already sorted list with a second parameter age. In this example that means that Albert with the age of 35 comes first, over Albert with the age of 41.

Doing this:

lstPersons.Sort(delegate(Person p1, Person p2) { return p1.name.CompareTo(p2.name); });
lstPersons.Sort(delegate(Person p1, Person p2) { return p1.age.CompareTo(p2.age); });

(obviously) results in the same list as the one sorted by age only.


Does anybody have any idea how I can achieve this? :scratchch

Thanks in advance,

René

xaotik
31st July 2008, 12:33
Dirtiest way ever? Concatenate the name and age and then compare.

return (p1.name + p1.age).CompareTo(p2.name + p2.age);

That way it will be comparing Albert35 with Albert41 and mr 35 wins.

EDIT:
For clarification though - you just have to handle it all by calling Sort once - whatever happens during the delegation sets the sorting for the list. If you call it again then naturally it will re-sort it based on the new criteria. Ideally you should have a decent method to delegate the handling all the situations or combinations to.

traxxion
31st July 2008, 12:37
Dirtiest way ever? Concatenate the name and age and then compare.
Hahaha great stuff, that'll do on my last day before holidays! :D

I do hope that in the mean time someone else will find a less dirty way! :tilt:

Thanks a lot xaotik!!

xaotik
31st July 2008, 12:44
Btw: http://www.codeproject.com/KB/dotnet/Multilist.aspx - a sane way of doing it in OO without dirty tricks.

(googled for: list multiple sort)

BurnOut69
31st July 2008, 12:52
You could also make your person class implement the comparable interface, so you define yourself how two persons compare to each other :)

public class person : IComparable

The above should give you a template for the Compare method.

EDIT:

More info and examples here:

http://en.csharp-online.net/IComparable

MrRoper
31st July 2008, 13:02
Doesn`t the sort method accept a class that implements IComparer?

If so just create your own compare class that implements IComparer..Check out MSDN for examples of this.

This class also can have properties to allow you to set what parts of a 'person' are checked before others

Im away at the moment so if you need the code knocking up you will have to wait until Saturday!!

you beat me to it burnout!! ;)

traxxion
31st July 2008, 13:05
Yeah I figured the word IComparer would come up quite a lot here. Unfortunately I haven't really got my head around that yet.. Seems like I will have to asap though!

Thanks for pointing me in the right direction guys :)

MrRoper
31st July 2008, 13:09
are you happy with what an interface is?

traxxion
31st July 2008, 13:16
are you happy with what an interface is?
I've heard of the word, but my theoretical knowledge of C# is nigh to zero. I usually only dive into the theory when I get stuck with my existing knowledge (like right now with the IComparer). BurnOut69 keeps telling me I have to read my C# bible though :(

MrRoper
31st July 2008, 13:31
OK so a quick interface tutorial!
im not at home though so the code will be untested!

An interface is just a definition of a "contract" i.e. it allows the creator of an interface to define a contract that consumers of the interface must adhear to. now the beauty of an interface is that it doesnt care about the underlying types. As long as an object implements the interface, you can interact with that object without caring what type the object actually is.

Example

I have a load of different vehicle types and they can all be started so i define an iStartable interface. The interface has a start method defined that returns true or false

interface iStartable
{
bool Start();
}

I can now implement this with different types of vehicles

class Car : iStartable
{
public bool Start()
{
debug.writeline("Car Started");
}
}

class Truck : iStartable
{
public bool Start()
{
debug.writeline("Truck Started");
}
}

the good thing is i dont need to worry about their concrete type so for example i can do the following

arraylist myList = new arrayList();
myList.Add (new car());
mylist.Add(new Truck());

foreach(iStartable startable in myList)
{
startable.Start();
}



does that make any sense???

traxxion
31st July 2008, 13:43
does that make any sense???It makes perfect sense actually, thanks for that!

The biggest difficulty will be to make an algorythm for the comparison though.. That's not gonna be today or the upcoming 10 days.. but I will definitely look into this, soon as I'm back from holidays.

Thanks again!

:surfing:

MrRoper
31st July 2008, 13:46
The code isnt that hard for the comparer,

Im happy to knock one up for you at the weekend if you like?

traxxion
31st July 2008, 13:55
The code isnt that hard for the comparer,

Im happy to knock one up for you at the weekend if you like?
Thanks for the offer man :) Have to say I normally want to give it a try myself first, but a good example would actually be great in this case. I'll send you my Class via pm. :thumb:

Woz
1st August 2008, 04:59
If you have a fixed max length on your name fields the quickest and safest way is to space extend the name and then for the ages 0 pad the age based on max age.

This sort of stuff comes up a lot and you either have to start building collections of collections etc or make a key. A key/hash is always fastest BUT you make to make sure your data is constrained.

So if name is 10 chars max and age is 150 max given the following data.

Bill age 20
Tom age 30
Jill Smith age 5

you would do the following keys

Bill 020
Tom 030
Jill Smith005

Your sort will now ALWAYS be correct eben if the names have numbers etc. Without the zero padding on the ages you get all sorts of crap where 20 < 5 because it would character compare 2 and 5 etc.

HTH

Woz
1st August 2008, 09:13
Here is a solution where name is not fixed length but will cost a lot if the array is too large


public class Person
{
public int age;
public string name;
public string city;

public Person(int age, string name, string city)
{
this.age = age;
this.name = name;
this.city = city;
}


public static void SortNameAge(List<Person> listToSort)
{
if (listToSort.Count == 0)
{
// Bail if nothing to sort
return;
}

// Get max name length
int maxNameLength = 0;
foreach (Person person in listToSort)
{
if (person.name.Length > maxNameLength)
{
maxNameLength = person.name.Length;
}
}

listToSort.Sort(delegate(Person p1, Person p2)
{
string key1 = p1.name.PadRight(maxNameLength) + p1.age.ToString().PadLeft(5, '0');
string key2 = p2.name.PadRight(maxNameLength) + p2.age.ToString().PadLeft(5, '0');
return key1.CompareTo(key2);
});
}
}

Shotglass
1st August 2008, 20:02
i though of that one too woz but you dont have to pad the name becuase unless theyre exaclty the same the age doesnt matter for sorting

Woz
1st August 2008, 20:37
i though of that one too woz but you dont have to pad the name becuase unless theyre exaclty the same the age doesnt matter for sorting

True but you also have the issues with names that have numbers. Years of protecting against user stupidity have made me a very defensive programmer. So I automtically look for problems now if any of the data can come from end users :)

We have a term for GUI at work... Gross User Incompetence :)

You would not believe some of the bug reports that come down the line at work :)

TheChad
2nd August 2008, 09:31
this would be how I would do it



public class Person : IComparable<Person>
{
public int age;
public string name;
public string city;
public Person(int age, string name, string city)
{
this.age = age;
this.name = name;
this.city = city;
}
public int CompareTo(Person other)
{
int nameSort = this.name.CompareTo(other.name);
int ageSort = this.age.CompareTo(other.age);
if (nameSort == 0)
{
return ageSort;
}
else
{
return nameSort;
}
}
}


Then just compare like you have


Console.WriteLine("Sorting by age");
lstPersons.Sort(delegate(Person p1, Person p2) { return p1.CompareTo(p2); });
foreach (Person p in lstPersons)
Console.WriteLine("Name: {0} - Age: {1} - City: {2}", p.name, p.age, p.city);

Woz
2nd August 2008, 10:40
this would be how I would do it



public class Person : IComparable<Person>
{
public int age;
public string name;
public string city;
public Person(int age, string name, string city)
{
this.age = age;
this.name = name;
this.city = city;
}
public int CompareTo(Person other)
{
int nameSort = this.name.CompareTo(other.name);
int ageSort = this.age.CompareTo(other.age);
if (nameSort == 0)
{
return ageSort;
}
else
{
return nameSort;
}
}
}
Then just compare like you have


Console.WriteLine("Sorting by age");
lstPersons.Sort(delegate(Person p1, Person p2) { return p1.CompareTo(p2); });
foreach (Person p in lstPersons)
Console.WriteLine("Name: {0} - Age: {1} - City: {2}", p.name, p.age, p.city);


Yep, nice and clean :)