Sort
Source
John Whitington, OCaml from the Very Beginning, Chapter 5: Sorting Things
Contents
Question 1
In
msort
, we calculate the value of the expressionlength l / 2
twice. Modifymsort
to remove this inefficiency.
My solution
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
let rec length l =
match l with
[] -> 0
| _::t -> 1 + length t
let rec take n l =
if n = 0 then [] else
match l with [] -> []
| h::t -> h :: take (n - 1) t
let rec drop n l =
if n = 0 then l else
match l with [] -> []
| h::t -> drop (n - 1) t
let rec merge x y =
match x, y with [], l -> l
| l, [] -> l
| hx::tx, hy::ty ->
if hx < hy then hx :: merge tx (hy :: ty)
else hy :: merge (hx :: tx) ty
(*
Change was made here, in the addition of the line
`let half = (length l / 2) in`.
*)
let rec msort l =
let half = (length l / 2) in
match l with [] -> []
| [x] -> [x]
| _ -> let left = take half l in
let right = drop half l in
merge (msort left) (msort right)
let _ =
assert ([] = msort []);
assert ([0] = msort [0]);
assert ([0; 0] = msort [0; 0]);
assert ([0; 1] = msort [1; 0]);
assert ([0; 1; 2] = msort [0; 1; 2]);
assert ([0; 1; 2] = msort [2; 1; 0]);
assert ([0; 1; 2] = msort [2; 0; 1]);
assert ([0; 1; 2] = msort [1; 0; 2]);
assert ([0; 1; 2] = msort [0; 2; 1]);
assert ([0; 2; 4] = msort [2; 4; 0]);
assert (['a'; 'b'; 'c'] = msort ['c'; 'a'; 'b']);
assert ([0; 1; 2; 3; 4; 5] = msort [5; 0; 2; 4; 3; 1]);
assert ([0; 1; 2; 3; 4; 4; 5; 5] = msort [5; 0; 2; 5; 4; 3; 1; 4]);
assert ([33; 43; 45; 99; 192; 875; 985] = msort [99; 192; 33; 985; 875; 45; 43]);
assert ([2; 6; 9; 19; 53] = msort [53; 9; 2; 6; 19]);
;;
Solution given in book
The added line of code is the same, but the book’s solution positions it more sensibly than I did:
Simply add an extra let to define a name representing the number we will take or drop:
1
2
3
4
5
6
7
let rec msort l =
match l with [] -> []
| [x] -> [x]
| _ -> let x = (length l / 2) in
let left = take x l in
let right = drop x l in
merge (msort left) (msort right)
Question 2
We know that
take
anddrop
can fail if called with incorrect arguments. Show that this is never the case inmsort
.
My answer
The expression match l with [] -> [] | [x] -> [x]
ensures
that take
and drop
are never called with lists of length less than
two.
Answer provided in book
The argument to
take
ordrop
islength l / 2
which is clearly less than or equal tolength l
for all possible values ofl
. Thus,take
anddrop
always succeed. In our case,take
anddrop
are only called with [sic]length l
is more than 1, due to the pattern matching.
Question 3
Write a version of insertion sort which sorts the argument list into reverse order.
My solution
The only change necessary is in the comparison operator in the insert
function: where insert
has if x <= h...
, the new function insertrev
has if x >= h...
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
let rec insertrev x l =
match l with [] -> [x]
| h::t ->
if x >= h then x :: h :: t
else h :: insertrev x t
;;
let _ =
assert ([0] = insertrev 0 []);
assert ([1; 0] = insertrev 1 (insertrev 0 []));
assert ([1; 0; 2] = insertrev 1 [0; 2]);
assert ([3; 1; 1; 2; 3; 5; 9] = insertrev 3 [1; 1; 2; 3; 5; 9]);
;;
let rec sortrev l =
match l with [] -> []
| h::t -> insertrev h (sortrev t)
;;
let _ =
assert ([] = sortrev []);
assert ([0] = sortrev [0]);
assert ([1; 0] = sortrev [0; 1]);
assert ([2; 1; 0] = sortrev [2; 1; 0]);
assert ([2; 1; 0] = sortrev [2; 0; 1]);
assert ([2; 1; 0] = sortrev [1; 0; 2]);
assert ([2; 1; 0] = sortrev [1; 2; 0]);
assert ([53; 19; 9; 6; 2] = sortrev [53; 9; 2; 6; 19]);
;;
Solution provided in book
Identical to mine.
Question 4
Write a function to detect if a list is already in sorted order.
My solution
Pass the first element of the list as an argument along with the list itself.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let rec is_sorted first_elt l =
match l with [] -> true
| h::t ->
if first_elt > h then false
else is_sorted h t
let _ =
assert (is_sorted 0 []);
assert (is_sorted 1 [1]);
assert (is_sorted 0 [0; 1]);
assert (is_sorted 0 [0; 1; 2]);
assert (is_sorted 0 [0; 1; 2; 3; 4; 5]);
assert (is_sorted 'a' ['a'; 'b'; 'c'; 'd'; 'e'; 'f']);
assert (false = is_sorted 0 [1; 0]);
assert (false = is_sorted 0 [1; 0; 2]);
assert (false = is_sorted 0 [2; 0; 1]);
assert (false = is_sorted 0 [0; 1; 2; 3; 4; 5; 1]);
assert (false = is_sorted 'a' ['a'; 'b'; 'c'; 'd'; 'e'; 'a']);
;;
Solution provided in book
I had not thought to use the expression a::b::t
, as here:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let rec is_sorted_book l =
match l with a::b::t -> a <= b && is_sorted_book(b::t)
| _ -> true
let _ =
assert (is_sorted_book []);
assert (is_sorted_book [1]);
assert (is_sorted_book [0; 1]);
assert (is_sorted_book [0; 1; 2]);
assert (is_sorted_book [0; 1; 2; 3; 4; 5]);
assert (is_sorted_book ['a'; 'b'; 'c'; 'd'; 'e'; 'f']);
assert (false = is_sorted_book [1; 0]);
assert (false = is_sorted_book [1; 0; 2]);
assert (false = is_sorted_book [2; 0; 1]);
assert (false = is_sorted_book [0; 1; 2; 3; 4; 5; 1]);
assert (false = is_sorted_book ['a'; 'b'; 'c'; 'd'; 'e'; 'a']);
;;
Question 5
We mentioned that the comparison functions like
<
work for many OCaml types. Can you determine, by experimentation, how they work for lists? For example, what is the result of[1; 2] < [2; 3]
? What happens when we sort the following list of type char list list? Why?
[['o'; 'n'; 'e']; ['t'; 'w'; 'o']; ['t'; 'h'; 'r'; 'e'; 'e']]
My solution
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
let _ =
assert ([] = []);
assert ([0; 1] = [0; 1]);
assert ([0] < [1]);
assert (false = ([1] < [0]));
assert ([0; 1] < [1; 2]);
assert (false = ([0; 1] > [1; 2]));
assert ([0; 1; 2] < [1; 2]);
assert ([1; 1; 2] < [1; 2]);
assert (false = ([1; 1; 2] < [1; 1]));
(*
My hypothesis at this point is that each element in the first list is
compared with the element at the corresponding position in the second
list.
*)
assert (false = ([0; 0] < [0; 0]));
assert ([0; 0] < [0; 1]);
assert (false = ([0; 0] < [0]));
assert (false = ([0; 1; 2] < [0; 1; 2]));
assert ([0; 1; 2] < [0; 1; 3]);
assert (false = ([0; 1; 2; 3; 4; 5; 6; 7] < [0; 1; 2; 3; 4; 5; 6; 7]));
assert ([0; 1; 2; 3; 4; 5; 6; 7] < [0; 1; 2; 3; 4; 5; 6; 8]);
;;
If each element in the first list is compared with the element at the
corresponding position in the second list, then when the list of lists
specified in the question is sorted, the result should be the following,
because the character o
evaluates to lesser than the character t
,
and the character h
evaluates to lesser than the character w
.
[['o'; 'n'; 'e']; ['t'; 'h'; 'r'; 'e'; 'e']]; ['t'; 'w'; 'o']
Solution provided in book
Confirms my hypothesis:
Lists are compared starting with their first elements. If the elements differ, they are compared, and that is the result of the comparison. If both have the same first element, the second elements are considered, and so on. (144)
Question 6
Combine the
sort
andinsert
functions into a singlesort
function.
My solution
Simply nest the insert
function inside sort
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let rec isort l =
let rec insert x l =
match l with [] -> [x]
| h::t ->
if x <= h then x :: h :: t
else h :: insert x t
in match l with [] -> []
| h::t -> insert h (isort t)
;;
let _ =
assert ([] = isort []);
assert ([0] = isort [0]);
assert ([0; 1] = isort [0; 1]);
assert ([0; 1] = isort [1; 0]);
assert ([0; 1; 2] = isort [2; 1; 0]);
assert ([0; 1; 2] = isort [2; 0; 1]);
assert ([0; 1; 2] = isort [1; 0; 2]);
assert ([0; 1; 2] = isort [1; 2; 0]);
assert ([2; 6; 9; 19; 53] = isort [53; 9; 2; 6; 19]);
;;
Solution provided in book
Identical to mine.
Execute this file
$ codedown ocaml < 2022-12-28-pattern-matching.md | grep . | ocaml -stdin