-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathloops.el
293 lines (248 loc) · 10.4 KB
/
loops.el
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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
;;; loops.el --- detect loops in lists
;; Copyright (C) 2002 Alex Schroeder, ZwaX
;; Author: Alex Schroeder <alex@gnu.org>
;; Maintainer: Alex Schroeder <alex@gnu.org>
;; Version: 1.0.6
;; Keywords: lisp
;; URL: http://www.emacswiki.org/cgi-bin/wiki.pl?LoopFindPackage
;; This file is not part of GNU Emacs.
;; This is free software; you can redistribute it and/or modify it under
;; the terms of the GNU General Public License as published by the Free
;; Software Foundation; either version 2, or (at your option) any later
;; version.
;; This is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING. If not, write to the
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
;; Boston, MA 02111-1307, USA.
;;; Commentary:
;; See the test cases at the end of the file to get an idea of how to
;; use this. I am sure this can be optimized a lot.
(require 'cl)
(defalias 'loop-find 'loop-find-longest)
(defvar loop-consecutive nil); to bind dynamically in the test
(defun loop-find-shortest (lst loop-min-repetitions loop-min-length loop-max-length &optional loop-consecutive)
"Find the shortest loop in LST.
There must be at least LOOP-MIN-REPETITIONS repetitions,
and a loop must be at least LOOP-MIN-LENGTH elements long,
but no longer than LOOP-MAX-LENGTH. If LOOP-CONSECUTIVE
is non-nil, the repetitions must be consecutive.
Examples:
(loop-find-shortest '(1 2 3 1 2 3 1 2 3 4 5 6) 2 2 10 nil)
=> (1 2)
(loop-find-shortest '(1 2 3 1 2 3 1 2 3 4 5 6) 2 2 10 t)
=> (1 2 3)
See also `loop-find-longest'."
(let ((len loop-min-length) result)
(while (and (<= len loop-max-length)
(not result))
(let ((candidate (subseq lst 0 len)))
(if (>= (loop-count lst candidate loop-min-repetitions len) loop-min-repetitions)
(setq result candidate)
(setq len (1+ len)))))
result))
(defun loop-find-longest (lst loop-min-repetitions loop-min-length loop-max-length &optional loop-consecutive)
"Find longest loop in LST.
There must be at least LOOP-MIN-REPETITIONS repetitions,
and a loop must be at least LOOP-MIN-LENGTH elements long,
but no longer than LOOP-MAX-LENGTH. If LOOP-CONSECUTIVE
is non-nil, the repetitions must be consecutive.
Examples:
(loop-find-longest '(1 2 1 2 3 1 2 1 2) 2 2 10 nil)
=> (1 2 1 2)
(loop-find-longest '(1 2 1 2 3 1 2 1 2) 2 2 10 t)
=> (1 2)
See also `loop-find-shortest'."
;; if they have to be consecutive, it's possible to find a pattern
;; of length 3 even if there is no pattern of length 2 so start by
;; looking at the longest possible pattern and reduce it in stages
(if loop-consecutive
(let ((len loop-max-length)
result longest)
(while (and (>= len loop-min-length)
(not result))
(let ((candidate (subseq lst 0 len)))
(if (= (loop-count lst candidate loop-min-repetitions len) loop-min-repetitions)
(setq result candidate)
(setq len (1- len)))))
result)
;; but when they needn't be consecutive, as soon as one length
;; fails, all larger lengths will also fail, so don't bother
;; looking for them
(let ((ret (loops-find-longest-loop lst loop-min-length loop-max-length loop-min-repetitions)))
(if ret (subseq lst 0 (car ret))))))
(defun loop-count (lst candidate &optional max-count len)
"Count how often LST contains CANDIDATE.
This counts the repetitions of CANDIDATE at the beginning
of LST, up to a maximum of MAX-COUNT."
(unless len (setq len (length candidate)))
(if (not loop-consecutive)
(loop-count-non-consecutive lst candidate max-count len)
(loop-count-consecutive lst candidate max-count len)))
(defun loop-count-consecutive (lst candidate max-count len)
;; don't look for more than the maximum number of repetitions possible
(let ((count 0)
max-possible)
(setq max-possible (/ (length lst) len))
(if max-count (setq max-possible (min max-count max-possible)))
(while (and (< count max-possible) (not (mismatch candidate lst :end2 len)))
(setq count (1+ count)
lst (nthcdr len lst)))
count))
(defun loop-count-non-consecutive (lst candidate max-count len)
(let ((count 0)
max-possible
(pos (loop-search candidate lst)))
(while (and pos (or (not max-count) (< count max-count)))
(setq count (1+ count)
lst (nthcdr (+ pos len) lst)
pos (loop-search candidate lst)))
count))
(defun loop-search (seq lst)
"Search for SEQ as a subsequence of LST.
Return the index of the leftmost element of the first match found;
return nil if there are no matches.
Examples:
(loop-search '(1 2 3) '(0 1 2 3 4 5 6))
=> 1
(loop-search '(1 2 3) '(0 1 2))
=> nil
"
(let ((pos 0)
(bad t)
s l ok)
(while (and lst bad)
(setq ok t
s seq
l lst)
(while (and s ok)
(if (equal (car s) (car l))
(setq s (cdr s)
l (cdr l))
(setq ok nil)))
(if ok
(setq bad nil)
(setq lst (cdr lst)
pos (1+ pos))))
(if bad nil pos)))
(defun loops-match (seq1 seq2 len)
(let ((ok t))
(while (and ok (> len 0))
(if (and (equal (car seq1) (car seq2))
(car seq1))
(setq len (1- len)
seq1 (cdr seq1)
seq2 (cdr seq2))
(setq ok nil)))
ok))
;; try to find REPS matches for the first LEN elements of START
;; optional LIST gives elements to search in (otherwise elements immediately after prefix are used
;; optional POS tells the index in the START that LIST can be found
(defun loops-find-initial-occurances (start len reps &optional list pos)
(let* ((rest (nthcdr len start))
occurances)
(if (not list)
(setq list (nthcdr len start)
pos len))
(while (and list (> reps 0))
(if (loops-match start list len)
(setq reps (1- reps)
list (nthcdr len list)
occurances (cons (cons pos list) occurances)
pos (+ pos len))
(setq pos (1+ pos)
list (cdr list))))
(setq ret (if (= reps 0) (cons (cons pos list) (cons rest occurances))))))
(defun loops-find-longest-loop (list min max reps)
(let* ((ret (loops-find-initial-occurances list min (1- reps)))
(next-pos (caar ret))
(next-list (cdar ret))
(rest (cadr ret))
(best-occurances (cddr ret))
(occurances best-occurances)
(ok t)
(len min)
new-occurances occurance pos lst)
(when ret
(while (and ok (< len max))
(while (and occurances ok)
(setq occurance (car occurances)
pos (car occurance)
lst (cdr occurance)
occurances (cdr occurances))
(if (equal (car lst) (car rest))
(setq new-occurances (cons (cons pos (cdr lst)) new-occurances))
(setq ret (loops-find-initial-occurances list (1+ len) 1 next-list next-pos))
(if ret
(setq next-pos (caar ret)
next-list (cdar ret)
new-occurances (cons (caddr ret) new-occurances))
(setq ok nil))))
(if ok (setq rest (cdr rest)
best-occurances new-occurances
occurances best-occurances
len (1+ len)
new-occurances nil)))
(cons len best-occurances))))
;;; Test
(defun string-to-list (str)
(if (not (equal str ""))
(cons (substring str 0 1) (string-to-list (substring str 1)))))
(eval-when-compile
(let ((test '(1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 6 7 8 9 0))
(loop-consecutive t))
(assert (= 4 (loop-count test '(1 2 3 4 5))))
(assert (= 1 (loop-count test '(1 2 3 4))))
(assert (equal '(1 2 3 4 5) (loop-find-longest test 3 2 6 t))))
(let ((test '(1 2 3 4 5 a a 1 2 3 4 5 b b b 1 2 3 4 5 c 1 2 3 4 5 d 6 7)))
(assert (= 4 (loop-count test '(1 2 3 4 5))))
(assert (= 4 (loop-count test '(1 2 3 4))))
(assert (equal '(1 2) (loop-find-shortest test 3 2 200)))
(assert (equal '(1 2 3 4 5) (loop-find-longest test 3 2 200))))
(assert (loops-match '("a" "b" "c" "d") '("a" "b" "d" "e") 2))
(assert (not (loops-match '("a" "b" "c" "d") '("a" "b" "d" "e") 3)))
(assert (not (loops-match '("a") '("a" "b" "d" "e") 2)))
(assert (not (loops-match '("a" "b" "c" "d") '("a") 2)))
;; 111111111122222222223333333
;; 0123456789012345678901234567890123456
(let ((test (string-to-list "abcdabcababcdabcdeabcdafabcaaabcdabde"))
(min-len 2)
(max-len 100)
(min-rep 2))
;; what is the longest initial sequence (2 <= length <= 100) which occurs twice?
(assert (equal (loops-find-longest-loop test min-len max-len min-rep)
'(7 ;it is length 7
;; it starts at position 9, and is followed by these characters
(9 "d" "e" "a" "b" "c" "d" "a" "f" "a" "b" "c" "a" "a" "a" "b" "c" "d" "a" "b" "d" "e"))))
;; what is the longest initial sequence (2 <= length <= 100) which occurs 3 times?
(assert (equal (loops-find-longest-loop test 2 100 3 )
'(6 ;it is length 6
;; and the 2 non-initial occurances start at positions 9 and 29:
(9 "c" "d" "e" "a" "b" "c" "d" "a" "f" "a" "b" "c" "a" "a" "a" "b" "c" "d" "a" "b" "d" "e")
(29 "d" "e"))))
;; what is the longest initial sequence (4 <= length <= 100) which occurs 3 times?
(assert (equal (loops-find-longest-loop test 4 100 3 )
'(6 ;it is length 6
;; and the 2 non-initial occurances start at positions 9 and 29:
(29 "d" "e")
(9 "c" "d" "e" "a" "b" "c" "d" "a" "f" "a" "b" "c" "a" "a" "a" "b" "c" "d" "a" "b" "d" "e"))))
;; what is the longest initial sequence (4 <= length <= 5) which occurs 3 times?
(assert (equal (loops-find-longest-loop test 4 5 3 )
'(5 ;it is length 5
;; and the 2 non-initial occurances start at positions 9 and 18:
(9 "b" "c" "d" "e" "a" "b" "c" "d" "a" "f" "a" "b" "c" "a" "a" "a" "b" "c" "d" "a" "b" "d" "e")
(18 "f" "a" "b" "c" "a" "a" "a" "b" "c" "d" "a" "b" "d" "e"))))
;; what is the longest initial sequence (4 <= length <= 4) which occurs 5 times?
(assert (equal (loops-find-longest-loop test 4 4 5 )
'(4 ;it is length 5
;; and the 4 non-initial occurances start at positions 29, 18, 13 and 9
(29 "a" "b" "d" "e")
(18 "a" "f" "a" "b" "c" "a" "a" "a" "b" "c" "d" "a" "b" "d" "e")
(13 "e" "a" "b" "c" "d" "a" "f" "a" "b" "c" "a" "a" "a" "b" "c" "d" "a" "b" "d" "e")
(9 "a" "b" "c" "d" "e" "a" "b" "c" "d" "a" "f" "a" "b" "c" "a" "a" "a" "b" "c" "d" "a" "b" "d" "e"))))
))
(provide 'loops)
;;; loops.el ends here