Láttuk, hogy sorba rendezni egy listát a “rendezés”, azaz list.Sort()
függvénnyel lehetett:
Listák és a Foreach ciklus
Ehhez hasonlóan létezik egy Array.Sort(array)
metódus is, ami hasonlóképp rendez, mint a korábban megismert listás változat, ám azzal szemben, ami egy tagmetódus, a tömb rendező egy statikus metódus, ami paraméterben várja a rendezni kívánt tömböt. Ennek túl nagy jelentősége nincs, csak egy kis szintaktikai különbség.
List<int> numberList = new List<int> {3, 7, 5, 1, 4};
int[] numberArray = {3, 7, 5, 1, 4};
numberList.Sort(); // Lista rendezése
Array.Sort(numberArray); // Tömb rendezése
Ám mindez csak akkor működik, ha definiált, hogy mit jelent az, hogy a típus két eleme közül melyik az “alacsonyabb”, és melyik a “nagyobb” értékű.
Ez bizonyos típusok esetén triviális:
Számok: Nagyobb szám magasabb értékű Sztringek: Amelyik előrébb áll az ABC sorrendben az alacsonyabb értékű
Ez alapján történik a rendezés is:
List<int> numbers = new List<int> {3, 7, 5, 1, 4};
numbers.Sort(); // {1, 3, 4, 5, 7}
List<string> strings = new List<string> {"datolya", "banán", "alma", "citrom"};
numbers.Sort(); // {"alma", "banán", "citrom", "datolya"};
Egyéb esetekben nem ilyen könnyű eldönteni, hogyan viszonyul egymáshoz két eleme egy típusnak. Ezek közé tartozik minden általunk definiált összetett típus is
Vegyük a klasszikus Dog
(kutya) példát.
struct Dog
{
public string name; // Neve
public int age; // Kora
public float speed; // Sebessége
public bool doesBite; // Harap-e?
}
Hogy állítunk sorba egy ilyen kutyákat tartalmazó sorozatot? Név szerint ABC sorrendben vagy a kor szerint? Természetesen mindkét módon megtehetjük ezt, sőt tetszőleges egyéb stratégiát is használhatunk, de először definiálnunk kell a rendezési szempontot, hiszen a számítógép nem tudja magától kitalálni, most épp mire is van szükségünk.
A rendezés szempontját egy összehasonlító más néven Compare metódussal tudjuk definiálni, ami két objektumot kap az adott típusból és eldönti, egymáshoz képest melyik van előrébb a sorozatban. A két paramétert nevezzük sorrendben A-nak és B-nek!
A függvény egy int-tel tér vissza, aminek értéke:
-1: Ha az A paraméter alacsonyabb értékű, mint a B. (Rendezve A van elől)
0: Ha a két paraméter azonos szintű. (Sorrendjük nem számít)
1: Ha az A paraméter magasabb értékű, mint a B. (Rendezve A van hátul)
Ha megírunk egy ilyen metódust egy tetszőleges típusra, akkor automatikusan sorba tudjuk rendezni azt, akkor is, ha a típuson nincsen alapértelmezve, hogy melyik magasabb.
// Kutyák sorba rendezése sebesség szerint növekedve
static int CompareDog(Dog a, Dog b)
{
if (a.speed < b.speed) return -1;
if (a.speed > b.speed) return 1;
return 0;
}
Ezután átadható ez a metódus a Sort()
metódusnak extra paraméterként.
List<Dog> dogs = new List<Dog>();
dogs.Sort(CompareDog); // Metódus átadása paraméterként
Igen, ilyet is lehet csinálni. Ennek részleteiről itt: Metódus mint objektum (Hamarosan)
Elérhető, hogy egy típus listáján automatikusan működjön a paraméter nélküli Sort()
függvény is.
Ehhez meg kell valósítani a típuson a IComparable<T>
interface-t. Hogy ez mit jelent és hogyan kell elvégezni, ahhoz meg kell érteni az interface-ek és generikusok fogalmát és használatát:
Interface-ek (Hamarosan), Generikusok (Hamarosan)
Primitív típusok Compare metódusai
A beépített típusoknak is vannak összehasonlító metódusai. Ezt használja a C# a háttérben, amikor mi az egyszerű paraméter nélküli Sort()
metódussal rendezünk.
int compareInts = 44.CompareTo(-95); // A = 44f, B = -95
int compareFloats = 12.4f.CompareTo(22.5f); // A = 12.4f, B = 22.5f
int compareStrings = 12.4f.CompareTo(22.5f); // A = 12.4f, B = 22.5f
int compareEnums = Direction.Up.CompareTo(Direction.Down); // A = Up, B = Down
// ...
// Eredmény minden típusra int: -1 vagy 0 vagy 1
Gyakran kapóra jöhet felhasználni ezeket a saját Compare függvényünkhöz is.
// Kutyák sorba rendezése ABC sorrendben. Ha a név egyezik, akkor kor szerint növekedve
static int CompareDog(Dog a, Dog b)
{
int compareByName = a.name.CompareTo(b.name); // string comparer
if (compareByName == 0)
return a.age.CompareTo(b.age); // int comparer
else
return compareByName;
}
Rendező algoritmusok
Eddig azt tekintettük át, hogy milyen módon tudjuk megadni a rendezési szempontot. Érdemes pár szót arra is áldozni, hogy hogyan is működik a sorba tétel maga.
Sok féle rendező algoritmus létezik:
- Insertion Sort
- Selection Sort
- Bubble Sort
- Shell Sort
- Heap Sort
- Quicksort
- és még sok másik…
Ezek közül mindnek megvan az előnye a többihez képest attól függően, hogy milyen méretű, és kevertségű sorozaton alkalmazzuk őket.
Részletesen nem fogjuk átnézni a különböző rendezőalgoritmusok működését, itt most egyedül a Quicksort, azaz gyors rendező algoritmusra fordítunk pár szót mivel a C# ezt használja fel tömb és lista rendezésre egyaránt.
Elsőre csupán annyit érdemes tudni, hogy az algoritmus alapja, hogy először szétválasztja a sorozatot két csoportra és gondoskodik róla, hogy a kisebb elemek az első részben legyenek, míg a nagyobbak a másodikban. Mindezt úgy, hogy az első rész legnagyobb eleme is kisebb legyen, mint a második csoport legkisebbike. Ezután rekurzívan ismétli ezt a műveletet a csoportokra, addig, amíg egy csoport már csak egy elemből nem áll.
Azért erre esett a C# fejlesztőinek választása mert a módszer elég jó idővel végez kis és nagy méretű sorozatokkal is és többé kevésbé független a sebessége a rendezettség szintjétől.
A rendezési problémák 99.9%-ában nincs szükség arra, hogy máshoz folyamodjatok.
Ha nem sorba rendezni, hanem keverni szeretnél egy listát: Véletlenek