123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475open!ImportexceptionCutoff_met(* As found here http://rosettacode.org/wiki/Levenshtein_distance#OCaml *)letlevenshtein_distancestcutoff=letm=String.lengthsandn=String.lengthtinifcutoff=0||abs(m-n)>=cutoffthenNoneelse(* 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 *)letd=Array.make_matrix~dimx:(m+1)~dimy:(n+1)0infori=0tomdo(* the distance of any first string to an empty second string *)d.(i).(0)<-idone;forj=0tondo(* the distance of any second string to an empty first string *)d.(0).(j)<-jdone;(* the minimum of each line together with the column index will be used
to notice cutoff exceeding and return early in that case *)letline_min=ref0inletdistance=tryforj=1tondoif!line_min>=cutoff-1&&j>=cutoff-1thenraiseCutoff_met;line_min:=maxmn;fori=1tomdoletvalue=ifChar.equals.[i-1]t.[j-1]thend.(i-1).(j-1)(* no operation required *)elsemin(d.(i-1).(j)+1)(* a deletion *)(min(d.(i).(j-1)+1)(* an insertion *)(d.(i-1).(j-1)+1)(* a substitution *))ind.(i).(j)<-value;line_min:=min!line_minvaluedonedone;ifd.(m).(n)<cutoffthenSomed.(m).(n)elseNonewithCutoff_met->Noneindistanceletspellchecknamesname=letcutoff=matchString.lengthnamewith|1|2->0|3|4->1|5|6->2|_->3inlet_,suggestions=List.fold_leftnames~init:(Int.max_int,[])~f:(fun((best_distance,names_at_best_distance)asacc)registered_name->matchlevenshtein_distancenameregistered_namecutoffwith|None->acc|Somedist->ifdist<best_distancethen(dist,[registered_name])elseifdist>best_distancethenaccelse(dist,registered_name::names_at_best_distance))inmatchList.revsuggestions|>List.filter~f:(String.(<>)name)with|[]->None|last::rev_rest->Some(Printf.sprintf"Hint: Did you mean %s%s%s?"(String.concat~sep:", "(List.revrev_rest))(ifList.is_emptyrev_restthen""else" or ")last)