tb 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,569 @@
1
+ # lib/tb/pathfinder.rb - pattern matcher for two-dimensional array.
2
+ #
3
+ # Copyright (C) 2011 Tanaka Akira <akr@fsij.org>
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without
6
+ # modification, are permitted provided that the following conditions are met:
7
+ #
8
+ # 1. Redistributions of source code must retain the above copyright notice, this
9
+ # list of conditions and the following disclaimer.
10
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
11
+ # this list of conditions and the following disclaimer in the documentation
12
+ # and/or other materials provided with the distribution.
13
+ # 3. The name of the author may not be used to endorse or promote products
14
+ # derived from this software without specific prior written permission.
15
+ #
16
+ # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17
+ # WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18
+ # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19
+ # EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20
+ # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
21
+ # OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
24
+ # IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
25
+ # OF SUCH DAMAGE.
26
+
27
+ module Tb::Pathfinder
28
+ module_function
29
+
30
+ def strary_to_aa(strary)
31
+ aa = []
32
+ strary.each_with_index {|str, y|
33
+ aa[y] = []
34
+ str.each_char.with_index {|ch, x|
35
+ aa[y][x] = ch
36
+ }
37
+ }
38
+ aa
39
+ end
40
+
41
+ def match(pat, aa, start=nil)
42
+ each_match(pat, aa, start) {|spos, epos, cap|
43
+ return spos, epos, cap
44
+ }
45
+ end
46
+
47
+ def each_match(pat, aa, spos=nil)
48
+ if spos
49
+ run {
50
+ try(pat, aa, Tb::Pathfinder::EmptyState.merge(:pos => spos)) {|st2|
51
+ yield spos, st2.fetch(:pos), st2.reject {|k,v| k == :pos }
52
+ nil
53
+ }
54
+ }
55
+ else
56
+ aa.each_with_index {|a, y|
57
+ a.each_index {|x|
58
+ spos = [x,y]
59
+ run {
60
+ try(pat, aa, Tb::Pathfinder::EmptyState.merge(:pos => spos)) {|st2|
61
+ yield spos, st2.fetch(:pos), st2.reject {|k,v| k == :pos }
62
+ nil
63
+ }
64
+ }
65
+ }
66
+ }
67
+ end
68
+ nil
69
+ end
70
+
71
+ def run(&b)
72
+ stack = [b]
73
+ while !stack.empty?
74
+ last = stack.pop
75
+ v = last.call
76
+ case v
77
+ when nil
78
+ when Array
79
+ v.each {|e|
80
+ raise TypeError, "result array contains non-proc: #{last.inspect}" unless Proc === e
81
+ }
82
+ stack.concat v.reverse
83
+ when Proc
84
+ stack << v
85
+ else
86
+ raise TypeError, "unexpected: #{v.inspect}"
87
+ end
88
+ end
89
+ end
90
+
91
+ def try(pat, aa, st, &b)
92
+ case pat
93
+ when nil; lambda { yield st }
94
+ when String; try_lit(pat, aa, st, &b)
95
+ when Regexp; try_regexp(pat, aa, st, &b)
96
+ when :n, :north; try_rmove(:north, aa, st, &b)
97
+ when :s, :south; try_rmove(:south, aa, st, &b)
98
+ when :e, :east; try_rmove(:east, aa, st, &b)
99
+ when :w, :west; try_rmove(:west, aa, st, &b)
100
+ when :debug_print_state; p st; yield st
101
+ when Array
102
+ case pat[0]
103
+ when :rmove; _, dir = pat; try_rmove(dir, aa, st, &b)
104
+ when :lit; _, val = pat; try_lit(val, aa, st, &b)
105
+ when :regexp; _, re = pat; try_regexp(re, aa, st, &b)
106
+ when :cat; _, *ps = pat; try_cat(ps, aa, st, &b)
107
+ when :alt; _, *ps = pat; try_alt(ps, aa, st, &b)
108
+ when :rep; _, *ps = pat; try_rep_generic(nil, 0, nil, true, ps, aa, st, &b)
109
+ when :rep1; _, *ps = pat; try_rep_generic(nil, 1, nil, true, ps, aa, st, &b)
110
+ when :rep_nongreedy; _, *ps = pat; try_rep_generic(nil, 0, nil, false, ps, aa, st, &b)
111
+ when :rep1_nongreedy; _, *ps = pat; try_rep_generic(nil, 1, nil, false, ps, aa, st, &b)
112
+ when :opt; _, *ps = pat; try_rep_generic(nil, 0, 1, true, ps, aa, st, &b)
113
+ when :opt_nongreedy; _, *ps = pat; try_rep_generic(nil, 0, 1, false, ps, aa, st, &b)
114
+ when :repn; _, num, *ps = pat; try_rep_generic(nil, num, num, true, ps, aa, st, &b)
115
+ when :repeat; _, var, min, max, greedy, *ps = pat; try_rep_generic(var, min, max, greedy, ps, aa, st, &b)
116
+ when :bfs; _, keys, *ps = pat; try_bfs(keys, ps, aa, st, &b)
117
+ when :grid; _, *arys = pat; try_grid(arys, aa, st, &b)
118
+ when :capval; _, n = pat; try_capval(n, aa, st, &b)
119
+ when :refval; _, n = pat; try_refval(n, aa, st, &b)
120
+ when :tmp_pos; _, dx, dy, *ps = pat; try_tmp_pos(dx, dy, ps, aa, st, &b)
121
+ when :save_pos; _, n = pat; try_save_pos(n, aa, st, &b)
122
+ when :push_pos; _, n = pat; try_push_pos(n, aa, st, &b)
123
+ when :pop_pos; _, n = pat; try_pop_pos(n, aa, st, &b)
124
+ when :update; _, pr = pat; try_update(pr, aa, st, &b)
125
+ when :assert; _, pr = pat; try_assert(pr, aa, st, &b)
126
+ else raise "unexpected: #{pat.inspect}"
127
+ end
128
+ else
129
+ raise TypeError, "unexpected pattern: #{pat.inspect}"
130
+ end
131
+ end
132
+
133
+ def try_rmove(dir, aa, st)
134
+ x, y = st.fetch(:pos)
135
+ case dir
136
+ when :east then x += 1
137
+ when :west then x -= 1
138
+ when :north then y -= 1
139
+ when :south then y += 1
140
+ end
141
+ if 0 <= y && y < aa.length &&
142
+ 0 <= x && x < aa[y].length
143
+ lambda { yield st.merge(:pos => [x,y]) }
144
+ else
145
+ nil
146
+ end
147
+ end
148
+
149
+ def try_lit(val, aa, st)
150
+ x, y = st.fetch(:pos)
151
+ if 0 <= y && y < aa.length &&
152
+ 0 <= x && x < aa[y].length &&
153
+ aa[y][x] == val
154
+ lambda { yield st }
155
+ else
156
+ nil
157
+ end
158
+ end
159
+
160
+ def try_regexp(re, aa, st)
161
+ x, y = st.fetch(:pos)
162
+ if 0 <= y && y < aa.length &&
163
+ 0 <= x && x < aa[y].length &&
164
+ re =~ aa[y][x]
165
+ lambda { yield st }
166
+ else
167
+ nil
168
+ end
169
+ end
170
+
171
+ # p1 p2 ...
172
+ def try_cat(ps, aa, st, &block)
173
+ if ps.empty?
174
+ lambda { yield st }
175
+ else
176
+ pat, *rest = ps
177
+ try(pat, aa, st) {|st2|
178
+ try_cat(rest, aa, st2, &block)
179
+ }
180
+ end
181
+ end
182
+
183
+ # p1 | p2 | ...
184
+ def try_alt(ps, aa, st, &block)
185
+ ps.map {|pat|
186
+ lambda { try(pat, aa, st, &block) }
187
+ }
188
+ end
189
+
190
+ # (p1 p2 ...)*
191
+ # (p1 p2 ...){min,}
192
+ # (p1 p2 ...){min,max}
193
+ # (p1 p2 ...)*?
194
+ # (p1 p2 ...){min,}?
195
+ # (p1 p2 ...){min,max}?
196
+ def try_rep_generic(var, min, max, greedy, ps, aa, st, visit_keys=[:pos], visited={}, &block)
197
+ min = st[min].to_int if Symbol === min
198
+ max = st[max].to_int if Symbol === max
199
+ visited2 = visited.dup
200
+ visited2[st.values_at(*visit_keys)] = true
201
+ result = []
202
+ if min <= 0 && !greedy
203
+ result << lambda {
204
+ st = st.merge(var => visited.size) if var
205
+ yield st
206
+ }
207
+ end
208
+ if max.nil? || 0 < max
209
+ min2 = min <= 0 ? 0 : min-1
210
+ max2 = max ? max-1 : nil
211
+ result << lambda {
212
+ try_cat(ps, aa, st) {|st2|
213
+ if !visited2[st2.values_at(*visit_keys)]
214
+ try_rep_generic(var, min2, max2, greedy, ps, aa, st2, visit_keys, visited2, &block)
215
+ else
216
+ nil
217
+ end
218
+ }
219
+ }
220
+ end
221
+ if min <= 0 && greedy
222
+ result << lambda {
223
+ st = st.merge(var => visited.size) if var
224
+ yield st
225
+ }
226
+ end
227
+ result
228
+ end
229
+
230
+ def try_bfs(keys, ps, aa, st, &b)
231
+ queue = [st]
232
+ visited = {st.values_at(*keys) => true}
233
+ try_bfs_loop(queue, visited, keys, ps, aa, &b)
234
+ end
235
+
236
+ def try_bfs_loop(queue, visited, keys, ps, aa, &b)
237
+ if queue.empty?
238
+ nil
239
+ else
240
+ st = queue.shift
241
+ result = []
242
+ result << lambda {
243
+ yield st
244
+ }
245
+ result << lambda {
246
+ try_cat(ps, aa, st) {|st2|
247
+ k = st2.values_at(*keys)
248
+ if !visited[k]
249
+ visited[k] = true
250
+ queue << st2
251
+ end
252
+ nil
253
+ }
254
+ }
255
+ result << lambda {
256
+ try_bfs_loop(queue, visited, keys, ps, aa, &b)
257
+ }
258
+ result
259
+ end
260
+ end
261
+
262
+ def try_grid(arys, aa, st)
263
+ start = nil
264
+ goal = nil
265
+ newarys = []
266
+ arys.each_with_index {|ary, y|
267
+ newarys << []
268
+ ary.each_with_index {|pat, x|
269
+ if pat == :start
270
+ raise ArgumentError, "multiple starts: #{arys.inspect}" if start
271
+ start = [x,y]
272
+ newarys.last << nil
273
+ elsif Array === pat && pat[0] == :start
274
+ raise ArgumentError, "multiple starts: #{arys.inspect}" if start
275
+ start = [x,y]
276
+ newarys.last << [:cat, *pat[1..-1]]
277
+ elsif pat == :goal
278
+ raise ArgumentError, "multiple goals: #{arys.inspect}" if goal
279
+ goal = [x,y]
280
+ newarys.last << nil
281
+ elsif Array === pat && pat[0] == :goal
282
+ raise ArgumentError, "multiple goals: #{arys.inspect}" if goal
283
+ goal = [x,y]
284
+ newarys.last << [:cat, *pat[1..-1]]
285
+ elsif pat == :origin
286
+ raise ArgumentError, "multiple starts: #{arys.inspect}" if start
287
+ raise ArgumentError, "multiple goals: #{arys.inspect}" if goal
288
+ start = goal = [x,y]
289
+ newarys.last << nil
290
+ elsif Array === pat && pat[0] == :origin
291
+ raise ArgumentError, "multiple starts: #{arys.inspect}" if start
292
+ raise ArgumentError, "multiple goals: #{arys.inspect}" if goal
293
+ start = goal = [x,y]
294
+ newarys.last << [:cat, *pat[1..-1]]
295
+ else
296
+ newarys.last << pat
297
+ end
298
+ }
299
+ }
300
+ raise ArgumentError, "no start" if !start
301
+ raise ArgumentError, "no goal" if !goal
302
+ pos = st.fetch(:pos)
303
+ try_grid_rect(newarys, aa, st.merge(:pos => [pos[0]-start[0], pos[1]-start[1]])) {|st2|
304
+ lambda { yield st2.merge(:pos => [pos[0]-start[0]+goal[0], pos[1]-start[1]+goal[1]]) }
305
+ }
306
+ end
307
+
308
+ def try_grid_rect(arys, aa, st)
309
+ if arys.empty?
310
+ lambda { yield st }
311
+ else
312
+ ary, *rest = arys
313
+ x, y = st.fetch(:pos)
314
+ pos1 = [x, y+1]
315
+ try_grid_row(ary, aa, st) {|st2|
316
+ try_grid_rect(rest, aa, st2.merge(:pos => pos1)) {|st3|
317
+ lambda { yield st3.merge(:pos => st.fetch(:pos)) }
318
+ }
319
+ }
320
+ end
321
+ end
322
+
323
+ def try_grid_row(ary, aa, st)
324
+ if ary.empty?
325
+ lambda { yield st }
326
+ else
327
+ pat, *rest = ary
328
+ x, y = st.fetch(:pos)
329
+ pos1 = [x+1, y]
330
+ try(pat, aa, st) {|st2|
331
+ try_grid_row(rest, aa, st2.merge(:pos => pos1)) {|st3|
332
+ lambda { yield st3.merge(:pos => st.fetch(:pos)) }
333
+ }
334
+ }
335
+ end
336
+ end
337
+
338
+ def try_capval(n, aa, st)
339
+ x, y = st.fetch(:pos)
340
+ if 0 <= y && y < aa.length &&
341
+ 0 <= x && x < aa[y].length &&
342
+ val = aa[y][x]
343
+ else
344
+ val = nil
345
+ end
346
+ st2 = st.merge(n => val)
347
+ lambda { yield st2 }
348
+ end
349
+
350
+ def try_refval(n, aa, st)
351
+ x, y = st.fetch(:pos)
352
+ if 0 <= y && y < aa.length &&
353
+ 0 <= x && x < aa[y].length
354
+ val = aa[y][x]
355
+ else
356
+ val = nil
357
+ end
358
+ if val == st[n]
359
+ lambda { yield st }
360
+ else
361
+ nil
362
+ end
363
+ end
364
+
365
+ def try_tmp_pos(dx, dy, ps, aa, st, &b)
366
+ x, y = st.fetch(:pos)
367
+ try_cat(ps, aa, st.merge(:pos => [x+dx, y+dy])) {|st2|
368
+ lambda { yield st2.merge(:pos => st.fetch(:pos)) }
369
+ }
370
+ end
371
+
372
+ def try_save_pos(n, aa, st, &b)
373
+ st2 = st.merge(n => st.fetch(:pos))
374
+ lambda { yield st2 }
375
+ end
376
+
377
+ def try_push_pos(n, aa, st, &b)
378
+ ary = (st[n] || []) + [st.fetch(:pos)]
379
+ st2 = st.merge(n => ary)
380
+ lambda { yield st2 }
381
+ end
382
+
383
+ def try_pop_pos(n, aa, st, &b)
384
+ ary = st[n]
385
+ raise TypeError, "array expected: #{ary.inspect} (#{n.inspect})" unless Array === ary
386
+ raise TypeError, "empty array (#{n.inspect})" if ary.empty?
387
+ pos2 = ary.last
388
+ raise TypeError, "array expected: #{pos2.inspect} (#{n.inspect})" unless Array === pos2
389
+ raise TypeError, "two-element array expected: #{pos2.inspect} (#{n.inspect})" if pos2.length != 2
390
+ raise TypeError, "integer elements expected: #{pos2.inspect} (#{n.inspect})" unless pos2.all? {|v| Integer === v }
391
+ st2 = st.merge(:pos => pos2, n => ary[0...-1])
392
+ lambda { yield st2 }
393
+ end
394
+
395
+ def try_update(pr, aa, st)
396
+ st2 = pr.call(st)
397
+ lambda { yield st2 }
398
+ end
399
+
400
+ def try_assert(pr, aa, st)
401
+ if pr.call(st)
402
+ lambda { yield st }
403
+ else
404
+ nil
405
+ end
406
+ end
407
+ end
408
+
409
+ module Tb::Pathfinder::EmptyState
410
+ module_function
411
+
412
+ def empty?
413
+ true
414
+ end
415
+
416
+ def each
417
+ end
418
+
419
+ def fetch(k, *rest)
420
+ if block_given?
421
+ yield k
422
+ elsif !rest.empty?
423
+ return rest[0]
424
+ else
425
+ raise KeyError, "key not found: #{k}"
426
+ end
427
+ end
428
+
429
+ def [](k)
430
+ nil
431
+ end
432
+
433
+ def values_at(*ks)
434
+ ks.map {|k| nil }
435
+ end
436
+
437
+ def keys
438
+ []
439
+ end
440
+
441
+ def merge(h)
442
+ pairs = self
443
+ h.reverse_each {|k, v|
444
+ pairs = Tb::Pathfinder::State.new(k, v, pairs)
445
+ }
446
+ pairs
447
+ end
448
+
449
+ def reject
450
+ self
451
+ end
452
+
453
+ def inspect
454
+ "\#<Tb::Pathfinder::EmptyState>"
455
+ end
456
+ end
457
+
458
+ class Tb::Pathfinder::State
459
+ def initialize(key, val, tail=nil)
460
+ @key = key
461
+ @val = val
462
+ @tail = tail
463
+ end
464
+ attr_reader :key, :val, :tail
465
+
466
+ def empty?
467
+ false
468
+ end
469
+
470
+ def each
471
+ pairs = self
472
+ while !pairs.empty?
473
+ yield [pairs.key, pairs.val]
474
+ pairs = pairs.tail
475
+ end
476
+ nil
477
+ end
478
+
479
+ def fetch(k, *rest)
480
+ pairs = self
481
+ while !pairs.empty?
482
+ return pairs.val if k == pairs.key
483
+ pairs = pairs.tail
484
+ end
485
+ if block_given?
486
+ yield k
487
+ elsif !rest.empty?
488
+ return rest[0]
489
+ else
490
+ raise KeyError, "key not found: #{k}"
491
+ end
492
+ end
493
+
494
+ def [](k)
495
+ fetch(k, nil)
496
+ end
497
+
498
+ def values_at(*ks)
499
+ ks.map {|k| self[k] }
500
+ end
501
+
502
+ def keys
503
+ result = []
504
+ pairs = self
505
+ while !pairs.empty?
506
+ result << pairs.key
507
+ pairs = pairs.tail
508
+ end
509
+ result
510
+ end
511
+
512
+ def merge(h)
513
+ return self if h.empty?
514
+ n = 0
515
+ pairs = self
516
+ ary = []
517
+ result = self
518
+ needs_copy = 0
519
+ while !pairs.empty?
520
+ if h.include? pairs.key
521
+ needs_copy = ary.length
522
+ result = pairs.tail
523
+ n += 1
524
+ break if n == h.size
525
+ else
526
+ ary << pairs
527
+ end
528
+ pairs = pairs.tail
529
+ end
530
+ (needs_copy-1).downto(0) {|i|
531
+ pairs = ary[i]
532
+ result = Tb::Pathfinder::State.new(pairs.key, pairs.val, result)
533
+ }
534
+ h.reverse_each {|k, v|
535
+ result = Tb::Pathfinder::State.new(k, v, result)
536
+ }
537
+ result
538
+ end
539
+
540
+ def reject
541
+ ary = []
542
+ pairs = self
543
+ while !pairs.empty?
544
+ unless yield pairs.key, pairs.val
545
+ ary << pairs
546
+ end
547
+ pairs = pairs.tail
548
+ end
549
+ result = Tb::Pathfinder::EmptyState
550
+ ary.reverse_each {|pairs|
551
+ result = Tb::Pathfinder::State.new(pairs.key, pairs.val, result)
552
+ }
553
+ result
554
+ end
555
+
556
+ def inspect
557
+ h = {}
558
+ pairs = self
559
+ str = ''
560
+ while !pairs.empty?
561
+ str << pairs.key.inspect << "=>" << pairs.val.inspect << ", "
562
+ pairs = pairs.tail
563
+ end
564
+ str.sub!(/, \z/, '')
565
+ "\#<#{self.class}: #{str}>"
566
+ end
567
+
568
+ end
569
+