;;; lcs.el --- find out the longest common sequence ;; Copyright (c) 2002-2003 by Alex Shinn, All rights reserved. ;; Copyright (c) 2002-2003 by Shiro Kawai, All rights reserved. ;; Copyright (c) 2006, 2012 by Jorgen Schaefer, All rights reserved. ;; Authors: Alex Shinn, Shiro Kawai ;; Maintainer: Jorgen Schaefer ;; URL: https://github.com/jorgenschaefer/circe/wiki/lcs ;; Redistribution and use in source and binary forms, with or without ;; modification, are permitted provided that the following conditions ;; are met: ;; 1. Redistributions of source code must retain the above copyright ;; notice, this list of conditions and the following disclaimer. ;; 2. Redistributions in binary form must reproduce the above copyright ;; notice, this list of conditions and the following disclaimer in the ;; documentation and/or other materials provided with the distribution. ;; 3. Neither the name of the authors nor the names of its contributors ;; may be used to endorse or promote products derived from this ;; software without specific prior written permission. ;; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ;; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT ;; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR ;; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT ;; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ;; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED ;; TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR ;; PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF ;; LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING ;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS ;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ;;; Commentary: ;; lcs.el is a library for other Emacs Lisp programs not useful by ;; itself. ;; This library provides functions to find the Longest Common Sequence ;; (LCS) of two sequences. This is used to create a unified diff of to ;; two lists. See `lcs-unified-diff' for a useful function to be ;; called. ;; The code is more or less a literal translation of (part of) ;; Gauche's util/lcs.scm module to Emacs Lisp. ;;; Code: (put 'lcs-for 'lisp-indent-function 4) (defmacro lcs-for (var from to step &rest body) "A simple FOR loop macro. Count VAR from FROM to TO by stepsize STEP. Evaluate BODY in each iteration." (let ((sto (make-symbol "to")) (sstep (make-symbol "step"))) `(let ((,var ,from) (,sto ,to) (,sstep ,step)) (while (<= ,var ,sto) (progn ,@body) (setq ,var (+ ,var ,sstep)))))) (defun lcs-split-at (lis pos) "Return a cons cell of the first POS elements of LIS and the rest." (let ((head nil)) (while (> pos 0) (setq head (cons (car lis) head) pos (- pos 1) lis (cdr lis))) (cons (reverse head) lis))) (defun lcs-finish (M+N V_l vl V_r vr) "Finalize the LCS algorithm. Should be used only by `lcs-with-positions'." (let ((maxl 0) (r '())) (lcs-for i (- M+N) M+N 1 (when (> (funcall vl i) maxl) (setq maxl (funcall vl i) r (funcall vr i)))) (list maxl (reverse r)))) (defun lcs-with-positions (a-ls b-ls &optional equalp) "Return the longest common subsequence (LCS) of A-LS and B-LS. EQUALP can be any procedure which returns non-nil when two elements should be considered equal." (let* ((A (vconcat a-ls)) (B (vconcat b-ls)) (N (length A)) (M (length B)) (M+N (+ M N)) (V_d (make-vector (+ 1 (* 2 M+N)) 0)) (V_r (make-vector (+ 1 (* 2 M+N)) nil)) (V_l (make-vector (+ 1 (* 2 M+N)) 0)) (vd (lambda (i &optional x) (if x (aset V_d (+ i M+N) x) (aref V_d (+ i M+N))))) (vr (lambda (i &optional x) (if x (aset V_r (+ i M+N) x) (aref V_r (+ i M+N))))) (vl (lambda (i &optional x) (if x (aset V_l (+ i M+N) x) (aref V_l (+ i M+N)))))) (when (not equalp) (setq equalp 'equal)) (catch 'return (if (= M+N 0) (throw 'return '(0 ())) (lcs-for d 0 M+N 1 (lcs-for k (- d) d 2 (let ((x nil) (y nil) (l nil) (r nil)) (if (or (= k (- d)) (and (not (= k d)) (< (funcall vd (- k 1)) (funcall vd (+ k 1))))) (setq x (funcall vd (+ k 1)) l (funcall vl (+ k 1)) r (funcall vr (+ k 1))) (setq x (+ 1 (funcall vd (- k 1))) l (funcall vl (- k 1)) r (funcall vr (- k 1)))) (setq y (- x k)) (while (and (< x N) (< y M) (funcall equalp (aref A x) (aref B y))) (setq r (cons (list (aref A x) x y) r) x (+ x 1) y (+ y 1) l (+ l 1))) (funcall vd k x) (funcall vr k r) (funcall vl k l) (when (and (>= x N) (>= y M)) (throw 'return(lcs-finish M+N V_l vl V_r vr))))))) (error "Can't happen")))) (defun lcs-unified-diff (a b &optional equalp) "Return a unified diff of the lists A and B. EQUALP should can be a procedure that returns non-nil when two elements of A and B should be considered equal. It's `equal' by default." (let ((common (cadr (lcs-with-positions a b equalp))) (a a) (a-pos 0) (b b) (b-pos 0) (diff '())) (while common (let* ((elt (car common)) (a-off (nth 1 elt)) (a-skip (- a-off a-pos)) (b-off (nth 2 elt)) (b-skip (- b-off b-pos)) (a-split (lcs-split-at a a-skip)) (a-head (car a-split)) (a-tail (cdr a-split)) (b-split (lcs-split-at b b-skip)) (b-head (car b-split)) (b-tail (cdr b-split))) (setq diff (append diff (mapcar (lambda (a) `(- ,a)) a-head) (mapcar (lambda (b) `(+ ,b)) b-head) `((! ,(car elt)))) common (cdr common) a (cdr a-tail) a-pos (+ a-off 1) b (cdr b-tail) b-pos (+ b-off 1)))) (append diff (mapcar (lambda (a) `(- ,a)) a) (mapcar (lambda (b) `(+ ,b)) b)))) (provide 'lcs) ;;; lcs.el ends here