2. díl - Seznam (List) pomocí pole v C#

C# .NET Kolekce a LINQ Seznam (List) pomocí pole v C#

V minulém díle seriálu tutoriálů o C# .NET jsme si udělali úvod do kolekcí a ukázali jsme si, co je to genericita. Dnes se budeme věnovat seznamům (listům), což je typ kolekcí, se kterým jsme se během seriálu již setkali.

Pole

Udělejme si na začátku malou odbočku zpět k poli, které bylo první kolekcí, kterou jsme v seriálu poznali. Pole se vyznačuje tím, že má pevně daný počet prvků. Z tohoto důvodu někdy dokonce nebývá považováno za kolekci. Prvky v poli jsou číselně indexovány a to od nuly.

Hlavní nevýhodou pole tedy je, že do něj nemůžeme za běhu aplikace prvky přidávat nebo je mazat. To bohužel často potřebujeme, i když jsou situace, kdy je pole ideální volba. Touto daní je vyvážena obrovská rychlost, se kterou můžeme s prvky pole pracovat. Jelikož data jsou stejného typu (ať již úplně stejného, nebo společného předka), zabírají v paměti stejně místa. Jednotlivé prvky pole jsou v paměti uložené zasebou, jako v řadě, která je nepřerušená. Pole celých čísel si můžeme představit např. takto:

Struktura pole

Pokud tedy chceme např. přistoupit na 5. prvek, jen vstoupíme tam, kde pole začíná a poté odskočíme 4 násobky velikosti typu (zde intu) dále. Jsme na 5. prvku. Čtení a zápis na indexy v poli má tedy konstantní časovou složitost. Pokud vás tento termín zmátl, můžete to chápat tak, že do pole zapisujeme okamžitě a stejně tak z něj i čteme.

Pozn. Pokud v C# .NET založíme prázdné číselné pole, je automaticky naplněno nulami.

Seznamy (Listy)

Seznamy (anglicky a často i česky List) jsou kolekce, které umožňují prvky za běhu programu přidávat a mazat. Mohou být číselně indexované jako pole, ale také nemusí. Jsou v zásadě 2 typy seznamů.

Seznamy s polem

Seznam nejčastěji využívá toho, že ačkoli velikost pole nemůžeme za běhu programu měnit, můžeme za běhu vytvořit pole nové.

Seznam je poté třída, která obsahuje metody pro přidání a odstranění prvků (a mnoho dalších, pro nás nyní nepodstatných metod). Třída v podstatě obaluje pole a obsahuje proměnnou, kde si uchovává počet prvků. Při vytvoření instance se vytvoří pole např. o 12ti prvcích a proměnná s počtem prvků se nastaví na 0. Při přidání prvního prvku se prvek vloží na 1. index v poli a počet prvků se inkrementuje. Takto můžeme přidat až 12 prvků, než pole naplníme. Ve chvíli, kdy vyčerpáme kapacitu pole, jednoduše vytvoříme nové, třeba 2x větší. Prvky ze starého pole do něj zkopírujeme a staré pole zahodíme. Až se toto nové pole opět naplní, budeme situaci opakovat. Takovýmto způsobem opravdu interně funguje kolekce List, se kterou jsme doteď pracovali. List s polem si můžeme představit asi takto:

Struktura list přes pole

List na obrázku má 8 prvků. Prvky jsou uloženy v interním poli, které má prvků 12. Poslední 4 prvky se nevyužívají a List se zvenku tváří jako že tam nejsou.

Výhodou je rychlost přístupu k prvkům pomocí indexů díky využití pole. Nevýhodou je samořejmě časová prodleva nutná k vytvoření nového pole a překopírování prvků, i když nastává jen občas. Další, i když méně bolestivou nevýhodou je, že kolekce zabírá v paměti více prostoru, než je nutné. Tento typ seznamu je přesto nejpoužívanější kolekcí v .NETu a je poměrně dobře optimalizován.

List s polem je tedy v .NETu zastoupen třídou List a jeho negenerický protějšek je ArrayList. Popišme si důležité metody na třídě List:

Metody a další prvky na třídě List

List implementuje interface IList. Ten tvoří základ kolekce a obsahuje následující metody:

  • Add - Přidá nový prvek do listu.
  • Clear - Vymaže všechny prvky.
  • Contains - Vrátí true, pokud list obsahuje daný prvek.
  • IndexOf - Vrátí index daného prvku v listu.
  • Insert - Vloží na daný index nový prvek (a další prvky posune o 1 dozadu).
  • Remove - Vymaže daný prvek. Tato funkce je velmi užitečná v případě, že máme v Listu instance nějaké třídy (např. uživatele), nemusíme si držet jejich číselné indexy, jen zavoláme např. list.Remove(karel).
  • RemoveAt - Vymaže prvek na daném indexu.

Ačkoli jsme si List vyzkoušeli již 1000krát, pro úplnost si přeci jen ukažme několik řádků kódu:

List<int> list = new List<int>();
list.Add(5);
list.Add(10);
Console.WriteLine(list[0]);

Výstup programu:

5

Kód výše vytvoří List typu int, přidá do něj 2 čísla a poté vypíše první prvek do konzole. Pracujeme s indexy jako bychom pracovali s polem, ale můžeme do něj za běhu programu přidávat prvky a také je mazat.

Samotný List ještě dodává další metody, popišme si i ty:

  • AddRange - Přidá do listu prvky z pole. Podobně můžeme volat i metody InsertRange() a RemoveRange(). Metoda se hojně používá, jelikož nám ušetří cyklus. Jedinou záludností je, že umí přidávat pouze z pole. Za chvíli si ukážeme co s tím.
  • AsReadOnly - Vrátí instanci listu, ze které lze prvky pouze číst. Vhodné pro zapouzdření kolekce dat.
  • CopyTo - Metodu již známe z pole, umožňuje zkopírovat prvky z listu do pole.
  • Count - Vlastnost vracející počet prvků v listu. Všimněte si, že se vlastnost nejmenuje Length (jako u pole), jelikož délka listu je o něco větší. Pravou délku listu získáme vlastností Capacity, i když nám tento údaj asi k ničemu není.
  • Find - Vyhledá daný prvek pomocí predikátu (který je, jak již víme, delegátem). Je to velmi jednoduché a efektivní, jelikož můžeme použít zápis přes lambda funkce. Ukažme si, jak by se na Listu typu int vyhledalo číslo větší než 25:
int cislo = list.Find(a => a > 25);

Find vrátí první nalezený prvek.

  • FindAll - Podobně jako Find() můžeme používat metodu FindAll(), která najde všechny odpovídající prvky a vrátí nový List, který tyto nalezené prvky obsahuje:
List<int> cislaVetsiNez25 = list.FindAll(a => a > 25);

Díky delegátům a lambda výrazům je vše tak snadné. Dále list nabízí metody FindIndex(), FindLast() a FindLastIndex(). Zajímavá je ještě metoda BinarySearch(), která vyhledává prvek stejně jako Find(), ale je mnohem rychlejší. Předpokladem je však fakt, že je list setříděný. Více u algoritmu binární vyhledávání

  • Exists - Exists funguje podobně jako Find, pouze nevrací nalezený prvek ale true pokud byl nějaký nalezený, jinak false.
  • LastIndexOf - Obdoba metody IndexOf, vrací index posledního výskytu daného prvku v listu.
  • RemoveAll - Vymaže všechny prvky, které odpovídají danému predikátu.
  • Reverse - Převrátí list tak, aby byl 1. prvek jako poslední a naopak poslední jako první.
  • Sort - Setřídí list. Je důležité, aby jeho prvky obsahovaly rozhraní IComparable, jinak metoda spadne s chybou. Základní třídy a struktury z .NETu IComparable implementují, u svých tříd ho umíme dodat.
  • ToArray - Velmi používaná metoda, která vytvoří pole prvků z listu a to vrátí. Jelikož pole je standardní výměnná struktura v .NETu, budeme metodu používat velmi často. Všimněte si, že např. metoda AddRange() bere v parametru pole, nikoli list. To aby byla univerzální. Pokud tedy chceme zkopírovat prvky jednoho listu do druhého, uděláme to takto:
list1.AddRange(list2.ToArray());

Až na několik metod jsme si popsali celý list.

Vyzkoušejte si další metody jako Sort(), vyhledávání a podobně. Detailnější práci s kolekcemi se budeme ještě věnovat, až se dostaneme k technologii LINQ.

Příště si uvedeme 2. typ listu, kterým je spojový seznam.


 

Stáhnout

Staženo 547x (22.98 kB)
Aplikace je včetně zdrojových kódů v jazyce C#

 

  Aktivity (1)

Článek pro vás napsal David Čápka
Avatar
Autor pracuje jako softwarový architekt a pedagog na projektu ITnetwork.cz (a jeho zahraničních verzích). Velmi si váží svobody podnikání v naší zemi a věří, že když se člověk neštítí práce, tak dokáže úplně cokoli.
Unicorn College Autor se informační technologie naučil na Unicorn College - prestižní soukromé vysoké škole IT a ekonomie.

Jak se ti líbí článek?
Celkem (4 hlasů) :
4.754.754.754.754.75


 


Miniatura
Předchozí článek
Úvod do kolekcí a genericita
Miniatura
Všechny články v sekci
Kolekce v C# .NET a LINQ
Miniatura
Následující článek
Spojový seznam v C#

 

 

Komentáře
Zobrazit starší komentáře (9)

Avatar
Petr Nymsa
Redaktor
Avatar
Petr Nymsa:

No trochu jsem to pojmenoval špatně. Napíšu příklad a snad to bude jasný

class NejakaTrida
{
   private int cislo;
   public string Jmeno {get; set;}
}
Odpovědět 2.4.2013 17:14
Pokrok nezastavíš, neusni a jdi s ním vpřed
Avatar
Kit
Redaktor
Avatar
Odpovídá na Petr Nymsa
Kit:

Právě proto tuto zvyklost nechápu. Interním vlastnostem a metodám malá písmena, externím velká. Přitom cislo i Jmeno mohou být neprimitivní objekty. Když pak chceš nějakou interní vlastnost zpřístupnit, musíš přejmenovat všechny výskyty ve třídě.

Odpovědět 2.4.2013 17:37
Vlastnosti objektů by neměly být veřejné. A to ani prostřednictvím getterů/setterů.
Avatar
Petr Nymsa
Redaktor
Avatar
Odpovídá na Kit
Petr Nymsa:

Je to zvyklost no. Nepřímo nás k tomu vede i sám pán Microsoft

Odpovědět 2.4.2013 17:41
Pokrok nezastavíš, neusni a jdi s ním vpřed
Avatar
Kit
Redaktor
Avatar
Odpovídá na Petr Nymsa
Kit:

Některé zvyklosti jsou podivné. V reálném životě se třídy píší malými písmeny, objekty velkými, ale například v Javě je tomu přesně naopak. A pokud se chceme domluvit se zbytkem světa, musíme se tomu podřídit.

Odpovědět 2.4.2013 17:47
Vlastnosti objektů by neměly být veřejné. A to ani prostřednictvím getterů/setterů.
Avatar
luxurya
Neregistrovaný
Avatar
luxurya:

jak mam tento kod upravit aby mi hledal podle čísel? mam na jednom řáadku slova a název(text), zkoušel sem plno věci ale moc tomu nerozumím

static void serad2(string[] pole)
{
try
{

ArrayList ar = new ArrayList();

string radek;
using (StreamReader sr = new StreamReader(@"c:\sem­\dat.txt"))
do
{
radek = sr.ReadLine();
ar.Add(radek);
} while (radek != null);

ar.Sort();

StreamWriter sw = new StreamWriter(@"c:\sem­\dat2.txt");

for (int i = 0; i < ar.Count; i++)
{
sw.WriteLine(ar[i]);
Console.Write­Line(ar[i]);
}
sw.Close();
Console.Write­Line("je seřazeno ------------");

}
catch (Exception e)
{
Console.Write­Line(e.Message);
}
}

 
Odpovědět 27.5.2013 22:21
Avatar
matesax
Redaktor
Avatar
Odpovídá na luxurya
matesax:

Nerozumím zadání, nerozumím cyklu pro čtení, nerozumí tomu, proč nepoužíváš tagy code pro vložení zdrojáku, nerozumím, proč cokoliv strkáš do C:\ (uživatelská data patří do ApplicationData - data aplikací...), nerozumím, proč na to používáš ArrayList, nerozumím, proč to píšeš sem - nerozumím tedy asi ničemu...

Nejčastěji se používá:

string line;

using (StreamReader sr = new StreamReader(...))
    while((line = sr.ReadLine()) != null)

Pokud je náhodou správně, že chceš data na určitém řádku, tak pokud jde o jeden řádek, tak se cyklus asi vyplatí:

string line;

using (StreamReader sr = new StreamReader(...))
   for(int row = 0; (line = sr.ReadLine()) != null && row != cisloHledanehoRadku; row++);

Což je prázdný cyklus - načte do proměnné line hledaný řádek...

A pokud by jsi chtěl více řádků, tak to pomocí toho cyklu uložíš do dynamického (znaje počet řádků statického) IEnumerable a taháš podle indexů - co více si přát?

Editováno 28.5.2013 6:11
 
Odpovědět  +1 28.5.2013 6:10
Avatar
luxurya
Neregistrovaný
Avatar
luxurya:

diky moc, no ja vim že to pišu blbě, jsem v tom uplně nový a zkouším si to teprve

 
Odpovědět 28.5.2013 9:50
Avatar
Michal Žůrek (misaz):

Ahoj, když bych si dělal vlastní kolekci, třeba něco jako List(), tak jak docílím aby se mi to navenek chovalo jako pole.

třeba:

MojeKolekce<int> mk = new MojeKolekce<int>();
mk.Add("něco");
// A teď otázka:
Console.WriteLine(mk[0]);

jak docílím abych mohl získat ten prvek kdesi zevnitř přes index?

Odpovědět 10.7.2013 10:26
Nesnáším {}, proto se jim vyhýbám.
Avatar
Odpovídá na Michal Žůrek (misaz)
Luboš Běhounek (Satik):

do tridy MojeKolekce pridas neco jako

private List<String> mojePrivatniStringy;

public String this[int index]
    {
        get
        {
            return mojePrivatniStringy[index];
        }
        set
        {
            mojePrivateniStringy[index] = value;
        }
    }

A mohly by te zajimat rozhrani - treba IEnumerable, pripadne rovnou IList.

Odpovědět 10.7.2013 10:35
:)
Avatar
petr.skolar
Člen
Avatar
petr.skolar:

Super vysvětlení, hezký článek, určitě z toho něco využiji, díky

 
Odpovědět 23.7.2015 13:19
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 10 zpráv z 19. Zobrazit vše