Path: blob/master/elisp/emacs-for-python/auto-complete/fuzzy.el
990 views
;;; fuzzy.el --- Fuzzy matching utilities12;; Copyright (C) 2010 Tomohiro Matsuyama34;; Author: Tomohiro Matsuyama <[email protected]>5;; Keywords: convenience67;; This program is free software; you can redistribute it and/or modify8;; it under the terms of the GNU General Public License as published by9;; the Free Software Foundation, either version 3 of the License, or10;; (at your option) any later version.1112;; This program is distributed in the hope that it will be useful,13;; but WITHOUT ANY WARRANTY; without even the implied warranty of14;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the15;; GNU General Public License for more details.1617;; You should have received a copy of the GNU General Public License18;; along with this program. If not, see <http://www.gnu.org/licenses/>.1920;;; Commentary:2122;;2324;;; Code:2526(eval-when-compile27(require 'cl))28(require 'regexp-opt)2930(defgroup fuzzy nil31"Fuzzy matching utilities."32:group 'convenience33:prefix "fuzzy-")3435(defcustom fuzzy-accept-error-rate 0.1036"Error threshold."37:group 'fuzzy)3839(defvar fuzzy-accept-length-difference 2)4041(defvar fuzzy-regexp-some-char (format "\\w\\{0,%s\\}" fuzzy-accept-length-difference))42434445;;; Functions4647(defun fuzzy-reverse-string (string)48(apply 'string (nreverse (append string nil))))4950(defun fuzzy-regexp-compile (string)51(labels ((oddp (n) (eq (logand n 1) 1))52(evenp (n) (eq (logand n 1) 0))53(opt (n) (regexp-opt-charset (append (substring string54(max 0 (- n 1))55(min (length string) (+ n 2))) nil))))56(concat57"\\("58(loop for i below (length string)59for c = (if (evenp i) (opt i) fuzzy-regexp-some-char)60concat c)61"\\|"62(loop for i below (length string)63for c = (if (oddp i) (opt i) fuzzy-regexp-some-char)64concat c)65"\\)")))6667(defalias 'fuzzy-edit-distance 'fuzzy-jaro-winkler-distance)6869(defun fuzzy-jaro-winkler-distance (s1 s2)70"http://en.wikipedia.org/wiki/Jaro-Winkler_distance"71(let* ((l1 (length s1))72(l2 (length s2))73(r (max 1 (1- (/ (max l1 l2) 2))))74(m 0)75(tr 0)76(p 0)77cs1 cs2)78(loop with seen = (make-vector l2 nil)79for i below l180for c1 = (aref s1 i) do81(loop for j from (max 0 (- i r)) below (min l2 (+ i r))82for c2 = (aref s2 j)83if (and (char-equal c1 c2)84(null (aref seen j))) do85(push c1 cs1)86(aset seen j c2)87(incf m)88and return nil)89finally90(setq cs1 (nreverse cs1)91cs2 (loop for i below l292for c = (aref seen i)93if c collect c)))94(loop for c1 in cs195for c2 in cs296if (not (char-equal c1 c2)) do97(incf tr))98(loop for i below (min m 5)99for c1 across s1100for c2 across s2101while (char-equal c1 c2) do102(incf p))103(if (eq m 0)1040.0105(setq m (float m))106(let* ((dj (/ (+ (/ m l1) (/ m l2) (/ (- m (/ tr 2)) m)) 3))107(dw (+ dj (* p 0.1 (- 1 dj)))))108dw))))109110;; this function should be compiled111(byte-compile 'fuzzy-jaro-winkler-distance)112113(defun fuzzy-match (s1 s2 &optional function)114(or function (setq function 'fuzzy-edit-distance))115(and (<= (abs (- (length s1) (length s2)))116fuzzy-accept-length-difference)117(>= (funcall function s1 s2)118(- 1 fuzzy-accept-error-rate))))119120(defun fuzzy-all-completions (string collection)121"all-completions family with fuzzy matching."122(loop with length = (length string)123for str in collection124for s = (substring str 0 (min (length str)125(+ length fuzzy-accept-length-difference)))126if (fuzzy-match string s)127collect str))128129130131;;; Search and Incremental Search132133(defvar fuzzy-search-cache nil)134(defvar fuzzy-search-cache-string nil)135136(defun fuzzy-search-cache-activate ()137(setq fuzzy-search-cache (make-hash-table))138(setq fuzzy-search-cache-string nil))139140(defun fuzzy-search-cache-deactive ()141(setq fuzzy-search-cache nil)142(setq fuzzy-search-cache-string nil))143144(defun fuzzy-search-edit-distance (s1 s2)145(or (and fuzzy-search-cache146(cond147((null fuzzy-search-cache-string)148(setq fuzzy-search-cache-string s1)149nil)150((not (equal fuzzy-search-cache-string s1))151(setq fuzzy-search-cache-string s1)152(clrhash fuzzy-search-cache)153nil)154(t))155(gethash s2 fuzzy-search-cache))156(let ((d (fuzzy-edit-distance s1 s2)))157(if fuzzy-search-cache158(puthash s2 d fuzzy-search-cache))159d)))160161(defun fuzzy-search-match (s1 s2)162(fuzzy-match s1 s2 'fuzzy-search-edit-distance))163164(defun fuzzy-search-forward (string &optional bound noerror count)165(let* ((regexp (fuzzy-regexp-compile string))166match-data)167(save-excursion168(while (and (null match-data)169(re-search-forward regexp bound t))170(if (fuzzy-search-match string (match-string 1))171(setq match-data (match-data))172(goto-char (1+ (match-beginning 1))))))173(when match-data174(store-match-data match-data)175(goto-char (match-end 1)))))176177(defun fuzzy-search-backward (string &optional bound noerror count)178(let* ((regexp (fuzzy-regexp-compile string))179match-data begin end)180(save-excursion181(while (and (null match-data)182(re-search-backward regexp bound t))183(setq begin (match-beginning 1)184end (match-end 1))185(store-match-data nil)186(goto-char (max (point-min) (- begin (* (length string) 2))))187(while (re-search-forward regexp end t)188(if (fuzzy-search-match string (match-string 1))189(setq match-data (match-data))190(goto-char (1+ (match-beginning 1)))))191(unless match-data192(goto-char begin)))193(if match-data194(progn195(store-match-data match-data)196(goto-char (match-beginning 1)))197(store-match-data nil)))))198199(defvar fuzzy-isearch nil)200(defvar fuzzy-isearch-failed-count 0)201(defvar fuzzy-isearch-enabled 'on-failed)202(defvar fuzzy-isearch-original-search-fun nil)203(defvar fuzzy-isearch-prefix "[FUZZY] ")204205(defun fuzzy-isearch-activate ()206(setq fuzzy-isearch t)207(setq fuzzy-isearch-failed-count 0)208(fuzzy-search-cache-activate))209210(defun fuzzy-isearch-deactivate ()211(setq fuzzy-isearch nil)212(setq fuzzy-isearch-failed-count 0)213(fuzzy-search-cache-deactive))214215(defun fuzzy-isearch ()216(cond (isearch-word217(if isearch-forward 'word-search-forward 'word-search-backward))218(isearch-regexp219(if isearch-forward 're-search-forward 're-search-backward))220((or fuzzy-isearch221(eq fuzzy-isearch-enabled 'always)222(and (eq fuzzy-isearch-enabled 'on-failed)223(null isearch-success)224isearch-wrapped225(> (setq fuzzy-isearch-failed-count (1+ fuzzy-isearch-failed-count))2261)))227(unless fuzzy-isearch228;(goto-char isearch-opoint)229(fuzzy-isearch-activate))230(if isearch-forward 'fuzzy-search-forward 'fuzzy-search-backward))231(t232(if isearch-forward 'search-forward 'search-backward))))233234(defun fuzzy-isearch-end-hook ()235(fuzzy-isearch-deactivate))236237(defun turn-on-fuzzy-isearch ()238(interactive)239(setq fuzzy-isearch-original-search-fun isearch-search-fun-function)240(setq isearch-search-fun-function 'fuzzy-isearch)241(add-hook 'isearch-mode-end-hook 'fuzzy-isearch-end-hook))242243(defun turn-off-fuzzy-isearch ()244(interactive)245(setq isearch-search-fun-function fuzzy-isearch-original-search-fun)246(remove-hook 'isearch-mode-end-hook 'fuzzy-isearch-end-hook))247248(defadvice isearch-message-prefix (after fuzzy-isearch-message-prefix activate)249(if fuzzy-isearch250(setq ad-return-value (concat fuzzy-isearch-prefix ad-return-value))251ad-return-value))252253(provide 'fuzzy)254;;; fuzzy.el ends here255256257