Source file spell_check.ml
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
let utf_8_decode_length_of_byte = function
| '\x00' .. '\x7F' -> 1
| '\x80' .. '\xC1' -> 0
| '\xC2' .. '\xDF' -> 2
| '\xE0' .. '\xEF' -> 3
| '\xF0' .. '\xF4' -> 4
| _ -> 0
let utf_8_uchar_length s =
let slen = String.length s in
let i = ref 0 and ulen = ref 0 in
while !i < slen do
let dec_len = utf_8_decode_length_of_byte (String.unsafe_get s !i) in
i := !i + if dec_len = 0 then 1 else dec_len;
incr ulen
done;
!ulen
let uchar_array_of_utf_8_string s =
let slen = String.length s in
let uchars = Array.make slen Uchar.max in
let k = ref 0 and i = ref 0 in
while !i < slen do
let dec = String.get_utf_8_uchar s !i in
i := !i + Uchar.utf_decode_length dec;
uchars.(!k) <- Uchar.utf_decode_uchar dec;
incr k
done;
(uchars, !k)
let edit_distance' ?(limit = Int.max_int) s (s0, len0) s1 =
if limit <= 1 then if String.equal s s1 then 0 else limit
else
let[@inline] minimum a b c = Int.min a (Int.min b c) in
let s1, len1 = uchar_array_of_utf_8_string s1 in
let limit = Int.min (Int.max len0 len1) limit in
if Int.abs (len1 - len0) >= limit then limit
else
let s0, s1 = if len0 > len1 then (s0, s1) else (s1, s0) in
let len0, len1 = if len0 > len1 then (len0, len1) else (len1, len0) in
let rec loop row_minus2 row_minus1 row i len0 limit s0 s1 =
if i > len0 then row_minus1.(Array.length row_minus1 - 1)
else
let len1 = Array.length row - 1 in
let row_min = ref Int.max_int in
row.(0) <- i;
let jmax =
let jmax = Int.min len1 (i + limit - 1) in
if jmax < 0 then len1 else jmax
in
for j = Int.max 1 (i - limit) to jmax do
let cost = if Uchar.equal s0.(i - 1) s1.(j - 1) then 0 else 1 in
let min =
minimum
(row_minus1.(j - 1) + cost)
(row_minus1.(j) + 1)
(row.(j - 1) + 1)
in
let min =
if
i > 1 && j > 1
&& Uchar.equal s0.(i - 1) s1.(j - 2)
&& Uchar.equal s0.(i - 2) s1.(j - 1)
then Int.min min (row_minus2.(j - 2) + cost)
else min
in
row.(j) <- min;
row_min := Int.min !row_min min
done;
if !row_min >= limit then limit
else loop row_minus1 row row_minus2 (i + 1) len0 limit s0 s1
in
let ignore =
limit + 1
in
let row_minus2 = Array.make (len1 + 1) ignore in
let row_minus1 = Array.init (len1 + 1) (fun x -> x) in
let row = Array.make (len1 + 1) ignore in
let d = loop row_minus2 row_minus1 row 1 len0 limit s0 s1 in
if d > limit then limit else d
let edit_distance ?limit s0 s1 =
let us0 = uchar_array_of_utf_8_string s0 in
edit_distance' ?limit s0 us0 s1
let default_max_dist s =
match utf_8_uchar_length s with 0 | 1 | 2 -> 0 | 3 | 4 -> 1 | _ -> 2
let f ?(max_dist = default_max_dist) iter_dict s =
let min = ref (max_dist s) in
let acc = ref [] in
let select_words s us word =
let d = edit_distance' ~limit:(!min + 1) s us word in
if d = !min then acc := word :: !acc
else if d < !min then (
min := d;
acc := [ word ])
else ()
in
let us = uchar_array_of_utf_8_string s in
iter_dict (select_words s us);
List.rev !acc