Mera sortering

Efter lektionen i onsdags har jag funderat på varför det finns så många olika sätt att åstadkomma sortering (i detta blogginlägg kommer jag visa ett tredje sätt). Och i morse när jag vaknade alldeles för tidigt och inte kunda somna om så slog det mig.

Tänk dig att du nån gång måste sortera objekt av en klass som du inte själv har skrivit och som du inte har tillgång att ändra i. Och programmeraren som gjort klassen har inte implementerat interface:t IComparable (eller den implementation av IComparable som finns, sorterar inte objekten på det sätt som du vill sortera).

Då går det ju bra att göra en helt fristående klass som implementerar IComparer (Se även tidigare inlägg). Kolla in följande Console Application:

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

// //////////////////////////////////////////
//Tänk dig att denna klass INTE går att ändra
public class Cat
{
public string Name { get; set; }
public int Age { get; set; }

public Cat(string name, int age)
{
Name = name;
Age = age;
}

public override string ToString()
{
return string.Format("Katt: {0}, {1}", Name, Age);
}
}
// //////////////////////////////////////////

class Program
{
public static List<Cat> catList = new List<Cat>();

static void Main(string[] args)
{
catList.Add(new Cat("Pussy", 4));
catList.Add(new Cat("Pelle Svanslös", 3));
catList.Add(new Cat("Disco", 5));
catList.Add(new Cat("Miffo", 1));
catList.Add(new Cat("Pricken", 2));

//Går ej! Klassen Cat implementerar inte IComparable
//catList.Sort();

catList.Sort(new CatComparer()); //Här används egenskapad klass
Console.WriteLine("== Sorterade via CatComparer ==");
foreach (Cat cat in catList)
{
Console.WriteLine(cat.ToString());
}

Console.ReadLine();
}
}

//Egen klass för att möjliggöra anrop till List.Sort
class CatComparer : IComparer<Cat>
{
#region IComparer<Cat> Members

public int Compare(Cat x, Cat y)
{
return x.Name.CompareTo(y.Name);
}

#endregion
}

Sortering med delegaten Comparison<T>
List-klassens Sort-metod har ytterligare en överlagring:

Även denna variant gör det möjligt att sortera objekt av en klass utan att behöva ändra något i själva klassen. Utan att gå in så mycket på vad delegater är, om man skriver in Comparison<T> i Visual Studio, högerklickar och väljer Go to definition... visas följande definition:

public delegate int Comparison<T>(T x, T y)

Comparison<T> är alltså en delegat som kan hantera metoder som returnerar en int och som tar emot två objekt av typen T (metodens namn är valfritt!). Delegaten är generisk, du väljer alltså själv vilken datatyp som T ska vara. Men som du ser ovan i bilden kräver Sort-metoden en Comparison<Cat> och det beror på att listan är en List<Cat>.

Ok, så vi skapar en metod som uppfyller kraven och som dessutom jämför de inskickade objekten;

static int CompareCats(Cat x, Cat y) //Metoden måste vara static
{
return x.Name.CompareTo(y.Name);
}


Nu kan Sort-metoden anropas så här:

catList.Sort(new Comparison<Cat>(CompareCats));

Vi skapar alltså en ny instans av Comparison<Cat> och till den skickar vi in namnet på vår metod.

Det fina med den här varianten är att du behöver inte skapa en helt separat klass och metoden ovan kan helt enkelt placeras i den klass där sorteringen genomförs, t.ex. i den aktuella Form-klassen (WinForms), Page-klassen (Asp.NET) eller direkt i Program-klassen som i detta fall. Och det är ju helt ok om objekt av den här typen endast ska sorteras på endast ett ställe i hela applikationen, annars är IComparer-varianten bättre eftersom en klass är tillgänglig över flera fönster/sidor(=klasser).

Här kommer hela programmet igen:

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

// //////////////////////////////////////////
//Tänk dig att denna klass INTE går att ändra
public class Cat
{
public string Name { get; set; }
public int Age { get; set; }

public Cat(string name, int age)
{
Name = name;
Age = age;
}

public override string ToString()
{
return string.Format("Katt: {0}, {1}", Name, Age);
}
}
// //////////////////////////////////////////

class Program
{
public static List<Cat> catList = new List<Cat>();

static void Main(string[] args)
{
catList.Add(new Cat("Pussy", 4));
catList.Add(new Cat("Pelle Svanslös", 3));
catList.Add(new Cat("Disco", 5));
catList.Add(new Cat("Miffo", 1));
catList.Add(new Cat("Pricken", 2));

catList.Sort(new CatComparer()); //Här används egenskapad klass
printCats("== Sorterade via CatComparer ==");

catList.Sort(new Comparison<Cat>(CompareCats)); //Här används egenskapad metod
printCats("== Sorterade via Comparison<T> ==");

Console.ReadLine();
}

//Egen metod för att möjliggöra sortering via delegaten Comparison<T>
static int CompareCats(Cat x, Cat y)
{
return x.Name.CompareTo(y.Name);
}

//Helper-metod för att skriva ut katterna
private static void printCats(string header)
{
Console.WriteLine(header);
foreach (Cat cat in catList)
{
Console.WriteLine(cat.ToString());
}
Console.WriteLine();
}
}

//Egen klass för att möjliggöra sortering via interfacet IComparer<T>
class CatComparer : IComparer<Cat>
{
#region IComparer<Cat> Members

public int Compare(Cat x, Cat y)
{
return x.Name.CompareTo(y.Name);
}

#endregion
}

IComparable, IComparer, Comparison - what a mess!
Visst kan det vara lite knepigt att hålla isär alla dessa snarlika namn, men om man översätter och tänker efter lite kanske följande definitioner kan hjälpa till:
  • En klass som implemeneterar IComparable<T> gör objekt av klassen jämförbara med varandra; Comparable = Jämförbar
  • En klass som implementerer IComparer<T> har som enda uppgift att kunna jämföra två objekt av samma typ, klassen är en jämförare; Comparer = Jämförare
  • En metod som uppfyller kraven för delegaten Comparison<T> kan genomföra en jämförelse mellan två objekt av samma typ; Comparison = Jämförelse
Att klura på!
Skapa en ny Console Application och ersätt hela Program.cs med koden ovan.
  • Förändra klassen CatComparer så att den kan sortera även på Age (Se tidigare inlägg angående enum). Använd förändringarna för att sortera katterna på ålder.
  • Gör en metod som jämför katterna på Age och som uppfyller kraven för Comparison<T>. Använd den för att sortera katterna på ålder.
  • Som genom ett mirakel får du möjlighet att ändra klassen Cat. Lägg till en katt i catList som heter "Pricken" men som bara är ett år gammal. Implementera IComparable<T> i klassen Cat så att den i första hand jämför på namn och i andra hand på ålder. Anropa Sort-metoden så att kattlistan sorteras enligt detta.
Lösning finns i "the LektionsBloggen Solution".

No comments :

Post a Comment