Vaje: programiranje s tokovi
Ker je OCaml neučakan jezik, v njem ne moremo neposredno definirati koinduktivnih podatkovnih tipov, kot so neskončni tokovi. Lahko pa jih simuliramo z uporabo funkcij. Definicija tipa α stream izgleda tako:
type 'a stream = Cons of 'a * (unit -> 'a stream)
Prvi element toka smo zapisali neposredno, preostanek toka pa smo predstavili s funkcijo, ki sprejme »prazen« argument (unit) in vrne preostanek toka. To nam omogoča, da računanje preostanka toka preložimo do takrat, ko ga dejansko potrebujemo. Funkciji, ki odloži računanje, pravimo thunk.
Pri tem se definiranje tokov malenkost zakomplicira: v konstruktorju Cons bo drugi element zmeraj oblike fun () -> …. Obratno moramo pri uporabi vrednosti oblike Cons (a, b) preostanek toka zmeraj najprej izračunati z b ().
Tok iz seznama
Sestavimo funkcijo from_list tipa α list → α → α stream. Izraz
from_list [e₁, e₂, ..., eᵢ] r
naj vrne neskončni tok, ki ima prvih i elementov iz danega seznama, nato pa ponavlja element r:
e₁, e₂, ..., eᵢ, r, r, r, r, r, r, ...
Primer:
# let four = from_list [1;2;3] 4 ;;
val four : int stream = Cons (1, <fun>)
Namig: pri obravnavi seznama l si lahko pomagamo z izrazom match:
let rec from_list l r =
match l with
| [] -> …
| x::xs -> …
Seznam iz toka
Sestavimo še obratno funkcijo to_list n s, ki vrne seznam prvih n elementov danega toka s. Primer:
# to_list 10 four ;;
- : int list = [1; 2; 3; 4; 4; 4; 4; 4; 4; 4]
Namig: operator x :: xs doda element x na začetek seznama xs.
n-ti element toka
Sestavite funkcijo element_at n s, ki vrne n-ti element danega toka s. Primer:
# element_at 100 four ;;
- : int = 4
Glava in rep
Sestavite funkciji head : α stream → α in tail : α stream → α stream, ki vrneta glavo
in rep toka. Primer:
# head four ;;
- : int = 1
# tail four ;;
- : int stream = Cons (2, <fun>)
# head (tail four) ;;
- : int = 2
(Opomba: takih funkcij običajno ne uporabljamo, ker so bolj praktični vzorci, s katerimi lahko izluščimo glavo in rep.)
Preslikava
Sestavite funkcijo map, ki sprejme funkcijo f in tok
e₁, e₂, e₃, ...
ter vrne tok
f e₁, f e₂, f e₃, ...
Primer:
# to_list 5 (map (fun x -> x*x) four) ;;
- : int list = [1; 4; 9; 16; 16]
Kakšnega tipa je map?
Naravna števila
Sestavite tok nat naravnih števil 0, 1, 2, 3, .... Primer:
# to_list 10 nat ;;
- : int list = [0; 1; 2; 3; 4; 5; 6; 7; 8; 9]
Namig: pomagajte si s pomožno rekurzivno funkcijo from k, ki vrne tok števil od k naprej.
Fibonaccijeva števila
Sestavite tok fib Fibonaccijevih števil 0, 1, 1, 2, 3, 5, 8, .... Primer:
# to_list 10 fib ;;
- : int list = [0; 1; 1; 2; 3; 5; 8; 13; 21; 34]
Preslikava parov
Sestavite funkcijo
zip : (α → β → γ) → α stream → β stream → γ stream
ki sprejme funkcijo f in toka
d₁, d₂, d₃, ...
e₁, e₂, e₃, ...
ter vrne tok
f d₁ e₁, f d₂ e₂, f d₃ e₃, ...
Primer:
# to_list 10 (zip (fun x -> fun y -> x + y) nat (tail nat)) ;;
- : int list = [1; 3; 5; 7; 9; 11; 13; 15; 17; 19]
Večkratniki
Sestavite funkcijo
veckratniki_stevila : int -> int stream, ki sprejmekin vrne tok večkratnikovk. Na primer,veckratniki_stevila 3vrne tok0, 3, 6, 9, 12, ....Sestavite tok tokov
veckratniki : int stream stream, katerega elementi so tokovi večkratnikov:(0, 0, 0, 0, ⋯), (0, 1, 2, 3, ⋯), (0, 2, 4, 6, ⋯), (0, 3, 6, 9, ⋯), ⋯
Primer:
# to_list 10 (map (to_list 15) veckratniki) ;;
- : int list list =
[[0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0];
[0; 1; 2; 3; 4; 5; 6; 7; 8; 9; 10; 11; 12; 13; 14];
[0; 2; 4; 6; 8; 10; 12; 14; 16; 18; 20; 22; 24; 26; 28];
[0; 3; 6; 9; 12; 15; 18; 21; 24; 27; 30; 33; 36; 39; 42];
[0; 4; 8; 12; 16; 20; 24; 28; 32; 36; 40; 44; 48; 52; 56];
[0; 5; 10; 15; 20; 25; 30; 35; 40; 45; 50; 55; 60; 65; 70];
[0; 6; 12; 18; 24; 30; 36; 42; 48; 54; 60; 66; 72; 78; 84];
[0; 7; 14; 21; 28; 35; 42; 49; 56; 63; 70; 77; 84; 91; 98];
[0; 8; 16; 24; 32; 40; 48; 56; 64; 72; 80; 88; 96; 104; 112];
[0; 9; 18; 27; 36; 45; 54; 63; 72; 81; 90; 99; 108; 117; 126]]
★ Sploščen tok
Ta naloga ni tako dolgočasna kot ostale. Sestavite funkcijo
flatten : (α stream) stream → α stream
ki sprejme tok tokov in vrne tok, v katerem se vsak element iz tokov pojavi natanko enkrat (lahko predpostaviš, da so vsi elementi tokov med seboj različni).
★ Tok parov
Sestavite funkcijo
product : α stream → β stream → (α * β) stream
ki sprejme tokova
d₁, d₂, d₃, ...
e₁, e₂, e₃, ...
in vrne tok parov (dᵢ, eⱼ), v katerem se vsak par pojavi natanko enkrat. Vrstni red, v
katerem se pojavijo pari, ni pomemben. Primer:
# to_list 100 (product nat nat) ;;
- : (int * int) list =
[(0, 0); (0, 1); (1, 0); (0, 2); (1, 1); (2, 0); (0, 3); (1, 2); (2, 1);
(3, 0); (0, 4); (1, 3); (2, 2); (3, 1); (4, 0); (0, 5); (1, 4); (2, 3);
(3, 2); (4, 1); (5, 0); (0, 6); (1, 5); (2, 4); (3, 3); (4, 2); (5, 1);
(6, 0); (0, 7); (1, 6); (2, 5); (3, 4); (4, 3); (5, 2); (6, 1); (7, 0);
(0, 8); (1, 7); (2, 6); (3, 5); (4, 4); (5, 3); (6, 2); (7, 1); (8, 0);
(0, 9); (1, 8); (2, 7); (3, 6); (4, 5); (5, 4); (6, 3); (7, 2); (8, 1);
(9, 0); (0, 10); (1, 9); (2, 8); (3, 7); (4, 6); (5, 5); (6, 4); (7, 3);
(8, 2); (9, 1); (10, 0); (0, 11); (1, 10); (2, 9); (3, 8); (4, 7); (5, 6);
(6, 5); (7, 4); (8, 3); (9, 2); (10, 1); (11, 0); (0, 12); (1, 11);
(2, 10); (3, 9); (4, 8); (5, 7); (6, 6); (7, 5); (8, 4); (9, 3); (10, 2);
(11, 1); (12, 0); (0, 13); (1, 12); (2, 11); (3, 10); (4, 9); (5, 8);
(6, 7); (7, 6); (8, ...); ...]
Namig: uporabite map, map in flatten.