Seznamy
                                   -=-=-=-=-

     A language that doesn't affect the way you think about programming is
                              not worth knowing.

          Jak to tak  už u neprocedurálních  jazyků  bývá,  seznamy  hrají
      v  prologu   důležitou  roli.  Prolog  reprezentuje  seznamy  pomocí
      funktoru tečka a prázdného seznamu - []. Má ale i zjednodušený zápis
      pomocí hranatých závorek:

      [1,2,3,4] je to samé jako .(1,.(2,.(3,.(4,'[]'))))
                                       .
                                      / \
                                     1   .
                                        / \
                                       3   .
                                          / \
                                         4   []

          Prolog navíc definuje operátor |, který odřezává hlavu od zbytku
      seznamu. Například pokud výraz [X|Y] potřebuje zunifikovat se seznamem
      [1,2,3,4,5] výsledek bude X=1, Y=[2,3,4,5]. Je možné odřezávat i víc
      prvků apod:

      [A,B|X] první dva prvky jsou A,B a zbytek je X (může to být i prázdný
              senzam
      [A|[B|X]] to samé.

      Tedy platí:

      [a,b,c] = [a|[b,c]] = [a,b|[c]] = [a,b,c|[]] = [a|[b|[c]]] =
      [a|[b|[c|[]]]] = .(a,.(b,.(c,'[]'

          Má i mnoho  zabudovaných  predikátů  pro  práci se  seznamy.  Ty
      nejzákladnější tu uvedu i s jejich implementací:

      member(X,Y) - zjištuje, jestli X je prvkem seznamu Y. V prologu
      jde implementovat například takto:

      member(X,[X,_]).    % X je prvkem seznamu, pokud je na prvním místě
      member(X,[_,L]) :- member(X,L). % nebo pokud je prvkem seznamu bez
                          % prvního prvku

      member má samozřejmě i jiné použití. Například:

      ?- member(X,[1,2,3]).
      X = 1 ;
      X = 2 ;
      X = 3 ;
      No

      Takto může být použit pro vyhledávání v seznamu.

      1 ?- member(a,X).
      X = [a|G980] ;
      X = [G976,a|G992] ;
      X = [G976,G988,a|G1004] ;
      X = [G976,G988,G1000,a|G1016] ;
      X = [G976,G988,G1000,G1012,a|G1028]
      ....

          a takto může být použit pro generování všech seznamů  obsahující
      daný prvek.  ([a|G980]  znamená  seznam z prvkem a na prvním  místě.
      G980 je volná proměná a program ji později může z něčím zunifikovat.
      [G976,a|G992] je zase seznam z prvkem a na druhém místě atd...

          Další užitečný  predikát je append(X,Y,Z), který spojuje seznamy
      X a Y do Z.

      append([],L,L). % seznam spojený z prázdným seznamem je ten samý seznam
      append([X|L1],L2,[X|L3]) :- append(L1,L2,L3).
                      % pokud chces spojit dva seznamy, odřízni první prvek,
                      % potom spoj zbytek a první prvek zase přidej.

          Append se opět může použít nejen pro spojování dvou seznamů, ale
      také na dělění jednoho seznamu na dvě půlky - append(X,Y,[1,2,3,4]),
      nebo odčítání jednoho seznamu od druhého.

          Pomocí appendu lze například (neoptimálně) implementovat i member:

      member(X,Y) :- append(_,[X|_],L).


          Jiný zajímavý  predikát je reverse,  který  otáčí  seznam.  Jeho
      nejjednodušší implementace asi je:

      reverse([],[]).         %otočený prázdný seznam je prázdný seznam
      reverse([X|L],R):-      %na otočení seznamu napřed odřízní první prvek
          reverse(L,R1),      %otoč zbytek
          append(R1,[X],R).   %a potom první prvek přidej nakonec

          Problém ale je, že potom  potřebuje  N^2 kroků - append  pracuje
      v  lineárním  čase  narozdíl  od  přidávání  na  začátek,  které  je
      jednoduché. To lze obejít pomocí akumulátoru:

      reverse(L,R) :- rev1(L,[],R).
      rev1([],R,R).
      rev1([X|L],A,R) :- rev1(L,[X|A],R).

          Tady se napřed zavolá pomocný  predikát a akumulátor se snuluje.
      Potom se na jeho první pozici přidá první  prvek z L. První prvek se
      z L odřízne a postup se opakuje. Probíhá to tedy následovně:

      X = [1,2,3,4] Acc=[]
      X = [2,3,4]   Acc=[1]
      X = [3,4]     Acc=[2,1]
      X = [4]       Acc=[3,2,1]
      X = []        Acc=[4,3,2,1]

          Jak  vidíte,  v  akumulátoru  se  nashromáždí  obrácený  seznam.
      Nakonec  je ho ale  nutné  dostat z rekurze  ven.  To se udělá  tak,
      že na konci se  akumulátor  zunifikuje z proměnou  R, která se jinde
      neměnila.  Dostane se tedy z rekurze  ven a výsledek se dostane do R
      ve volání z predikátu reverse.

          Rozdíl  oproti  předchozímu je, že se vždy  přidávalo na začátek
      a nebylo tedy nutné seznam procházet.

          Predikát  msort  zadaný list setřidí. K tomu používá  algoritmus
      mergesort.   Mergesort   je  jeden z nejvýkonějších  a nejznámějších
      algoritmů na třídění.  Oproti  asi  nejpoužívanějšímu  quicksortu ma
      tu výhodu, že jeho  nejhorší  časová  složitost je O(N*log(N)) a tak
      v nejhorším  případě je výrazně rychlejší než quicksort. V průměrném
      případě je rychlost (až na konstantu) stejná.

          Algoritmus  probíhá  tak,  že aby  setřídil n prvků,  je  napřed
      libovolně rozdělí na dvě části o velikosti n/2. Potom tyto dvě části
      stejným  způsobem  setřidí a nakonec  setříděné  posloupnosti spojí.
      Spojit  dve  setříděné  posloupnosti je  jednoduché.  Vždy se koukne
      na první  prvky obou posloupností a vybere ten menší,  načež  postup
      opakuje.

          Nejprve je tedy nutné posloupnost rozdělit. To lze implementovat
      například takto:

      split([],[],[]).
      split([X],[X],[]).
      split([A,B|X],[A|Y],[B|Z]) :- split(X,Y,Z).

          Tato implementace rozděluje seznam podle lichých a sudých prvků.
      Potom je nutné posloupnosti opět spojit:

      mmerge([],[],[]).    %prázdné seznamy se spojují do prázdného
      mmerge([X],[],[X]).  %jednoprvkový a prázdný seznam se spojuje do
                           %jednoprvkového
      mmerge([],[X],[X]).  %prázdný a jednoprvkový
      mmerge([A|X],[B|Y],[A|Z]) :- A @< B,mmerge(X,[B|Y],Z).
                           %pokud je první prvek z prvního seznamu menší
      mmerge([A|X],[B|Y],[B|Z]) :- A @>= B,mmerge([A|X],Y,Z).
                           %pokud je první prvek z prvního seznamu větší

          Operátor X @< Y určuje jestli term X je menší, než term Y. Pokud
      termy jsou čísla, je to jednoduché.  Pokud se jedná o atomy,  pořadí
      je abecední a pokud jsou to struktury, napřed se porovnají  funktory
      a potom stejně jednotlivé parametry. Operátor @>= funguje stejně pro
      větší, nebo rovno.

          A nakonec můžeme napsat vlastní msort:

      mmsort([],[]).
      mmsort([X],[X]).
      mmsort([X1,X2|X],Y) :- split([X1,X2|X],A,B),mmsort(A,C),mmsort(B,D),
                             mmerge(C,D,Y).

          [X1,X2|X]  je zde  nutné,  protože  jinak  by prolog  unifikovat
      poslední  predikát  i z prázdnými a jednoprvkovými  seznamy a to  je
      špatně - dával by několik různých výsledků.

          Jak vídíte, prolog se hodí i pro některé algoritmické  problémy.
      Třídění by samozžejmě  šlo popsat  deklarativně,  ale  prolog by pro
      hledání setříděné  posloupnosti použil nějakou metodu pokusu a omylu
      a to by asi  nebylo  nejlepší. Na druhou  stranu by ale  asi  takový
      predikát šel použít i obráceně (na vygenerování všech permutací dané
      posloupnosti)


            výheň