OSDN Git Service

Bleah.
[joypy/Thun.git] / docs / sphinx_docs / notebooks / Derivatives_of_Regular_Expressions.rst
1 ∂RE
2 ===
3
4 Brzozowski’s Derivatives of Regular Expressions
5 -----------------------------------------------
6
7 Legend:
8
9 ::
10
11    ∧ intersection
12    ∨ union
13    ∘ concatenation (see below)
14    ¬ complement
15    ϕ empty set (aka ∅)
16    λ singleton set containing just the empty string
17    I set of all letters in alphabet
18
19 Derivative of a set ``R`` of strings and a string ``a``:
20
21 ::
22
23    ∂a(R)
24
25    ∂a(a) → λ
26    ∂a(λ) → ϕ
27    ∂a(ϕ) → ϕ
28    ∂a(¬a) → ϕ
29    ∂a(R*) → ∂a(R)∘R*
30    ∂a(¬R) → ¬∂a(R)
31    ∂a(R∘S) → ∂a(R)∘S ∨ δ(R)∘∂a(S)
32    ∂a(R ∧ S) → ∂a(R) ∧ ∂a(S)
33    ∂a(R ∨ S) → ∂a(R) ∨ ∂a(S)
34
35    ∂ab(R) = ∂b(∂a(R))
36
37 Auxiliary predicate function ``δ`` (I call it ``nully``) returns either
38 ``λ`` if ``λ ⊆ R`` or ``ϕ`` otherwise:
39
40 ::
41
42    δ(a) → ϕ
43    δ(λ) → λ
44    δ(ϕ) → ϕ
45    δ(R*) → λ
46    δ(¬R) δ(R)≟ϕ → λ
47    δ(¬R) δ(R)≟λ → ϕ
48    δ(R∘S) → δ(R) ∧ δ(S)
49    δ(R ∧ S) → δ(R) ∧ δ(S)
50    δ(R ∨ S) → δ(R) ∨ δ(S)
51
52 Some rules we will use later for “compaction”:
53
54 ::
55
56    R ∧ ϕ = ϕ ∧ R = ϕ
57
58    R ∧ I = I ∧ R = R
59
60    R ∨ ϕ = ϕ ∨ R = R
61
62    R ∨ I = I ∨ R = I
63
64    R∘ϕ = ϕ∘R = ϕ
65
66    R∘λ = λ∘R = R
67
68 Concatination of sets: for two sets A and B the set A∘B is defined as:
69
70 {a∘b for a in A for b in B}
71
72 E.g.:
73
74 {‘a’, ‘b’}∘{‘c’, ‘d’} → {‘ac’, ‘ad’, ‘bc’, ‘bd’}
75
76 Implementation
77 --------------
78
79 .. code:: ipython2
80
81     from functools import partial as curry
82     from itertools import product
83
84 ``ϕ`` and ``λ``
85 ~~~~~~~~~~~~~~~
86
87 The empty set and the set of just the empty string.
88
89 .. code:: ipython2
90
91     phi = frozenset()   # ϕ
92     y = frozenset({''}) # λ
93
94 Two-letter Alphabet
95 ~~~~~~~~~~~~~~~~~~~
96
97 I’m only going to use two symbols (at first) becaase this is enough to
98 illustrate the algorithm and because you can represent any other
99 alphabet with two symbols (if you had to.)
100
101 I chose the names ``O`` and ``l`` (uppercase “o” and lowercase “L”) to
102 look like ``0`` and ``1`` (zero and one) respectively.
103
104 .. code:: ipython2
105
106     syms = O, l = frozenset({'0'}), frozenset({'1'})
107
108 Representing Regular Expressions
109 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
110
111 To represent REs in Python I’m going to use tagged tuples. A *regular
112 expression* is one of:
113
114 ::
115
116    O
117    l
118    (KSTAR, R)
119    (NOT, R)
120    (AND, R, S)
121    (CONS, R, S)
122    (OR, R, S)
123
124 Where ``R`` and ``S`` stand for *regular expressions*.
125
126 .. code:: ipython2
127
128     AND, CONS, KSTAR, NOT, OR = 'and cons * not or'.split()  # Tags are just strings.
129
130 Because they are formed of ``frozenset``, ``tuple`` and ``str`` objects
131 only, these datastructures are immutable.
132
133 String Representation of RE Datastructures
134 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
135
136 .. code:: ipython2
137
138     def stringy(re):
139         '''
140         Return a nice string repr for a regular expression datastructure.
141         '''
142         if re == I: return '.'
143         if re in syms: return next(iter(re))
144         if re == y: return '^'
145         if re == phi: return 'X'
146     
147         assert isinstance(re, tuple), repr(re)
148         tag = re[0]
149     
150         if tag == KSTAR:
151             body = stringy(re[1])
152             if not body: return body
153             if len(body) > 1: return '(' + body + ")*"
154             return body + '*'
155     
156         if tag == NOT:
157             body = stringy(re[1])
158             if not body: return body
159             if len(body) > 1: return '(' + body + ")'"
160             return body + "'"
161     
162         r, s = stringy(re[1]), stringy(re[2])
163         if tag == CONS: return r + s
164         if tag == OR:   return '%s | %s' % (r, s)
165         if tag == AND:  return '(%s) & (%s)' % (r, s)
166     
167         raise ValueError
168
169 ``I``
170 ~~~~~
171
172 Match anything. Often spelled “.”
173
174 ::
175
176    I = (0|1)*
177
178 .. code:: ipython2
179
180     I = (KSTAR, (OR, O, l))
181
182 .. code:: ipython2
183
184     print stringy(I)
185
186
187 .. parsed-literal::
188
189     .
190
191
192 ``(.111.) & (.01 + 11*)'``
193 ~~~~~~~~~~~~~~~~~~~~~~~~~~
194
195 The example expression from Brzozowski:
196
197 ::
198
199    (.111.) & (.01 + 11*)'
200       a    &  (b  +  c)'
201
202 Note that it contains one of everything.
203
204 .. code:: ipython2
205
206     a = (CONS, I, (CONS, l, (CONS, l, (CONS, l, I))))
207     b = (CONS, I, (CONS, O, l))
208     c = (CONS, l, (KSTAR, l))
209     it = (AND, a, (NOT, (OR, b, c)))
210
211 .. code:: ipython2
212
213     print stringy(it)
214
215
216 .. parsed-literal::
217
218     (.111.) & ((.01 | 11*)')
219
220
221 ``nully()``
222 ~~~~~~~~~~~
223
224 Let’s get that auxiliary predicate function ``δ`` out of the way.
225
226 .. code:: ipython2
227
228     def nully(R):
229         '''
230         δ - Return λ if λ ⊆ R otherwise ϕ.
231         '''
232     
233         # δ(a) → ϕ
234         # δ(ϕ) → ϕ
235         if R in syms or R == phi:
236             return phi
237     
238         # δ(λ) → λ
239         if R == y:
240             return y
241     
242         tag = R[0]
243     
244         # δ(R*) → λ
245         if tag == KSTAR:
246             return y
247     
248         # δ(¬R) δ(R)≟ϕ → λ
249         # δ(¬R) δ(R)≟λ → ϕ
250         if tag == NOT:
251             return phi if nully(R[1]) else y
252     
253         # δ(R∘S) → δ(R) ∧ δ(S)
254         # δ(R ∧ S) → δ(R) ∧ δ(S)
255         # δ(R ∨ S) → δ(R) ∨ δ(S)
256         r, s = nully(R[1]), nully(R[2])
257         return r & s if tag in {AND, CONS} else r | s
258
259 No “Compaction”
260 ~~~~~~~~~~~~~~~
261
262 This is the straightforward version with no “compaction”. It works fine,
263 but does waaaay too much work because the expressions grow each
264 derivation.
265
266 .. code:: ipython2
267
268     def D(symbol):
269     
270         def derv(R):
271     
272             # ∂a(a) → λ
273             if R == {symbol}:
274                 return y
275     
276             # ∂a(λ) → ϕ
277             # ∂a(ϕ) → ϕ
278             # ∂a(¬a) → ϕ
279             if R == y or R == phi or R in syms:
280                 return phi
281             
282             tag = R[0]
283     
284             # ∂a(R*) → ∂a(R)∘R*
285             if tag == KSTAR:
286                 return (CONS, derv(R[1]), R)
287     
288             # ∂a(¬R) → ¬∂a(R)
289             if tag == NOT:
290                 return (NOT, derv(R[1]))
291     
292             r, s = R[1:]
293     
294             # ∂a(R∘S) → ∂a(R)∘S ∨ δ(R)∘∂a(S)
295             if tag == CONS:
296                 A = (CONS, derv(r), s)  # A = ∂a(R)∘S
297                 # A ∨ δ(R) ∘ ∂a(S)
298                 # A ∨  λ   ∘ ∂a(S) → A ∨ ∂a(S)
299                 # A ∨  ϕ   ∘ ∂a(S) → A ∨ ϕ → A
300                 return (OR, A, derv(s)) if nully(r) else A
301     
302             # ∂a(R ∧ S) → ∂a(R) ∧ ∂a(S)
303             # ∂a(R ∨ S) → ∂a(R) ∨ ∂a(S)
304             return (tag, derv(r), derv(s))
305     
306         return derv
307
308 Compaction Rules
309 ~~~~~~~~~~~~~~~~
310
311 .. code:: ipython2
312
313     def _compaction_rule(relation, one, zero, a, b):
314         return (
315             b if a == one else  # R*1 = 1*R = R
316             a if b == one else
317             zero if a == zero or b == zero else  # R*0 = 0*R = 0
318             (relation, a, b)
319             )
320
321 An elegant symmetry.
322
323 .. code:: ipython2
324
325     # R ∧ I = I ∧ R = R
326     # R ∧ ϕ = ϕ ∧ R = ϕ
327     _and = curry(_compaction_rule, AND, I, phi)
328     
329     # R ∨ ϕ = ϕ ∨ R = R
330     # R ∨ I = I ∨ R = I
331     _or = curry(_compaction_rule, OR, phi, I)
332     
333     # R∘λ = λ∘R = R
334     # R∘ϕ = ϕ∘R = ϕ
335     _cons = curry(_compaction_rule, CONS, y, phi)
336
337 Memoizing
338 ~~~~~~~~~
339
340 We can save re-processing by remembering results we have already
341 computed. RE datastructures are immutable and the ``derv()`` functions
342 are *pure* so this is fine.
343
344 .. code:: ipython2
345
346     class Memo(object):
347     
348         def __init__(self, f):
349             self.f = f
350             self.calls = self.hits = 0
351             self.mem = {}
352     
353         def __call__(self, key):
354             self.calls += 1
355             try:
356                 result = self.mem[key]
357                 self.hits += 1
358             except KeyError:
359                 result = self.mem[key] = self.f(key)
360             return result
361
362 With “Compaction”
363 ~~~~~~~~~~~~~~~~~
364
365 This version uses the rules above to perform compaction. It keeps the
366 expressions from growing too large.
367
368 .. code:: ipython2
369
370     def D_compaction(symbol):
371     
372         @Memo
373         def derv(R):
374     
375             # ∂a(a) → λ
376             if R == {symbol}:
377                 return y
378     
379             # ∂a(λ) → ϕ
380             # ∂a(ϕ) → ϕ
381             # ∂a(¬a) → ϕ
382             if R == y or R == phi or R in syms:
383                 return phi
384     
385             tag = R[0]
386     
387             # ∂a(R*) → ∂a(R)∘R*
388             if tag == KSTAR:
389                 return _cons(derv(R[1]), R)
390     
391             # ∂a(¬R) → ¬∂a(R)
392             if tag == NOT:
393                 return (NOT, derv(R[1]))
394     
395             r, s = R[1:]
396     
397             # ∂a(R∘S) → ∂a(R)∘S ∨ δ(R)∘∂a(S)
398             if tag == CONS:
399                 A = _cons(derv(r), s)  # A = ∂a(r)∘s
400                 # A ∨ δ(R) ∘ ∂a(S)
401                 # A ∨  λ   ∘ ∂a(S) → A ∨ ∂a(S)
402                 # A ∨  ϕ   ∘ ∂a(S) → A ∨ ϕ → A
403                 return _or(A, derv(s)) if nully(r) else A
404     
405             # ∂a(R ∧ S) → ∂a(R) ∧ ∂a(S)
406             # ∂a(R ∨ S) → ∂a(R) ∨ ∂a(S)
407             dr, ds = derv(r), derv(s)
408             return _and(dr, ds) if tag == AND else _or(dr, ds)
409     
410         return derv
411
412 Let’s try it out…
413 -----------------
414
415 (FIXME: redo.)
416
417 .. code:: ipython2
418
419     o, z = D_compaction('0'), D_compaction('1')
420     REs = set()
421     N = 5
422     names = list(product(*(N * [(0, 1)])))
423     dervs = list(product(*(N * [(o, z)])))
424     for name, ds in zip(names, dervs):
425         R = it
426         ds = list(ds)
427         while ds:
428             R = ds.pop()(R)
429             if R == phi or R == I:
430                 break
431             REs.add(R)
432     
433     print stringy(it) ; print
434     print o.hits, '/', o.calls
435     print z.hits, '/', z.calls
436     print
437     for s in sorted(map(stringy, REs), key=lambda n: (len(n), n)):
438         print s
439
440
441 .. parsed-literal::
442
443     (.111.) & ((.01 | 11*)')
444     
445     92 / 122
446     92 / 122
447     
448     (.01)'
449     (.01 | 1)'
450     (.01 | ^)'
451     (.01 | 1*)'
452     (.111.) & ((.01 | 1)')
453     (.111. | 11.) & ((.01 | ^)')
454     (.111. | 11. | 1.) & ((.01)')
455     (.111. | 11.) & ((.01 | 1*)')
456     (.111. | 11. | 1.) & ((.01 | 1*)')
457
458
459 Should match:
460
461 ::
462
463    (.111.) & ((.01 | 11*)')
464
465    92 / 122
466    92 / 122
467
468    (.01     )'
469    (.01 | 1 )'
470    (.01 | ^ )'
471    (.01 | 1*)'
472    (.111.)            & ((.01 | 1 )')
473    (.111. | 11.)      & ((.01 | ^ )')
474    (.111. | 11.)      & ((.01 | 1*)')
475    (.111. | 11. | 1.) & ((.01     )')
476    (.111. | 11. | 1.) & ((.01 | 1*)')
477
478 Larger Alphabets
479 ----------------
480
481 We could parse larger alphabets by defining patterns for e.g. each byte
482 of the ASCII code. Or we can generalize this code. If you study the code
483 above you’ll see that we never use the “set-ness” of the symbols ``O``
484 and ``l``. The only time Python set operators (``&`` and ``|``) appear
485 is in the ``nully()`` function, and there they operate on (recursively
486 computed) outputs of that function, never ``O`` and ``l``.
487
488 What if we try:
489
490 ::
491
492    (OR, O, l)
493
494    ∂1((OR, O, l))
495                                ∂a(R ∨ S) → ∂a(R) ∨ ∂a(S)
496    ∂1(O) ∨ ∂1(l)
497                                ∂a(¬a) → ϕ
498    ϕ ∨ ∂1(l)
499                                ∂a(a) → λ
500    ϕ ∨ λ
501                                ϕ ∨ R = R
502    λ
503
504 And compare it to:
505
506 ::
507
508    {'0', '1')
509
510    ∂1({'0', '1'))
511                                ∂a(R ∨ S) → ∂a(R) ∨ ∂a(S)
512    ∂1({'0')) ∨ ∂1({'1'))
513                                ∂a(¬a) → ϕ
514    ϕ ∨ ∂1({'1'))
515                                ∂a(a) → λ
516    ϕ ∨ λ
517                                ϕ ∨ R = R
518    λ
519
520 This suggests that we should be able to alter the functions above to
521 detect sets and deal with them appropriately. Exercise for the Reader
522 for now.
523
524 State Machine
525 -------------
526
527 We can drive the regular expressions to flesh out the underlying state
528 machine transition table.
529
530 ::
531
532    .111. & (.01 + 11*)'
533
534 Says, “Three or more 1’s and not ending in 01 nor composed of all 1’s.”
535
536 .. figure:: omg.svg
537    :alt: State Machine Graph
538
539    State Machine Graph
540
541 Start at ``a`` and follow the transition arrows according to their
542 labels. Accepting states have a double outline. (Graphic generated with
543 `Dot from Graphviz <http://www.graphviz.org/>`__.) You’ll see that only
544 paths that lead to one of the accepting states will match the regular
545 expression. All other paths will terminate at one of the non-accepting
546 states.
547
548 There’s a happy path to ``g`` along 111:
549
550 ::
551
552    a→c→e→g
553
554 After you reach ``g`` you’re stuck there eating 1’s until you see a 0,
555 which takes you to the ``i→j→i|i→j→h→i`` “trap”. You can’t reach any
556 other states from those two loops.
557
558 If you see a 0 before you see 111 you will reach ``b``, which forms
559 another “trap” with ``d`` and ``f``. The only way out is another happy
560 path along 111 to ``h``:
561
562 ::
563
564    b→d→f→h
565
566 Once you have reached ``h`` you can see as many 1’s or as many 0’ in a
567 row and still be either still at ``h`` (for 1’s) or move to ``i`` (for
568 0’s). If you find yourself at ``i`` you can see as many 0’s, or
569 repetitions of 10, as there are, but if you see just a 1 you move to
570 ``j``.
571
572 RE to FSM
573 ~~~~~~~~~
574
575 So how do we get the state machine from the regular expression?
576
577 It turns out that each RE is effectively a state, and each arrow points
578 to the derivative RE in respect to the arrow’s symbol.
579
580 If we label the initial RE ``a``, we can say:
581
582 ::
583
584    a --0--> ∂0(a)
585    a --1--> ∂1(a)
586
587 And so on, each new unique RE is a new state in the FSM table.
588
589 Here are the derived REs at each state:
590
591 ::
592
593    a = (.111.) & ((.01 | 11*)')
594    b = (.111.) & ((.01 | 1)')
595    c = (.111. | 11.) & ((.01 | 1*)')
596    d = (.111. | 11.) & ((.01 | ^)')
597    e = (.111. | 11. | 1.) & ((.01 | 1*)')
598    f = (.111. | 11. | 1.) & ((.01)')
599    g = (.01 | 1*)'
600    h = (.01)'
601    i = (.01 | 1)'
602    j = (.01 | ^)'
603
604 You can see the one-way nature of the ``g`` state and the ``hij`` “trap”
605 in the way that the ``.111.`` on the left-hand side of the ``&``
606 disappears once it has been matched.
607
608 .. code:: ipython2
609
610     from collections import defaultdict
611     from pprint import pprint
612     from string import ascii_lowercase
613
614 .. code:: ipython2
615
616     d0, d1 = D_compaction('0'), D_compaction('1')
617
618 ``explore()``
619 ~~~~~~~~~~~~~
620
621 .. code:: ipython2
622
623     def explore(re):
624     
625         # Don't have more than 26 states...
626         names = defaultdict(iter(ascii_lowercase).next)
627     
628         table, accepting = dict(), set()
629     
630         to_check = {re}
631         while to_check:
632     
633             re = to_check.pop()
634             state_name = names[re]
635     
636             if (state_name, 0) in table:
637                 continue
638     
639             if nully(re):
640                 accepting.add(state_name)
641     
642             o, i = d0(re), d1(re)
643             table[state_name, 0] = names[o] ; to_check.add(o)
644             table[state_name, 1] = names[i] ; to_check.add(i)
645     
646         return table, accepting
647
648 .. code:: ipython2
649
650     table, accepting = explore(it)
651     table
652
653
654
655
656 .. parsed-literal::
657
658     {('a', 0): 'b',
659      ('a', 1): 'c',
660      ('b', 0): 'b',
661      ('b', 1): 'd',
662      ('c', 0): 'b',
663      ('c', 1): 'e',
664      ('d', 0): 'b',
665      ('d', 1): 'f',
666      ('e', 0): 'b',
667      ('e', 1): 'g',
668      ('f', 0): 'b',
669      ('f', 1): 'h',
670      ('g', 0): 'i',
671      ('g', 1): 'g',
672      ('h', 0): 'i',
673      ('h', 1): 'h',
674      ('i', 0): 'i',
675      ('i', 1): 'j',
676      ('j', 0): 'i',
677      ('j', 1): 'h'}
678
679
680
681 .. code:: ipython2
682
683     accepting
684
685
686
687
688 .. parsed-literal::
689
690     {'h', 'i'}
691
692
693
694 Generate Diagram
695 ~~~~~~~~~~~~~~~~
696
697 Once we have the FSM table and the set of accepting states we can
698 generate the diagram above.
699
700 .. code:: ipython2
701
702     _template = '''\
703     digraph finite_state_machine {
704       rankdir=LR;
705       size="8,5"
706       node [shape = doublecircle]; %s;
707       node [shape = circle];
708     %s
709     }
710     '''
711     
712     def link(fr, nm, label):
713         return '  %s -> %s [ label = "%s" ];' % (fr, nm, label)
714     
715     
716     def make_graph(table, accepting):
717         return _template % (
718             ' '.join(accepting),
719             '\n'.join(
720               link(from_, to, char)
721               for (from_, char), (to) in sorted(table.iteritems())
722               )
723             )
724
725 .. code:: ipython2
726
727     print make_graph(table, accepting)
728
729
730 .. parsed-literal::
731
732     digraph finite_state_machine {
733       rankdir=LR;
734       size="8,5"
735       node [shape = doublecircle]; i h;
736       node [shape = circle];
737       a -> b [ label = "0" ];
738       a -> c [ label = "1" ];
739       b -> b [ label = "0" ];
740       b -> d [ label = "1" ];
741       c -> b [ label = "0" ];
742       c -> e [ label = "1" ];
743       d -> b [ label = "0" ];
744       d -> f [ label = "1" ];
745       e -> b [ label = "0" ];
746       e -> g [ label = "1" ];
747       f -> b [ label = "0" ];
748       f -> h [ label = "1" ];
749       g -> i [ label = "0" ];
750       g -> g [ label = "1" ];
751       h -> i [ label = "0" ];
752       h -> h [ label = "1" ];
753       i -> i [ label = "0" ];
754       i -> j [ label = "1" ];
755       j -> i [ label = "0" ];
756       j -> h [ label = "1" ];
757     }
758     
759
760
761 Drive a FSM
762 ~~~~~~~~~~~
763
764 There are *lots* of FSM libraries already. Once you have the state
765 transition table they should all be straightforward to use. State
766 Machine code is very simple. Just for fun, here is an implementation in
767 Python that imitates what “compiled” FSM code might look like in an
768 “unrolled” form. Most FSM code uses a little driver loop and a table
769 datastructure, the code below instead acts like JMP instructions
770 (“jump”, or GOTO in higher-level-but-still-low-level languages) to
771 hard-code the information in the table into a little patch of branches.
772
773 Trampoline Function
774 ^^^^^^^^^^^^^^^^^^^
775
776 Python has no GOTO statement but we can fake it with a “trampoline”
777 function.
778
779 .. code:: ipython2
780
781     def trampoline(input_, jump_from, accepting):
782         I = iter(input_)
783         while True:
784             try:
785                 bounce_to = jump_from(I)
786             except StopIteration:
787                 return jump_from in accepting
788             jump_from = bounce_to
789
790 Stream Functions
791 ^^^^^^^^^^^^^^^^
792
793 Little helpers to process the iterator of our data (a “stream” of “1”
794 and “0” characters, not bits.)
795
796 .. code:: ipython2
797
798     getch = lambda I: int(next(I))
799     
800     
801     def _1(I):
802         '''Loop on ones.'''
803         while getch(I): pass
804     
805     
806     def _0(I):
807         '''Loop on zeros.'''
808         while not getch(I): pass
809
810 A Finite State Machine
811 ^^^^^^^^^^^^^^^^^^^^^^
812
813 With those preliminaries out of the way, from the state table of
814 ``.111. & (.01 + 11*)'`` we can immediately write down state machine
815 code. (You have to imagine that these are GOTO statements in C or
816 branches in assembly and that the state names are branch destination
817 labels.)
818
819 .. code:: ipython2
820
821     a = lambda I: c if getch(I) else b
822     b = lambda I: _0(I) or d
823     c = lambda I: e if getch(I) else b
824     d = lambda I: f if getch(I) else b
825     e = lambda I: g if getch(I) else b
826     f = lambda I: h if getch(I) else b
827     g = lambda I: _1(I) or i
828     h = lambda I: _1(I) or i
829     i = lambda I: _0(I) or j
830     j = lambda I: h if getch(I) else i
831
832 Note that the implementations of ``h`` and ``g`` are identical ergo
833 ``h = g`` and we could eliminate one in the code but ``h`` is an
834 accepting state and ``g`` isn’t.
835
836 .. code:: ipython2
837
838     def acceptable(input_):
839         return trampoline(input_, a, {h, i})
840
841 .. code:: ipython2
842
843     for n in range(2**5):
844         s = bin(n)[2:]
845         print '%05s' % s, acceptable(s)
846
847
848 .. parsed-literal::
849
850         0 False
851         1 False
852        10 False
853        11 False
854       100 False
855       101 False
856       110 False
857       111 False
858      1000 False
859      1001 False
860      1010 False
861      1011 False
862      1100 False
863      1101 False
864      1110 True
865      1111 False
866     10000 False
867     10001 False
868     10010 False
869     10011 False
870     10100 False
871     10101 False
872     10110 False
873     10111 True
874     11000 False
875     11001 False
876     11010 False
877     11011 False
878     11100 True
879     11101 False
880     11110 True
881     11111 False
882
883
884 Reversing the Derivatives to Generate Matching Strings
885 ------------------------------------------------------
886
887 (UNFINISHED) Brzozowski also shewed how to go from the state machine to
888 strings and expressions…
889
890 Each of these states is just a name for a Brzozowskian RE, and so, other
891 than the initial state ``a``, they can can be described in terms of the
892 derivative-with-respect-to-N of some other state/RE:
893
894 ::
895
896    c = d1(a)
897    b = d0(a)
898    b = d0(c)
899    ...
900    i = d0(j)
901    j = d1(i)
902
903 Consider:
904
905 ::
906
907    c = d1(a)
908    b = d0(c)
909
910 Substituting:
911
912 ::
913
914    b = d0(d1(a))
915
916 Unwrapping:
917
918 ::
919
920    b = d10(a)
921
922 ’’’
923
924 ::
925
926    j = d1(d0(j))
927
928 Unwrapping:
929
930 ::
931
932    j = d1(d0(j)) = d01(j)
933
934 We have a loop or “fixed point”.
935
936 ::
937
938    j = d01(j) = d0101(j) = d010101(j) = ...
939
940 hmm…
941
942 ::
943
944    j = (01)*
945
946