Blind Watch Maker 1 Sample
Blind Watch Maker 1
Download
Find Words
forth
Fractal Benchmark
Genetic Algorithm
Gtk Source Editor
Hex Dump
Notepad
Point
Shapes
Simple English Parser
Sizes
TPK
Word Count
"""
BlindWatchMaker1.cobra

The book is "The Blind Watchmaker" by Richard Dawkins. In Chapter 3,
"Accumulating Small Changes", Dawkins distinguishes between "single-step
selection" and "cumulative selection". This doc string is a poor substitute for
reading the book, but here we go:

Suppose you wish to randomly generate a desired line from Shakespeare:

    Methinks it is like a weasel

Using a "single-step selection", you would generate 28 fresh, random letters at
a time like so:

    Attempt 1: oufswgonxgjsqvfwlpmhhkhfcrxe
    Attempt 2: eqnhydvqghckpyzrvayjepnvzbbd
    Attempt 3: m qekjigucvn pqemcsjmqlbuo q
    Attempt 4: dsiwcrdgrz fqtytgljqziixjtsw
    Attempt 5: pplxjtccuzkilknocmtutsvjekrw
    Attempt 6: jmmj hrbndxyqw iabepvvvx ydy
    Attempt 7: vbs bgtimbidtmazqwdxtnjdfcap
    Attempt 8: rqjceuhlmyxjcswztawjuwheugfh
    Attempt 9: tpu gerxtg  eqnt uvarklegsvv

...until you hit upon the desired line. The odds of success are astronomical.
You would die before it happened.

But using "cumulative selection", you would only *start* with 28 random letters.
Thereafter, you reproduce the string with a slight mutation, evaluate its
fitness with respect to the goal and choose between it or the original. The
fittest survives and the weakest is cast aside. In this way, progress
accumulates to reach the goal in seconds even though the micro-engineering was
still random (mutating the string). That's because the macro-engineering was not
(selection and reproduction).

This Cobra program demonstrates the above.


At the Lang.NET 2008 Conference (http://www.langnetsymposium.com/), I met
Peli de Halleux from the Pex team at http://research.microsoft.com/pex/.
We put this program through Pex and it automatically found legit errors in the
code resulting in improvements to the contracts. Those additional contracts are
marked "Pex" below.
"""


class BlindWatchMaker1

    var _random = Random()

    def main
        goal = 'methinks it is like a weasel'
        alphabet = 'abcdefghijklmnopqrstuvwxyz '
        duration1 = .runCumulativeSelection(goal, alphabet)
        duration2 = .runSingleStepSelection(goal, alphabet)
        factor = duration2 / duration1
        print 'Single step selection took [Math.round(factor)] times longer than cumulative selection and *still* failed.'

    def runCumulativeSelection(goal as String, alphabet as String) as float
        require
            goal.length  # Pex
            alphabet.length  # Pex
        ensure
            result > 0
        body
            print 'Cumulative Selection:'
            start = DateTime.now
            # wip stands for "work in progress"
            wip = .randomString(goal.length, alphabet)
            showAttempt = true
            generation = 1
            while true
                score = .score(wip, goal)  # 0..100
                if showAttempt
                    print '    attempt [generation.toString.padLeft(4)] [wip] ==> [score]%'
                if score >= 100
                    break
                mutantChar = alphabet[_random.next(alphabet.length)]
                mutantIndex = _random.next(wip.length)
                firstPart = wip[:mutantIndex]
                secondPart = wip[mutantIndex+1:]
                offspring = '[firstPart][mutantChar][secondPart]'
                if .score(offspring, goal) > .score(wip, goal)
                    # we have a new, improved string
                    showAttempt = true
                    wip = offspring
                else
                    # the offspring is no better than the parent (and possibly worse)
                    showAttempt = false
                generation += 1
            duration = DateTime.now.subtract(start)
            print '    duration: [duration]'
            return duration.totalSeconds

    def runSingleStepSelection(goal as String, alphabet as String) as float
        require
            goal.length
            alphabet.length
        ensure
            result > 0
        body
            print 'Single Step Selection:'
            start = DateTime.now
            maxAttempts = 1_000_000
            reportInterval = 100_000
            maxScore = 0
            for i in maxAttempts
                s = .randomString(goal.length, alphabet)
                if s == goal
                    # this could theoretically happen, but never does
                    print '    !!! SUCCESS !!!'
                    print '    On attempt [i+1], a randomly generated string matched:'
                    print '    [s]'
                    break
                if .score(s, goal) > maxScore
                    maxScore = .score(s, goal)
                if (i+1) % reportInterval == 0
                    print '    [i+1]...'
            if s <> goal
                print '    Giving up after [i] attempts of single step selection.'
                print '    Maximum score was [maxScore]%.'
            duration = DateTime.now.subtract(start)
            print '    duration: [duration]'
            return duration.totalSeconds

    def randomString(length as int, alphabet as String) as String
        require
            length > 0
            alphabet <> ''
        ensure
            result.length == length
        test
            x = BlindWatchMaker1()
            assert x.randomString(5, 'ab').length == 5
            s = x.randomString(1000, 'a')
            for c in s, assert c == c'a'
        body
            sb = StringBuilder()
            for i in length
                c = alphabet[_random.next(alphabet.length)]
                sb.append(c)
            return sb.toString

    def score(candidate as String, goal as String) as int
        """
        Returns the score of the candidate string with regards to how closely it
        matches the goal.
        """
        require
            candidate.length  # Pex
            goal.length  # Pex
            candidate.length == goal.length
        ensure
            result >= 0 and result <= 100
        test
            bwcc = BlindWatchMaker1()
            assert bwcc.score('aoeu', 'aoeu') == 100
            assert bwcc.score('aabb', 'aacc') == 50
            assert bwcc.score('a b ', 'axbx') == 50
        body
            score = 0
            for i in goal.length
                if candidate[i] == goal[i], score += 1
            value = (score / goal.length * 100) to int
            return value