|
|
Część II
|
Zadanie 1 (4p.)
Rozważmy język imperatywny IMP, którego składnia zawiera następujące kategorie syntaktyczne:
- wyrażenia arytmetyczne: a := n | x | plus a a | mult a a
- wyrażenia logiczne: b := tt | ff | and b b | or b b |
not b | equal a a
- instrukcje: c := assign x a | skip | seq c c |
if b then c else c | for x from a to a do c
| while b do c
gdzie n oznacza dowolną liczbę całkowitą, a x oznacza dowolną zmienną.
Programem w języku IMP nazywamy parę złożoną z jednej instrukcji oraz
wyrażenia arytmetycznego. Wykonanie
programu (c, a) rozpoczyna
się przy pustej pamięci i polega na wykonaniu instrukcji c (według
semantyki podanej poniżej), a następnie obliczeniu wartości wyrażenia
a przy stanie pamięci otrzymanym jako wynik wykonania
instrukcji c. Semantyka wyrażeń arytmetycznych i logicznych
jest standardowa, a ich ewaluacja (tj. obliczanie wartości) nie
zmienia stanu pamięci. Odwołanie się do pamięci, w której nie ma
wartości szukanej zmiennej powoduje natychmiastowe przerwanie
obliczenia i rzucenie wyjątku.
Napisz w Ocamlu interpreter języka IMP według następujących wskazówek:
- zdefiniuj typy danych: aexp dla wyrażeń
arytmetycznych, bexp dla wyrażeń logicznych i cmd
dla instrukcji
- zdefiniuj typ zmiennych jako string: type var = string
- niech mem będzie typem stanów pamięci; interpreter może
odwoływać się do pamięci wyłącznie za pomocą funkcji lookup :
mem -> var -> int (do wyszukiwania wartości zmiennej w pamięci)
oraz update : mem -> var -> int -> mem (do zmiany wartości
zmiennej w pamięci)
- zaimplementuj powyższe funkcje dostępu do pamięci przyjmując
mem = var -> int (pamięć jest funkcją przyporządkowującą
zmiennym ich wartości) oraz zdefiniuj pustą pamięć empty_mem :
mem jako funkcję rzucającą wyjątek dla dowolnego argumentu
- napisz funkcję aeval ewaluacji wyrażeń arytmetycznych
- napisz funkcję beval ewaluacji wyrażeń logicznych
- napisz funkcję exec interpretującą instrukcje według semantyki podanej poniżej
- napisz funkcję run interpretującą program
- po zrealizowaniu powyższych punktów napisz w języku IMP program
obliczania silni i przetestuj na nim swój interpreter
Semantyka instrukcji:
(a,σ) → n
-------------------------------
(assign x a, σ) → σ[x:=n]
|
-------------------------------
(skip , σ) → σ
|
(c1,σ) → σ' (c2,σ') → σ''
--------------------------------
(seq c1 c2, σ) → σ''
|
(b,σ) → tt (c1,σ) → σ'
--------------------------------
(if b then c1 else c2, σ) → σ'
|
(b,σ) → ff (c2,σ) → σ'
--------------------------------
(if b then c1 else c2, σ) → σ'
|
(b,σ) → tt (c,σ) → σ'
(while b do c,σ') → σ''
--------------------------------
(while b do c, σ) → σ''
|
(b,σ) → ff
--------------------------------
(while b do c, σ) → σ
|
(a1,σ) → n (a2,σ) → m Repeat (x,n,m,c,σ,σ')
--------------------------------
(for x from a1 to a2 do c, σ) → σ'
|
n > m
--------------------------------
Repeat (x,n,m,c,σ,σ)
|
n <= m (c,σ[x:=n]) → σ' Repeat (x,n+1,m,c,σ',σ'')
--------------------------------
Repeat (x,n,m,c,σ,σ'')
|
|
Zadanie 2 (8p.)
Rozważmy typ danych do reprezentacji termów
w rachunku lambda (ze zmiennymi jako napisami):
type term =
Var of string
| Abs of string * term
| App of term * term
[6p.] Napisz dwa ewaluatory implementujące semantykę małych kroków dla termów zamkniętych w tym języku: jeden stosujący strategię gorliwą (definicja
relacji ->v poniżej), a drugi leniwą (definicja
relacji ->n poniżej).
s jest wartością
---------------------------
(\x.t) s ->v t[x:=s]
|
t ->v t'
---------------------
t s ->v t' s
|
s ->v s' i t jest wartością
---------------------
t s ->v t s'
|
---------------------------
(\x.t) s ->n t[x:=s]
|
t ->n t'
---------------------
t s ->n t' s
|
t[x:=s] oznacza podstawienie termu s pod wolne
wystąpienia zmiennej x w termie t. Aby zapewnić
poprawność operacji podstawienia, żadna zmienna wolna w s nie
może występować jako zmienna związana w termie t. W przypadku
jednak, gdy rozważamy wyłącznie termy zamknięte
(tj. bez wolnych zmiennych), w trakcie ich ewaluacji ten problem
nie wystąpi.
Wartością nazywamy taki term, który nie redukuje się do
innego termu; np. dla relacji ->v wartością jest
każdy taki term t, dla którego nie istnieje term s taki,
że t ->v s.
Przetestuj ewaluatory. Znajdź przykłady termów, dla których działanie
obu ewaluatorów się różni (dają w wyniku różne wartości; jeden się
zatrzymuje, a drugi nie).
[2p.] Rozszerz język i ewaluatory o termy logiczne true,
false oraz konstrukcję warunkową if o standardowej
semantyce. Jak zmieni się zbiór wartości dla obu relacji?
|
|
|
|
Część II
|
Zadanie 1 (4p.)
Definiujemy typ danych
do reprezentacji drzew binarnych przechowujących
wartości w liściach:
type 'a btree = Leaf of 'a
| Node of 'a btree * 'a btree
Dwa drzewa binarne typu t btree mają jednakowe korony
(zakładamy, że elementy typu 'a są porównywalne), jeśli
listy utworzone przez odczytanie wartości w ich liściach od lewej
do prawej są równe. Na przykład drzewa
Node (Node (Leaf 1,
Leaf 2),
Leaf 3)
i
Node (Leaf 1,
Node (Leaf 2,
Leaf 3))
mają jednakowe korony, równe [1; 2; 3].
- (1p.) Napisz funkcję rozstrzygającą czy dwa drzewa mają
jednakowe korony, bazując bezpośrednio na definicji i nie
dbając o efektywność rozwiązania.
- (3p.) Wykorzystując pojęcie odroczonego obliczenia, napisz
efektywną i czysto funkcyjną wersję
funkcji samefringe, tj. taką, która przerywa
obliczenia w momencie napotkania pierwszej różnicy między
koronami drzew. Podpowiedź: należy odraczać trawersowanie
prawego poddrzewa.
|
Zadanie 2 (4p.)
Rozwiąż zadanie kontrolne do Wykładu 8 (gra nim w Haskellu).
|
Zadanie 3 (6p.)
Przeanalizuj interpreter Prologa zamieszczony tutaj.
Rozważmy typ danych do reprezentowania wyrażeń regularnych:
type regexp =
| Atom of char
| And of regexp * regexp (* r1r2 *)
| Or of regexp * regexp (* r1 | r2 *)
| Star of regexp (* r* *)
Bazując na modelu obliczeń z nawrotami przy użyciu kontynuacji sukcesu
oraz kontynuacji porażki, zaprezentowanym na przykładzie interpretera
Prologa, napisz funkcje
match_regexp : regexp -> char list -> (char list -> (unit -> 'a) -> 'a) -> (unit -> 'a) -> 'a
oraz
run : regexp -> char list -> bool
które dla danego wyrażenia regularnego
implementują niedeterministyczny automat
rozpoznający język opisany przez to wyrażenie.
|
|
|
Część I
|
Zadanie 1 (5p.)
Rozwiąż zadanie 1 z listy zadań kontrolnych
do wykładu 7 (4p.). Następnie przetestuj implementację z punktu c) używając jej do
napisania funkcji przechodzenia wszerz drzewa binarnego (1p.).
|
Zadanie 2 (2p.)
Rozważmy modyfikowalne listy zdefiniowane następująco:
type 'a list_mutable = LMnil | LMcons of 'a * 'a list_mutable ref
Zaimplementuj konkatenację list typu 'a list_mutable na dwa sposoby:
- funkcja concat_copy buduje listę wynikową kopiując pierwszy argument;
- funkcja concat_share buduje listę wynikową bez kopiowania argumentów.
Skonstruuj odpowiednie przykłady testujące implementację.
|
|
Część II
|
Zadanie 3 (4p.)
Technika memoizacji pozwala wykorzystać
cechy imperatywne języka w celu zwiększenia efektywności działania
funkcji, która sama jest czysto funkcyjna, tj. kolejne wywołanie
takiej funkcji dla tego samego argumentu zwróci tę samą
wartość. Memoizacja polega na zapamiętywaniu wartości wywołań funkcji
dla konkretnych argumentów w pewnej strukturze danych, i na
wyszukiwaniu potrzebnych wartości przy kolejnych wywołaniach tej
funkcji.
Aby umożliwić memoizację dowolnej funkcji, zaimplementuj następujący schemat:
-
zdefiniuj typ polimorficzny ('a, 'b) memo służący jako tablica wartości
wywołań dowolnej funkcji
-
napisz funkcję tworzenia pustej tablicy create_memo : unit ->
('a, 'b) memo
-
napisz funkcję wyszukiwania w tablicy wartości funkcji dla
zadanego argumentu memo_find : ('a, 'b) memo -> 'a -> 'b option
-
napisz funkcję dopisującą do tablicy nową wartość wywołania
funkcji memo_add : ('a, 'b) memo -> 'a -> 'b -> unit
-
napisz funkcję memo_f : ('a -> 'b) -> 'a -> 'b, która dla
zadanej jako argument funkcji f : 'a -> 'b zwraca funkcję, która
działa tak samo jak f, ale wykorzystuje tablicę
memoizacji, więc jest potencjalnie bardziej
efektywna
Wykorzystaj ten schemat do memoizacji funkcji wyznaczającej kolejne
liczby Fibonacciego: napisz funkcję fib : int -> int według
standardowej definicji oraz funkcję fib_memo = memo_f fib wykorzystującą
memoizację. Porównaj czasy wykonania obu funkcji (użyj funkcji Sys.time).
Następnie zauważ, że ogólny schemat memoizacji nie uwzględnia faktu,
że fib jest funkcją rekurencyjną. Napisz ulepszoną
funkcję fib_memo' : int -> int, która wykorzystuje jawną
memoizację, i porównaj czasy wykonań z fib_memo.
|
Zadanie 4 (4p.)
Rozważmy sygnaturę dla funkcyjnych kolejek priorytetowych:
module type PQUEUE =
sig
type priority
type 'a t
exception EmptyPQueue
val empty : 'a t
val insert : 'a t -> priority -> 'a -> 'a t
val remove : 'a t -> priority * 'a * 'a t
end
- (1p.) Zdefiniuj moduł PQueue : PQUEUE, przyjmując
typ priority = int. Reprezentacja kolejki może być dowolna.
- (1p.) Wykorzystaj moduł PQueue do napisania funkcji sortowania list liczb
typu int.
- (2p.) Uogólnij rozwiązanie punktów 1 i 2 definiując funktor, który dla
zadanego modułu OrdType : ORDTYPE zwraca moduł o sygnaturze PQUEUE, gdzie
module type ORDTYPE =
sig
type t
val compare : t -> t -> int
end
Zmodyfikuj odpowiednio funkcję sortowania list z p. 2 i przetestuj ją.
|
|
|
Część I
|
Zadanie 1 (2p.)
Napisz funkcję next, która
produkuje kolejne wartości silni w następujący sposób:
# next();;
- : int = 1
# next();;
- : int = 2
# next();;
- : int = 6
# next();;
- : int = 24
itd... oraz funkcję reset, która ustawia początkową wartość
argumentu dla następnych wywołań funkcji next, np.
# next();;
- : int = 120
# reset();;
- : unit = ()
# next();;
- : int = 1
# next();;
- : int = 2
Uwaga! Funkcje nie mogą wykorzystywać żadnych zmiennych globalnych.
|
Zadanie 2 (4p.) Rozważmy typ danych comp służący do reprezentowania obliczeń:
type 'a computation =
| Value of 'a
| Delayed of (unit -> 'a)
type 'a comp = 'a computation ref
Wykonanie obliczenia implementuje funkcja compute : 'a comp -> 'a zdefiniowana następująco:
let compute c =
match !c with
| Value a -> a
| Delayed t -> let a = t() in c := Value a; a
Zdefiniujmy teraz typ strumieni wykorzystujący takie obliczenia:
type 'a stream = Cons of 'a * 'a stream computation ref
Zdefiniuj funkcje:
- map : ('a -> 'b) -> 'a stream -> 'b stream
- take : int -> 'a stream -> 'a list
działające tak, jak ich odpowiedniki na listach,
oraz inne funkcje przydatne do testowania tak zdefiniowanych
strumieni.
Następnie zdefiniuj strumienie:
- ones złożony z samych jedynek,
- oraz nats złożony z kolejnych liczb
naturalnych.
|
|
Część II
|
Zadanie 3 (4p.)
Definiujemy typ danych do reprezentacji
drzew binarnych przechowujących wartości zarówno w węzłach jak i w
liściach:
type 'a btree = Leaf of 'a
| Node of 'a btree * 'a * 'a btree
- Napisz funkcję numerującą węzły i liście drzewa binarnego w
kolejności przechodzenia go w głąb (preorder). Na przykład, tak
ponumerowaną wersją drzewa
Node (Node (Leaf 'a', 'b', Leaf 'c'), 'd', Leaf 'e')
jest
Node (Node (Leaf 3, 2, Leaf 4), 1, Leaf 5).
- Napisz funkcję numerującą węzły i
liście drzewa binarnego w kolejności
przechodzenia go wszerz. Na przykład, tak
ponumerowaną wersją drzewa
Node (Node (Leaf 'a', 'b', Leaf 'c'), 'd', Leaf 'e')
jest
Node (Node (Leaf 4, 2, Leaf 5), 1, Leaf 3).
|
Zadanie 4 (4p.) Dalszy ciąg zadania 2: zdefiniuj strumień
złożony z kolejnych liczb o rozkładzie 2i*3j*5k (i, j, k -
naturalne) w porządku rosnącym i bez powtórzeń.
|
|
|
Część I
|
W poniższych zadaniach wykorzystaj typ do reprezentacji leniwych list
(strumieni) zdefiniowany na wykładzie.
Zadanie 1 (2p.)
- Zdefiniuj funkcję generującą nieskończony strumień o okresie zadanym jako zwykła
lista skończona, np. dla okresu [0;1] ma powstać strumień o
elementach 0,1,0,1,...
- Zdefiniuj funkcję, która dla zadanej listy leniwej ll generuje
listę leniwą, w której element stojący na i-tym miejscu w
liście ll jest powtórzony i razy, np. dla listy leniwej o
elementach 1,2,3,4, powinna powstać lista o elementach
1,2,2,3,3,3,4,4,4,4.
|
Zadanie 2 (4p.)
- Zdefiniuj strumień obliczający wartość liczby pi z rosnącą dokładnością,
korzystając ze wzoru Leibniza:
π/4 = 1 - 1/3 + 1/5 - 1/7 + ...
- Napisz funkcję przekształcającą dowolny strumień x1,x2,x3,... w strumień postaci
f x1 x2 x3,
f x2 x3 x4, f x3 x4 x5,..., dla dowolnej funkcji f.
- Korzystając z powyższych definicji i transformacji Eulera:
F x y z = z - (y-z)2/(x-2y+z)
utwórz nowy strumień, szybciej zdążający do liczby π.
- Znajdź inny wzór przybliżający liczbę π, zdefiniuj odpowiedni strumień i porównaj z wcześniej zdefiniowanymi strumieniami.
|
|
Część II
|
Zadanie 3 (4p.) Wykorzystując schemat algorytmu z nawrotami
podany na wykładzie, napisz funkcję generate_palindromes
generującą wszystkie palindromy złożone z zadanych liter. Przykładowo,
wywołanie funkcji
ltake (10, generate_palindromes ["a";"b";"c"]) powinno dać w wyniku listę:
[ []; ["a"]; ["b"]; ["c"]; ["aa"]; ["bb"]; ["cc"];
["aaa"]; ["aba"]; ["aca"]]
|
Zadanie 4 (2p.)
Napisz program rozwiązujący problem skoczka
szachowego: w jaki sposób skoczek może obejść całą szachownicę o
wymiarach n x n tak, by odwiedzić każde pole dokładnie raz?
Rozwiązaniem powinna być lista kolejnych pozycji na szachownicy
odwiedzonych przez skoczka. Wykorzystaj schemat
algorytmu z nawrotami omówiony na wykładzie.
|
|
|
Część I
|
Zadanie 1 (2p.) Napisz funkcję odwracania list
zagnieżdżonych zdefiniowanych na wykładzie. Funkcja powinna odwracać
każdą listę zagnieżdżoną w innej liście, np. reverse (N (2, N (3, NN (N
(4, N (5, Nil)), Nil)))) powinno zwracać NN (NN (Nil, N (5, N (4, Nil))), N (3, N
(2, Nil))), a reverse (N (2, Nil)) powinno zwracać N (2, Nil).
|
Zadanie 2 (4p.)
Rozważmy typ danych dla drzew binarnych, zdefiniowany następująco:
type 'a btree = Leaf | Node of 'a btree * 'a * 'a btree
Mówimy, że drzewo jest zbalansowane, jeśli dla każdego
węzła v liczby
węzłów w lewym i prawym poddrzewie zakorzenionym w v różnią się co najwyżej o 1.
- [1p.] Napisz funkcję, która dla zadanej listy etykiet tworzy
zbalansowane drzewo, dla którego tę listę można otrzymać przechodząc
je w porządku preorder.
- [1p.] Napisz funkcję, która dla zadanej listy etykiet tworzy
zbalansowane drzewo, dla którego tę listę można otrzymać przechodząc
je w porządku inorder.
- [2p.] Napisz funkcję, która dla zadanej listy etykiet konstruuje
wszystkie drzewa, których przejście w porządku preorder daje tę listę.
Przetestuj funkcje na przykładach.
|
Zadanie 3 (6p., dla chętnych) Zdefiniuj w Ocamlu operator
punktu stałego Y, a raczej taki jego wariant, który się typuje
(powinien być typu (('a -> 'b) -> 'a -> 'b) -> 'a -> 'b ) i
działa poprawnie. Problem z typowaniem można rozwiązać używając typu
rekurencyjnego. Wykorzystaj ten operator do zdefiniowania funkcji
liczącej np. silnię. Jaka jest różnica, gdy próbujemy to samo zrobić w Haskellu?
|
|
Część II
|
Zadanie 4 (6p.)
Zdefiniuj typ służący do reprezentacji formuł rachunku zdań
składających się ze zmiennych zdaniowych, negacji, koniunkcji i alternatywy.
- Napisz (pierwszą) funkcję sprawdzającą, czy dana formuła jest tautologią. W
tym celu należy generować kolejne wartościowania zmiennych zdaniowych
występujących w danej formule i sprawdzać dla nich wartość formuły. W
przypadku, gdy formuła nie jest tautologią, funkcja powinna - oprócz
tej informacji - podać jedno z wartościowań, dla których formuła nie
jest prawdziwa.
- W podobny sposób napisz funkcje sprawdzające, czy formuła jest sprzeczna i czy jest spełnialna.
- Napisz funkcję, która przekształca zadaną formułę w formułę jej
równoważną w koniunkcyjnej
postaci normalnej.
- Napisz (drugą) funkcję sprawdzającą (czysto syntaktycznie!), czy zadana formuła jest tautologią,
korzystając z faktu, że każdą formułę można przedstawić w koniunkcyjnej postaci normalnej.
- Napisz funkcję, która przekształca zadaną formułę w formułę jej
równoważną w dyzjunkcyjnej
postaci normalnej.
- Napisz (trzecią) funkcję sprawdzającą (czysto syntaktycznie!), czy zadana formuła jest sprzeczna,
korzystając z faktu, że każdą formułę można przedstawić w dyzjunkcyjnej postaci normalnej.
- Zadbaj, by wyświetlanie formuł i wartościowań było czytelne -
napisz odpowiednie funkcje konwersji i uźywaj ich do wyświetlania wyników.
- Przygotuj 5 różnych przykładów formuł (z 1,2,3 zmiennymi zdaniowymi) i przetestuj funkcje.
|
|
|
Część I
|
Zapoznaj się
ze wskazówkami
dotyczącymi dobrego stylu programowania w OCamlu i stosuj je.
|
Zadanie 1 (5p.)
Niech macierz kwadratowa n x n będzie reprezentowana wierszami jako
lista list, np. lista l = [[1.;2.;3.];[4.;5.;6.];[7.;8.;9.]] reprezentuje macierz
- Napisz funkcję, która dla zadanej macierzy kwadratowej i liczby
naturalnej n wyznacza n-tą kolumnę macierzy.
- Napisz funkcję transpozycji
macierzy; transpozycja macierzy l jest reprezentowana jako lista [[1.;4.;7.];[2.;5.;8.];[3.;6.;9.]].
- Napisz funkcję zipf,
która dla danych dwóch list typów 'a list i 'b list i funkcji dwuargumentowej f typu 'a -> 'b -> 'c tworzy listę
złożoną z wartości funkcji f na argumentach z obu list położonych na
tych samych pozycjach, np. zipf ( +. ) [1.;2.;3.] [4.;5.;6.] =
[5.;7.;9.].
- Wykorzystując funkcję zipf napisz funkcję mult_vec, która oblicza iloczyn zadanego wektora i zadanej macierzy,
np. mult_vec [1.;2.] [[2.;0.];[4.;5.]] = [10.;10.].
- Korzystając z funkcji mult_vec napisz funkcję mnożenia dwóch macierzy
kwadratowych tego samego rozmiaru.
Powyższe funkcje napisz korzystając z funkcji z modułu List, unikając używania konstrukcji let rec.
|
Zadanie 2 (2p.)
Napisz funkcję znajdującą wszystkie permutacje listy zadanej jako
argument.
|
|
Część II
|
Zadanie 3 (3p.)
Napisz funkcję, która dla zadanej listy - traktowanej jako permutacja pewnych elementów
- znajduje kolejną permutację tych samych elementów w porządku
leksykograficznym, np. dla listy [2;3;1] zadanej jako argument,
funkcja powinna zwrócić listę [3;1;2], a dla listy ['a';'b';'e';'a']
wynikiem powinna być lista ['a';'e';'b';'a'].
W przypadku, w którym zadana permutacja jest największa, funkcja
powinna zwracać permutację najmniejszą.
|
Zadanie 4 (3p.)
Napisz funkcję group, która grupuje w listę sąsiednie wystąpienia tego samego
elementu w zadanej liście początkowej (i zwraca listę list).
Przykład: wywołanie group [1;2;2;3;3;3;4] powinno zwrócić
listę [[1];[2;2];[3;3;3];[4]].
|
|
|
Część I
|
Znajdź implementacje ocamlowych funkcji map, rev i flatten z
modułu List. Czy wykorzystują rekursję ogonową?
Zadanie 1 (2p.)
Przetestuj działanie funkcji map
na listach o różnej długości, a następnie napisz i przetestuj jej
lepszą wersję, która nie powoduje przepełnienia stosu dla bardzo
dużych list (np. o 1 000 000 elementów).
|
Zadanie 2 (2p.)
Podobnie jak w Zadaniu 1, napisz i przetestuj funkcję flatten, która działa na dużych listach.
|
Zadanie 3 (2p.)
Stosując rekursję ogonową, napisz funkcję obliczającą n-ty wyraz ciągu
zdefiniowanego wzorem:
- a0 = 1
- a1 = 2
- an = 2 * an-2 - an-1 + 1 dla n>1
|
|
Część II
|
Zadanie 4 (3p.)
- Napisz funkcję merge, która łączy dwie listy posortowane rosnąco w pewnym porządku <= tak, by wynik działania funkcji był
także listą posortowaną rosnąco w tym samym porządku.
Argumentami funkcji merge powinny być: funkcja cmp typu 'a -> 'a -> bool (zakładamy, że cmp a b = true,
gdy a<=b), i dwie listy elementów typu 'a.
Przykład: merge (<=) [1;2;5] [3;4;5] utworzy listę
[1;2;3;4;5;5].
-
Zapisz tę funkcję używając rekursji ogonowej i skonstruuj przykład pokazujący różnicę w efektywności tych dwóch definicji.
- Wykorzystaj funkcję merge do napisania funkcji sortowania przez scalanie.
|
Zadanie 5 (3p.)
Załóżmy, że wielomiany o współczynnikach
rzeczywistych są reprezentowane jako listy współczynników od
najwyższej potęgi do najniższej, np. [1.;0.;-1.;2.] oznacza wielomian
x3-x+2 (wielomian zerowy reprezentujemy jako listę [0.]).
Napisz funkcję, która dla zadanej listy reprezentującej wielomian i
dla danego argumentu x typu float, obliczy wartość tego
wielomianu w punkcie x. Napisz tę funkcję w dwóch wersjach: raz za
pomocą rekursji ogonowej, a następnie bez jawnego użycia rekurencji,
korzystając z odpowiedniej funkcji bibliotecznej modułu List.
|
|
|
Część I
|
Zadanie 1 (0p.)
-
Dla każdego z
typów int, float, char, string,
bool oraz unit napisz wyrażenie tego typu i
wykorzystaj interpreter OCamla (program ocaml, z linii poleceń
lub zintegrowany z emacsem) do obliczenia jego wartości.
-
Skompiluj i wykonaj przykładowy program za
pomocą kompilatorów ocamlc i ocamlopt
(por. Ocaml
Manual, Part III).
|
Zadanie 2 (0p.)
Czy następujące wyrażenia są dobrze typowane? Sprawdź ich typy i
wartości w interpreterze.
- if false then 1 else 3.5
- false || "ab">"cd"
- x = 5
- if true then ()
- let z = 3 and y = z + 4 in y * z
- let x = 2 in x ^ "aa"
- let y = "abc" in y ^ y
- (fun x -> x.[1]) "abcdef"
- (fun x -> x) true
- let x = [1;2] in x @ x
- let rec f f = f + f in f 42
|
Zadanie 3 (0p.)
Zidentyfikuj zmienne wolne i związane w wyrażeniach:
- let x = x in x ^ x
- let x = 10. in let y = x ** 2. in y *. x
- let x = 1 and y = x in x + y
- let x = 1 in fun y z -> x * y * z
|
Zadanie 4 (0p.) Anonimową funkcję dodawania dwóch liczb
całkowitych możemy zapisać za pomocą wyrażenia fun x y -> x +
y. Zdefiniuj tę funkcję nadając jej
identyfikator plus na 2 różne (składniowo) sposoby. Następnie
korzystając z jednej z tych definicji napisz funkcję plus_3
dodawania 3 do dowolnej liczby całkowitej.
|
Zadanie 5 (3p.)
Jaki jest typ wyrażenia fun x -> x? Napisz wyrażenie
anonimowe reprezentujące funkcję identycznościową, którego typem
w OCamlu jest typ int -> int.
Napisz dowolne wyrażenie typu ('a -> 'b) -> ('c -> 'a) -> 'c -> 'b
oraz dowolne wyrażenie typu 'a -> 'b.
|
Zadanie 6 (3p.)
Napisz funkcję definiującą złożenie dwóch funkcji oraz funkcję iterowania
wywołania funkcji wykorzystującą funkcję złożenia. Za pomocą iteracji
zdefiniuj mnożenie i potęgowanie.
|
|
Część II
|
Zadanie 7 (6p.)
Strumień (tj. nieskończony ciąg) liczb całkowitych
możemy reprezentować za pomocą funkcji typu int -> int w taki
sposób, że dla dowolnej takiej funkcji s, s 0
oznacza pierwszy element strumienia, s 1 następny, itd.
Używając powyższej reprezentacji zdefiniuj następujące funkcje na
strumieniach (tam, gdzie to możliwe, funkcje te powinny być polimorficzne, tj. powinny
działać na strumieniach o elementach dowolnego typu):
- hd, tl - funkcje zwracające odpowiednio głowę (pierwszy
element) i ogon strumienia (strumień zaczynający się od drugiego elementu)
- add - funkcja dodawania zadanej stałej do każdego
elementu strumienia i zwracająca powstały w ten sposób strumień
- map - funkcja, która dla zadanej operacji 1-argumentowej
przetwarza elementy zadanego strumienia za pomocą tej operacji i
zwraca powstały w ten sposób nowy strumień (tak, jak map na
listach skończonych)
- map2 - jak wyżej, ale dla zadanych: funkcji 2-argumentowej
i 2 strumieni
- replace - funkcja, która dla zadanego indeksu n,
wartości a i strumienia
s zastępuje co n-ty element strumienia s
przez wartość a i zwraca powstały w ten sposób strumień
- take - funkcja, która dla zadanego indeksu n i
strumienia s tworzy nowy strumień złożony z
co n-tego elementu strumienia s
- fold - funkcja, która dla zadanej
funkcji f:'a->'b->'a, wartości początkowej a:'a i
strumienia s elementów typu 'b tworzy nowy
strumień, którego każdy element jest wynikiem "zwinięcia"
początkowego segmentu strumienia s aż do bieżącego elementu
włącznie za pomocą funkcji f, tj. w strumieniu wynikowym
element o indeksie n ma wartość (... (f (f a s_0)
s_1)... s_n)
- tabulate - funkcja tablicowania strumienia, której
wartością powinna być lista elementów strumienia leżąca w zadanym
zakresie indeksów
Zdefiniuj strumień złożony z kolejnych liczb Fibonacciego
wykorzystując niektóre z powyższych funkcji i przetestuj
implementację.
W definicji funkcji tabulate wykorzystaj możliwość definiowania
parametrów opcjonalnych dla funkcji (niech początek zakresu indeksów
będzie opcjonalny i domyślnie równy 0).
Przykład.
Pisząc let f ?(x=0) y = x + y deklarujemy, że pierwszy
argument funkcji f o etykiecie x jest opcjonalny, a jego
wartość domyślna wynosi 0. Funkcję f można zatem wywołać za
pomocą wyrażenia f 3 (= 3) lub jawnie podając wartość
parametru opcjonalnego, za pomocą składni f ~x:42 3 (= 45).
|
|
|