diff --git a/docs/0._This_Implementation_of_Joy_in_Python.ipynb b/docs/0._This_Implementation_of_Joy_in_Python.ipynb index 8b3ce7e..125137f 100644 --- a/docs/0._This_Implementation_of_Joy_in_Python.ipynb +++ b/docs/0._This_Implementation_of_Joy_in_Python.ipynb @@ -643,7 +643,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", - "version": "2.7.13" + "version": "2.7.12" } }, "nbformat": 4, diff --git a/docs/1._Basic_Use_of_Joy_in_a_Notebook.ipynb b/docs/1._Basic_Use_of_Joy_in_a_Notebook.ipynb index 7c8f2a3..3b99e49 100644 --- a/docs/1._Basic_Use_of_Joy_in_a_Notebook.ipynb +++ b/docs/1._Basic_Use_of_Joy_in_a_Notebook.ipynb @@ -232,7 +232,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", - "version": "2.7.13" + "version": "2.7.12" } }, "nbformat": 4, diff --git a/docs/Derivatives_of_Regular_Expressions.html b/docs/Derivatives_of_Regular_Expressions.html new file mode 100644 index 0000000..72a0866 --- /dev/null +++ b/docs/Derivatives_of_Regular_Expressions.html @@ -0,0 +1,13152 @@ + + + +Derivatives_of_Regular_Expressions + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+

∂RE

Brzozowski's Derivatives of Regular Expressions

+
+
+
+
+
+
+
+

Legend:

+ +
∧ intersection
+∨ union
+∘ concatenation (see below)
+¬ complement
+ϕ empty set (aka ∅)
+λ singleton set containing just the empty string
+I set of all letters in alphabet
+ +
+
+
+
+
+
+
+

Derivative of a set R of strings and a string a:

+ +
∂a(R)
+
+∂a(a) → λ
+∂a(λ) → ϕ
+∂a(ϕ) → ϕ
+∂a(¬a) → ϕ
+∂a(R*) → ∂a(R)∘R*
+∂a(¬R) → ¬∂a(R)
+∂a(R∘S) → ∂a(R)∘S ∨ δ(R)∘∂a(S)
+∂a(R ∧ S) → ∂a(R) ∧ ∂a(S)
+∂a(R ∨ S) → ∂a(R) ∨ ∂a(S)
+
+∂ab(R) = ∂b(∂a(R))
+ +
+
+
+
+
+
+
+

Auxiliary predicate function δ (I call it nully) returns either λ if λ ⊆ R or ϕ otherwise:

+ +
δ(a) → ϕ
+δ(λ) → λ
+δ(ϕ) → ϕ
+δ(R*) → λ
+δ(¬R) δ(R)≟ϕ → λ
+δ(¬R) δ(R)≟λ → ϕ
+δ(R∘S) → δ(R) ∧ δ(S)
+δ(R ∧ S) → δ(R) ∧ δ(S)
+δ(R ∨ S) → δ(R) ∨ δ(S)
+ +
+
+
+
+
+
+
+

Some rules we will use later for "compaction":

+ +
R ∧ ϕ = ϕ ∧ R = ϕ
+
+R ∧ I = I ∧ R = R
+
+R ∨ ϕ = ϕ ∨ R = R
+
+R ∨ I = I ∨ R = I
+
+R∘ϕ = ϕ∘R = ϕ
+
+R∘λ = λ∘R = R
+ +
+
+
+
+
+
+
+

Concatination of sets: for two sets A and B the set A∘B is defined as:

+

{a∘b for a in A for b in B}

+

E.g.:

+

{'a', 'b'}∘{'c', 'd'} → {'ac', 'ad', 'bc', 'bd'}

+ +
+
+
+
+
+
+
+

Implementation

+
+
+
+
+
+
In [1]:
+
+
+
from functools import partial as curry
+from itertools import product
+
+ +
+
+
+ +
+
+
+
+
+

ϕ and λ

The empty set and the set of just the empty string.

+ +
+
+
+
+
+
In [2]:
+
+
+
phi = frozenset()   # ϕ
+y = frozenset({''}) # λ
+
+ +
+
+
+ +
+
+
+
+
+

Two-letter Alphabet

I'm only going to use two symbols (at first) becaase this is enough to illustrate the algorithm and because you can represent any other alphabet with two symbols (if you had to.)

+

I chose the names O and l (uppercase "o" and lowercase "L") to look like 0 and 1 (zero and one) respectively.

+ +
+
+
+
+
+
In [3]:
+
+
+
syms = O, l = frozenset({'0'}), frozenset({'1'})
+
+ +
+
+
+ +
+
+
+
+
+

Representing Regular Expressions

To represent REs in Python I'm going to use tagged tuples. A regular expression is one of:

+ +
O
+l
+(KSTAR, R)
+(NOT, R)
+(AND, R, S)
+(CONS, R, S)
+(OR, R, S)
+
+
+

Where R and S stand for regular expressions.

+ +
+
+
+
+
+
In [4]:
+
+
+
AND, CONS, KSTAR, NOT, OR = 'and cons * not or'.split()  # Tags are just strings.
+
+ +
+
+
+ +
+
+
+
+
+

Because they are formed of frozenset, tuple and str objects only, these datastructures are immutable.

+ +
+
+
+
+
+
+
+

String Representation of RE Datastructures

+
+
+
+
+
+
In [5]:
+
+
+
def stringy(re):
+    '''
+    Return a nice string repr for a regular expression datastructure.
+    '''
+    if re == I: return '.'
+    if re in syms: return next(iter(re))
+    if re == y: return '^'
+    if re == phi: return 'X'
+
+    assert isinstance(re, tuple), repr(re)
+    tag = re[0]
+
+    if tag == KSTAR:
+        body = stringy(re[1])
+        if not body: return body
+        if len(body) > 1: return '(' + body + ")*"
+        return body + '*'
+
+    if tag == NOT:
+        body = stringy(re[1])
+        if not body: return body
+        if len(body) > 1: return '(' + body + ")'"
+        return body + "'"
+
+    r, s = stringy(re[1]), stringy(re[2])
+    if tag == CONS: return r + s
+    if tag == OR:   return '%s | %s' % (r, s)
+    if tag == AND:  return '(%s) & (%s)' % (r, s)
+
+    raise ValueError
+
+ +
+
+
+ +
+
+
+
+
+

I

Match anything. Often spelled "."

+ +
I = (0|1)*
+ +
+
+
+
+
+
In [6]:
+
+
+
I = (KSTAR, (OR, O, l))
+
+ +
+
+
+ +
+
+
+
In [7]:
+
+
+
print stringy(I)
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
.
+
+
+
+ +
+
+ +
+
+
+
+
+

(.111.) & (.01 + 11*)'

The example expression from Brzozowski:

+ +
(.111.) & (.01 + 11*)'
+   a    &  (b  +  c)'
+
+
+

Note that it contains one of everything.

+ +
+
+
+
+
+
In [8]:
+
+
+
a = (CONS, I, (CONS, l, (CONS, l, (CONS, l, I))))
+b = (CONS, I, (CONS, O, l))
+c = (CONS, l, (KSTAR, l))
+it = (AND, a, (NOT, (OR, b, c)))
+
+ +
+
+
+ +
+
+
+
In [9]:
+
+
+
print stringy(it)
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
(.111.) & ((.01 | 11*)')
+
+
+
+ +
+
+ +
+
+
+
+
+

nully()

Let's get that auxiliary predicate function δ out of the way.

+ +
+
+
+
+
+
In [10]:
+
+
+
def nully(R):
+    '''
+    δ - Return λ if λ ⊆ R otherwise ϕ.
+    '''
+
+    # δ(a) → ϕ
+    # δ(ϕ) → ϕ
+    if R in syms or R == phi:
+        return phi
+
+    # δ(λ) → λ
+    if R == y:
+        return y
+
+    tag = R[0]
+
+    # δ(R*) → λ
+    if tag == KSTAR:
+        return y
+
+    # δ(¬R) δ(R)≟ϕ → λ
+    # δ(¬R) δ(R)≟λ → ϕ
+    if tag == NOT:
+        return phi if nully(R[1]) else y
+
+    # δ(R∘S) → δ(R) ∧ δ(S)
+    # δ(R ∧ S) → δ(R) ∧ δ(S)
+    # δ(R ∨ S) → δ(R) ∨ δ(S)
+    r, s = nully(R[1]), nully(R[2])
+    return r & s if tag in {AND, CONS} else r | s
+
+ +
+
+
+ +
+
+
+
+
+

No "Compaction"

This is the straightforward version with no "compaction". +It works fine, but does waaaay too much work because the +expressions grow each derivation.

+ +
+
+
+
+
+
In [11]:
+
+
+
# This is the straightforward version with no "compaction".
+# It works fine, but does waaaay too much work because the
+# expressions grow each derivation.
+
+def D(symbol):
+
+    def derv(R):
+
+        # ∂a(a) → λ
+        if R == {symbol}:
+            return y
+
+        # ∂a(λ) → ϕ
+        # ∂a(ϕ) → ϕ
+        # ∂a(¬a) → ϕ
+        if R == y or R == phi or R in syms:
+            return phi
+        
+        tag = R[0]
+
+        # ∂a(R*) → ∂a(R)∘R*
+        if tag == KSTAR:
+            return (CONS, derv(R[1]), R)
+
+        # ∂a(¬R) → ¬∂a(R)
+        if tag == NOT:
+            return (NOT, derv(R[1]))
+
+        r, s = R[1:]
+
+        # ∂a(R∘S) → ∂a(R)∘S ∨ δ(R)∘∂a(S)
+        if tag == CONS:
+            A = (CONS, derv(r), s)  # A = ∂a(R)∘S
+            # A ∨ δ(R) ∘ ∂a(S)
+            # A ∨  λ   ∘ ∂a(S) → A ∨ ∂a(S)
+            # A ∨  ϕ   ∘ ∂a(S) → A ∨ ϕ → A
+            return (OR, A, derv(s)) if nully(r) else A
+
+        # ∂a(R ∧ S) → ∂a(R) ∧ ∂a(S)
+        # ∂a(R ∨ S) → ∂a(R) ∨ ∂a(S)
+        return (tag, derv(r), derv(s))
+
+    return derv
+
+ +
+
+
+ +
+
+
+
+
+

Compaction Rules

+
+
+
+
+
+
In [12]:
+
+
+
def _compaction_rule(relation, one, zero, a, b):
+    return (
+        b if a == one else  # R*1 = 1*R = R
+        a if b == one else
+        zero if a == zero or b == zero else  # R*0 = 0*R = 0
+        (relation, a, b)
+        )
+
+ +
+
+
+ +
+
+
+
+
+

An elegant symmetry.

+ +
+
+
+
+
+
In [13]:
+
+
+
# R ∧ I = I ∧ R = R
+# R ∧ ϕ = ϕ ∧ R = ϕ
+_and = curry(_compaction_rule, AND, I, phi)
+
+# R ∨ ϕ = ϕ ∨ R = R
+# R ∨ I = I ∨ R = I
+_or = curry(_compaction_rule, OR, phi, I)
+
+# R∘λ = λ∘R = R
+# R∘ϕ = ϕ∘R = ϕ
+_cons = curry(_compaction_rule, CONS, y, phi)
+
+ +
+
+
+ +
+
+
+
+
+

Memoizing

We can save re-processing by remembering results we have already computed. RE datastructures are immutable and the derv() functions are pure so this is fine.

+ +
+
+
+
+
+
In [14]:
+
+
+
class Memo(object):
+
+    def __init__(self, f):
+        self.f = f
+        self.calls = self.hits = 0
+        self.mem = {}
+
+    def __call__(self, key):
+        self.calls += 1
+        try:
+            result = self.mem[key]
+            self.hits += 1
+        except KeyError:
+            result = self.mem[key] = self.f(key)
+        return result
+
+ +
+
+
+ +
+
+
+
+
+

With "Compaction"

This version uses the rules above to perform compaction. It keeps the expressions from growing too large.

+ +
+
+
+
+
+
In [15]:
+
+
+
def D_compaction(symbol):
+
+    @Memo
+    def derv(R):
+
+        # ∂a(a) → λ
+        if R == {symbol}:
+            return y
+
+        # ∂a(λ) → ϕ
+        # ∂a(ϕ) → ϕ
+        # ∂a(¬a) → ϕ
+        if R == y or R == phi or R in syms:
+            return phi
+
+        tag = R[0]
+
+        # ∂a(R*) → ∂a(R)∘R*
+        if tag == KSTAR:
+            return _cons(derv(R[1]), R)
+
+        # ∂a(¬R) → ¬∂a(R)
+        if tag == NOT:
+            return (NOT, derv(R[1]))
+
+        r, s = R[1:]
+
+        # ∂a(R∘S) → ∂a(R)∘S ∨ δ(R)∘∂a(S)
+        if tag == CONS:
+            A = _cons(derv(r), s)  # A = ∂a(r)∘s
+            # A ∨ δ(R) ∘ ∂a(S)
+            # A ∨  λ   ∘ ∂a(S) → A ∨ ∂a(S)
+            # A ∨  ϕ   ∘ ∂a(S) → A ∨ ϕ → A
+            return _or(A, derv(s)) if nully(r) else A
+
+        # ∂a(R ∧ S) → ∂a(R) ∧ ∂a(S)
+        # ∂a(R ∨ S) → ∂a(R) ∨ ∂a(S)
+        dr, ds = derv(r), derv(s)
+        return _and(dr, ds) if tag == AND else _or(dr, ds)
+
+    return derv
+
+ +
+
+
+ +
+
+
+
+
+

Let's try it out...

(FIXME: redo.)

+ +
+
+
+
+
+
In [16]:
+
+
+
o, z = D_compaction('0'), D_compaction('1')
+REs = set()
+N = 5
+names = list(product(*(N * [(0, 1)])))
+dervs = list(product(*(N * [(o, z)])))
+for name, ds in zip(names, dervs):
+    R = it
+    ds = list(ds)
+    while ds:
+        R = ds.pop()(R)
+        if R == phi or R == I:
+            break
+        REs.add(R)
+
+print stringy(it) ; print
+print o.hits, '/', o.calls
+print z.hits, '/', z.calls
+print
+for s in sorted(map(stringy, REs), key=lambda n: (len(n), n)):
+    print s
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
(.111.) & ((.01 | 11*)')
+
+92 / 122
+92 / 122
+
+(.01)'
+(.01 | 1)'
+(.01 | ^)'
+(.01 | 1*)'
+(.111.) & ((.01 | 1)')
+(.111. | 11.) & ((.01 | ^)')
+(.111. | 11. | 1.) & ((.01)')
+(.111. | 11.) & ((.01 | 1*)')
+(.111. | 11. | 1.) & ((.01 | 1*)')
+
+
+
+ +
+
+ +
+
+
+
+
+

Should match:

+ +
(.111.) & ((.01 | 11*)')
+
+92 / 122
+92 / 122
+
+(.01     )'
+(.01 | 1 )'
+(.01 | ^ )'
+(.01 | 1*)'
+(.111.)            & ((.01 | 1 )')
+(.111. | 11.)      & ((.01 | ^ )')
+(.111. | 11.)      & ((.01 | 1*)')
+(.111. | 11. | 1.) & ((.01     )')
+(.111. | 11. | 1.) & ((.01 | 1*)')
+ +
+
+
+
+
+
+
+

Larger Alphabets

We could parse larger alphabets by defining patterns for e.g. each byte of the ASCII code. Or we can generalize this code. If you study the code above you'll see that we never use the "set-ness" of the symbols O and l. The only time Python set operators (& and |) appear is in the nully() function, and there they operate on (recursively computed) outputs of that function, never O and l.

+

What if we try:

+ +
(OR, O, l)
+
+∂1((OR, O, l))
+                            ∂a(R ∨ S) → ∂a(R) ∨ ∂a(S)
+∂1(O) ∨ ∂1(l)
+                            ∂a(¬a) → ϕ
+ϕ ∨ ∂1(l)
+                            ∂a(a) → λ
+ϕ ∨ λ
+                            ϕ ∨ R = R
+λ
+
+
+

And compare it to:

+ +
{'0', '1')
+
+∂1({'0', '1'))
+                            ∂a(R ∨ S) → ∂a(R) ∨ ∂a(S)
+∂1({'0')) ∨ ∂1({'1'))
+                            ∂a(¬a) → ϕ
+ϕ ∨ ∂1({'1'))
+                            ∂a(a) → λ
+ϕ ∨ λ
+                            ϕ ∨ R = R
+λ
+
+
+

This suggests that we should be able to alter the functions above to detect sets and deal with them appropriately. Exercise for the Reader for now.

+ +
+
+
+
+
+
+
+

State Machine

We can drive the regular expressions to flesh out the underlying state machine transition table.

+ +
.111. & (.01 + 11*)'
+
+
+

Says, "Three or more 1's and not ending in 01 nor composed of all 1's."

+

omg.svg

+

Start at a and follow the transition arrows according to their labels. Accepting states have a double outline. (Graphic generated with Dot from Graphviz.) You'll see that only paths that lead to one of the accepting states will match the regular expression. All other paths will terminate at one of the non-accepting states.

+ +
+
+
+
+
+
+
+

There's a happy path to g along 111:

+ +
a→c→e→g
+
+
+

After you reach g you're stuck there eating 1's until you see a 0, which takes you to the i→j→i|i→j→h→i "trap". You can't reach any other states from those two loops.

+

If you see a 0 before you see 111 you will reach b, which forms another "trap" with d and f. The only way out is another happy path along 111 to h:

+ +
b→d→f→h
+
+
+

Once you have reached h you can see as many 1's or as many 0' in a row and still be either still at h (for 1's) or move to i (for 0's). If you find yourself at i you can see as many 0's, or repetitions of 10, as there are, but if you see just a 1 you move to j.

+ +
+
+
+
+
+
+
+

RE to FSM

So how do we get the state machine from the regular expression?

+

It turns out that each RE is effectively a state, and each arrow points to the derivative RE in respect to the arrow's symbol.

+

If we label the initial RE a, we can say:

+ +
a --0--> ∂0(a)
+a --1--> ∂1(a)
+
+
+

And so on, each new unique RE is a new state in the FSM table.

+ +
+
+
+
+
+
+
+

Here are the derived REs at each state:

+ +
a = (.111.) & ((.01 | 11*)')
+b = (.111.) & ((.01 | 1)')
+c = (.111. | 11.) & ((.01 | 1*)')
+d = (.111. | 11.) & ((.01 | ^)')
+e = (.111. | 11. | 1.) & ((.01 | 1*)')
+f = (.111. | 11. | 1.) & ((.01)')
+g = (.01 | 1*)'
+h = (.01)'
+i = (.01 | 1)'
+j = (.01 | ^)'
+
+
+

You can see the one-way nature of the g state and the hij "trap" in the way that the .111. on the left-hand side of the & disappears once it has been matched.

+ +
+
+
+
+
+
In [17]:
+
+
+
from collections import defaultdict
+from pprint import pprint
+from string import ascii_lowercase
+
+ +
+
+
+ +
+
+
+
In [18]:
+
+
+
d0, d1 = D_compaction('0'), D_compaction('1')
+
+ +
+
+
+ +
+
+
+
+
+

explore()

+
+
+
+
+
+
In [19]:
+
+
+
def explore(re):
+
+    # Don't have more than 26 states...
+    names = defaultdict(iter(ascii_lowercase).next)
+
+    table, accepting = dict(), set()
+
+    to_check = {re}
+    while to_check:
+
+        re = to_check.pop()
+        state_name = names[re]
+
+        if (state_name, 0) in table:
+            continue
+
+        if nully(re):
+            accepting.add(state_name)
+
+        o, i = d0(re), d1(re)
+        table[state_name, 0] = names[o] ; to_check.add(o)
+        table[state_name, 1] = names[i] ; to_check.add(i)
+
+    return table, accepting
+
+ +
+
+
+ +
+
+
+
In [20]:
+
+
+
table, accepting = explore(it)
+table
+
+ +
+
+
+ +
+
+ + +
+ +
Out[20]:
+ + + + +
+
{('a', 0): 'b',
+ ('a', 1): 'c',
+ ('b', 0): 'b',
+ ('b', 1): 'd',
+ ('c', 0): 'b',
+ ('c', 1): 'e',
+ ('d', 0): 'b',
+ ('d', 1): 'f',
+ ('e', 0): 'b',
+ ('e', 1): 'g',
+ ('f', 0): 'b',
+ ('f', 1): 'h',
+ ('g', 0): 'i',
+ ('g', 1): 'g',
+ ('h', 0): 'i',
+ ('h', 1): 'h',
+ ('i', 0): 'i',
+ ('i', 1): 'j',
+ ('j', 0): 'i',
+ ('j', 1): 'h'}
+
+ +
+ +
+
+ +
+
+
+
In [21]:
+
+
+
accepting
+
+ +
+
+
+ +
+
+ + +
+ +
Out[21]:
+ + + + +
+
{'h', 'i'}
+
+ +
+ +
+
+ +
+
+
+
+
+

Generate Diagram

Once we have the FSM table and the set of accepting states we can generate the diagram above.

+ +
+
+
+
+
+
In [22]:
+
+
+
_template = '''\
+digraph finite_state_machine {
+  rankdir=LR;
+  size="8,5"
+  node [shape = doublecircle]; %s;
+  node [shape = circle];
+%s
+}
+'''
+
+def link(fr, nm, label):
+    return '  %s -> %s [ label = "%s" ];' % (fr, nm, label)
+
+
+def make_graph(table, accepting):
+    return _template % (
+        ' '.join(accepting),
+        '\n'.join(
+          link(from_, to, char)
+          for (from_, char), (to) in sorted(table.iteritems())
+          )
+        )
+
+ +
+
+
+ +
+
+
+
In [23]:
+
+
+
print make_graph(table, accepting)
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
digraph finite_state_machine {
+  rankdir=LR;
+  size="8,5"
+  node [shape = doublecircle]; i h;
+  node [shape = circle];
+  a -> b [ label = "0" ];
+  a -> c [ label = "1" ];
+  b -> b [ label = "0" ];
+  b -> d [ label = "1" ];
+  c -> b [ label = "0" ];
+  c -> e [ label = "1" ];
+  d -> b [ label = "0" ];
+  d -> f [ label = "1" ];
+  e -> b [ label = "0" ];
+  e -> g [ label = "1" ];
+  f -> b [ label = "0" ];
+  f -> h [ label = "1" ];
+  g -> i [ label = "0" ];
+  g -> g [ label = "1" ];
+  h -> i [ label = "0" ];
+  h -> h [ label = "1" ];
+  i -> i [ label = "0" ];
+  i -> j [ label = "1" ];
+  j -> i [ label = "0" ];
+  j -> h [ label = "1" ];
+}
+
+
+
+
+ +
+
+ +
+
+
+
+
+

Drive a FSM

There are lots of FSM libraries already. Once you have the state transition table they should all be straightforward to use. State Machine code is very simple. Just for fun, here is an implementation in Python that imitates what "compiled" FSM code might look like in an "unrolled" form. Most FSM code uses a little driver loop and a table datastructure, the code below instead acts like JMP instructions ("jump", or GOTO in higher-level-but-still-low-level languages) to hard-code the information in the table into a little patch of branches.

+ +
+
+
+
+
+
+
+

Trampoline Function

Python has no GOTO statement but we can fake it with a "trampoline" function.

+ +
+
+
+
+
+
In [24]:
+
+
+
def trampoline(input_, jump_from, accepting):
+    I = iter(input_)
+    while True:
+        try:
+            bounce_to = jump_from(I)
+        except StopIteration:
+            return jump_from in accepting
+        jump_from = bounce_to
+
+ +
+
+
+ +
+
+
+
+
+

Stream Functions

Little helpers to process the iterator of our data (a "stream" of "1" and "0" characters, not bits.)

+ +
+
+
+
+
+
In [25]:
+
+
+
getch = lambda I: int(next(I))
+
+
+def _1(I):
+    '''Loop on ones.'''
+    while getch(I): pass
+
+
+def _0(I):
+    '''Loop on zeros.'''
+    while not getch(I): pass
+
+ +
+
+
+ +
+
+
+
+
+

A Finite State Machine

With those preliminaries out of the way, from the state table of .111. & (.01 + 11*)' we can immediately write down state machine code. (You have to imagine that these are GOTO statements in C or branches in assembly and that the state names are branch destination labels.)

+ +
+
+
+
+
+
In [26]:
+
+
+
a = lambda I: c if getch(I) else b
+b = lambda I: _0(I) or d
+c = lambda I: e if getch(I) else b
+d = lambda I: f if getch(I) else b
+e = lambda I: g if getch(I) else b
+f = lambda I: h if getch(I) else b
+g = lambda I: _1(I) or i
+h = lambda I: _1(I) or i
+i = lambda I: _0(I) or j
+j = lambda I: h if getch(I) else i
+
+ +
+
+
+ +
+
+
+
+
+

Note that the implementations of h and g are identical ergo h = g and we could eliminate one in the code but h is an accepting state and g isn't.

+ +
+
+
+
+
+
In [27]:
+
+
+
def acceptable(input_):
+    return trampoline(input_, a, {h, i})
+
+ +
+
+
+ +
+
+
+
In [28]:
+
+
+
for n in range(2**5):
+    s = bin(n)[2:]
+    print '%05s' % s, acceptable(s)
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
    0 False
+    1 False
+   10 False
+   11 False
+  100 False
+  101 False
+  110 False
+  111 False
+ 1000 False
+ 1001 False
+ 1010 False
+ 1011 False
+ 1100 False
+ 1101 False
+ 1110 True
+ 1111 False
+10000 False
+10001 False
+10010 False
+10011 False
+10100 False
+10101 False
+10110 False
+10111 True
+11000 False
+11001 False
+11010 False
+11011 False
+11100 True
+11101 False
+11110 True
+11111 False
+
+
+
+ +
+
+ +
+
+
+
+
+

Reversing the Derivatives to Generate Matching Strings

(UNFINISHED) +Brzozowski also shewed how to go from the state machine to strings and expressions...

+ +
+
+
+
+
+
+
+

Each of these states is just a name for a Brzozowskian RE, and so, other than the initial state a, they can can be described in terms of the derivative-with-respect-to-N of some other state/RE:

+ +
c = d1(a)
+b = d0(a)
+b = d0(c)
+...
+i = d0(j)
+j = d1(i)
+
+
+

Consider:

+ +
c = d1(a)
+b = d0(c)
+
+
+

Substituting:

+ +
b = d0(d1(a))
+
+
+

Unwrapping:

+ +
b = d10(a)
+
+
+

'''

+ +
j = d1(d0(j))
+
+
+

Unwrapping:

+ +
j = d1(d0(j)) = d01(j)
+
+
+

We have a loop or "fixed point".

+ +
j = d01(j) = d0101(j) = d010101(j) = ...
+
+
+

hmm...

+ +
j = (01)*
+ +
+
+
+
+
+ + + + + + diff --git a/docs/Derivatives_of_Regular_Expressions.ipynb b/docs/Derivatives_of_Regular_Expressions.ipynb new file mode 100644 index 0000000..928cc58 --- /dev/null +++ b/docs/Derivatives_of_Regular_Expressions.ipynb @@ -0,0 +1,1182 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# ∂RE\n", + "\n", + "## Brzozowski's Derivatives of Regular Expressions" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Legend:\n", + "\n", + " ∧ intersection\n", + " ∨ union\n", + " ∘ concatenation (see below)\n", + " ¬ complement\n", + " ϕ empty set (aka ∅)\n", + " λ singleton set containing just the empty string\n", + " I set of all letters in alphabet" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Derivative of a set `R` of strings and a string `a`:\n", + "\n", + " ∂a(R)\n", + "\n", + " ∂a(a) → λ\n", + " ∂a(λ) → ϕ\n", + " ∂a(ϕ) → ϕ\n", + " ∂a(¬a) → ϕ\n", + " ∂a(R*) → ∂a(R)∘R*\n", + " ∂a(¬R) → ¬∂a(R)\n", + " ∂a(R∘S) → ∂a(R)∘S ∨ δ(R)∘∂a(S)\n", + " ∂a(R ∧ S) → ∂a(R) ∧ ∂a(S)\n", + " ∂a(R ∨ S) → ∂a(R) ∨ ∂a(S)\n", + "\n", + " ∂ab(R) = ∂b(∂a(R))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Auxiliary predicate function `δ` (I call it `nully`) returns either `λ` if `λ ⊆ R` or `ϕ` otherwise:\n", + "\n", + " δ(a) → ϕ\n", + " δ(λ) → λ\n", + " δ(ϕ) → ϕ\n", + " δ(R*) → λ\n", + " δ(¬R) δ(R)≟ϕ → λ\n", + " δ(¬R) δ(R)≟λ → ϕ\n", + " δ(R∘S) → δ(R) ∧ δ(S)\n", + " δ(R ∧ S) → δ(R) ∧ δ(S)\n", + " δ(R ∨ S) → δ(R) ∨ δ(S)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Some rules we will use later for \"compaction\":\n", + "\n", + " R ∧ ϕ = ϕ ∧ R = ϕ\n", + "\n", + " R ∧ I = I ∧ R = R\n", + "\n", + " R ∨ ϕ = ϕ ∨ R = R\n", + "\n", + " R ∨ I = I ∨ R = I\n", + "\n", + " R∘ϕ = ϕ∘R = ϕ\n", + "\n", + " R∘λ = λ∘R = R" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Concatination of sets: for two sets A and B the set A∘B is defined as:\n", + "\n", + " {a∘b for a in A for b in B}\n", + "\n", + "E.g.:\n", + "\n", + " {'a', 'b'}∘{'c', 'd'} → {'ac', 'ad', 'bc', 'bd'}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Implementation" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from functools import partial as curry\n", + "from itertools import product" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### `ϕ` and `λ`\n", + "The empty set and the set of just the empty string." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "phi = frozenset() # ϕ\n", + "y = frozenset({''}) # λ" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Two-letter Alphabet\n", + "I'm only going to use two symbols (at first) becaase this is enough to illustrate the algorithm and because you can represent any other alphabet with two symbols (if you had to.)\n", + "\n", + "I chose the names `O` and `l` (uppercase \"o\" and lowercase \"L\") to look like `0` and `1` (zero and one) respectively." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "syms = O, l = frozenset({'0'}), frozenset({'1'})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Representing Regular Expressions\n", + "To represent REs in Python I'm going to use tagged tuples. A _regular expression_ is one of:\n", + "\n", + " O\n", + " l\n", + " (KSTAR, R)\n", + " (NOT, R)\n", + " (AND, R, S)\n", + " (CONS, R, S)\n", + " (OR, R, S)\n", + "\n", + "Where `R` and `S` stand for _regular expressions_." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "AND, CONS, KSTAR, NOT, OR = 'and cons * not or'.split() # Tags are just strings." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Because they are formed of `frozenset`, `tuple` and `str` objects only, these datastructures are immutable." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### String Representation of RE Datastructures" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "def stringy(re):\n", + " '''\n", + " Return a nice string repr for a regular expression datastructure.\n", + " '''\n", + " if re == I: return '.'\n", + " if re in syms: return next(iter(re))\n", + " if re == y: return '^'\n", + " if re == phi: return 'X'\n", + "\n", + " assert isinstance(re, tuple), repr(re)\n", + " tag = re[0]\n", + "\n", + " if tag == KSTAR:\n", + " body = stringy(re[1])\n", + " if not body: return body\n", + " if len(body) > 1: return '(' + body + \")*\"\n", + " return body + '*'\n", + "\n", + " if tag == NOT:\n", + " body = stringy(re[1])\n", + " if not body: return body\n", + " if len(body) > 1: return '(' + body + \")'\"\n", + " return body + \"'\"\n", + "\n", + " r, s = stringy(re[1]), stringy(re[2])\n", + " if tag == CONS: return r + s\n", + " if tag == OR: return '%s | %s' % (r, s)\n", + " if tag == AND: return '(%s) & (%s)' % (r, s)\n", + "\n", + " raise ValueError" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### `I`\n", + "Match anything. Often spelled \".\"\n", + "\n", + " I = (0|1)*" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "I = (KSTAR, (OR, O, l))" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + ".\n" + ] + } + ], + "source": [ + "print stringy(I)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### `(.111.) & (.01 + 11*)'`\n", + "The example expression from Brzozowski:\n", + "\n", + " (.111.) & (.01 + 11*)'\n", + " a & (b + c)'\n", + "\n", + "Note that it contains one of everything." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "a = (CONS, I, (CONS, l, (CONS, l, (CONS, l, I))))\n", + "b = (CONS, I, (CONS, O, l))\n", + "c = (CONS, l, (KSTAR, l))\n", + "it = (AND, a, (NOT, (OR, b, c)))" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(.111.) & ((.01 | 11*)')\n" + ] + } + ], + "source": [ + "print stringy(it)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### `nully()`\n", + "Let's get that auxiliary predicate function `δ` out of the way." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "def nully(R):\n", + " '''\n", + " δ - Return λ if λ ⊆ R otherwise ϕ.\n", + " '''\n", + "\n", + " # δ(a) → ϕ\n", + " # δ(ϕ) → ϕ\n", + " if R in syms or R == phi:\n", + " return phi\n", + "\n", + " # δ(λ) → λ\n", + " if R == y:\n", + " return y\n", + "\n", + " tag = R[0]\n", + "\n", + " # δ(R*) → λ\n", + " if tag == KSTAR:\n", + " return y\n", + "\n", + " # δ(¬R) δ(R)≟ϕ → λ\n", + " # δ(¬R) δ(R)≟λ → ϕ\n", + " if tag == NOT:\n", + " return phi if nully(R[1]) else y\n", + "\n", + " # δ(R∘S) → δ(R) ∧ δ(S)\n", + " # δ(R ∧ S) → δ(R) ∧ δ(S)\n", + " # δ(R ∨ S) → δ(R) ∨ δ(S)\n", + " r, s = nully(R[1]), nully(R[2])\n", + " return r & s if tag in {AND, CONS} else r | s" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### No \"Compaction\"\n", + "This is the straightforward version with no \"compaction\".\n", + "It works fine, but does waaaay too much work because the\n", + "expressions grow each derivation." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "# This is the straightforward version with no \"compaction\".\n", + "# It works fine, but does waaaay too much work because the\n", + "# expressions grow each derivation.\n", + "\n", + "def D(symbol):\n", + "\n", + " def derv(R):\n", + "\n", + " # ∂a(a) → λ\n", + " if R == {symbol}:\n", + " return y\n", + "\n", + " # ∂a(λ) → ϕ\n", + " # ∂a(ϕ) → ϕ\n", + " # ∂a(¬a) → ϕ\n", + " if R == y or R == phi or R in syms:\n", + " return phi\n", + " \n", + " tag = R[0]\n", + "\n", + " # ∂a(R*) → ∂a(R)∘R*\n", + " if tag == KSTAR:\n", + " return (CONS, derv(R[1]), R)\n", + "\n", + " # ∂a(¬R) → ¬∂a(R)\n", + " if tag == NOT:\n", + " return (NOT, derv(R[1]))\n", + "\n", + " r, s = R[1:]\n", + "\n", + " # ∂a(R∘S) → ∂a(R)∘S ∨ δ(R)∘∂a(S)\n", + " if tag == CONS:\n", + " A = (CONS, derv(r), s) # A = ∂a(R)∘S\n", + " # A ∨ δ(R) ∘ ∂a(S)\n", + " # A ∨ λ ∘ ∂a(S) → A ∨ ∂a(S)\n", + " # A ∨ ϕ ∘ ∂a(S) → A ∨ ϕ → A\n", + " return (OR, A, derv(s)) if nully(r) else A\n", + "\n", + " # ∂a(R ∧ S) → ∂a(R) ∧ ∂a(S)\n", + " # ∂a(R ∨ S) → ∂a(R) ∨ ∂a(S)\n", + " return (tag, derv(r), derv(s))\n", + "\n", + " return derv" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Compaction Rules" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "def _compaction_rule(relation, one, zero, a, b):\n", + " return (\n", + " b if a == one else # R*1 = 1*R = R\n", + " a if b == one else\n", + " zero if a == zero or b == zero else # R*0 = 0*R = 0\n", + " (relation, a, b)\n", + " )" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "An elegant symmetry." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "# R ∧ I = I ∧ R = R\n", + "# R ∧ ϕ = ϕ ∧ R = ϕ\n", + "_and = curry(_compaction_rule, AND, I, phi)\n", + "\n", + "# R ∨ ϕ = ϕ ∨ R = R\n", + "# R ∨ I = I ∨ R = I\n", + "_or = curry(_compaction_rule, OR, phi, I)\n", + "\n", + "# R∘λ = λ∘R = R\n", + "# R∘ϕ = ϕ∘R = ϕ\n", + "_cons = curry(_compaction_rule, CONS, y, phi)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Memoizing\n", + "We can save re-processing by remembering results we have already computed. RE datastructures are immutable and the `derv()` functions are _pure_ so this is fine." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [], + "source": [ + "class Memo(object):\n", + "\n", + " def __init__(self, f):\n", + " self.f = f\n", + " self.calls = self.hits = 0\n", + " self.mem = {}\n", + "\n", + " def __call__(self, key):\n", + " self.calls += 1\n", + " try:\n", + " result = self.mem[key]\n", + " self.hits += 1\n", + " except KeyError:\n", + " result = self.mem[key] = self.f(key)\n", + " return result" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### With \"Compaction\"\n", + "This version uses the rules above to perform compaction. It keeps the expressions from growing too large." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [], + "source": [ + "def D_compaction(symbol):\n", + "\n", + " @Memo\n", + " def derv(R):\n", + "\n", + " # ∂a(a) → λ\n", + " if R == {symbol}:\n", + " return y\n", + "\n", + " # ∂a(λ) → ϕ\n", + " # ∂a(ϕ) → ϕ\n", + " # ∂a(¬a) → ϕ\n", + " if R == y or R == phi or R in syms:\n", + " return phi\n", + "\n", + " tag = R[0]\n", + "\n", + " # ∂a(R*) → ∂a(R)∘R*\n", + " if tag == KSTAR:\n", + " return _cons(derv(R[1]), R)\n", + "\n", + " # ∂a(¬R) → ¬∂a(R)\n", + " if tag == NOT:\n", + " return (NOT, derv(R[1]))\n", + "\n", + " r, s = R[1:]\n", + "\n", + " # ∂a(R∘S) → ∂a(R)∘S ∨ δ(R)∘∂a(S)\n", + " if tag == CONS:\n", + " A = _cons(derv(r), s) # A = ∂a(r)∘s\n", + " # A ∨ δ(R) ∘ ∂a(S)\n", + " # A ∨ λ ∘ ∂a(S) → A ∨ ∂a(S)\n", + " # A ∨ ϕ ∘ ∂a(S) → A ∨ ϕ → A\n", + " return _or(A, derv(s)) if nully(r) else A\n", + "\n", + " # ∂a(R ∧ S) → ∂a(R) ∧ ∂a(S)\n", + " # ∂a(R ∨ S) → ∂a(R) ∨ ∂a(S)\n", + " dr, ds = derv(r), derv(s)\n", + " return _and(dr, ds) if tag == AND else _or(dr, ds)\n", + "\n", + " return derv" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Let's try it out...\n", + "(FIXME: redo.)" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(.111.) & ((.01 | 11*)')\n", + "\n", + "92 / 122\n", + "92 / 122\n", + "\n", + "(.01)'\n", + "(.01 | 1)'\n", + "(.01 | ^)'\n", + "(.01 | 1*)'\n", + "(.111.) & ((.01 | 1)')\n", + "(.111. | 11.) & ((.01 | ^)')\n", + "(.111. | 11. | 1.) & ((.01)')\n", + "(.111. | 11.) & ((.01 | 1*)')\n", + "(.111. | 11. | 1.) & ((.01 | 1*)')\n" + ] + } + ], + "source": [ + "o, z = D_compaction('0'), D_compaction('1')\n", + "REs = set()\n", + "N = 5\n", + "names = list(product(*(N * [(0, 1)])))\n", + "dervs = list(product(*(N * [(o, z)])))\n", + "for name, ds in zip(names, dervs):\n", + " R = it\n", + " ds = list(ds)\n", + " while ds:\n", + " R = ds.pop()(R)\n", + " if R == phi or R == I:\n", + " break\n", + " REs.add(R)\n", + "\n", + "print stringy(it) ; print\n", + "print o.hits, '/', o.calls\n", + "print z.hits, '/', z.calls\n", + "print\n", + "for s in sorted(map(stringy, REs), key=lambda n: (len(n), n)):\n", + " print s" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Should match:\n", + "\n", + " (.111.) & ((.01 | 11*)')\n", + " \n", + " 92 / 122\n", + " 92 / 122\n", + " \n", + " (.01 )'\n", + " (.01 | 1 )'\n", + " (.01 | ^ )'\n", + " (.01 | 1*)'\n", + " (.111.) & ((.01 | 1 )')\n", + " (.111. | 11.) & ((.01 | ^ )')\n", + " (.111. | 11.) & ((.01 | 1*)')\n", + " (.111. | 11. | 1.) & ((.01 )')\n", + " (.111. | 11. | 1.) & ((.01 | 1*)')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Larger Alphabets\n", + "\n", + "We could parse larger alphabets by defining patterns for e.g. each byte of the ASCII code. Or we can generalize this code. If you study the code above you'll see that we never use the \"set-ness\" of the symbols `O` and `l`. The only time Python set operators (`&` and `|`) appear is in the `nully()` function, and there they operate on (recursively computed) outputs of that function, never `O` and `l`.\n", + "\n", + "\n", + "What if we try:\n", + "\n", + " (OR, O, l)\n", + "\n", + " ∂1((OR, O, l))\n", + " ∂a(R ∨ S) → ∂a(R) ∨ ∂a(S)\n", + " ∂1(O) ∨ ∂1(l)\n", + " ∂a(¬a) → ϕ\n", + " ϕ ∨ ∂1(l)\n", + " ∂a(a) → λ\n", + " ϕ ∨ λ\n", + " ϕ ∨ R = R\n", + " λ\n", + "\n", + "And compare it to:\n", + "\n", + " {'0', '1')\n", + " \n", + " ∂1({'0', '1'))\n", + " ∂a(R ∨ S) → ∂a(R) ∨ ∂a(S)\n", + " ∂1({'0')) ∨ ∂1({'1'))\n", + " ∂a(¬a) → ϕ\n", + " ϕ ∨ ∂1({'1'))\n", + " ∂a(a) → λ\n", + " ϕ ∨ λ\n", + " ϕ ∨ R = R\n", + " λ\n", + "\n", + "This suggests that we should be able to alter the functions above to detect sets and deal with them appropriately. Exercise for the Reader for now." + ] + }, + { + "attachments": { + "omg.svg": { + "image/svg+xml": [ + "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIKICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPgo8IS0tIEdlbmVyYXRlZCBieSBncmFwaHZpeiB2ZXJzaW9uIDIuMzguMCAoMjAxNDA0MTMuMjA0MSkKIC0tPgo8IS0tIFRpdGxlOiBmaW5pdGVfc3RhdGVfbWFjaGluZSBQYWdlczogMSAtLT4KPHN2ZyB3aWR0aD0iNTM0cHQiIGhlaWdodD0iMjcwcHQiCiB2aWV3Qm94PSIwLjAwIDAuMDAgNTM0LjAwIDI3MC4wMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+CjxnIGlkPSJncmFwaDAiIGNsYXNzPSJncmFwaCIgdHJhbnNmb3JtPSJzY2FsZSgxIDEpIHJvdGF0ZSgwKSB0cmFuc2xhdGUoNCAyNjYpIj4KPHRpdGxlPmZpbml0ZV9zdGF0ZV9tYWNoaW5lPC90aXRsZT4KPHBvbHlnb24gZmlsbD0id2hpdGUiIHN0cm9rZT0ibm9uZSIgcG9pbnRzPSItNCw0IC00LC0yNjYgNTMwLC0yNjYgNTMwLDQgLTQsNCIvPgo8IS0tIGkgLS0+CjxnIGlkPSJub2RlMSIgY2xhc3M9Im5vZGUiPjx0aXRsZT5pPC90aXRsZT4KPGVsbGlwc2UgZmlsbD0ibm9uZSIgc3Ryb2tlPSJibGFjayIgY3g9IjMzOCIgY3k9Ii0xNDYiIHJ4PSIxOCIgcnk9IjE4Ii8+CjxlbGxpcHNlIGZpbGw9Im5vbmUiIHN0cm9rZT0iYmxhY2siIGN4PSIzMzgiIGN5PSItMTQ2IiByeD0iMjIiIHJ5PSIyMiIvPgo8dGV4dCB0ZXh0LWFuY2hvcj0ibWlkZGxlIiB4PSIzMzgiIHk9Ii0xNDIuMyIgZm9udC1mYW1pbHk9IlRpbWVzLHNlcmlmIiBmb250LXNpemU9IjE0LjAwIj5pPC90ZXh0Pgo8L2c+CjwhLS0gaSYjNDU7Jmd0O2kgLS0+CjxnIGlkPSJlZGdlMTciIGNsYXNzPSJlZGdlIj48dGl0bGU+aSYjNDU7Jmd0O2k8L3RpdGxlPgo8cGF0aCBmaWxsPSJub25lIiBzdHJva2U9ImJsYWNrIiBkPSJNMzMwLjMxNywtMTY2Ljk5MUMzMjkuMzY5LC0xNzcuMDg3IDMzMS45MywtMTg2IDMzOCwtMTg2IDM0MS44ODksLTE4NiAzNDQuMzM3LC0xODIuMzQyIDM0NS4zNDYsLTE3Ny4wNTkiLz4KPHBvbHlnb24gZmlsbD0iYmxhY2siIHN0cm9rZT0iYmxhY2siIHBvaW50cz0iMzQ4Ljg0NiwtMTc3LjEwMiAzNDUuNjgzLC0xNjYuOTkxIDM0MS44NSwtMTc2Ljg2OCAzNDguODQ2LC0xNzcuMTAyIi8+Cjx0ZXh0IHRleHQtYW5jaG9yPSJtaWRkbGUiIHg9IjMzOCIgeT0iLTE4OS44IiBmb250LWZhbWlseT0iVGltZXMsc2VyaWYiIGZvbnQtc2l6ZT0iMTQuMDAiPjA8L3RleHQ+CjwvZz4KPCEtLSBqIC0tPgo8ZyBpZD0ibm9kZTEwIiBjbGFzcz0ibm9kZSI+PHRpdGxlPmo8L3RpdGxlPgo8ZWxsaXBzZSBmaWxsPSJub25lIiBzdHJva2U9ImJsYWNrIiBjeD0iNDIxIiBjeT0iLTEzNiIgcng9IjE4IiByeT0iMTgiLz4KPHRleHQgdGV4dC1hbmNob3I9Im1pZGRsZSIgeD0iNDIxIiB5PSItMTMyLjMiIGZvbnQtZmFtaWx5PSJUaW1lcyxzZXJpZiIgZm9udC1zaXplPSIxNC4wMCI+ajwvdGV4dD4KPC9nPgo8IS0tIGkmIzQ1OyZndDtqIC0tPgo8ZyBpZD0iZWRnZTE4IiBjbGFzcz0iZWRnZSI+PHRpdGxlPmkmIzQ1OyZndDtqPC90aXRsZT4KPHBhdGggZmlsbD0ibm9uZSIgc3Ryb2tlPSJibGFjayIgZD0iTTM1Ny40NjYsLTEzNS40OTVDMzYzLjc3NSwtMTMyLjQ1MSAzNzEuMDA4LC0xMjkuNTM2IDM3OCwtMTI4IDM4My4yMTMsLTEyNi44NTUgMzg4LjgxMSwtMTI2Ljk4NCAzOTQuMTY3LC0xMjcuNzYzIi8+Cjxwb2x5Z29uIGZpbGw9ImJsYWNrIiBzdHJva2U9ImJsYWNrIiBwb2ludHM9IjM5My40ODcsLTEzMS4xOTcgNDA0LjAwMiwtMTI5Ljg5NCAzOTQuOTcsLTEyNC4zNTUgMzkzLjQ4NywtMTMxLjE5NyIvPgo8dGV4dCB0ZXh0LWFuY2hvcj0ibWlkZGxlIiB4PSIzODEuNSIgeT0iLTEzMS44IiBmb250LWZhbWlseT0iVGltZXMsc2VyaWYiIGZvbnQtc2l6ZT0iMTQuMDAiPjE8L3RleHQ+CjwvZz4KPCEtLSBoIC0tPgo8ZyBpZD0ibm9kZTIiIGNsYXNzPSJub2RlIj48dGl0bGU+aDwvdGl0bGU+CjxlbGxpcHNlIGZpbGw9Im5vbmUiIHN0cm9rZT0iYmxhY2siIGN4PSI1MDQiIGN5PSItODUiIHJ4PSIxOCIgcnk9IjE4Ii8+CjxlbGxpcHNlIGZpbGw9Im5vbmUiIHN0cm9rZT0iYmxhY2siIGN4PSI1MDQiIGN5PSItODUiIHJ4PSIyMiIgcnk9IjIyIi8+Cjx0ZXh0IHRleHQtYW5jaG9yPSJtaWRkbGUiIHg9IjUwNCIgeT0iLTgxLjMiIGZvbnQtZmFtaWx5PSJUaW1lcyxzZXJpZiIgZm9udC1zaXplPSIxNC4wMCI+aDwvdGV4dD4KPC9nPgo8IS0tIGgmIzQ1OyZndDtpIC0tPgo8ZyBpZD0iZWRnZTE1IiBjbGFzcz0iZWRnZSI+PHRpdGxlPmgmIzQ1OyZndDtpPC90aXRsZT4KPHBhdGggZmlsbD0ibm9uZSIgc3Ryb2tlPSJibGFjayIgZD0iTTQ4MS44NjgsLTgzLjQwMjVDNDYxLjAzMywtODIuNjIgNDI4LjY3NiwtODMuNTY0NSA0MDMsLTk0IDM4Ny4yNjcsLTEwMC4zOTQgMzcyLjM3MywtMTEyLjAyOCAzNjAuOTE4LC0xMjIuNjczIi8+Cjxwb2x5Z29uIGZpbGw9ImJsYWNrIiBzdHJva2U9ImJsYWNrIiBwb2ludHM9IjM1OC4zMDYsLTEyMC4zMyAzNTMuNTY5LC0xMjkuODA3IDM2My4xODIsLTEyNS4zNTMgMzU4LjMwNiwtMTIwLjMzIi8+Cjx0ZXh0IHRleHQtYW5jaG9yPSJtaWRkbGUiIHg9IjQyMSIgeT0iLTk3LjgiIGZvbnQtZmFtaWx5PSJUaW1lcyxzZXJpZiIgZm9udC1zaXplPSIxNC4wMCI+MDwvdGV4dD4KPC9nPgo8IS0tIGgmIzQ1OyZndDtoIC0tPgo8ZyBpZD0iZWRnZTE2IiBjbGFzcz0iZWRnZSI+PHRpdGxlPmgmIzQ1OyZndDtoPC90aXRsZT4KPHBhdGggZmlsbD0ibm9uZSIgc3Ryb2tlPSJibGFjayIgZD0iTTQ5Ni4zMTcsLTEwNS45OTFDNDk1LjM2OSwtMTE2LjA4NyA0OTcuOTMsLTEyNSA1MDQsLTEyNSA1MDcuODg5LC0xMjUgNTEwLjMzNywtMTIxLjM0MiA1MTEuMzQ2LC0xMTYuMDU5Ii8+Cjxwb2x5Z29uIGZpbGw9ImJsYWNrIiBzdHJva2U9ImJsYWNrIiBwb2ludHM9IjUxNC44NDYsLTExNi4xMDIgNTExLjY4MywtMTA1Ljk5MSA1MDcuODUsLTExNS44NjggNTE0Ljg0NiwtMTE2LjEwMiIvPgo8dGV4dCB0ZXh0LWFuY2hvcj0ibWlkZGxlIiB4PSI1MDQiIHk9Ii0xMjguOCIgZm9udC1mYW1pbHk9IlRpbWVzLHNlcmlmIiBmb250LXNpemU9IjE0LjAwIj4xPC90ZXh0Pgo8L2c+CjwhLS0gYSAtLT4KPGcgaWQ9Im5vZGUzIiBjbGFzcz0ibm9kZSI+PHRpdGxlPmE8L3RpdGxlPgo8ZWxsaXBzZSBmaWxsPSJub25lIiBzdHJva2U9ImJsYWNrIiBjeD0iMTgiIGN5PSItMTI4IiByeD0iMTgiIHJ5PSIxOCIvPgo8dGV4dCB0ZXh0LWFuY2hvcj0ibWlkZGxlIiB4PSIxOCIgeT0iLTEyNC4zIiBmb250LWZhbWlseT0iVGltZXMsc2VyaWYiIGZvbnQtc2l6ZT0iMTQuMDAiPmE8L3RleHQ+CjwvZz4KPCEtLSBiIC0tPgo8ZyBpZD0ibm9kZTQiIGNsYXNzPSJub2RlIj48dGl0bGU+YjwvdGl0bGU+CjxlbGxpcHNlIGZpbGw9Im5vbmUiIHN0cm9rZT0iYmxhY2siIGN4PSIyNTUiIGN5PSItMTEzIiByeD0iMTgiIHJ5PSIxOCIvPgo8dGV4dCB0ZXh0LWFuY2hvcj0ibWlkZGxlIiB4PSIyNTUiIHk9Ii0xMDkuMyIgZm9udC1mYW1pbHk9IlRpbWVzLHNlcmlmIiBmb250LXNpemU9IjE0LjAwIj5iPC90ZXh0Pgo8L2c+CjwhLS0gYSYjNDU7Jmd0O2IgLS0+CjxnIGlkPSJlZGdlMSIgY2xhc3M9ImVkZ2UiPjx0aXRsZT5hJiM0NTsmZ3Q7YjwvdGl0bGU+CjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iYmxhY2siIGQ9Ik0zNi4yODAxLC0xMjYuODk3Qzc2Ljc4MTYsLTEyNC4zMTIgMTc4LjA5MSwtMTE3Ljg0NSAyMjYuODksLTExNC43MyIvPgo8cG9seWdvbiBmaWxsPSJibGFjayIgc3Ryb2tlPSJibGFjayIgcG9pbnRzPSIyMjcuMjU1LC0xMTguMjE0IDIzNy4wMTEsLTExNC4wODQgMjI2LjgwOSwtMTExLjIyOSAyMjcuMjU1LC0xMTguMjE0Ii8+Cjx0ZXh0IHRleHQtYW5jaG9yPSJtaWRkbGUiIHg9IjEzNi41IiB5PSItMTIzLjgiIGZvbnQtZmFtaWx5PSJUaW1lcyxzZXJpZiIgZm9udC1zaXplPSIxNC4wMCI+MDwvdGV4dD4KPC9nPgo8IS0tIGMgLS0+CjxnIGlkPSJub2RlNSIgY2xhc3M9Im5vZGUiPjx0aXRsZT5jPC90aXRsZT4KPGVsbGlwc2UgZmlsbD0ibm9uZSIgc3Ryb2tlPSJibGFjayIgY3g9Ijk3IiBjeT0iLTE1NSIgcng9IjE4IiByeT0iMTgiLz4KPHRleHQgdGV4dC1hbmNob3I9Im1pZGRsZSIgeD0iOTciIHk9Ii0xNTEuMyIgZm9udC1mYW1pbHk9IlRpbWVzLHNlcmlmIiBmb250LXNpemU9IjE0LjAwIj5jPC90ZXh0Pgo8L2c+CjwhLS0gYSYjNDU7Jmd0O2MgLS0+CjxnIGlkPSJlZGdlMiIgY2xhc3M9ImVkZ2UiPjx0aXRsZT5hJiM0NTsmZ3Q7YzwvdGl0bGU+CjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iYmxhY2siIGQ9Ik0zNS4zMjk3LC0xMzMuNzI2QzQ1LjQzNjQsLTEzNy4yNyA1OC42MzUsLTE0MS44OTggNzAuMTM5OCwtMTQ1LjkzMiIvPgo8cG9seWdvbiBmaWxsPSJibGFjayIgc3Ryb2tlPSJibGFjayIgcG9pbnRzPSI2OS4wOTksLTE0OS4yNzYgNzkuNjkzOCwtMTQ5LjI4MiA3MS40MTUzLC0xNDIuNjcgNjkuMDk5LC0xNDkuMjc2Ii8+Cjx0ZXh0IHRleHQtYW5jaG9yPSJtaWRkbGUiIHg9IjU3LjUiIHk9Ii0xNDUuOCIgZm9udC1mYW1pbHk9IlRpbWVzLHNlcmlmIiBmb250LXNpemU9IjE0LjAwIj4xPC90ZXh0Pgo8L2c+CjwhLS0gYiYjNDU7Jmd0O2IgLS0+CjxnIGlkPSJlZGdlMyIgY2xhc3M9ImVkZ2UiPjx0aXRsZT5iJiM0NTsmZ3Q7YjwvdGl0bGU+CjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iYmxhY2siIGQ9Ik0yNDguMjY2LC0xMzAuMDM3QzI0Ni44OTIsLTEzOS44NTggMjQ5LjEzNywtMTQ5IDI1NSwtMTQ5IDI1OC42NjUsLTE0OSAyNjAuOTE2LC0xNDUuNDI5IDI2MS43NTMsLTE0MC4zNTMiLz4KPHBvbHlnb24gZmlsbD0iYmxhY2siIHN0cm9rZT0iYmxhY2siIHBvaW50cz0iMjY1LjI1MiwtMTQwLjAzMSAyNjEuNzM0LC0xMzAuMDM3IDI1OC4yNTIsLTE0MC4wNDQgMjY1LjI1MiwtMTQwLjAzMSIvPgo8dGV4dCB0ZXh0LWFuY2hvcj0ibWlkZGxlIiB4PSIyNTUiIHk9Ii0xNTIuOCIgZm9udC1mYW1pbHk9IlRpbWVzLHNlcmlmIiBmb250LXNpemU9IjE0LjAwIj4wPC90ZXh0Pgo8L2c+CjwhLS0gZCAtLT4KPGcgaWQ9Im5vZGU2IiBjbGFzcz0ibm9kZSI+PHRpdGxlPmQ8L3RpdGxlPgo8ZWxsaXBzZSBmaWxsPSJub25lIiBzdHJva2U9ImJsYWNrIiBjeD0iMzM4IiBjeT0iLTc5IiByeD0iMTgiIHJ5PSIxOCIvPgo8dGV4dCB0ZXh0LWFuY2hvcj0ibWlkZGxlIiB4PSIzMzgiIHk9Ii03NS4zIiBmb250LWZhbWlseT0iVGltZXMsc2VyaWYiIGZvbnQtc2l6ZT0iMTQuMDAiPmQ8L3RleHQ+CjwvZz4KPCEtLSBiJiM0NTsmZ3Q7ZCAtLT4KPGcgaWQ9ImVkZ2U0IiBjbGFzcz0iZWRnZSI+PHRpdGxlPmImIzQ1OyZndDtkPC90aXRsZT4KPHBhdGggZmlsbD0ibm9uZSIgc3Ryb2tlPSJibGFjayIgZD0iTTI3Mi4wMDMsLTEwNi4yODNDMjgzLjMxOSwtMTAxLjUzMyAyOTguNzIyLC05NS4wNjc0IDMxMS42OTMsLTg5LjYyMjciLz4KPHBvbHlnb24gZmlsbD0iYmxhY2siIHN0cm9rZT0iYmxhY2siIHBvaW50cz0iMzEzLjE2NCwtOTIuODAxIDMyMS4wMywtODUuNzAzNCAzMTAuNDU1LC04Ni4zNDY2IDMxMy4xNjQsLTkyLjgwMSIvPgo8dGV4dCB0ZXh0LWFuY2hvcj0ibWlkZGxlIiB4PSIyOTQuNSIgeT0iLTEwMS44IiBmb250LWZhbWlseT0iVGltZXMsc2VyaWYiIGZvbnQtc2l6ZT0iMTQuMDAiPjE8L3RleHQ+CjwvZz4KPCEtLSBjJiM0NTsmZ3Q7YiAtLT4KPGcgaWQ9ImVkZ2U1IiBjbGFzcz0iZWRnZSI+PHRpdGxlPmMmIzQ1OyZndDtiPC90aXRsZT4KPHBhdGggZmlsbD0ibm9uZSIgc3Ryb2tlPSJibGFjayIgZD0iTTExNC44NjIsLTE1MC42NTNDMTM4LjI2OSwtMTQ0LjU5MyAxODEuOTE3LC0xMzMuMiAyMTksLTEyMyAyMjEuNzk5LC0xMjIuMjMgMjI0LjcyMSwtMTIxLjQxNCAyMjcuNjMxLC0xMjAuNTk0Ii8+Cjxwb2x5Z29uIGZpbGw9ImJsYWNrIiBzdHJva2U9ImJsYWNrIiBwb2ludHM9IjIyOC42MjMsLTEyMy45NTEgMjM3LjI4NCwtMTE3Ljg0OSAyMjYuNzA4LC0xMTcuMjE4IDIyOC42MjMsLTEyMy45NTEiLz4KPHRleHQgdGV4dC1hbmNob3I9Im1pZGRsZSIgeD0iMTc2IiB5PSItMTQyLjgiIGZvbnQtZmFtaWx5PSJUaW1lcyxzZXJpZiIgZm9udC1zaXplPSIxNC4wMCI+MDwvdGV4dD4KPC9nPgo8IS0tIGUgLS0+CjxnIGlkPSJub2RlNyIgY2xhc3M9Im5vZGUiPjx0aXRsZT5lPC90aXRsZT4KPGVsbGlwc2UgZmlsbD0ibm9uZSIgc3Ryb2tlPSJibGFjayIgY3g9IjE3NiIgY3k9Ii0yMDYiIHJ4PSIxOCIgcnk9IjE4Ii8+Cjx0ZXh0IHRleHQtYW5jaG9yPSJtaWRkbGUiIHg9IjE3NiIgeT0iLTIwMi4zIiBmb250LWZhbWlseT0iVGltZXMsc2VyaWYiIGZvbnQtc2l6ZT0iMTQuMDAiPmU8L3RleHQ+CjwvZz4KPCEtLSBjJiM0NTsmZ3Q7ZSAtLT4KPGcgaWQ9ImVkZ2U2IiBjbGFzcz0iZWRnZSI+PHRpdGxlPmMmIzQ1OyZndDtlPC90aXRsZT4KPHBhdGggZmlsbD0ibm9uZSIgc3Ryb2tlPSJibGFjayIgZD0iTTExMi40ODMsLTE2NC41OTNDMTIzLjY2OCwtMTcyLjAwMSAxMzkuMzU2LC0xODIuMzkyIDE1Mi4yMTksLTE5MC45MTEiLz4KPHBvbHlnb24gZmlsbD0iYmxhY2siIHN0cm9rZT0iYmxhY2siIHBvaW50cz0iMTUwLjMxMiwtMTkzLjg0NiAxNjAuNTgyLC0xOTYuNDUgMTU0LjE3NywtMTg4LjAxIDE1MC4zMTIsLTE5My44NDYiLz4KPHRleHQgdGV4dC1hbmNob3I9Im1pZGRsZSIgeD0iMTM2LjUiIHk9Ii0xODUuOCIgZm9udC1mYW1pbHk9IlRpbWVzLHNlcmlmIiBmb250LXNpemU9IjE0LjAwIj4xPC90ZXh0Pgo8L2c+CjwhLS0gZCYjNDU7Jmd0O2IgLS0+CjxnIGlkPSJlZGdlNyIgY2xhc3M9ImVkZ2UiPjx0aXRsZT5kJiM0NTsmZ3Q7YjwvdGl0bGU+CjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iYmxhY2siIGQ9Ik0zMjAuMjA1LC03NC44NzYzQzMxMS4yMDgsLTczLjQ5MTEgMzAwLjEzMSwtNzMuMTQyNCAyOTEsLTc3IDI4NC4wOTQsLTc5LjkxNzUgMjc3Ljg3OSwtODQuOTM3NiAyNzIuNjY5LC05MC4zMTgzIi8+Cjxwb2x5Z29uIGZpbGw9ImJsYWNrIiBzdHJva2U9ImJsYWNrIiBwb2ludHM9IjI2OS42OTQsLTg4LjQwNjcgMjY1Ljc5MSwtOTguMjU2OCAyNzQuOTg1LC05Mi45OTAyIDI2OS42OTQsLTg4LjQwNjciLz4KPHRleHQgdGV4dC1hbmNob3I9Im1pZGRsZSIgeD0iMjk0LjUiIHk9Ii04MC44IiBmb250LWZhbWlseT0iVGltZXMsc2VyaWYiIGZvbnQtc2l6ZT0iMTQuMDAiPjA8L3RleHQ+CjwvZz4KPCEtLSBmIC0tPgo8ZyBpZD0ibm9kZTgiIGNsYXNzPSJub2RlIj48dGl0bGU+ZjwvdGl0bGU+CjxlbGxpcHNlIGZpbGw9Im5vbmUiIHN0cm9rZT0iYmxhY2siIGN4PSIxNzYiIGN5PSItNDYiIHJ4PSIxOCIgcnk9IjE4Ii8+Cjx0ZXh0IHRleHQtYW5jaG9yPSJtaWRkbGUiIHg9IjE3NiIgeT0iLTQyLjMiIGZvbnQtZmFtaWx5PSJUaW1lcyxzZXJpZiIgZm9udC1zaXplPSIxNC4wMCI+ZjwvdGV4dD4KPC9nPgo8IS0tIGQmIzQ1OyZndDtmIC0tPgo8ZyBpZD0iZWRnZTgiIGNsYXNzPSJlZGdlIj48dGl0bGU+ZCYjNDU7Jmd0O2Y8L3RpdGxlPgo8cGF0aCBmaWxsPSJub25lIiBzdHJva2U9ImJsYWNrIiBkPSJNMzE5LjkyMywtNzUuNDc4QzI5Mi4wOTgsLTY5LjczODkgMjM2Ljc2OCwtNTguMzI3MSAyMDMuNzA4LC01MS41MDg2Ii8+Cjxwb2x5Z29uIGZpbGw9ImJsYWNrIiBzdHJva2U9ImJsYWNrIiBwb2ludHM9IjIwNC4zMjEsLTQ4LjA2MTQgMTkzLjgyLC00OS40NjkyIDIwMi45MDcsLTU0LjkxNzEgMjA0LjMyMSwtNDguMDYxNCIvPgo8dGV4dCB0ZXh0LWFuY2hvcj0ibWlkZGxlIiB4PSIyNTUiIHk9Ii02OS44IiBmb250LWZhbWlseT0iVGltZXMsc2VyaWYiIGZvbnQtc2l6ZT0iMTQuMDAiPjE8L3RleHQ+CjwvZz4KPCEtLSBlJiM0NTsmZ3Q7YiAtLT4KPGcgaWQ9ImVkZ2U5IiBjbGFzcz0iZWRnZSI+PHRpdGxlPmUmIzQ1OyZndDtiPC90aXRsZT4KPHBhdGggZmlsbD0ibm9uZSIgc3Ryb2tlPSJibGFjayIgZD0iTTE5MC4yNDEsLTE5NC43OTZDMTk4LjkwOCwtMTg3LjEzNiAyMTAuMjEyLC0xNzYuNTAzIDIxOSwtMTY2IDIyNi41MDcsLTE1Ny4wMjggMjMzLjgwMywtMTQ2LjM4OSAyMzkuNzc0LC0xMzcuMDA3Ii8+Cjxwb2x5Z29uIGZpbGw9ImJsYWNrIiBzdHJva2U9ImJsYWNrIiBwb2ludHM9IjI0Mi43NTksLTEzOC44MzQgMjQ1LjA1NiwtMTI4LjQ5MSAyMzYuODEsLTEzNS4xNDQgMjQyLjc1OSwtMTM4LjgzNCIvPgo8dGV4dCB0ZXh0LWFuY2hvcj0ibWlkZGxlIiB4PSIyMTUuNSIgeT0iLTE3Ni44IiBmb250LWZhbWlseT0iVGltZXMsc2VyaWYiIGZvbnQtc2l6ZT0iMTQuMDAiPjA8L3RleHQ+CjwvZz4KPCEtLSBnIC0tPgo8ZyBpZD0ibm9kZTkiIGNsYXNzPSJub2RlIj48dGl0bGU+ZzwvdGl0bGU+CjxlbGxpcHNlIGZpbGw9Im5vbmUiIHN0cm9rZT0iYmxhY2siIGN4PSIyNTUiIGN5PSItMjExIiByeD0iMTgiIHJ5PSIxOCIvPgo8dGV4dCB0ZXh0LWFuY2hvcj0ibWlkZGxlIiB4PSIyNTUiIHk9Ii0yMDcuMyIgZm9udC1mYW1pbHk9IlRpbWVzLHNlcmlmIiBmb250LXNpemU9IjE0LjAwIj5nPC90ZXh0Pgo8L2c+CjwhLS0gZSYjNDU7Jmd0O2cgLS0+CjxnIGlkPSJlZGdlMTAiIGNsYXNzPSJlZGdlIj48dGl0bGU+ZSYjNDU7Jmd0O2c8L3RpdGxlPgo8cGF0aCBmaWxsPSJub25lIiBzdHJva2U9ImJsYWNrIiBkPSJNMTk0LjA4OSwtMjA3LjExQzIwMy42NTksLTIwNy43MzEgMjE1LjgxNywtMjA4LjUyMSAyMjYuNjc3LC0yMDkuMjI2Ii8+Cjxwb2x5Z29uIGZpbGw9ImJsYWNrIiBzdHJva2U9ImJsYWNrIiBwb2ludHM9IjIyNi43NTMsLTIxMi43MzggMjM2Ljk1OSwtMjA5Ljg5MyAyMjcuMjA3LC0yMDUuNzUzIDIyNi43NTMsLTIxMi43MzgiLz4KPHRleHQgdGV4dC1hbmNob3I9Im1pZGRsZSIgeD0iMjE1LjUiIHk9Ii0yMTEuOCIgZm9udC1mYW1pbHk9IlRpbWVzLHNlcmlmIiBmb250LXNpemU9IjE0LjAwIj4xPC90ZXh0Pgo8L2c+CjwhLS0gZiYjNDU7Jmd0O2ggLS0+CjxnIGlkPSJlZGdlMTIiIGNsYXNzPSJlZGdlIj48dGl0bGU+ZiYjNDU7Jmd0O2g8L3RpdGxlPgo8cGF0aCBmaWxsPSJub25lIiBzdHJva2U9ImJsYWNrIiBkPSJNMTg5LjAyLC0zMy4xODY0QzIwMy4xNTEsLTE5LjU3NTQgMjI3Ljk5NSwtMCAyNTQsLTAgMjU0LC0wIDI1NCwtMCA0MjIsLTAgNDUzLjYzMiwtMCA0NzYuNjc3LC0zMS4yMzExIDQ4OS45MjQsLTU1LjgzMTQiLz4KPHBvbHlnb24gZmlsbD0iYmxhY2siIHN0cm9rZT0iYmxhY2siIHBvaW50cz0iNDg2Ljg2MiwtNTcuNTMyNSA0OTQuNTE4LC02NC44NTYyIDQ5My4xLC01NC4zNTY2IDQ4Ni44NjIsLTU3LjUzMjUiLz4KPHRleHQgdGV4dC1hbmNob3I9Im1pZGRsZSIgeD0iMzM4IiB5PSItMy44IiBmb250LWZhbWlseT0iVGltZXMsc2VyaWYiIGZvbnQtc2l6ZT0iMTQuMDAiPjE8L3RleHQ+CjwvZz4KPCEtLSBmJiM0NTsmZ3Q7YiAtLT4KPGcgaWQ9ImVkZ2UxMSIgY2xhc3M9ImVkZ2UiPjx0aXRsZT5mJiM0NTsmZ3Q7YjwvdGl0bGU+CjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iYmxhY2siIGQ9Ik0xOTAuODM0LC01Ni43Njg5QzE5OS4xMywtNjMuMzMxOSAyMDkuODE3LC03MS45NzQyIDIxOSwtODAgMjI0LjAzNCwtODQuNDAwMSAyMjkuMzQzLC04OS4yNzU3IDIzNC4yNjIsLTkzLjg5OSIvPgo8cG9seWdvbiBmaWxsPSJibGFjayIgc3Ryb2tlPSJibGFjayIgcG9pbnRzPSIyMzEuOTE3LC05Ni40OTg1IDI0MS41NzYsLTEwMC44NTIgMjM2Ljc0LC05MS40MjUyIDIzMS45MTcsLTk2LjQ5ODUiLz4KPHRleHQgdGV4dC1hbmNob3I9Im1pZGRsZSIgeD0iMjE1LjUiIHk9Ii04My44IiBmb250LWZhbWlseT0iVGltZXMsc2VyaWYiIGZvbnQtc2l6ZT0iMTQuMDAiPjA8L3RleHQ+CjwvZz4KPCEtLSBnJiM0NTsmZ3Q7aSAtLT4KPGcgaWQ9ImVkZ2UxMyIgY2xhc3M9ImVkZ2UiPjx0aXRsZT5nJiM0NTsmZ3Q7aTwvdGl0bGU+CjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iYmxhY2siIGQ9Ik0yNjkuNzQxLC0xOTkuOTc0QzI4MS40MzcsLTE5MC41ODcgMjk4LjUyNCwtMTc2Ljg3NiAzMTIuNTQ4LC0xNjUuNjIyIi8+Cjxwb2x5Z29uIGZpbGw9ImJsYWNrIiBzdHJva2U9ImJsYWNrIiBwb2ludHM9IjMxNC43NzgsLTE2OC4zMiAzMjAuMzg3LC0xNTkuMzMxIDMxMC4zOTcsLTE2Mi44NiAzMTQuNzc4LC0xNjguMzIiLz4KPHRleHQgdGV4dC1hbmNob3I9Im1pZGRsZSIgeD0iMjk0LjUiIHk9Ii0xODUuOCIgZm9udC1mYW1pbHk9IlRpbWVzLHNlcmlmIiBmb250LXNpemU9IjE0LjAwIj4wPC90ZXh0Pgo8L2c+CjwhLS0gZyYjNDU7Jmd0O2cgLS0+CjxnIGlkPSJlZGdlMTQiIGNsYXNzPSJlZGdlIj48dGl0bGU+ZyYjNDU7Jmd0O2c8L3RpdGxlPgo8cGF0aCBmaWxsPSJub25lIiBzdHJva2U9ImJsYWNrIiBkPSJNMjQ4LjI2NiwtMjI4LjAzN0MyNDYuODkyLC0yMzcuODU4IDI0OS4xMzcsLTI0NyAyNTUsLTI0NyAyNTguNjY1LC0yNDcgMjYwLjkxNiwtMjQzLjQyOSAyNjEuNzUzLC0yMzguMzUzIi8+Cjxwb2x5Z29uIGZpbGw9ImJsYWNrIiBzdHJva2U9ImJsYWNrIiBwb2ludHM9IjI2NS4yNTIsLTIzOC4wMzEgMjYxLjczNCwtMjI4LjAzNyAyNTguMjUyLC0yMzguMDQ0IDI2NS4yNTIsLTIzOC4wMzEiLz4KPHRleHQgdGV4dC1hbmNob3I9Im1pZGRsZSIgeD0iMjU1IiB5PSItMjUwLjgiIGZvbnQtZmFtaWx5PSJUaW1lcyxzZXJpZiIgZm9udC1zaXplPSIxNC4wMCI+MTwvdGV4dD4KPC9nPgo8IS0tIGomIzQ1OyZndDtpIC0tPgo8ZyBpZD0iZWRnZTE5IiBjbGFzcz0iZWRnZSI+PHRpdGxlPmomIzQ1OyZndDtpPC90aXRsZT4KPHBhdGggZmlsbD0ibm9uZSIgc3Ryb2tlPSJibGFjayIgZD0iTTQwMy4zNCwtMTM5LjhDMzk3LjU2MSwtMTQwLjk5MyAzOTEuMDIxLC0xNDIuMjA1IDM4NSwtMTQzIDM4MC4zMjEsLTE0My42MTggMzc1LjM1NywtMTQ0LjExIDM3MC40ODgsLTE0NC41MDIiLz4KPHBvbHlnb24gZmlsbD0iYmxhY2siIHN0cm9rZT0iYmxhY2siIHBvaW50cz0iMzY5Ljg2NCwtMTQxLjAzNiAzNjAuMTI2LC0xNDUuMjA5IDM3MC4zNDEsLTE0OC4wMiAzNjkuODY0LC0xNDEuMDM2Ii8+Cjx0ZXh0IHRleHQtYW5jaG9yPSJtaWRkbGUiIHg9IjM4MS41IiB5PSItMTQ2LjgiIGZvbnQtZmFtaWx5PSJUaW1lcyxzZXJpZiIgZm9udC1zaXplPSIxNC4wMCI+MDwvdGV4dD4KPC9nPgo8IS0tIGomIzQ1OyZndDtoIC0tPgo8ZyBpZD0iZWRnZTIwIiBjbGFzcz0iZWRnZSI+PHRpdGxlPmomIzQ1OyZndDtoPC90aXRsZT4KPHBhdGggZmlsbD0ibm9uZSIgc3Ryb2tlPSJibGFjayIgZD0iTTQzNi44NTcsLTEyNi42NDZDNDQ3Ljg0MSwtMTE5LjczIDQ2My4xLC0xMTAuMTIyIDQ3Ni4xOTQsLTEwMS44NzgiLz4KPHBvbHlnb24gZmlsbD0iYmxhY2siIHN0cm9rZT0iYmxhY2siIHBvaW50cz0iNDc4LjIzNywtMTA0LjcyNyA0ODQuODM1LC05Ni40Mzc1IDQ3NC41MDcsLTk4LjgwMzggNDc4LjIzNywtMTA0LjcyNyIvPgo8dGV4dCB0ZXh0LWFuY2hvcj0ibWlkZGxlIiB4PSI0NjAuNSIgeT0iLTExNi44IiBmb250LWZhbWlseT0iVGltZXMsc2VyaWYiIGZvbnQtc2l6ZT0iMTQuMDAiPjE8L3RleHQ+CjwvZz4KPC9nPgo8L3N2Zz4K" + ] + } + }, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## State Machine\n", + "We can drive the regular expressions to flesh out the underlying state machine transition table.\n", + "\n", + " .111. & (.01 + 11*)'\n", + "\n", + "Says, \"Three or more 1's and not ending in 01 nor composed of all 1's.\"\n", + "\n", + "![omg.svg](attachment:omg.svg)\n", + "\n", + "\n", + "Start at `a` and follow the transition arrows according to their labels. Accepting states have a double outline. (Graphic generated with [Dot from Graphviz](http://www.graphviz.org/).) You'll see that only paths that lead to one of the accepting states will match the regular expression. All other paths will terminate at one of the non-accepting states.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There's a happy path to `g` along 111:\n", + "\n", + " a→c→e→g\n", + "\n", + "After you reach `g` you're stuck there eating 1's until you see a 0, which takes you to the `i→j→i|i→j→h→i` \"trap\". You can't reach any other states from those two loops.\n", + "\n", + "If you see a 0 before you see 111 you will reach `b`, which forms another \"trap\" with `d` and `f`. The only way out is another happy path along 111 to `h`:\n", + "\n", + " b→d→f→h\n", + "\n", + "Once you have reached `h` you can see as many 1's or as many 0' in a row and still be either still at `h` (for 1's) or move to `i` (for 0's). If you find yourself at `i` you can see as many 0's, or repetitions of 10, as there are, but if you see just a 1 you move to `j`." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### RE to FSM\n", + "So how do we get the state machine from the regular expression?\n", + "\n", + "It turns out that each RE is effectively a state, and each arrow points to the derivative RE in respect to the arrow's symbol.\n", + "\n", + "If we label the initial RE `a`, we can say:\n", + "\n", + " a --0--> ∂0(a)\n", + " a --1--> ∂1(a)\n", + "\n", + "And so on, each new unique RE is a new state in the FSM table." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here are the derived REs at each state:\n", + "\n", + " a = (.111.) & ((.01 | 11*)')\n", + " b = (.111.) & ((.01 | 1)')\n", + " c = (.111. | 11.) & ((.01 | 1*)')\n", + " d = (.111. | 11.) & ((.01 | ^)')\n", + " e = (.111. | 11. | 1.) & ((.01 | 1*)')\n", + " f = (.111. | 11. | 1.) & ((.01)')\n", + " g = (.01 | 1*)'\n", + " h = (.01)'\n", + " i = (.01 | 1)'\n", + " j = (.01 | ^)'\n", + "\n", + "You can see the one-way nature of the `g` state and the `hij` \"trap\" in the way that the `.111.` on the left-hand side of the `&` disappears once it has been matched." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [], + "source": [ + "from collections import defaultdict\n", + "from pprint import pprint\n", + "from string import ascii_lowercase" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [], + "source": [ + "d0, d1 = D_compaction('0'), D_compaction('1')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### `explore()`" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [], + "source": [ + "def explore(re):\n", + "\n", + " # Don't have more than 26 states...\n", + " names = defaultdict(iter(ascii_lowercase).next)\n", + "\n", + " table, accepting = dict(), set()\n", + "\n", + " to_check = {re}\n", + " while to_check:\n", + "\n", + " re = to_check.pop()\n", + " state_name = names[re]\n", + "\n", + " if (state_name, 0) in table:\n", + " continue\n", + "\n", + " if nully(re):\n", + " accepting.add(state_name)\n", + "\n", + " o, i = d0(re), d1(re)\n", + " table[state_name, 0] = names[o] ; to_check.add(o)\n", + " table[state_name, 1] = names[i] ; to_check.add(i)\n", + "\n", + " return table, accepting" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{('a', 0): 'b',\n", + " ('a', 1): 'c',\n", + " ('b', 0): 'b',\n", + " ('b', 1): 'd',\n", + " ('c', 0): 'b',\n", + " ('c', 1): 'e',\n", + " ('d', 0): 'b',\n", + " ('d', 1): 'f',\n", + " ('e', 0): 'b',\n", + " ('e', 1): 'g',\n", + " ('f', 0): 'b',\n", + " ('f', 1): 'h',\n", + " ('g', 0): 'i',\n", + " ('g', 1): 'g',\n", + " ('h', 0): 'i',\n", + " ('h', 1): 'h',\n", + " ('i', 0): 'i',\n", + " ('i', 1): 'j',\n", + " ('j', 0): 'i',\n", + " ('j', 1): 'h'}" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "table, accepting = explore(it)\n", + "table" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'h', 'i'}" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "accepting" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Generate Diagram\n", + "Once we have the FSM table and the set of accepting states we can generate the diagram above." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [], + "source": [ + "_template = '''\\\n", + "digraph finite_state_machine {\n", + " rankdir=LR;\n", + " size=\"8,5\"\n", + " node [shape = doublecircle]; %s;\n", + " node [shape = circle];\n", + "%s\n", + "}\n", + "'''\n", + "\n", + "def link(fr, nm, label):\n", + " return ' %s -> %s [ label = \"%s\" ];' % (fr, nm, label)\n", + "\n", + "\n", + "def make_graph(table, accepting):\n", + " return _template % (\n", + " ' '.join(accepting),\n", + " '\\n'.join(\n", + " link(from_, to, char)\n", + " for (from_, char), (to) in sorted(table.iteritems())\n", + " )\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "digraph finite_state_machine {\n", + " rankdir=LR;\n", + " size=\"8,5\"\n", + " node [shape = doublecircle]; i h;\n", + " node [shape = circle];\n", + " a -> b [ label = \"0\" ];\n", + " a -> c [ label = \"1\" ];\n", + " b -> b [ label = \"0\" ];\n", + " b -> d [ label = \"1\" ];\n", + " c -> b [ label = \"0\" ];\n", + " c -> e [ label = \"1\" ];\n", + " d -> b [ label = \"0\" ];\n", + " d -> f [ label = \"1\" ];\n", + " e -> b [ label = \"0\" ];\n", + " e -> g [ label = \"1\" ];\n", + " f -> b [ label = \"0\" ];\n", + " f -> h [ label = \"1\" ];\n", + " g -> i [ label = \"0\" ];\n", + " g -> g [ label = \"1\" ];\n", + " h -> i [ label = \"0\" ];\n", + " h -> h [ label = \"1\" ];\n", + " i -> i [ label = \"0\" ];\n", + " i -> j [ label = \"1\" ];\n", + " j -> i [ label = \"0\" ];\n", + " j -> h [ label = \"1\" ];\n", + "}\n", + "\n" + ] + } + ], + "source": [ + "print make_graph(table, accepting)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Drive a FSM\n", + "There are _lots_ of FSM libraries already. Once you have the state transition table they should all be straightforward to use. State Machine code is very simple. Just for fun, here is an implementation in Python that imitates what \"compiled\" FSM code might look like in an \"unrolled\" form. Most FSM code uses a little driver loop and a table datastructure, the code below instead acts like JMP instructions (\"jump\", or GOTO in higher-level-but-still-low-level languages) to hard-code the information in the table into a little patch of branches." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Trampoline Function\n", + "Python has no GOTO statement but we can fake it with a \"trampoline\" function." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [], + "source": [ + "def trampoline(input_, jump_from, accepting):\n", + " I = iter(input_)\n", + " while True:\n", + " try:\n", + " bounce_to = jump_from(I)\n", + " except StopIteration:\n", + " return jump_from in accepting\n", + " jump_from = bounce_to" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Stream Functions\n", + "Little helpers to process the iterator of our data (a \"stream\" of \"1\" and \"0\" characters, not bits.)" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [], + "source": [ + "getch = lambda I: int(next(I))\n", + "\n", + "\n", + "def _1(I):\n", + " '''Loop on ones.'''\n", + " while getch(I): pass\n", + "\n", + "\n", + "def _0(I):\n", + " '''Loop on zeros.'''\n", + " while not getch(I): pass" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### A Finite State Machine\n", + "With those preliminaries out of the way, from the state table of `.111. & (.01 + 11*)'` we can immediately write down state machine code. (You have to imagine that these are GOTO statements in C or branches in assembly and that the state names are branch destination labels.)" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [], + "source": [ + "a = lambda I: c if getch(I) else b\n", + "b = lambda I: _0(I) or d\n", + "c = lambda I: e if getch(I) else b\n", + "d = lambda I: f if getch(I) else b\n", + "e = lambda I: g if getch(I) else b\n", + "f = lambda I: h if getch(I) else b\n", + "g = lambda I: _1(I) or i\n", + "h = lambda I: _1(I) or i\n", + "i = lambda I: _0(I) or j\n", + "j = lambda I: h if getch(I) else i" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note that the implementations of `h` and `g` are identical ergo `h = g` and we could eliminate one in the code but `h` is an accepting state and `g` isn't." + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": {}, + "outputs": [], + "source": [ + "def acceptable(input_):\n", + " return trampoline(input_, a, {h, i})" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " 0 False\n", + " 1 False\n", + " 10 False\n", + " 11 False\n", + " 100 False\n", + " 101 False\n", + " 110 False\n", + " 111 False\n", + " 1000 False\n", + " 1001 False\n", + " 1010 False\n", + " 1011 False\n", + " 1100 False\n", + " 1101 False\n", + " 1110 True\n", + " 1111 False\n", + "10000 False\n", + "10001 False\n", + "10010 False\n", + "10011 False\n", + "10100 False\n", + "10101 False\n", + "10110 False\n", + "10111 True\n", + "11000 False\n", + "11001 False\n", + "11010 False\n", + "11011 False\n", + "11100 True\n", + "11101 False\n", + "11110 True\n", + "11111 False\n" + ] + } + ], + "source": [ + "for n in range(2**5):\n", + " s = bin(n)[2:]\n", + " print '%05s' % s, acceptable(s)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Reversing the Derivatives to Generate Matching Strings\n", + "(UNFINISHED)\n", + "Brzozowski also shewed how to go from the state machine to strings and expressions..." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Each of these states is just a name for a Brzozowskian RE, and so, other than the initial state `a`, they can can be described in terms of the derivative-with-respect-to-N of some other state/RE:\n", + "\n", + " c = d1(a)\n", + " b = d0(a)\n", + " b = d0(c)\n", + " ...\n", + " i = d0(j)\n", + " j = d1(i)\n", + "\n", + "Consider:\n", + "\n", + " c = d1(a)\n", + " b = d0(c)\n", + "\n", + "Substituting:\n", + "\n", + " b = d0(d1(a))\n", + "\n", + "Unwrapping:\n", + "\n", + "\n", + " b = d10(a)\n", + "\n", + "'''\n", + "\n", + " j = d1(d0(j))\n", + "\n", + "Unwrapping:\n", + "\n", + " j = d1(d0(j)) = d01(j)\n", + "\n", + "We have a loop or \"fixed point\".\n", + "\n", + " j = d01(j) = d0101(j) = d010101(j) = ...\n", + "\n", + "hmm...\n", + "\n", + " j = (01)*\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 2", + "language": "python", + "name": "python2" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.12" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/Derivatives_of_Regular_Expressions.md b/docs/Derivatives_of_Regular_Expressions.md new file mode 100644 index 0000000..9d2cd68 --- /dev/null +++ b/docs/Derivatives_of_Regular_Expressions.md @@ -0,0 +1,820 @@ + +# ∂RE + +## Brzozowski's Derivatives of Regular Expressions + +Legend: + + ∧ intersection + ∨ union + ∘ concatenation (see below) + ¬ complement + ϕ empty set (aka ∅) + λ singleton set containing just the empty string + I set of all letters in alphabet + +Derivative of a set `R` of strings and a string `a`: + + ∂a(R) + + ∂a(a) → λ + ∂a(λ) → ϕ + ∂a(ϕ) → ϕ + ∂a(¬a) → ϕ + ∂a(R*) → ∂a(R)∘R* + ∂a(¬R) → ¬∂a(R) + ∂a(R∘S) → ∂a(R)∘S ∨ δ(R)∘∂a(S) + ∂a(R ∧ S) → ∂a(R) ∧ ∂a(S) + ∂a(R ∨ S) → ∂a(R) ∨ ∂a(S) + + ∂ab(R) = ∂b(∂a(R)) + +Auxiliary predicate function `δ` (I call it `nully`) returns either `λ` if `λ ⊆ R` or `ϕ` otherwise: + + δ(a) → ϕ + δ(λ) → λ + δ(ϕ) → ϕ + δ(R*) → λ + δ(¬R) δ(R)≟ϕ → λ + δ(¬R) δ(R)≟λ → ϕ + δ(R∘S) → δ(R) ∧ δ(S) + δ(R ∧ S) → δ(R) ∧ δ(S) + δ(R ∨ S) → δ(R) ∨ δ(S) + +Some rules we will use later for "compaction": + + R ∧ ϕ = ϕ ∧ R = ϕ + + R ∧ I = I ∧ R = R + + R ∨ ϕ = ϕ ∨ R = R + + R ∨ I = I ∨ R = I + + R∘ϕ = ϕ∘R = ϕ + + R∘λ = λ∘R = R + +Concatination of sets: for two sets A and B the set A∘B is defined as: + + {a∘b for a in A for b in B} + +E.g.: + + {'a', 'b'}∘{'c', 'd'} → {'ac', 'ad', 'bc', 'bd'} + +## Implementation + + +```python +from functools import partial as curry +from itertools import product +``` + +### `ϕ` and `λ` +The empty set and the set of just the empty string. + + +```python +phi = frozenset() # ϕ +y = frozenset({''}) # λ +``` + +### Two-letter Alphabet +I'm only going to use two symbols (at first) becaase this is enough to illustrate the algorithm and because you can represent any other alphabet with two symbols (if you had to.) + +I chose the names `O` and `l` (uppercase "o" and lowercase "L") to look like `0` and `1` (zero and one) respectively. + + +```python +syms = O, l = frozenset({'0'}), frozenset({'1'}) +``` + +### Representing Regular Expressions +To represent REs in Python I'm going to use tagged tuples. A _regular expression_ is one of: + + O + l + (KSTAR, R) + (NOT, R) + (AND, R, S) + (CONS, R, S) + (OR, R, S) + +Where `R` and `S` stand for _regular expressions_. + + +```python +AND, CONS, KSTAR, NOT, OR = 'and cons * not or'.split() # Tags are just strings. +``` + +Because they are formed of `frozenset`, `tuple` and `str` objects only, these datastructures are immutable. + +### String Representation of RE Datastructures + + +```python +def stringy(re): + ''' + Return a nice string repr for a regular expression datastructure. + ''' + if re == I: return '.' + if re in syms: return next(iter(re)) + if re == y: return '^' + if re == phi: return 'X' + + assert isinstance(re, tuple), repr(re) + tag = re[0] + + if tag == KSTAR: + body = stringy(re[1]) + if not body: return body + if len(body) > 1: return '(' + body + ")*" + return body + '*' + + if tag == NOT: + body = stringy(re[1]) + if not body: return body + if len(body) > 1: return '(' + body + ")'" + return body + "'" + + r, s = stringy(re[1]), stringy(re[2]) + if tag == CONS: return r + s + if tag == OR: return '%s | %s' % (r, s) + if tag == AND: return '(%s) & (%s)' % (r, s) + + raise ValueError +``` + +### `I` +Match anything. Often spelled "." + + I = (0|1)* + + +```python +I = (KSTAR, (OR, O, l)) +``` + + +```python +print stringy(I) +``` + + . + + +### `(.111.) & (.01 + 11*)'` +The example expression from Brzozowski: + + (.111.) & (.01 + 11*)' + a & (b + c)' + +Note that it contains one of everything. + + +```python +a = (CONS, I, (CONS, l, (CONS, l, (CONS, l, I)))) +b = (CONS, I, (CONS, O, l)) +c = (CONS, l, (KSTAR, l)) +it = (AND, a, (NOT, (OR, b, c))) +``` + + +```python +print stringy(it) +``` + + (.111.) & ((.01 | 11*)') + + +### `nully()` +Let's get that auxiliary predicate function `δ` out of the way. + + +```python +def nully(R): + ''' + δ - Return λ if λ ⊆ R otherwise ϕ. + ''' + + # δ(a) → ϕ + # δ(ϕ) → ϕ + if R in syms or R == phi: + return phi + + # δ(λ) → λ + if R == y: + return y + + tag = R[0] + + # δ(R*) → λ + if tag == KSTAR: + return y + + # δ(¬R) δ(R)≟ϕ → λ + # δ(¬R) δ(R)≟λ → ϕ + if tag == NOT: + return phi if nully(R[1]) else y + + # δ(R∘S) → δ(R) ∧ δ(S) + # δ(R ∧ S) → δ(R) ∧ δ(S) + # δ(R ∨ S) → δ(R) ∨ δ(S) + r, s = nully(R[1]), nully(R[2]) + return r & s if tag in {AND, CONS} else r | s +``` + +### No "Compaction" +This is the straightforward version with no "compaction". +It works fine, but does waaaay too much work because the +expressions grow each derivation. + + +```python +# This is the straightforward version with no "compaction". +# It works fine, but does waaaay too much work because the +# expressions grow each derivation. + +def D(symbol): + + def derv(R): + + # ∂a(a) → λ + if R == {symbol}: + return y + + # ∂a(λ) → ϕ + # ∂a(ϕ) → ϕ + # ∂a(¬a) → ϕ + if R == y or R == phi or R in syms: + return phi + + tag = R[0] + + # ∂a(R*) → ∂a(R)∘R* + if tag == KSTAR: + return (CONS, derv(R[1]), R) + + # ∂a(¬R) → ¬∂a(R) + if tag == NOT: + return (NOT, derv(R[1])) + + r, s = R[1:] + + # ∂a(R∘S) → ∂a(R)∘S ∨ δ(R)∘∂a(S) + if tag == CONS: + A = (CONS, derv(r), s) # A = ∂a(R)∘S + # A ∨ δ(R) ∘ ∂a(S) + # A ∨ λ ∘ ∂a(S) → A ∨ ∂a(S) + # A ∨ ϕ ∘ ∂a(S) → A ∨ ϕ → A + return (OR, A, derv(s)) if nully(r) else A + + # ∂a(R ∧ S) → ∂a(R) ∧ ∂a(S) + # ∂a(R ∨ S) → ∂a(R) ∨ ∂a(S) + return (tag, derv(r), derv(s)) + + return derv +``` + +### Compaction Rules + + +```python +def _compaction_rule(relation, one, zero, a, b): + return ( + b if a == one else # R*1 = 1*R = R + a if b == one else + zero if a == zero or b == zero else # R*0 = 0*R = 0 + (relation, a, b) + ) +``` + +An elegant symmetry. + + +```python +# R ∧ I = I ∧ R = R +# R ∧ ϕ = ϕ ∧ R = ϕ +_and = curry(_compaction_rule, AND, I, phi) + +# R ∨ ϕ = ϕ ∨ R = R +# R ∨ I = I ∨ R = I +_or = curry(_compaction_rule, OR, phi, I) + +# R∘λ = λ∘R = R +# R∘ϕ = ϕ∘R = ϕ +_cons = curry(_compaction_rule, CONS, y, phi) +``` + +### Memoizing +We can save re-processing by remembering results we have already computed. RE datastructures are immutable and the `derv()` functions are _pure_ so this is fine. + + +```python +class Memo(object): + + def __init__(self, f): + self.f = f + self.calls = self.hits = 0 + self.mem = {} + + def __call__(self, key): + self.calls += 1 + try: + result = self.mem[key] + self.hits += 1 + except KeyError: + result = self.mem[key] = self.f(key) + return result +``` + +### With "Compaction" +This version uses the rules above to perform compaction. It keeps the expressions from growing too large. + + +```python +def D_compaction(symbol): + + @Memo + def derv(R): + + # ∂a(a) → λ + if R == {symbol}: + return y + + # ∂a(λ) → ϕ + # ∂a(ϕ) → ϕ + # ∂a(¬a) → ϕ + if R == y or R == phi or R in syms: + return phi + + tag = R[0] + + # ∂a(R*) → ∂a(R)∘R* + if tag == KSTAR: + return _cons(derv(R[1]), R) + + # ∂a(¬R) → ¬∂a(R) + if tag == NOT: + return (NOT, derv(R[1])) + + r, s = R[1:] + + # ∂a(R∘S) → ∂a(R)∘S ∨ δ(R)∘∂a(S) + if tag == CONS: + A = _cons(derv(r), s) # A = ∂a(r)∘s + # A ∨ δ(R) ∘ ∂a(S) + # A ∨ λ ∘ ∂a(S) → A ∨ ∂a(S) + # A ∨ ϕ ∘ ∂a(S) → A ∨ ϕ → A + return _or(A, derv(s)) if nully(r) else A + + # ∂a(R ∧ S) → ∂a(R) ∧ ∂a(S) + # ∂a(R ∨ S) → ∂a(R) ∨ ∂a(S) + dr, ds = derv(r), derv(s) + return _and(dr, ds) if tag == AND else _or(dr, ds) + + return derv +``` + +## Let's try it out... +(FIXME: redo.) + + +```python +o, z = D_compaction('0'), D_compaction('1') +REs = set() +N = 5 +names = list(product(*(N * [(0, 1)]))) +dervs = list(product(*(N * [(o, z)]))) +for name, ds in zip(names, dervs): + R = it + ds = list(ds) + while ds: + R = ds.pop()(R) + if R == phi or R == I: + break + REs.add(R) + +print stringy(it) ; print +print o.hits, '/', o.calls +print z.hits, '/', z.calls +print +for s in sorted(map(stringy, REs), key=lambda n: (len(n), n)): + print s +``` + + (.111.) & ((.01 | 11*)') + + 92 / 122 + 92 / 122 + + (.01)' + (.01 | 1)' + (.01 | ^)' + (.01 | 1*)' + (.111.) & ((.01 | 1)') + (.111. | 11.) & ((.01 | ^)') + (.111. | 11. | 1.) & ((.01)') + (.111. | 11.) & ((.01 | 1*)') + (.111. | 11. | 1.) & ((.01 | 1*)') + + +Should match: + + (.111.) & ((.01 | 11*)') + + 92 / 122 + 92 / 122 + + (.01 )' + (.01 | 1 )' + (.01 | ^ )' + (.01 | 1*)' + (.111.) & ((.01 | 1 )') + (.111. | 11.) & ((.01 | ^ )') + (.111. | 11.) & ((.01 | 1*)') + (.111. | 11. | 1.) & ((.01 )') + (.111. | 11. | 1.) & ((.01 | 1*)') + +## Larger Alphabets + +We could parse larger alphabets by defining patterns for e.g. each byte of the ASCII code. Or we can generalize this code. If you study the code above you'll see that we never use the "set-ness" of the symbols `O` and `l`. The only time Python set operators (`&` and `|`) appear is in the `nully()` function, and there they operate on (recursively computed) outputs of that function, never `O` and `l`. + + +What if we try: + + (OR, O, l) + + ∂1((OR, O, l)) + ∂a(R ∨ S) → ∂a(R) ∨ ∂a(S) + ∂1(O) ∨ ∂1(l) + ∂a(¬a) → ϕ + ϕ ∨ ∂1(l) + ∂a(a) → λ + ϕ ∨ λ + ϕ ∨ R = R + λ + +And compare it to: + + {'0', '1') + + ∂1({'0', '1')) + ∂a(R ∨ S) → ∂a(R) ∨ ∂a(S) + ∂1({'0')) ∨ ∂1({'1')) + ∂a(¬a) → ϕ + ϕ ∨ ∂1({'1')) + ∂a(a) → λ + ϕ ∨ λ + ϕ ∨ R = R + λ + +This suggests that we should be able to alter the functions above to detect sets and deal with them appropriately. Exercise for the Reader for now. + +## State Machine +We can drive the regular expressions to flesh out the underlying state machine transition table. + + .111. & (.01 + 11*)' + +Says, "Three or more 1's and not ending in 01 nor composed of all 1's." + +![omg.svg](attachment:omg.svg) + + +Start at `a` and follow the transition arrows according to their labels. Accepting states have a double outline. (Graphic generated with [Dot from Graphviz](http://www.graphviz.org/).) You'll see that only paths that lead to one of the accepting states will match the regular expression. All other paths will terminate at one of the non-accepting states. + + +There's a happy path to `g` along 111: + + a→c→e→g + +After you reach `g` you're stuck there eating 1's until you see a 0, which takes you to the `i→j→i|i→j→h→i` "trap". You can't reach any other states from those two loops. + +If you see a 0 before you see 111 you will reach `b`, which forms another "trap" with `d` and `f`. The only way out is another happy path along 111 to `h`: + + b→d→f→h + +Once you have reached `h` you can see as many 1's or as many 0' in a row and still be either still at `h` (for 1's) or move to `i` (for 0's). If you find yourself at `i` you can see as many 0's, or repetitions of 10, as there are, but if you see just a 1 you move to `j`. + +### RE to FSM +So how do we get the state machine from the regular expression? + +It turns out that each RE is effectively a state, and each arrow points to the derivative RE in respect to the arrow's symbol. + +If we label the initial RE `a`, we can say: + + a --0--> ∂0(a) + a --1--> ∂1(a) + +And so on, each new unique RE is a new state in the FSM table. + +Here are the derived REs at each state: + + a = (.111.) & ((.01 | 11*)') + b = (.111.) & ((.01 | 1)') + c = (.111. | 11.) & ((.01 | 1*)') + d = (.111. | 11.) & ((.01 | ^)') + e = (.111. | 11. | 1.) & ((.01 | 1*)') + f = (.111. | 11. | 1.) & ((.01)') + g = (.01 | 1*)' + h = (.01)' + i = (.01 | 1)' + j = (.01 | ^)' + +You can see the one-way nature of the `g` state and the `hij` "trap" in the way that the `.111.` on the left-hand side of the `&` disappears once it has been matched. + + +```python +from collections import defaultdict +from pprint import pprint +from string import ascii_lowercase +``` + + +```python +d0, d1 = D_compaction('0'), D_compaction('1') +``` + +### `explore()` + + +```python +def explore(re): + + # Don't have more than 26 states... + names = defaultdict(iter(ascii_lowercase).next) + + table, accepting = dict(), set() + + to_check = {re} + while to_check: + + re = to_check.pop() + state_name = names[re] + + if (state_name, 0) in table: + continue + + if nully(re): + accepting.add(state_name) + + o, i = d0(re), d1(re) + table[state_name, 0] = names[o] ; to_check.add(o) + table[state_name, 1] = names[i] ; to_check.add(i) + + return table, accepting +``` + + +```python +table, accepting = explore(it) +table +``` + + + + + {('a', 0): 'b', + ('a', 1): 'c', + ('b', 0): 'b', + ('b', 1): 'd', + ('c', 0): 'b', + ('c', 1): 'e', + ('d', 0): 'b', + ('d', 1): 'f', + ('e', 0): 'b', + ('e', 1): 'g', + ('f', 0): 'b', + ('f', 1): 'h', + ('g', 0): 'i', + ('g', 1): 'g', + ('h', 0): 'i', + ('h', 1): 'h', + ('i', 0): 'i', + ('i', 1): 'j', + ('j', 0): 'i', + ('j', 1): 'h'} + + + + +```python +accepting +``` + + + + + {'h', 'i'} + + + +### Generate Diagram +Once we have the FSM table and the set of accepting states we can generate the diagram above. + + +```python +_template = '''\ +digraph finite_state_machine { + rankdir=LR; + size="8,5" + node [shape = doublecircle]; %s; + node [shape = circle]; +%s +} +''' + +def link(fr, nm, label): + return ' %s -> %s [ label = "%s" ];' % (fr, nm, label) + + +def make_graph(table, accepting): + return _template % ( + ' '.join(accepting), + '\n'.join( + link(from_, to, char) + for (from_, char), (to) in sorted(table.iteritems()) + ) + ) +``` + + +```python +print make_graph(table, accepting) +``` + + digraph finite_state_machine { + rankdir=LR; + size="8,5" + node [shape = doublecircle]; i h; + node [shape = circle]; + a -> b [ label = "0" ]; + a -> c [ label = "1" ]; + b -> b [ label = "0" ]; + b -> d [ label = "1" ]; + c -> b [ label = "0" ]; + c -> e [ label = "1" ]; + d -> b [ label = "0" ]; + d -> f [ label = "1" ]; + e -> b [ label = "0" ]; + e -> g [ label = "1" ]; + f -> b [ label = "0" ]; + f -> h [ label = "1" ]; + g -> i [ label = "0" ]; + g -> g [ label = "1" ]; + h -> i [ label = "0" ]; + h -> h [ label = "1" ]; + i -> i [ label = "0" ]; + i -> j [ label = "1" ]; + j -> i [ label = "0" ]; + j -> h [ label = "1" ]; + } + + + +### Drive a FSM +There are _lots_ of FSM libraries already. Once you have the state transition table they should all be straightforward to use. State Machine code is very simple. Just for fun, here is an implementation in Python that imitates what "compiled" FSM code might look like in an "unrolled" form. Most FSM code uses a little driver loop and a table datastructure, the code below instead acts like JMP instructions ("jump", or GOTO in higher-level-but-still-low-level languages) to hard-code the information in the table into a little patch of branches. + +#### Trampoline Function +Python has no GOTO statement but we can fake it with a "trampoline" function. + + +```python +def trampoline(input_, jump_from, accepting): + I = iter(input_) + while True: + try: + bounce_to = jump_from(I) + except StopIteration: + return jump_from in accepting + jump_from = bounce_to +``` + +#### Stream Functions +Little helpers to process the iterator of our data (a "stream" of "1" and "0" characters, not bits.) + + +```python +getch = lambda I: int(next(I)) + + +def _1(I): + '''Loop on ones.''' + while getch(I): pass + + +def _0(I): + '''Loop on zeros.''' + while not getch(I): pass +``` + +#### A Finite State Machine +With those preliminaries out of the way, from the state table of `.111. & (.01 + 11*)'` we can immediately write down state machine code. (You have to imagine that these are GOTO statements in C or branches in assembly and that the state names are branch destination labels.) + + +```python +a = lambda I: c if getch(I) else b +b = lambda I: _0(I) or d +c = lambda I: e if getch(I) else b +d = lambda I: f if getch(I) else b +e = lambda I: g if getch(I) else b +f = lambda I: h if getch(I) else b +g = lambda I: _1(I) or i +h = lambda I: _1(I) or i +i = lambda I: _0(I) or j +j = lambda I: h if getch(I) else i +``` + +Note that the implementations of `h` and `g` are identical ergo `h = g` and we could eliminate one in the code but `h` is an accepting state and `g` isn't. + + +```python +def acceptable(input_): + return trampoline(input_, a, {h, i}) +``` + + +```python +for n in range(2**5): + s = bin(n)[2:] + print '%05s' % s, acceptable(s) +``` + + 0 False + 1 False + 10 False + 11 False + 100 False + 101 False + 110 False + 111 False + 1000 False + 1001 False + 1010 False + 1011 False + 1100 False + 1101 False + 1110 True + 1111 False + 10000 False + 10001 False + 10010 False + 10011 False + 10100 False + 10101 False + 10110 False + 10111 True + 11000 False + 11001 False + 11010 False + 11011 False + 11100 True + 11101 False + 11110 True + 11111 False + + +## Reversing the Derivatives to Generate Matching Strings +(UNFINISHED) +Brzozowski also shewed how to go from the state machine to strings and expressions... + +Each of these states is just a name for a Brzozowskian RE, and so, other than the initial state `a`, they can can be described in terms of the derivative-with-respect-to-N of some other state/RE: + + c = d1(a) + b = d0(a) + b = d0(c) + ... + i = d0(j) + j = d1(i) + +Consider: + + c = d1(a) + b = d0(c) + +Substituting: + + b = d0(d1(a)) + +Unwrapping: + + + b = d10(a) + +''' + + j = d1(d0(j)) + +Unwrapping: + + j = d1(d0(j)) = d01(j) + +We have a loop or "fixed point". + + j = d01(j) = d0101(j) = d010101(j) = ... + +hmm... + + j = (01)* + diff --git a/docs/Derivatives_of_Regular_Expressions.rst b/docs/Derivatives_of_Regular_Expressions.rst new file mode 100644 index 0000000..6f87ac6 --- /dev/null +++ b/docs/Derivatives_of_Regular_Expressions.rst @@ -0,0 +1,949 @@ + +∂RE +=== + +Brzozowski's Derivatives of Regular Expressions +----------------------------------------------- + +Legend: + +:: + + ∧ intersection + ∨ union + ∘ concatenation (see below) + ¬ complement + ϕ empty set (aka ∅) + λ singleton set containing just the empty string + I set of all letters in alphabet + +Derivative of a set ``R`` of strings and a string ``a``: + +:: + + ∂a(R) + + ∂a(a) → λ + ∂a(λ) → ϕ + ∂a(ϕ) → ϕ + ∂a(¬a) → ϕ + ∂a(R*) → ∂a(R)∘R* + ∂a(¬R) → ¬∂a(R) + ∂a(R∘S) → ∂a(R)∘S ∨ δ(R)∘∂a(S) + ∂a(R ∧ S) → ∂a(R) ∧ ∂a(S) + ∂a(R ∨ S) → ∂a(R) ∨ ∂a(S) + + ∂ab(R) = ∂b(∂a(R)) + +Auxiliary predicate function ``δ`` (I call it ``nully``) returns either +``λ`` if ``λ ⊆ R`` or ``ϕ`` otherwise: + +:: + + δ(a) → ϕ + δ(λ) → λ + δ(ϕ) → ϕ + δ(R*) → λ + δ(¬R) δ(R)≟ϕ → λ + δ(¬R) δ(R)≟λ → ϕ + δ(R∘S) → δ(R) ∧ δ(S) + δ(R ∧ S) → δ(R) ∧ δ(S) + δ(R ∨ S) → δ(R) ∨ δ(S) + +Some rules we will use later for "compaction": + +:: + + R ∧ ϕ = ϕ ∧ R = ϕ + + R ∧ I = I ∧ R = R + + R ∨ ϕ = ϕ ∨ R = R + + R ∨ I = I ∨ R = I + + R∘ϕ = ϕ∘R = ϕ + + R∘λ = λ∘R = R + +Concatination of sets: for two sets A and B the set A∘B is defined as: + +{a∘b for a in A for b in B} + +E.g.: + +{'a', 'b'}∘{'c', 'd'} → {'ac', 'ad', 'bc', 'bd'} + +Implementation +-------------- + +.. code:: ipython2 + + from functools import partial as curry + from itertools import product + +``ϕ`` and ``λ`` +~~~~~~~~~~~~~~~ + +The empty set and the set of just the empty string. + +.. code:: ipython2 + + phi = frozenset() # ϕ + y = frozenset({''}) # λ + +Two-letter Alphabet +~~~~~~~~~~~~~~~~~~~ + +I'm only going to use two symbols (at first) becaase this is enough to +illustrate the algorithm and because you can represent any other +alphabet with two symbols (if you had to.) + +I chose the names ``O`` and ``l`` (uppercase "o" and lowercase "L") to +look like ``0`` and ``1`` (zero and one) respectively. + +.. code:: ipython2 + + syms = O, l = frozenset({'0'}), frozenset({'1'}) + +Representing Regular Expressions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To represent REs in Python I'm going to use tagged tuples. A *regular +expression* is one of: + +:: + + O + l + (KSTAR, R) + (NOT, R) + (AND, R, S) + (CONS, R, S) + (OR, R, S) + +Where ``R`` and ``S`` stand for *regular expressions*. + +.. code:: ipython2 + + AND, CONS, KSTAR, NOT, OR = 'and cons * not or'.split() # Tags are just strings. + +Because they are formed of ``frozenset``, ``tuple`` and ``str`` objects +only, these datastructures are immutable. + +String Representation of RE Datastructures +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code:: ipython2 + + def stringy(re): + ''' + Return a nice string repr for a regular expression datastructure. + ''' + if re == I: return '.' + if re in syms: return next(iter(re)) + if re == y: return '^' + if re == phi: return 'X' + + assert isinstance(re, tuple), repr(re) + tag = re[0] + + if tag == KSTAR: + body = stringy(re[1]) + if not body: return body + if len(body) > 1: return '(' + body + ")*" + return body + '*' + + if tag == NOT: + body = stringy(re[1]) + if not body: return body + if len(body) > 1: return '(' + body + ")'" + return body + "'" + + r, s = stringy(re[1]), stringy(re[2]) + if tag == CONS: return r + s + if tag == OR: return '%s | %s' % (r, s) + if tag == AND: return '(%s) & (%s)' % (r, s) + + raise ValueError + +``I`` +~~~~~ + +Match anything. Often spelled "." + +:: + + I = (0|1)* + +.. code:: ipython2 + + I = (KSTAR, (OR, O, l)) + +.. code:: ipython2 + + print stringy(I) + + +.. parsed-literal:: + + . + + +``(.111.) & (.01 + 11*)'`` +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The example expression from Brzozowski: + +:: + + (.111.) & (.01 + 11*)' + a & (b + c)' + +Note that it contains one of everything. + +.. code:: ipython2 + + a = (CONS, I, (CONS, l, (CONS, l, (CONS, l, I)))) + b = (CONS, I, (CONS, O, l)) + c = (CONS, l, (KSTAR, l)) + it = (AND, a, (NOT, (OR, b, c))) + +.. code:: ipython2 + + print stringy(it) + + +.. parsed-literal:: + + (.111.) & ((.01 | 11*)') + + +``nully()`` +~~~~~~~~~~~ + +Let's get that auxiliary predicate function ``δ`` out of the way. + +.. code:: ipython2 + + def nully(R): + ''' + δ - Return λ if λ ⊆ R otherwise ϕ. + ''' + + # δ(a) → ϕ + # δ(ϕ) → ϕ + if R in syms or R == phi: + return phi + + # δ(λ) → λ + if R == y: + return y + + tag = R[0] + + # δ(R*) → λ + if tag == KSTAR: + return y + + # δ(¬R) δ(R)≟ϕ → λ + # δ(¬R) δ(R)≟λ → ϕ + if tag == NOT: + return phi if nully(R[1]) else y + + # δ(R∘S) → δ(R) ∧ δ(S) + # δ(R ∧ S) → δ(R) ∧ δ(S) + # δ(R ∨ S) → δ(R) ∨ δ(S) + r, s = nully(R[1]), nully(R[2]) + return r & s if tag in {AND, CONS} else r | s + +No "Compaction" +~~~~~~~~~~~~~~~ + +This is the straightforward version with no "compaction". It works fine, +but does waaaay too much work because the expressions grow each +derivation. + +.. code:: ipython2 + + # This is the straightforward version with no "compaction". + # It works fine, but does waaaay too much work because the + # expressions grow each derivation. + + def D(symbol): + + def derv(R): + + # ∂a(a) → λ + if R == {symbol}: + return y + + # ∂a(λ) → ϕ + # ∂a(ϕ) → ϕ + # ∂a(¬a) → ϕ + if R == y or R == phi or R in syms: + return phi + + tag = R[0] + + # ∂a(R*) → ∂a(R)∘R* + if tag == KSTAR: + return (CONS, derv(R[1]), R) + + # ∂a(¬R) → ¬∂a(R) + if tag == NOT: + return (NOT, derv(R[1])) + + r, s = R[1:] + + # ∂a(R∘S) → ∂a(R)∘S ∨ δ(R)∘∂a(S) + if tag == CONS: + A = (CONS, derv(r), s) # A = ∂a(R)∘S + # A ∨ δ(R) ∘ ∂a(S) + # A ∨ λ ∘ ∂a(S) → A ∨ ∂a(S) + # A ∨ ϕ ∘ ∂a(S) → A ∨ ϕ → A + return (OR, A, derv(s)) if nully(r) else A + + # ∂a(R ∧ S) → ∂a(R) ∧ ∂a(S) + # ∂a(R ∨ S) → ∂a(R) ∨ ∂a(S) + return (tag, derv(r), derv(s)) + + return derv + +Compaction Rules +~~~~~~~~~~~~~~~~ + +.. code:: ipython2 + + def _compaction_rule(relation, one, zero, a, b): + return ( + b if a == one else # R*1 = 1*R = R + a if b == one else + zero if a == zero or b == zero else # R*0 = 0*R = 0 + (relation, a, b) + ) + +An elegant symmetry. + +.. code:: ipython2 + + # R ∧ I = I ∧ R = R + # R ∧ ϕ = ϕ ∧ R = ϕ + _and = curry(_compaction_rule, AND, I, phi) + + # R ∨ ϕ = ϕ ∨ R = R + # R ∨ I = I ∨ R = I + _or = curry(_compaction_rule, OR, phi, I) + + # R∘λ = λ∘R = R + # R∘ϕ = ϕ∘R = ϕ + _cons = curry(_compaction_rule, CONS, y, phi) + +Memoizing +~~~~~~~~~ + +We can save re-processing by remembering results we have already +computed. RE datastructures are immutable and the ``derv()`` functions +are *pure* so this is fine. + +.. code:: ipython2 + + class Memo(object): + + def __init__(self, f): + self.f = f + self.calls = self.hits = 0 + self.mem = {} + + def __call__(self, key): + self.calls += 1 + try: + result = self.mem[key] + self.hits += 1 + except KeyError: + result = self.mem[key] = self.f(key) + return result + +With "Compaction" +~~~~~~~~~~~~~~~~~ + +This version uses the rules above to perform compaction. It keeps the +expressions from growing too large. + +.. code:: ipython2 + + def D_compaction(symbol): + + @Memo + def derv(R): + + # ∂a(a) → λ + if R == {symbol}: + return y + + # ∂a(λ) → ϕ + # ∂a(ϕ) → ϕ + # ∂a(¬a) → ϕ + if R == y or R == phi or R in syms: + return phi + + tag = R[0] + + # ∂a(R*) → ∂a(R)∘R* + if tag == KSTAR: + return _cons(derv(R[1]), R) + + # ∂a(¬R) → ¬∂a(R) + if tag == NOT: + return (NOT, derv(R[1])) + + r, s = R[1:] + + # ∂a(R∘S) → ∂a(R)∘S ∨ δ(R)∘∂a(S) + if tag == CONS: + A = _cons(derv(r), s) # A = ∂a(r)∘s + # A ∨ δ(R) ∘ ∂a(S) + # A ∨ λ ∘ ∂a(S) → A ∨ ∂a(S) + # A ∨ ϕ ∘ ∂a(S) → A ∨ ϕ → A + return _or(A, derv(s)) if nully(r) else A + + # ∂a(R ∧ S) → ∂a(R) ∧ ∂a(S) + # ∂a(R ∨ S) → ∂a(R) ∨ ∂a(S) + dr, ds = derv(r), derv(s) + return _and(dr, ds) if tag == AND else _or(dr, ds) + + return derv + +Let's try it out... +------------------- + +(FIXME: redo.) + +.. code:: ipython2 + + o, z = D_compaction('0'), D_compaction('1') + REs = set() + N = 5 + names = list(product(*(N * [(0, 1)]))) + dervs = list(product(*(N * [(o, z)]))) + for name, ds in zip(names, dervs): + R = it + ds = list(ds) + while ds: + R = ds.pop()(R) + if R == phi or R == I: + break + REs.add(R) + + print stringy(it) ; print + print o.hits, '/', o.calls + print z.hits, '/', z.calls + print + for s in sorted(map(stringy, REs), key=lambda n: (len(n), n)): + print s + + +.. parsed-literal:: + + (.111.) & ((.01 | 11*)') + + 92 / 122 + 92 / 122 + + (.01)' + (.01 | 1)' + (.01 | ^)' + (.01 | 1*)' + (.111.) & ((.01 | 1)') + (.111. | 11.) & ((.01 | ^)') + (.111. | 11. | 1.) & ((.01)') + (.111. | 11.) & ((.01 | 1*)') + (.111. | 11. | 1.) & ((.01 | 1*)') + + +Should match: + +:: + + (.111.) & ((.01 | 11*)') + + 92 / 122 + 92 / 122 + + (.01 )' + (.01 | 1 )' + (.01 | ^ )' + (.01 | 1*)' + (.111.) & ((.01 | 1 )') + (.111. | 11.) & ((.01 | ^ )') + (.111. | 11.) & ((.01 | 1*)') + (.111. | 11. | 1.) & ((.01 )') + (.111. | 11. | 1.) & ((.01 | 1*)') + +Larger Alphabets +---------------- + +We could parse larger alphabets by defining patterns for e.g. each byte +of the ASCII code. Or we can generalize this code. If you study the code +above you'll see that we never use the "set-ness" of the symbols ``O`` +and ``l``. The only time Python set operators (``&`` and ``|``) appear +is in the ``nully()`` function, and there they operate on (recursively +computed) outputs of that function, never ``O`` and ``l``. + +What if we try: + +:: + + (OR, O, l) + + ∂1((OR, O, l)) + ∂a(R ∨ S) → ∂a(R) ∨ ∂a(S) + ∂1(O) ∨ ∂1(l) + ∂a(¬a) → ϕ + ϕ ∨ ∂1(l) + ∂a(a) → λ + ϕ ∨ λ + ϕ ∨ R = R + λ + +And compare it to: + +:: + + {'0', '1') + + ∂1({'0', '1')) + ∂a(R ∨ S) → ∂a(R) ∨ ∂a(S) + ∂1({'0')) ∨ ∂1({'1')) + ∂a(¬a) → ϕ + ϕ ∨ ∂1({'1')) + ∂a(a) → λ + ϕ ∨ λ + ϕ ∨ R = R + λ + +This suggests that we should be able to alter the functions above to +detect sets and deal with them appropriately. Exercise for the Reader +for now. + +State Machine +------------- + +We can drive the regular expressions to flesh out the underlying state +machine transition table. + +:: + + .111. & (.01 + 11*)' + +Says, "Three or more 1's and not ending in 01 nor composed of all 1's." + +.. figure:: attachment:omg.svg + :alt: omg.svg + + omg.svg + +Start at ``a`` and follow the transition arrows according to their +labels. Accepting states have a double outline. (Graphic generated with +`Dot from Graphviz `__.) You'll see that only +paths that lead to one of the accepting states will match the regular +expression. All other paths will terminate at one of the non-accepting +states. + +There's a happy path to ``g`` along 111: + +:: + + a→c→e→g + +After you reach ``g`` you're stuck there eating 1's until you see a 0, +which takes you to the ``i→j→i|i→j→h→i`` "trap". You can't reach any +other states from those two loops. + +If you see a 0 before you see 111 you will reach ``b``, which forms +another "trap" with ``d`` and ``f``. The only way out is another happy +path along 111 to ``h``: + +:: + + b→d→f→h + +Once you have reached ``h`` you can see as many 1's or as many 0' in a +row and still be either still at ``h`` (for 1's) or move to ``i`` (for +0's). If you find yourself at ``i`` you can see as many 0's, or +repetitions of 10, as there are, but if you see just a 1 you move to +``j``. + +RE to FSM +~~~~~~~~~ + +So how do we get the state machine from the regular expression? + +It turns out that each RE is effectively a state, and each arrow points +to the derivative RE in respect to the arrow's symbol. + +If we label the initial RE ``a``, we can say: + +:: + + a --0--> ∂0(a) + a --1--> ∂1(a) + +And so on, each new unique RE is a new state in the FSM table. + +Here are the derived REs at each state: + +:: + + a = (.111.) & ((.01 | 11*)') + b = (.111.) & ((.01 | 1)') + c = (.111. | 11.) & ((.01 | 1*)') + d = (.111. | 11.) & ((.01 | ^)') + e = (.111. | 11. | 1.) & ((.01 | 1*)') + f = (.111. | 11. | 1.) & ((.01)') + g = (.01 | 1*)' + h = (.01)' + i = (.01 | 1)' + j = (.01 | ^)' + +You can see the one-way nature of the ``g`` state and the ``hij`` "trap" +in the way that the ``.111.`` on the left-hand side of the ``&`` +disappears once it has been matched. + +.. code:: ipython2 + + from collections import defaultdict + from pprint import pprint + from string import ascii_lowercase + +.. code:: ipython2 + + d0, d1 = D_compaction('0'), D_compaction('1') + +``explore()`` +~~~~~~~~~~~~~ + +.. code:: ipython2 + + def explore(re): + + # Don't have more than 26 states... + names = defaultdict(iter(ascii_lowercase).next) + + table, accepting = dict(), set() + + to_check = {re} + while to_check: + + re = to_check.pop() + state_name = names[re] + + if (state_name, 0) in table: + continue + + if nully(re): + accepting.add(state_name) + + o, i = d0(re), d1(re) + table[state_name, 0] = names[o] ; to_check.add(o) + table[state_name, 1] = names[i] ; to_check.add(i) + + return table, accepting + +.. code:: ipython2 + + table, accepting = explore(it) + table + + + + +.. parsed-literal:: + + {('a', 0): 'b', + ('a', 1): 'c', + ('b', 0): 'b', + ('b', 1): 'd', + ('c', 0): 'b', + ('c', 1): 'e', + ('d', 0): 'b', + ('d', 1): 'f', + ('e', 0): 'b', + ('e', 1): 'g', + ('f', 0): 'b', + ('f', 1): 'h', + ('g', 0): 'i', + ('g', 1): 'g', + ('h', 0): 'i', + ('h', 1): 'h', + ('i', 0): 'i', + ('i', 1): 'j', + ('j', 0): 'i', + ('j', 1): 'h'} + + + +.. code:: ipython2 + + accepting + + + + +.. parsed-literal:: + + {'h', 'i'} + + + +Generate Diagram +~~~~~~~~~~~~~~~~ + +Once we have the FSM table and the set of accepting states we can +generate the diagram above. + +.. code:: ipython2 + + _template = '''\ + digraph finite_state_machine { + rankdir=LR; + size="8,5" + node [shape = doublecircle]; %s; + node [shape = circle]; + %s + } + ''' + + def link(fr, nm, label): + return ' %s -> %s [ label = "%s" ];' % (fr, nm, label) + + + def make_graph(table, accepting): + return _template % ( + ' '.join(accepting), + '\n'.join( + link(from_, to, char) + for (from_, char), (to) in sorted(table.iteritems()) + ) + ) + +.. code:: ipython2 + + print make_graph(table, accepting) + + +.. parsed-literal:: + + digraph finite_state_machine { + rankdir=LR; + size="8,5" + node [shape = doublecircle]; i h; + node [shape = circle]; + a -> b [ label = "0" ]; + a -> c [ label = "1" ]; + b -> b [ label = "0" ]; + b -> d [ label = "1" ]; + c -> b [ label = "0" ]; + c -> e [ label = "1" ]; + d -> b [ label = "0" ]; + d -> f [ label = "1" ]; + e -> b [ label = "0" ]; + e -> g [ label = "1" ]; + f -> b [ label = "0" ]; + f -> h [ label = "1" ]; + g -> i [ label = "0" ]; + g -> g [ label = "1" ]; + h -> i [ label = "0" ]; + h -> h [ label = "1" ]; + i -> i [ label = "0" ]; + i -> j [ label = "1" ]; + j -> i [ label = "0" ]; + j -> h [ label = "1" ]; + } + + + +Drive a FSM +~~~~~~~~~~~ + +There are *lots* of FSM libraries already. Once you have the state +transition table they should all be straightforward to use. State +Machine code is very simple. Just for fun, here is an implementation in +Python that imitates what "compiled" FSM code might look like in an +"unrolled" form. Most FSM code uses a little driver loop and a table +datastructure, the code below instead acts like JMP instructions +("jump", or GOTO in higher-level-but-still-low-level languages) to +hard-code the information in the table into a little patch of branches. + +Trampoline Function +^^^^^^^^^^^^^^^^^^^ + +Python has no GOTO statement but we can fake it with a "trampoline" +function. + +.. code:: ipython2 + + def trampoline(input_, jump_from, accepting): + I = iter(input_) + while True: + try: + bounce_to = jump_from(I) + except StopIteration: + return jump_from in accepting + jump_from = bounce_to + +Stream Functions +^^^^^^^^^^^^^^^^ + +Little helpers to process the iterator of our data (a "stream" of "1" +and "0" characters, not bits.) + +.. code:: ipython2 + + getch = lambda I: int(next(I)) + + + def _1(I): + '''Loop on ones.''' + while getch(I): pass + + + def _0(I): + '''Loop on zeros.''' + while not getch(I): pass + +A Finite State Machine +^^^^^^^^^^^^^^^^^^^^^^ + +With those preliminaries out of the way, from the state table of +``.111. & (.01 + 11*)'`` we can immediately write down state machine +code. (You have to imagine that these are GOTO statements in C or +branches in assembly and that the state names are branch destination +labels.) + +.. code:: ipython2 + + a = lambda I: c if getch(I) else b + b = lambda I: _0(I) or d + c = lambda I: e if getch(I) else b + d = lambda I: f if getch(I) else b + e = lambda I: g if getch(I) else b + f = lambda I: h if getch(I) else b + g = lambda I: _1(I) or i + h = lambda I: _1(I) or i + i = lambda I: _0(I) or j + j = lambda I: h if getch(I) else i + +Note that the implementations of ``h`` and ``g`` are identical ergo +``h = g`` and we could eliminate one in the code but ``h`` is an +accepting state and ``g`` isn't. + +.. code:: ipython2 + + def acceptable(input_): + return trampoline(input_, a, {h, i}) + +.. code:: ipython2 + + for n in range(2**5): + s = bin(n)[2:] + print '%05s' % s, acceptable(s) + + +.. parsed-literal:: + + 0 False + 1 False + 10 False + 11 False + 100 False + 101 False + 110 False + 111 False + 1000 False + 1001 False + 1010 False + 1011 False + 1100 False + 1101 False + 1110 True + 1111 False + 10000 False + 10001 False + 10010 False + 10011 False + 10100 False + 10101 False + 10110 False + 10111 True + 11000 False + 11001 False + 11010 False + 11011 False + 11100 True + 11101 False + 11110 True + 11111 False + + +Reversing the Derivatives to Generate Matching Strings +------------------------------------------------------ + +(UNFINISHED) Brzozowski also shewed how to go from the state machine to +strings and expressions... + +Each of these states is just a name for a Brzozowskian RE, and so, other +than the initial state ``a``, they can can be described in terms of the +derivative-with-respect-to-N of some other state/RE: + +:: + + c = d1(a) + b = d0(a) + b = d0(c) + ... + i = d0(j) + j = d1(i) + +Consider: + +:: + + c = d1(a) + b = d0(c) + +Substituting: + +:: + + b = d0(d1(a)) + +Unwrapping: + +:: + + b = d10(a) + +''' + +:: + + j = d1(d0(j)) + +Unwrapping: + +:: + + j = d1(d0(j)) = d01(j) + +We have a loop or "fixed point". + +:: + + j = d01(j) = d0101(j) = d010101(j) = ... + +hmm... + +:: + + j = (01)* diff --git a/docs/Generator_Programs.html b/docs/Generator_Programs.html index a8d71e6..aac2fe9 100644 --- a/docs/Generator_Programs.html +++ b/docs/Generator_Programs.html @@ -12824,11 +12824,8 @@ fib_gen == [1 1 F]
-

Project Euler Problem Two

-
By considering the terms in the Fibonacci sequence whose values do not exceed four million,
-find the sum of the even-valued terms.
-
-
+

Project Euler Problem Two

By considering the terms in the Fibonacci sequence whose values do not exceed four million, find the sum of the even-valued terms.

+

Now that we have a generator for the Fibonacci sequence, we need a function that adds a term in the sequence to a sum if it is even, and pops it otherwise.

diff --git a/docs/Generator_Programs.ipynb b/docs/Generator_Programs.ipynb index dcb3f61..5cda477 100644 --- a/docs/Generator_Programs.ipynb +++ b/docs/Generator_Programs.ipynb @@ -731,8 +731,7 @@ "metadata": {}, "source": [ "## Project Euler Problem Two\n", - " By considering the terms in the Fibonacci sequence whose values do not exceed four million,\n", - " find the sum of the even-valued terms.\n", + "> By considering the terms in the Fibonacci sequence whose values do not exceed four million, find the sum of the even-valued terms.\n", "\n", "Now that we have a generator for the Fibonacci sequence, we need a function that adds a term in the sequence to a sum if it is even, and `pop`s it otherwise." ] diff --git a/docs/Generator_Programs.md b/docs/Generator_Programs.md index b10028c..ff7a4c2 100644 --- a/docs/Generator_Programs.md +++ b/docs/Generator_Programs.md @@ -363,8 +363,7 @@ J('fib_gen 10 [x] times') ## Project Euler Problem Two - By considering the terms in the Fibonacci sequence whose values do not exceed four million, - find the sum of the even-valued terms. +> By considering the terms in the Fibonacci sequence whose values do not exceed four million, find the sum of the even-valued terms. Now that we have a generator for the Fibonacci sequence, we need a function that adds a term in the sequence to a sum if it is even, and `pop`s it otherwise. diff --git a/docs/Generator_Programs.rst b/docs/Generator_Programs.rst index a201c0d..e518de6 100644 --- a/docs/Generator_Programs.rst +++ b/docs/Generator_Programs.rst @@ -468,10 +468,8 @@ Putting it all together: Project Euler Problem Two ------------------------- -:: - - By considering the terms in the Fibonacci sequence whose values do not exceed four million, - find the sum of the even-valued terms. + By considering the terms in the Fibonacci sequence whose values do + not exceed four million, find the sum of the even-valued terms. Now that we have a generator for the Fibonacci sequence, we need a function that adds a term in the sequence to a sum if it is even, and diff --git a/docs/Recursion_Combinators.html b/docs/Recursion_Combinators.html index 122cb59..c7264dc 100644 --- a/docs/Recursion_Combinators.html +++ b/docs/Recursion_Combinators.html @@ -11788,7 +11788,7 @@ div#notebook {
-

Recursive Combinators

This article describes the genrec combinator, how to use it, and several generic specializations.

+

Recursion Combinators

This article describes the genrec combinator, how to use it, and several generic specializations.

                      [if] [then] [rec1] [rec2] genrec
 ---------------------------------------------------------------------
diff --git a/docs/Recursion_Combinators.ipynb b/docs/Recursion_Combinators.ipynb
index f912ff2..262e702 100644
--- a/docs/Recursion_Combinators.ipynb
+++ b/docs/Recursion_Combinators.ipynb
@@ -13,7 +13,7 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "# Recursive Combinators\n",
+    "# Recursion Combinators\n",
     "\n",
     "This article describes the `genrec` combinator, how to use it, and several generic specializations.\n",
     "\n",
diff --git a/docs/Recursion_Combinators.md b/docs/Recursion_Combinators.md
index 97ae948..1ed976c 100644
--- a/docs/Recursion_Combinators.md
+++ b/docs/Recursion_Combinators.md
@@ -4,7 +4,7 @@
 from notebook_preamble import D, DefinitionWrapper, J, V, define
 ```
 
-# Recursive Combinators
+# Recursion Combinators
 
 This article describes the `genrec` combinator, how to use it, and several generic specializations.
 
diff --git a/docs/Recursion_Combinators.rst b/docs/Recursion_Combinators.rst
index b4dc1a7..b882803 100644
--- a/docs/Recursion_Combinators.rst
+++ b/docs/Recursion_Combinators.rst
@@ -3,7 +3,7 @@
 
     from notebook_preamble import D, DefinitionWrapper, J, V, define
 
-Recursive Combinators
+Recursion Combinators
 =====================
 
 This article describes the ``genrec`` combinator, how to use it, and
diff --git a/docs/The_Four_Operations.html b/docs/The_Four_Operations.html
index d9fd921..c2094ad 100644
--- a/docs/The_Four_Operations.html
+++ b/docs/The_Four_Operations.html
@@ -11980,25 +11980,9 @@ while == swap [nullary] cons dup dipd concat loop
                    ... a b
 
 
-

The cleave combinator expects a value and two quotes and it executes each quote in "separate universes" such that neither can affect the other, then it takes the first item from the stack in each universe and replaces the quotes with their respective results.

- -
-
- -
-
-
-
-

(I'm not sure why it was specified to take that value, I may make a combinator that does the same thing but without expecting a value.)

- -
cleavish  == unit cons pam uncons uncons pop
-
-[A] [B] cleavish
-[A] [B] unit cons pam uncons uncons pop
-[A] [[B]] cons pam uncons uncons pop
-[[A] [B]] pam uncons uncons pop
-[a b] uncons uncons pop
-a b
+

The cleave combinator expects a value and two quotes and it executes each quote in "separate universes" such that neither can affect the other, then it takes the first item from the stack in each universe and replaces the value and quotes with their respective results.

+

(I think this corresponds to the "fork" operator, the little upward-pointed triangle, that takes two functions A :: x -> a and B :: x -> b and returns a function F :: x -> (a, b), in Conal Elliott's "Compiling to Categories" paper, et. al.)

+

Just a thought, if you cleave two jobs and one requires more time to finish than the other you'd like to be able to assign resources accordingly so that they both finish at the same time.

@@ -12025,6 +12009,21 @@ a b
cleave == [i] app2 [popd] dip
+
+ + +
+
+
+
+

(I'm not sure why cleave was specified to take that value, I may make a combinator that does the same thing but without expecting a value.)

+ +
clv == [i] app2
+
+   [A] [B] clv
+------------------
+     a b
+
@@ -12064,10 +12063,30 @@ a b
-

Handling Other Kinds of Join

We can imagine a few different potentially useful patterns of "joining" results from parallel combinators.

+

Handling Other Kinds of Join

The cleave operators and others all have pretty brutal join semantics: everything works and we always wait for every sub-computation. We can imagine a few different potentially useful patterns of "joining" results from parallel combinators.

+ +
+
+ +
+
+
+

first-to-finish

Thinking about variations of pam there could be one that only returns the first result of the first-to-finish sub-program, or the stack could be replaced by its output stack.

The other sub-programs would be cancelled.

+
+
+
+
+
+
+
+

"Fulminators"

Also known as "Futures" or "Promises" (by everybody else. "Fulinators" is what I was going to call them when I was thinking about implementing them in Thun.)

+

The runtime could be amended to permit "thunks" representing the results of in-progress computations to be left on the stack and picked up by subsequent functions. These would themselves be able to leave behind more "thunks", the values of which depend on the eventual resolution of the values of the previous thunks.

+

In this way you can create "chains" (and more complex shapes) out of normal-looking code that consist of a kind of call-graph interspersed with "asyncronous" ... events?

+

In any case, until I can find a rigorous theory that shows that this sort of thing works perfectly in Joy code I'm not going to worry about it. (And I think the Categories can deal with it anyhow? Incremental evaluation, yeah?)

+
diff --git a/docs/The_Four_Operations.md b/docs/The_Four_Operations.md index 7e03d1b..6541d93 100644 --- a/docs/The_Four_Operations.md +++ b/docs/The_Four_Operations.md @@ -154,18 +154,11 @@ Joy has a few parallel combinators, the main one being `cleave`: --------------------------------------------------------- ... a b -The `cleave` combinator expects a value and two quotes and it executes each quote in "separate universes" such that neither can affect the other, then it takes the first item from the stack in each universe and replaces the quotes with their respective results. +The `cleave` combinator expects a value and two quotes and it executes each quote in "separate universes" such that neither can affect the other, then it takes the first item from the stack in each universe and replaces the value and quotes with their respective results. -(I'm not sure why it was specified to take that value, I may make a combinator that does the same thing but without expecting a value.) +(I think this corresponds to the "fork" operator, the little upward-pointed triangle, that takes two functions `A :: x -> a` and `B :: x -> b` and returns a function `F :: x -> (a, b)`, in Conal Elliott's "Compiling to Categories" paper, et. al.) - cleavish == unit cons pam uncons uncons pop - - [A] [B] cleavish - [A] [B] unit cons pam uncons uncons pop - [A] [[B]] cons pam uncons uncons pop - [[A] [B]] pam uncons uncons pop - [a b] uncons uncons pop - a b +Just a thought, if you `cleave` two jobs and one requires more time to finish than the other you'd like to be able to assign resources accordingly so that they both finish at the same time. ### "Apply" Functions @@ -186,6 +179,14 @@ Because the quoted program can be `i` we can define `cleave` in terms of `app2`: cleave == [i] app2 [popd] dip +(I'm not sure why `cleave` was specified to take that value, I may make a combinator that does the same thing but without expecting a value.) + + clv == [i] app2 + + [A] [B] clv + ------------------ + a b + ### `map` The common `map` function in Joy should also be though of as a *parallel* operator: @@ -208,10 +209,22 @@ This can be used to run any number of programs separately on the current stack a ### Handling Other Kinds of Join -We can imagine a few different potentially useful patterns of "joining" results from parallel combinators. +The `cleave` operators and others all have pretty brutal join semantics: everything works and we always wait for every sub-computation. We can imagine a few different potentially useful patterns of "joining" results from parallel combinators. #### first-to-finish Thinking about variations of `pam` there could be one that only returns the first result of the first-to-finish sub-program, or the stack could be replaced by its output stack. The other sub-programs would be cancelled. + +#### "Fulminators" + +Also known as "Futures" or "Promises" (by *everybody* else. "Fulinators" is what I was going to call them when I was thinking about implementing them in Thun.) + +The runtime could be amended to permit "thunks" representing the results of in-progress computations to be left on the stack and picked up by subsequent functions. These would themselves be able to leave behind more "thunks", the values of which depend on the eventual resolution of the values of the previous thunks. + +In this way you can create "chains" (and more complex shapes) out of normal-looking code that consist of a kind of call-graph interspersed with "asyncronous" ... events? + +In any case, until I can find a rigorous theory that shows that this sort of thing works perfectly in Joy code I'm not going to worry about it. (And I think the Categories can deal with it anyhow? Incremental evaluation, yeah?) + + diff --git a/docs/The_Four_Operations.rst b/docs/The_Four_Operations.rst index 572236e..f099eea 100644 --- a/docs/The_Four_Operations.rst +++ b/docs/The_Four_Operations.rst @@ -217,21 +217,16 @@ Joy has a few parallel combinators, the main one being ``cleave``: The ``cleave`` combinator expects a value and two quotes and it executes each quote in "separate universes" such that neither can affect the other, then it takes the first item from the stack in each universe and -replaces the quotes with their respective results. +replaces the value and quotes with their respective results. -(I'm not sure why it was specified to take that value, I may make a -combinator that does the same thing but without expecting a value.) +(I think this corresponds to the "fork" operator, the little +upward-pointed triangle, that takes two functions ``A :: x -> a`` and +``B :: x -> b`` and returns a function ``F :: x -> (a, b)``, in Conal +Elliott's "Compiling to Categories" paper, et. al.) -:: - - cleavish == unit cons pam uncons uncons pop - - [A] [B] cleavish - [A] [B] unit cons pam uncons uncons pop - [A] [[B]] cons pam uncons uncons pop - [[A] [B]] pam uncons uncons pop - [a b] uncons uncons pop - a b +Just a thought, if you ``cleave`` two jobs and one requires more time to +finish than the other you'd like to be able to assign resources +accordingly so that they both finish at the same time. "Apply" Functions ~~~~~~~~~~~~~~~~~ @@ -259,6 +254,18 @@ terms of ``app2``: cleave == [i] app2 [popd] dip +(I'm not sure why ``cleave`` was specified to take that value, I may +make a combinator that does the same thing but without expecting a +value.) + +:: + + clv == [i] app2 + + [A] [B] clv + ------------------ + a b + ``map`` ~~~~~~~ @@ -293,8 +300,10 @@ stack and combine their (first) outputs in a result list. Handling Other Kinds of Join ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -We can imagine a few different potentially useful patterns of "joining" -results from parallel combinators. +The ``cleave`` operators and others all have pretty brutal join +semantics: everything works and we always wait for every +sub-computation. We can imagine a few different potentially useful +patterns of "joining" results from parallel combinators. first-to-finish ^^^^^^^^^^^^^^^ @@ -304,3 +313,25 @@ returns the first result of the first-to-finish sub-program, or the stack could be replaced by its output stack. The other sub-programs would be cancelled. + +"Fulminators" +^^^^^^^^^^^^^ + +Also known as "Futures" or "Promises" (by *everybody* else. "Fulinators" +is what I was going to call them when I was thinking about implementing +them in Thun.) + +The runtime could be amended to permit "thunks" representing the results +of in-progress computations to be left on the stack and picked up by +subsequent functions. These would themselves be able to leave behind +more "thunks", the values of which depend on the eventual resolution of +the values of the previous thunks. + +In this way you can create "chains" (and more complex shapes) out of +normal-looking code that consist of a kind of call-graph interspersed +with "asyncronous" ... events? + +In any case, until I can find a rigorous theory that shows that this +sort of thing works perfectly in Joy code I'm not going to worry about +it. (And I think the Categories can deal with it anyhow? Incremental +evaluation, yeah?)