Programowanie funkcyjne/Procedury wyższych rzędów i listy/Ćwiczenia: Różnice pomiędzy wersjami
m (→Ćwiczenia) |
|||
Linia 113: | Linia 113: | ||
{{cwiczenie|[<tt>Podział na listy rosnące</tt>]||}} | {{cwiczenie|[<tt>Podział na listy rosnące</tt>]||}} | ||
Napisz procedurę <tt>podzial: int list -> int list list</tt>, która dla danej listy liczb całkowitych <math>l = [x_1; x_2; \dots; x_n]</math> podzieli ją na listę list <math>[l_1; \dots; l_k]</math>, przy czym: | Napisz procedurę <tt>podzial: int list -> int list list</tt>, która dla danej listy liczb całkowitych <math>l = [x_1; x_2; \dots; x_n]</math> podzieli ją na listę list <math>[l_1; \dots; l_k]</math>, przy czym: | ||
* <math>l = l_1 @ \dots @ l_k</math>, | * <math>l = l_1 </math>@<math> \dots</math>@<math> l_k</math>, | ||
* każda z list <math>l_i</math> jest ściśle rosnąca, | * każda z list <math>l_i</math> jest ściśle rosnąca, | ||
* <math>k</math> jest najmniejsze możliwe. | * <math>k </math> jest najmniejsze możliwe. | ||
Przykład: <tt>podzial [1;3;0;-2;-2;4;9] = [[1; 3]; [0]; [-2]; [-2;4;9]]</tt>. | Przykład: <tt>podzial [1;3;0;-2;-2;4;9] = [[1; 3]; [0]; [-2]; [-2;4;9]]</tt>. | ||
Linia 121: | Linia 121: | ||
{{cwiczenie|[<tt>Podział na listy liczb tego samego znaku</tt>]||}} | {{cwiczenie|[<tt>Podział na listy liczb tego samego znaku</tt>]||}} | ||
Napisz procedurę <tt>podzial: int list -> int list list</tt>, która dla danej listy liczb całkowitych <math>l = [x_1; x_2; \dots; x_n]</math> podzieli ją na listę list <math>[l_1; \dots; l_k]</math>, przy czym: | Napisz procedurę <tt>podzial: int list -> int list list</tt>, która dla danej listy liczb całkowitych <math>l = [x_1; x_2; \dots; x_n]</math> podzieli ją na listę list <math>[l_1; \dots; l_k]</math>, przy czym: | ||
* <math>l = l_1 @ \dots @ l_k</math>, | * <math>l = l_1 </math>@<math> \dots </math>@<math> l_k</math>, | ||
* dla każdej listy <math>l_i</math> wszystkie elementy na takiej liście są tego samego znaku, | * dla każdej listy <math>l_i</math> wszystkie elementy na takiej liście są tego samego znaku, | ||
* <math>k</math> jest najmniejsze możliwe. | * <math>k</math> jest najmniejsze możliwe. |
Wersja z 12:32, 28 wrz 2020
Praca domowa
W rozwiązaniach poniższych zadań zamiast rekurencji, należy użyć standardowych procedur wyższych rzędów przetwarzających listy.
- Zapisz procedurę append za pomocą fold_right lub fold_left.
- Napisz procedurę, która dla danej listy funkcji oblicza ich złożenie.
- Napisz procedurę obliczającą sumę elementów listy występujących po ostatniej liczbie ujemnej (lub wszystkich, jeżeli na liście nie ma liczb ujemnych).
Ćwiczenia
W rozwiązaniach poniższych zadań zamiast rekurencji, należy użyć standardowych procedur wyższych rzędów przetwarzających listy.
Ćwiczenie [Ciągi różnicowe]
Przypomnij sobie zadanie o ciągu różnicowym danej listy liczb całkowitych. Rozwiąż je za pomocą procedur wyższych rzędów.
Rozwiązanie
Rozwiązanie
Ćwiczenie [Nawiasy]
Dany jest ciąg nawiasów, otwierających i zamykających. Napisz procedurę nawiasy, która obliczy minimalną liczbę nawiasów które należy obrócić, tak aby uzyskać poprawne wyrażenie nawiasowe. Jeżeli nie jest to możliwe, to należy podnieść wyjątek NieDaSie.
exception NieDaSię;; type nawias = Otwierający | Zamykający;; let nawiasy (l: nawias list) = ... ;;
Ćwiczenie [Suma funkcji]
Napisz procedurę, która dla danej listy funkcji, oblicza funkcję będącą sumą funkcji z danej listy.
Rozwiązanie
Ćwiczenie [Łączenie funkcji]
Rozszerz rozwiązanie poprzedniego zadania tak, żeby zamiast dodawania można było zastosować dowolną dwuargumentową operację na wynikach funkcji.
Rozwiązanie
Ćwiczenie [Exists]
Napisz procedurę exists, która dla danego predykatu i listy sprawdzi, czy na liście jest element spełniający predykat. Wykorzystaj wyjątki tak, aby nie przeglądać listy, gdy to już nie jest potrzebne.
Rozwiązanie
Ćwiczenie [Forall]
Napisz procedurę negującą predykat non: ('a -> bool) -> ('a -> bool). Za pomocą tej procedury oraz procedury exists zdefiniuj procedurę forall, która sprawdza, czy dany predykat jest spełniony przez wszystkie elementy danej listy. Czy zastosowanie wyjątków w implementacji procedury exists nadal powoduje, że przeglądane są tylko niezbędne elementy listy?
Ćwiczenie [Sumy]
Napisz procedurę sumy: int list -> int list, której wynikiem dla danej listy jest lista: .
Przykład: sumy [1; 5; 2; 7; 12; 10; 5] = [1; 6; 8; 15; 27; 37; 42].
Ćwiczenie [Podział na listy rosnące]
Napisz procedurę podzial: int list -> int list list, która dla danej listy liczb całkowitych podzieli ją na listę list , przy czym:
- @@,
- każda z list jest ściśle rosnąca,
- jest najmniejsze możliwe.
Przykład: podzial [1;3;0;-2;-2;4;9] = [[1; 3]; [0]; [-2]; [-2;4;9]].
Ćwiczenie [Podział na listy liczb tego samego znaku]
Napisz procedurę podzial: int list -> int list list, która dla danej listy liczb całkowitych podzieli ją na listę list , przy czym:
- @@,
- dla każdej listy wszystkie elementy na takiej liście są tego samego znaku,
- jest najmniejsze możliwe.
Przykład: podzial [1;3;0;-2;-2;-4;9] = [[1; 3]; [0]; [-2;-2;-4]; [9]].
Ćwiczenie [Prefiksy zakończone zerami]
Napisz procedurę prefiksy : int list -> int list list, która dla danej listy liczb całkowitych zwróci listę wszystkich jej prefiksów zakończonych zerem, w kolejności rosnącej ich długości. Przykład: prefiksy [0;1;2;3;0;5;6;0;1] = [[0]; [0;1;2;3;0]; [0;1;2;3;0;5;6;0]]. Zwróć uwagę na złożoność rozwiązania.
Ćwiczenie [Zamiana iloczynu sum na sumę iloczynów]
Wyobraźmy sobie, że jest dana lista list elementów:
Reprezentuje ona iloczyn sum elementów postaci:
Taki iloczyn sum jest równy sumie iloczynów postaci:
którą z kolei można przedstawić jako listę list postaci:
Napisz procedurę rozwin: 'a list list -> 'a list list, która przekształca listę list reprezentującą iloczyn sum w listę list reprezentującą sumę iloczynów. Kolejność składników w sumie i czynników w iloczynach może być dowolna.
Przykład: rozwin [[1;2;3]; [4]; [5;6]] = [[1; 4; 5]; [1; 4; 6]; [2; 4; 5]; [2; 4; 6]; [3; 4; 5]; [3; 4; 6]].
Rozwiązanie
Laboratorium
W rozwiązaniach poniższych zadań zamiast rekurencji, należy użyć standardowych procedur wyższych rzędów przetwarzających listy.
Ćwiczenie [Heads]
Za pomocą map i flatten zapisz procedurę heads, której wynikiem dla danej listy jest lista pierwszych elementów list składowych.
Rozwiązanie
Ćwiczenie [Quick-sort]
Korzystając z filter zaimplementuj alorytm quick-sort.
Rozwiązanie
Ćwiczenie [Kompresja ciągu liczb]
Rozważmy następującą metodę kompresji ciągów liczb całkowitych: jeżeli w oryginalnym ciągu ta sama liczba powtarza się kilka razy z rzędu, to jej kolejne wystąpienia reprezentujemy za pomocą jednej tylko liczby. Konkretnie, powtórzeń liczby reprezentujemy w ciągu skompresowanym jako .
Napisz procedury: kompresującą i dekompresującą zadaną listę. Lista skompresowana powinna być oczywiście jak najkrótsza. Przy dekompresji możesz założyć, że lista skompresowana nie zawiera zer.
kompresuj [1; 2; 2; 5; 11; 11; 2];; - : int list = [1; 6; 9; 42; 3]
Rozwiązanie
Ćwiczenie [Tails]
Załóżmy, że dana jest lista . Sufiksem tej listy nazwiemy każdą listę, którą można uzyskać przez usunięcie pewnej liczby (od 0 do ) jej początkowych elementów. Tak więc sufiksami danej listy będzie np. ona sama, pusta lista, a także . Napisz procedurę tails: 'a list -> 'a list list, która dla danej listy tworzy listę wszystkich jej sufiksów, uporządkowaną wg. malejących ich długości.
Rozwiązanie
Ćwiczenie [Uśrednienie listy]
Dana jest lista liczb zmiennopozycyjnych . Jej uśrednienie, to lista postaci: . Uśrednieniem listy jednoelementowej oraz pustej jest lista pusta. Napisz procedurę uśrednienie, która dla danej listy obliczy jej uśrednienie.
Rozwiązanie
Ćwiczenie [Od końca do końca]
Napisz funkcję od_końca_do_końca: int list -> int, która dla danej niepustej listy obliczy .
Ćwiczenie [Fold_tree]
type 'a tree = Node of 'a * 'a tree * 'a tree | Leaf;;
let rec fold_tree f a t = match t with Leaf -> a | Node (x, l, r) -> f x (fold_tree f a l) (fold_tree f a r);;
Użyj procedury fold_tree do:
- Policzenia liczby węzłów w drzewie.
- Policzenia wysokości drzewa.
- Policzenia średnicy drzewa (tj. najdłuższej prostej ścieżki w drzewie łączącej dwa liście).
- Sprawdzenia czy drzewo jest drzewem BST (dla drzewa liczb float).
Ćwiczenie [Obejście infiksowe]
Wyznacz listę wartości w węzłach danego drzewa w kolejności infiksowej, używając procedury fold_tree. Czy potrafisz to zrobić efektywnie?
Rozwiązanie [Rozwiązanie nieefektywne]
Rozwiązanie [Rozwiązanie efektywne]
Ćwiczenie [Liczba widocznych elementów]
Powiemy, że liczba w węźle drzewa jest widoczna, jeżeli na ścieżce od tego węzła do korzenia drzewa nie ma większej liczby. W szczególności liczba w korzeniu drzewa jest zawsze widoczna, a liczby mniejsze od niej nie są nigdy widoczne. Napisz procedurę widoczne:drzewo int, która dla zadanego drzewa (zawierającego wyłącznie liczby nieujemne) obliczy liczbę widocznych liczb. Rozwiązując to zadanie należy skorzystać z procedury fold_tree. Możesz założyć, że w drzewie nie ma liczb ujemnych.
Rozwiązanie
Ćwiczenie [Procedury wyższych rzędów dla drzew]
Przypomnij sobie rozwiązanie jednego z poprzednich zadań, gdzie trzeba było zdefiniować typ danych reprezentujący drzewa dowolnego (skończonego) stopnia. Zdefiniuj dla takich drzew odpowiedniki procedur map, filter i fold.
W procedurze filter, jeżeli odrzucamy jakiś wierzchołek, to odrzucamy również wszystkich jego potomków. Odpowiednik procedury fold powinien być sparametryzowany dwiema funkcjami: Jedna powinna działać "w poziomie", kumulując wyniki policzone dla poddrzew zakorzenionych w synach danego węzła. Druga powinna działać "w pionie", okreslając wynik dla poddrzewa zakorzenionego w danym węźle, na podstawie wartości przechowywanej w tym węźle oraz wyniku skumulowanego dla poddrzew zakorzenionych w jego synach.