package ppxlib

  1. Overview
  2. Docs
Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source

Source file spellcheck.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
open! Import

exception Cutoff_met

(* As found here http://rosettacode.org/wiki/Levenshtein_distance#OCaml *)
let levenshtein_distance s t cutoff =
  let m = String.length s and n = String.length t in
  if cutoff = 0 || abs (m - n) >= cutoff then None
  else
    (* for all i and j, d.(i).(j) will hold the Levenshtein distance between the
       first i characters of s and the first j characters of t *)
    let d = Array.make_matrix ~dimx:(m + 1) ~dimy:(n + 1) 0 in
    for i = 0 to m do
      (* the distance of any first string to an empty second string *)
      d.(i).(0) <- i
    done;
    for j = 0 to n do
      (* the distance of any second string to an empty first string *)
      d.(0).(j) <- j
    done;
    (* the minimum of each line together with the column index will be used
       to notice cutoff exceeding and return early in that case *)
    let line_min = ref 0 in
    let distance =
      try
        for j = 1 to n do
          if !line_min >= cutoff - 1 && j >= cutoff - 1 then raise Cutoff_met;
          line_min := max m n;
          for i = 1 to m do
            let value =
              if Char.equal s.[i - 1] t.[j - 1] then d.(i - 1).(j - 1)
                (* no operation required *)
              else
                min
                  (d.(i - 1).(j) + 1) (* a deletion *)
                  (min
                     (d.(i).(j - 1) + 1) (* an insertion *)
                     (d.(i - 1).(j - 1) + 1) (* a substitution *))
            in
            d.(i).(j) <- value;
            line_min := min !line_min value
          done
        done;
        if d.(m).(n) < cutoff then Some d.(m).(n) else None
      with Cutoff_met -> None
    in
    distance

let spellcheck names name =
  let cutoff =
    match String.length name with
    | 1 | 2 -> 0
    | 3 | 4 -> 1
    | 5 | 6 -> 2
    | _ -> 3
  in
  let _, suggestions =
    List.fold_left names ~init:(Int.max_int, [])
      ~f:(fun ((best_distance, names_at_best_distance) as acc) registered_name
         ->
        match levenshtein_distance name registered_name cutoff with
        | None -> acc
        | Some dist ->
            if dist < best_distance then (dist, [ registered_name ])
            else if dist > best_distance then acc
            else (dist, registered_name :: names_at_best_distance))
  in
  match List.rev suggestions |> List.filter ~f:(String.( <> ) name) with
  | [] -> None
  | last :: rev_rest ->
      Some
        (Printf.sprintf "Hint: Did you mean %s%s%s?"
           (String.concat ~sep:", " (List.rev rev_rest))
           (if List.is_empty rev_rest then "" else " or ")
           last)
OCaml

Innovation. Community. Security.