Programowanie funkcyjne/Procedury wyższych rzędów i listy: Różnice pomiędzy wersjami

Z Studia Informatyczne
Przejdź do nawigacjiPrzejdź do wyszukiwania
Kubica (dyskusja | edycje)
Kubica (dyskusja | edycje)
Linia 13: Linia 13:
W trakcie przeglądania listy przechowujemy wynik pośredni, obliczony dla już przejrzanych elementów.  
W trakcie przeglądania listy przechowujemy wynik pośredni, obliczony dla już przejrzanych elementów.  
W każdym kroku przeglądamy jeden element listy i uwzględniamy go w wyniku pośrednim.  
W każdym kroku przeglądamy jeden element listy i uwzględniamy go w wyniku pośrednim.  
Możemy powiedzieć, ....
Możemy powiedzieć, że '''kumulujemy''' wpływ kolejnych elementów listy na wynik.  
Po przejrzeniu wszystkich elementów listy mamy gotowy wynik.  
Po przejrzeniu wszystkich elementów listy mamy gotowy wynik.  
</p>
<p align="justify">
W schemacie tym mamy następujące elementy zmienne:  
W schemacie tym mamy następujące elementy zmienne:  
* elementy listy przetwarzamy od lewej do prawej, lub odwrotnie,
* wynik dla pustej listy,  
* wynik dla pustej listy,  
* wynik dla nie pustej listy, jako funkcja głowy i wyniku obliczonego dla ogona.
* procedura ''kumulująca'' wpływ kolejnych elementów listy na wynik,  
* listę do przetworzenia.
Procedura realizująca powyższy schemat, przeglądająca elementy zgodnie z ich kolejnością na liście,
nosi tradycyjną nazwę <tt>fold_left</tt> i jest zdefiniowana następująco:
</p>
 
'''let rec''' fold_left f a l =
  '''match''' l '''with'''
    []  -> a |
    h::t -> fold_left f (f a h) t;;
''val fold_left : ('a -> 'b -> 'a) -> 'a -> 'b list -> 'a = <fun>''


W zależności od kierunku przetwarzania listy, schemat ten możemy ująć w postaci następujących dwóch procedur:
<p align="justify">
     
Pierwszym parametrem jest procedura kumulująca wynik.
'''let''' '''rec''' foldr f l a =  
Ma ona dwa argumenty: dotychczas obliczony wynik i kolejny element listy do przetworzenia.
&nbsp;&nbsp;'''match''' l '''with'''
Drugi argument to wynik dla pustej listy, a trzeci to lista do przetworzenia.
&nbsp;&nbsp;&nbsp;&nbsp;[]&nbsp;&nbsp; -> a |
</p>
&nbsp;&nbsp;&nbsp;&nbsp;h::t -> f h (foldr f t a);;
 
&nbsp;&nbsp;
Oto kilka prostych przykładów.
'''let''' '''rec''' foldl f a l =
&nbsp;&nbsp;'''match''' l '''with'''
&nbsp;&nbsp;&nbsp;&nbsp;[]&nbsp;&nbsp; -> a |
&nbsp;&nbsp;&nbsp;&nbsp;h::t -> foldl f (f a h) t;;
&nbsp;&nbsp;
'''let''' fold = foldl;;       
     
Oto kilka prostych zastosowań:
     
'''let''' sum l = fold (+) 0 l;;
'''let''' prod l = fold ( * ) 1 l;;
        
        
{{przyklad|[Przykład zastosowania <tt>fold_left</tt>]||}}
<p align="justify">
Poniższe dwie procedury obliczają odpowiednio sumę i iloczyn wszystkich elementów listy:
</p>
'''let''' sum l = fold_left (+) 0 l;;
''val sum : int list -> int = <fun>''
'''let''' prod l = fold_left ( * ) 1 l;;
''val prod : int list -> int = <fun>''
Inny schemat, to przetwarzanie wszystkich elementów listy, element po elemencie.
Inny schemat, to przetwarzanie wszystkich elementów listy, element po elemencie.
        
        
Linia 46: Linia 58:
  &nbsp;&nbsp;&nbsp;&nbsp;l  
  &nbsp;&nbsp;&nbsp;&nbsp;l  
  &nbsp;&nbsp;&nbsp;&nbsp;[];;
  &nbsp;&nbsp;&nbsp;&nbsp;[];;
     
* elementy listy przetwarzamy od lewej do prawej, lub odwrotnie,
W zależności od kierunku przetwarzania listy, schemat ten możemy ująć w postaci następujących dwóch procedur:
     
'''let''' '''rec''' foldr f l a =
&nbsp;&nbsp;'''match''' l '''with'''
&nbsp;&nbsp;&nbsp;&nbsp;[]&nbsp;&nbsp; -> a |
&nbsp;&nbsp;&nbsp;&nbsp;h::t -> f h (foldr f t a);;
&nbsp;&nbsp;
        
        



Wersja z 14:44, 3 wrz 2006

Wstęp

Istnieje zestaw standardowych procedur wyższych rzędów specjalnie przeznaczonych do przetwarzania list. Będą one tematem tego wykładu. Większość procedur przetwarzających listy jest budowana według kilku powtarzających się schematów. Ujmując te schematy w postaci procedur wyższych rzędów uzyskamy procedury wyższych rzędów, o których jest mowa.

Fold

Jeden z najczęściej występujących schematów procedur przetwarzających listy polega na tym, że przeglądamy kolejne elementy listy i obliczamy na ich podstawie pewien wynik. W trakcie przeglądania listy przechowujemy wynik pośredni, obliczony dla już przejrzanych elementów. W każdym kroku przeglądamy jeden element listy i uwzględniamy go w wyniku pośrednim. Możemy powiedzieć, że kumulujemy wpływ kolejnych elementów listy na wynik. Po przejrzeniu wszystkich elementów listy mamy gotowy wynik.

W schemacie tym mamy następujące elementy zmienne:

  • wynik dla pustej listy,
  • procedura kumulująca wpływ kolejnych elementów listy na wynik,
  • listę do przetworzenia.

Procedura realizująca powyższy schemat, przeglądająca elementy zgodnie z ich kolejnością na liście, nosi tradycyjną nazwę fold_left i jest zdefiniowana następująco:

let rec fold_left f a l = 
  match l with 
    []   -> a |
    h::t -> fold_left f (f a h) t;;
val fold_left : ('a -> 'b -> 'a) -> 'a -> 'b list -> 'a = <fun>

Pierwszym parametrem jest procedura kumulująca wynik. Ma ona dwa argumenty: dotychczas obliczony wynik i kolejny element listy do przetworzenia. Drugi argument to wynik dla pustej listy, a trzeci to lista do przetworzenia.

Oto kilka prostych przykładów.

Przykład [Przykład zastosowania fold_left]

Poniższe dwie procedury obliczają odpowiednio sumę i iloczyn wszystkich elementów listy:

let sum l = fold_left (+) 0 l;;
val sum : int list -> int = <fun>

let prod l = fold_left ( * ) 1 l;;
val prod : int list -> int = <fun>

Inny schemat, to przetwarzanie wszystkich elementów listy, element po elemencie.

let map f l = 
  foldr 
    (fun h t -> (f h)::t) 
    l 
    [];;
     
  • elementy listy przetwarzamy od lewej do prawej, lub odwrotnie,

W zależności od kierunku przetwarzania listy, schemat ten możemy ująć w postaci następujących dwóch procedur:

let rec foldr f l a = 
  match l with
    []   -> a |
    h::t -> f h (foldr f t a);;
  
     

Kolejny częsty schemat, to selekcja elementów listy --- sprawdzamy pewien warunek i zostawiamy tylko elementy spełniające go.

let filter p l = 
  foldr (fun h t -> if p h then h::t else t) l [];;