Haskell Hero

Interaktivní učebnice pro začínající Haskellisty

Seznamy II

Hromadný výčet

Doposud jsme seznamy zapisovali běžným vypsáním jednotlivých prvků. Tzn. když jsme potřebovali seznam celých čísel od jedné do pěti, zapsali jsme jej takto: [1,2,3,4,5] Zapsat takový seznam nám nedělá žádný větší problém. Toto řešení ovšem začíná být nevhodné, pokud budeme potřebovat například seznam čísel od jedné do sta. Nebo třeba od jedné do milionu. A nebo ještě lépe – nekonečný seznam od jedné do nekonečna. V tuto chvíli nám pomůže zápis seznamů hromadným výčtem.

Seznam čísel od jedné do pěti zapíšeme [1..5]. Tím pádem nám nedělá problém zapsat seznam od jedné do milionu: [1..1000000] Seznam od jedné do nekonečna zapíšeme podobně, bez udání vrchní hranice: [1..] Pokud si výraz [1..] necháme vyhodnotit, Hugs začne do nekonečna vypisovat na obrazovku čísla od jedné do nekonečna. Zastavit jej můžeme klávesovou kombinací Ctrl+C.

Příklady

[1..10]        ~>*  [1,2,3,4,5,6,7,8,9,10]
[10..]          =   [10,11,12,13,14,15, ... ]
take 3 [10..]  ~>*  [10,11,12]
[20..10]       ~>*  []
['a'..'f']     ~>*  "abcdef"


Dále by se nám určitě hodil seznam všech lichých čísel od jedné do desíti. Ten zapíšeme následovně: [1,3..10], obecně [m,s..n]. Hodnota m udává první prvek seznamu, s druhý, rozdíl mezi s a m udává rozdíl mezi sousedními prvky seznamu a n udává horní (při rostoucím seznamu) nebo dolní (při klesajícím seznamu) hranici.

Pokud je s větší než m, bude výsledný seznam rostoucí. Pokud je s menší než m, bude výsledný seznam klesající. Pokud s == m, bude výsledný seznam konstantní. I zde můžeme použít nekonečnou variantu bez horní / dolní hranice.

Příklady

[1,3..10]       ~>*  [1,3,5,7,9]
[10,20..]        =   [10,20,30,40,50 ... ]
[1,2..0]        ~>*  []
[10,9..1]       ~>*  [10,9,8,7,6,5,4,3,2,1]
[10,9..20]      ~>*  []
['f','d'..'a']  ~>*  "fdb"
['&','&'..]      =   "&&&&&&&&&&&& ... "

Intenzionální zápis

Příklad

Vytvořte rostoucí seznam všech čísel od jedné do milionu, která jsou buď sudá nebo dělitelná pěti.


Z matematiky víme, jak bychom zapsali množinu čísel splňující zadané podmínky:

{ x | x je z množiny {1,..., 1000000},
      x je sudé nebo x je dělitelné pěti}
V Haskellu je zápis velice podobný:
[ x | x <- [1..1000000], even x || x `mod` 5 == 0]
Jak bude probíhat vyhodnocení takového výrazu? Hugs postupně projde všechny prvky ze seznamu [1..1000000] a každý otestuje, zda vyhovuje podmínce
even x || x `mod` 5 == 0
Pokud vyhoví, přidá se do výsledného seznamu. Pokud nevyhoví, testuje se další prvek.

Konstrukci x <- s, kde s je seznam, se říká generátor.


Co když se nám v zápise objeví generátorů více? Uveďme si krátký příklad:

[ (x,y) | x <- [1..3], y <- [1..x] ]
Jak bude vypadat vyhodnocení tohoto seznamu?

  • Za x se dosadí 1.
    • Za y se dosadí 1.
    • Do výsledného seznamu se přidá dvojice (1,1).
  • Za x se dosadí 2.
    • Za y se dosadí 1.
    • Do výsledného seznamu se přidá dvojice (2,1).
    • Za y se dosadí 2.
    • Do výsledného seznamu se přidá dvojice (2,2).
  • Za x se dosadí 3.
    • Za y se dosadí 1.
    • Do výsledného seznamu se přidá dvojice (3,1).
    • Za y se dosadí 2.
    • Do výsledného seznamu se přidá dvojice (3,2).
    • Za y se dosadí 3.
    • Do výsledného seznamu se přidá dvojice (3,3).

Výsledný seznam tedy bude vypadat následovně:

[ (1,1), (2,1), (2,2), (3,1), (3,2), (3,3) ]
Podobně by vyhodnocení vypadalo, kdyby se generovalo ze tří generátorů.

Další příklady

[ 2*x | x <- [1..5] ]  ~>*  [2,4,6,8,10]

map    f s  =  [ f x | x <- s ]
filter p s  =  [  x  | x <- s, p x ]