Wiki

root/cobra/trunk/Samples/BlindWatchMaker1.cobra

Revision 2339, 5.2 KB (checked in by Chuck.Esterbrook, 2 years ago)

Code cleanup.

  • Property svn:eol-style set to native
Line 
1"""
2BlindWatchMaker1.cobra
3
4The book is "The Blind Watchmaker" by Richard Dawkins. In Chapter 3,
5"Accumulating Small Changes", Dawkins distinguishes between "single-step
6selection" and "cumulative selection". This doc string is a poor substitute for
7reading the book, but here we go:
8
9Suppose you wish to randomly generate a desired line from Shakespeare:
10
11    Methinks it is like a weasel
12
13Using a "single-step selection", you would generate 28 fresh, random letters at
14a time like so:
15
16    Attempt 1: oufswgonxgjsqvfwlpmhhkhfcrxe
17    Attempt 2: eqnhydvqghckpyzrvayjepnvzbbd
18    Attempt 3: m qekjigucvn pqemcsjmqlbuo q
19    Attempt 4: dsiwcrdgrz fqtytgljqziixjtsw
20    Attempt 5: pplxjtccuzkilknocmtutsvjekrw
21    Attempt 6: jmmj hrbndxyqw iabepvvvx ydy
22    Attempt 7: vbs bgtimbidtmazqwdxtnjdfcap
23    Attempt 8: rqjceuhlmyxjcswztawjuwheugfh
24    Attempt 9: tpu gerxtg  eqnt uvarklegsvv
25
26...until you hit upon the desired line. The odds of success are astronomical.
27You would die before it happened.
28
29But using "cumulative selection", you would only *start* with 28 random letters.
30Thereafter, you reproduce the string with a slight mutation, evaluate its
31fitness with respect to the goal and choose between it or the original. The
32fittest survives and the weakest is cast aside. In this way, progress
33accumulates to reach the goal in seconds even though the micro-engineering was
34still random (mutating the string). That's because the macro-engineering was not
35(selection and reproduction).
36
37This Cobra program demonstrates the above.
38
39
40At the Lang.NET 2008 Conference (http://www.langnetsymposium.com/), I met
41Peli de Halleux from the Pex team at http://research.microsoft.com/pex/.
42We put this program through Pex and it automatically found legit errors in the
43code resulting in improvements to the contracts. Those additional contracts are
44marked "Pex" below.
45"""
46
47
48class BlindWatchMaker1
49
50    var _random = Random()
51
52    def main
53        goal = 'methinks it is like a weasel'
54        alphabet = 'abcdefghijklmnopqrstuvwxyz '
55        duration1 = .runCumulativeSelection(goal, alphabet)
56        duration2 = .runSingleStepSelection(goal, alphabet)
57        factor = duration2 / duration1
58        print 'Single step selection took [Math.round(factor)] times longer than cumulative selection and *still* failed.'
59
60    def runCumulativeSelection(goal as String, alphabet as String) as float
61        require
62            goal.length  # Pex
63            alphabet.length  # Pex
64        ensure
65            result > 0
66        body
67            print 'Cumulative Selection:'
68            start = DateTime.now
69            # wip stands for "work in progress"
70            wip = .randomString(goal.length, alphabet)
71            showAttempt = true
72            generation = 1
73            while true
74                score = .score(wip, goal)  # 0..100
75                if showAttempt
76                    print '    attempt [generation.toString.padLeft(4)] [wip] ==> [score]%'
77                if score >= 100
78                    break
79                mutantChar = alphabet[_random.next(alphabet.length)]
80                mutantIndex = _random.next(wip.length)
81                firstPart = wip[:mutantIndex]
82                secondPart = wip[mutantIndex+1:]
83                offspring = '[firstPart][mutantChar][secondPart]'
84                if .score(offspring, goal) > .score(wip, goal)
85                    # we have a new, improved string
86                    showAttempt = true
87                    wip = offspring
88                else
89                    # the offspring is no better than the parent (and possibly worse)
90                    showAttempt = false
91                generation += 1
92            duration = DateTime.now.subtract(start)
93            print '    duration: [duration]'
94            return duration.totalSeconds
95
96    def runSingleStepSelection(goal as String, alphabet as String) as float
97        require
98            goal.length
99            alphabet.length
100        ensure
101            result > 0
102        body
103            print 'Single Step Selection:'
104            start = DateTime.now
105            maxAttempts = 1_000_000
106            reportInterval = 100_000
107            maxScore = 0
108            for i in maxAttempts
109                s = .randomString(goal.length, alphabet)
110                if s == goal
111                    # this could theoretically happen, but never does
112                    print '    !!! SUCCESS !!!'
113                    print '    On attempt [i+1], a randomly generated string matched:'
114                    print '    [s]'
115                    break
116                if .score(s, goal) > maxScore
117                    maxScore = .score(s, goal)
118                if (i+1) % reportInterval == 0
119                    print '    [i+1]...'
120            if s <> goal
121                print '    Giving up after [i] attempts of single step selection.'
122                print '    Maximum score was [maxScore]%.'
123            duration = DateTime.now.subtract(start)
124            print '    duration: [duration]'
125            return duration.totalSeconds
126
127    def randomString(length as int, alphabet as String) as String
128        require
129            length > 0
130            alphabet <> ''
131        ensure
132            result.length == length
133        test
134            x = BlindWatchMaker1()
135            assert x.randomString(5, 'ab').length == 5
136            s = x.randomString(1000, 'a')
137            for c in s, assert c == c'a'
138        body
139            sb = StringBuilder()
140            for i in length
141                c = alphabet[_random.next(alphabet.length)]
142                sb.append(c)
143            return sb.toString
144
145    def score(candidate as String, goal as String) as int
146        """
147        Returns the score of the candidate string with regards to how closely it
148        matches the goal.
149        """
150        require
151            candidate.length  # Pex
152            goal.length  # Pex
153            candidate.length == goal.length
154        ensure
155            result >= 0 and result <= 100
156        test
157            bwcc = BlindWatchMaker1()
158            assert bwcc.score('aoeu', 'aoeu') == 100
159            assert bwcc.score('aabb', 'aacc') == 50
160            assert bwcc.score('a b ', 'axbx') == 50
161        body
162            score = 0
163            for i in goal.length
164                if candidate[i] == goal[i], score += 1
165            value = (score / goal.length * 100) to int
166            return value
Note: See TracBrowser for help on using the browser.