(defun find-minimum-window-substring-pointers (string search)
(declare (type simple-string string)
(type simple-string search)
(optimize (speed 3) (safety 0) (debug 0)))
(let ((required (length (remove-duplicates search)))
(formed 0))
(cond ((<= required 1)
(let ((min-start (search search string)))
(cons min-start (+ min-start required))))
(t
(let* ((search-count
(loop with counter = (make-array 256
:element-type 'fixnum
:initial-element 0)
for c across (the simple-string search)
for i = (char-code c)
do (incf (aref counter i))
finally (return counter)))
(window-count (make-array 256 :element-type 'fixnum
:initial-element 0))
(string-len (length string))
(min-len (1+ string-len))
(min-start 0)
(window-start 0))
;; ウィンドウを右側へ広げながら走査する。
(dotimes (window-end string-len)
(let ((i (char-code (char string window-end))))
;; カウンターを更新。
(incf (aref window-count i))
(when (and (> (aref search-count i) 0)
(= (aref window-count i) 1))
(incf (the fixnum formed)))
;; 検索文字がすべてウィンドウ内に揃ったらウィンドウの
;; 左端を縮める。
(loop while (= formed required)
for i fixnum = (char-code (char string window-start))
do ;; 現在のウィンドウが条件を満たす最短部分列
;; ならばそれを記録。
(when (< (1+ (- window-end window-start)) min-len)
(setq min-len (1+ (- window-end window-start))
min-start window-start))
;; カウンターを更新。
(decf (aref window-count i))
(when (and (> (aref search-count i) 0)
(= (aref window-count i) 0))
(decf formed))
(incf window-start))))
(if (= min-len (1+ string-len))
(cons nil nil)
(cons min-start (+ min-start min-len))))))))
(defun find-minimum-window-substring (string search)
(declare (type simple-string string)
(type simple-string search)
(optimize (speed 3) (safety 0) (debug 0)))
(destructuring-bind (start . end)
(find-minimum-window-substring-pointers string search)
(if (null start)
(values nil (cons nil nil))
(values (subseq string start end) (cons start end)))))
(defun string-repeat (string n)
(format nil "~v@{~A~:*~}" n string))
(defmacro real-time (n-times &body form)
`(let ((start-time (get-internal-real-time))
(values nil))
(loop repeat ,n-times
do (setq values (multiple-value-list (progn ,@form))))
(let ((elapsed-time (- (get-internal-real-time) start-time)))
(values (/ elapsed-time internal-time-units-per-second)
values))))
(defun omit-long-string (string max-length &optional (min-length 0))
(let ((max-length (max max-length min-length)))
(let* ((omitted "...")
(prefix-len (- (floor max-length 2)
(floor (length omitted) 2)))
(suffix-len (- (ceiling max-length 2)
(ceiling (length omitted) 2))))
(if (<= (length string) max-length)
string
(format nil "~A~A~A"
(subseq string 0 (min prefix-len (length string)))
omitted
(subseq string (- (length string)
(min suffix-len (length string)))))))))
(defun make-test-case-524 ()
(let ((search "ruby"))
(list (with-output-to-string (*standard-output*)
(loop for i below (length search) do
(loop for c across "abcdefghijklmnopqrstuvwxyz"
do (loop for d across "abcdefghijklmnopqrstuvwxyz"
do (princ c)
(princ d)))))
search "rbsbtbubvbwbxby" 87)))
(defun make-test-case-549 ()
(let ((search "TheRubyProgrammingLanguage"))
(list (with-output-to-string (*standard-output*)
(dotimes (x 4)
(loop for i across "abcdefghijklmnopqrstuvwxyz"
do (loop for j across "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
do (princ i)
(princ j))
(loop for j across "abcdefghijklmnopqrstuvwxyz"
do (princ i)
(princ j)))))
search
(concatenate 'string
"LyMyNyOyPyQyRySyTyUyVyWyXyYyZyaybycydyeyfygyh"
"yiyjykylymynyoypyqyrysytyu")
2519)))
(defparameter *n-times* 2000)
(defparameter *test-cases*
;; (string search expected-substring expected-position)
`(("DHBICEJAFG" "EIC" "ICE" 3)
("FDGJHCBIEA" "EIC" "CBIE" 5)
("FBHDCIJGEA" "EIC" "CIJGE" 4)
("JDIBGHCEAF" "EIC" "IBGHCE" 2)
("JBCIAGDHEF" "EIC" "CIAGDHE" 2)
("EJFBCAGIHD" "EIC" "EJFBCAGI" 0)
("IADCGJFBEH" "EIC" "IADCGJFBE" 0)
("IDFHBJGAEC" "EIC" "IDFHBJGAEC" 0)
(,@(make-test-case-524))
(,@(make-test-case-549))))
(loop initially
(format t "TEST \"STRING\" \"SEARCH\" → \"SUBSTR\" POS~72TTIME~%")
(write-line (string-repeat "=" 78))
for (string search expected expected-pos) in *test-cases*
do (destructuring-bind (time (substring (substr-start . substr-end)))
(multiple-value-list (real-time *n-times*
(find-minimum-window-substring string
search)))
(declare (ignore substr-end))
(format t "~:[❌~;✓~] ~S ~S → ~S ~D~71T~,3F~%"
(and (equal substring expected)
(eql substr-start expected-pos))
(omit-long-string string 18)
(omit-long-string search 18)
(omit-long-string substring 16)
substr-start
time)))