Skip to content

Commit 8edc916

Browse files
authored
Merge pull request mouredev#1485 from luishendrix92/main
#6 - OCaml
2 parents bc40005 + 96d2820 commit 8edc916

File tree

1 file changed

+295
-0
lines changed

1 file changed

+295
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
(**************************************************************************)
2+
(* *)
3+
(* Recursion *)
4+
(* *)
5+
(* I understand {b recursion} as a function that calls itself, or, that *)
6+
(* is defined in terms of itself. Its use cases are various: *)
7+
(* *)
8+
(* - {b Mathematics}: Factorial, fractals, fibonacci sequence. *)
9+
(* - {b Computer Science}: *)
10+
(* - Dynamic programming algorithms that solve problems by breaking *)
11+
(* them into smaller sub-problems (divide and conquer). *)
12+
(* - Tree-like data structures and their algorithms. *)
13+
(* - Graph theory algorithms. *)
14+
(* - Purely Functional programming, such as those algorithms that *)
15+
(* operate on iterative data structures (lists, sets, maps, etc). *)
16+
(* - {b Arts}: Matrioshka dolls, infinity mirror, droste effect. *)
17+
(* *)
18+
(* Functional programming languages rely heavily on recursion due to *)
19+
(* procedural loops being (mostly) rejected in favour of it. Take for *)
20+
(* example a purely FP language like Haskell; there is NO way to write *)
21+
(* loops so in order to iterate you are forced to think in terms of *)
22+
(* recursion. OCaml is not a purely FP language and provides both loops *)
23+
(* and mutable references for cases in which performance is needed. *)
24+
(* *)
25+
(* In OCaml, in order to mark a function as recursive, we need to add the *)
26+
(* [rec] keyword in front of [let], or pass an existing function to a *)
27+
(* {e fixing} function so that it works as intended. On top of that, the *)
28+
(* compiler guarantees that it will perform {b tail-call optimization} *)
29+
(* if it's possible. A tail-call optimized recursive function avoids *)
30+
(* creating multiple stack frames (leads to a {e stack overflow} if it's *)
31+
(* written in a way that the last call inside the function is the *)
32+
(* function itself with nothing in front or behind it, thus avoiding the *)
33+
(* need to backtrack. To achieve this, most programmers agree'd on a *)
34+
(* convention that involves an {e auxiliar} function defined inside or *)
35+
(* outside the main one, which in turn is called with some initial *)
36+
(* value for the accumulator and any dependency it might require. The *)
37+
(* main tradeoff is that some functions are incredibly complex to write *)
38+
(* with TCO in mind and may even require you to make use of stacks. *)
39+
(* *)
40+
(**************************************************************************)
41+
42+
module type Recursion = sig
43+
(** [print_range a b] prints lines with integer numbers from [a] to [b]. If no
44+
value is passed to [a], it will default to [0]. The order in which the
45+
printing happens depends on if [a > b] (descending order) or if [a <= b]
46+
(ascending order). This function is {b tail-call optimized}. *)
47+
val print_range : ?a:int -> int -> unit
48+
49+
(** [factorial n] is the result of multiplying [n * n-1 * n-2 * ...] until
50+
[n] reaches 0 (base case that results in 1). The recursive mathematical
51+
definition of the {e factorial} is: {m n! = n \cdot (n - 1)!}. This
52+
function is {b not tail-call optimized}. *)
53+
val factorial : float -> float
54+
55+
(** [factorial_tc n] is the {b tail-call optimized} version of {! factorial}.
56+
57+
@raise Failure if [n < 0] (negative factorials are impossible). *)
58+
val factorial_tco : float -> float
59+
60+
(** [fib n] is the nth integer in the fibonacci sequence
61+
([0, 1, 1, 2, 3, 5, 8, 13, ...]). [n] represents the index in the
62+
aforementioned sequence, which is computed by adding the previous two
63+
numbers starting with [0] and [1]. It can be solved iteratively with
64+
a [for] loop but it's best represented as a recursive evaluation tree:
65+
66+
{[
67+
⨍(5)
68+
/ \
69+
⨍(4) + ⨍(3) = 5
70+
/ \ / \
71+
⨍(3) + ⨍(2) = 3 ⨍(2) + ⨍(1) = 2
72+
/ \ / \
73+
⨍(2) + ⨍(1) = 2 ⨍(1) + ⨍(0) = 1
74+
/ \
75+
⨍(1) + ⨍(0) = 1
76+
]}
77+
78+
Few things can be observed in this evaluation tree:
79+
80+
+ From a very small number ([5]), a lot of branches and individual calls
81+
to [fib] are created, making it exponentially inefficient. This is
82+
called a {b naive implementation} of a dynamic programming algorithm.
83+
+ A lot of calls to [fib n] share the same input [n], making it possible
84+
to optimize using a technique called {b memoization}.
85+
86+
It is possible to run the 2 branches [n-1] and [n-2] in parallel which
87+
reduces the execution time by a decent margin (depending on how many
88+
{e physical cores} the CPU has), though it does come with a cost.
89+
90+
See https://v2.ocaml.org/releases/5.0/manual/parallelism.html. *)
91+
val fib : int -> int
92+
93+
(** [fib_memo n] is the {b memoized} version of [fib n]. Memoization is a
94+
of optimizing dynamic programic algorithms and expensive functions by
95+
having a cache map (implemented as a key-value pair) that saves computed
96+
results to it and if the function receives the same input(s) again, the
97+
cached value can be retrieved thus saving tons of execution time.
98+
99+
[fib_memo]'s cache is implemented with a [Hashtbl] (initial size [100]).
100+
101+
@raise Failure if [n < 0]. [n] should be positive (or [0]). *)
102+
val fib_memo : int -> int
103+
104+
(** [fib_tco n] is the {b tail-call optimized} version of [fib n].
105+
106+
@raise Failure if [n < 0]. [n] should be positive (or [0]). *)
107+
val fib_tco : int -> int
108+
end
109+
110+
module Exercise6 : Recursion = struct
111+
let rec print_range ?(a = 0) b =
112+
let delta = if a > b then -1 else 1 in
113+
print_endline (string_of_int a);
114+
if a <> b then print_range ~a:(a + delta) b
115+
;;
116+
117+
(* DIFICULTAD EXTRA (opcional):
118+
----------------------------
119+
Utiliza el concepto de recursividad para:
120+
- Calcular el factorial de un número concreto (la función recibe ese número).
121+
- Calcular el valor de un elemento concreto (según su posición) en la
122+
sucesión de Fibonacci (la función recibe la posición).
123+
*)
124+
125+
let rec factorial n = if n < 2. then 1. else n *. factorial (n -. 1.)
126+
127+
let factorial_tco n =
128+
let rec aux ~acc n =
129+
if n < 0.
130+
then failwith "Negative factorial"
131+
else if n < 2.
132+
then acc
133+
else aux ~acc:(acc *. n) (n -. 1.)
134+
in
135+
aux ~acc:1. n
136+
;;
137+
138+
let rec fib n = if n < 2 then n else fib (n - 1) + fib (n - 2)
139+
140+
let fib_memo =
141+
let cache = Hashtbl.create 100 in
142+
let rec aux n =
143+
if n < 0
144+
then failwith "Negative fibonacci sequence index"
145+
else if n < 2
146+
then n
147+
else begin
148+
match Hashtbl.find_opt cache n with
149+
| Some n -> n
150+
| None ->
151+
let result = aux (n - 1) + aux (n - 2) in
152+
Hashtbl.replace cache n result;
153+
result
154+
end
155+
in
156+
aux
157+
;;
158+
159+
let fib_tco n =
160+
let rec aux ~b ~a i = if i <= 0 then a else aux ~a:b ~b:(a + b) (i - 1) in
161+
if n < 0
162+
then failwith "Negative fibonacci sequence index"
163+
else aux ~a:0 ~b:1 n
164+
;;
165+
end
166+
;;
167+
168+
begin
169+
let open Exercise6 in
170+
let open Printf in
171+
print_range ~a:100 0;
172+
printf "Factorial of 5: %f\n" (factorial 5.);
173+
printf
174+
"Tail-Call Optimized Factorial of -5: %s\n"
175+
(try factorial_tco (-5.) |> string_of_float with
176+
| Failure _ -> "ERROR");
177+
printf "Tail-Call Optimized Factorial of 50.0: %f\n" (factorial_tco 50.);
178+
(* Running [fib 37] is expensive so you will notice a slight delay*)
179+
printf "37th number in the fibonacci sequence (naive): %d\n" (fib 37);
180+
printf
181+
"100th number in the fibonacci sequence (memoized): %d\n"
182+
(fib_memo 100);
183+
printf "52nd number in the fibonacci sequence (tco): %d\n" (fib_tco 52)
184+
end
185+
186+
(* Output of [ocaml luishendrix92.ml]:
187+
188+
100
189+
99
190+
98
191+
97
192+
96
193+
95
194+
94
195+
93
196+
92
197+
91
198+
90
199+
89
200+
88
201+
87
202+
86
203+
85
204+
84
205+
83
206+
82
207+
81
208+
80
209+
79
210+
78
211+
77
212+
76
213+
75
214+
74
215+
73
216+
72
217+
71
218+
70
219+
69
220+
68
221+
67
222+
66
223+
65
224+
64
225+
63
226+
62
227+
61
228+
60
229+
59
230+
58
231+
57
232+
56
233+
55
234+
54
235+
53
236+
52
237+
51
238+
50
239+
49
240+
48
241+
47
242+
46
243+
45
244+
44
245+
43
246+
42
247+
41
248+
40
249+
39
250+
38
251+
37
252+
36
253+
35
254+
34
255+
33
256+
32
257+
31
258+
30
259+
29
260+
28
261+
27
262+
26
263+
25
264+
24
265+
23
266+
22
267+
21
268+
20
269+
19
270+
18
271+
17
272+
16
273+
15
274+
14
275+
13
276+
12
277+
11
278+
10
279+
9
280+
8
281+
7
282+
6
283+
5
284+
4
285+
3
286+
2
287+
1
288+
0
289+
Factorial of 5: 120.000000
290+
Tail-Call Optimized Factorial of -5: ERROR
291+
Tail-Call Optimized Factorial of 50.0: 30414093201713375576366966406747986832057064836514787179557289984.000000
292+
37th number in the fibonacci sequence (naive): 24157817
293+
100th number in the fibonacci sequence (memoized): 3736710778780434371
294+
52nd number in the fibonacci sequence (tco): 32951280099
295+
*)

0 commit comments

Comments
 (0)