Below is the file 'forms.py' from this revision. You can also download the file.


import random
import config

def haiku(doc, form=[5,7,5]):
    random.seed()
    markov = doc.symbol_state.forward_markov

    def pickfrom(possible, total, get_total):
        k = random.randint(0, total - 1)
        for seq in possible:
            k -= get_total(seq)
            if k < 0:
                return seq
        raise Exception("Failed to pick a number - 'total' miscalcuation? (%d, %d)" % (k, total))

    def generate_line(target, state):
        state = tuple(state)
        line = []
        count = 0
        syl = doc.syllables.lookup

        if len(state) < markov.size:
            # we'll have to pick a starting point

            # this is the most accurate, but way too slow
            #
            #possible = filter(lambda seq: sum(map(syl, seq)) < form[0], markov.scores)
            #total_possible = sum(map(lambda seq: markov.scores[seq].total, possible))
            #seq = pickfrom(possible, total_possible, lambda seq: markov.scores[seq].total)
            #
            seq = pickfrom(markov.scores, markov.total, lambda seq: markov.scores[seq].total)
            count += sum(map(syl, seq))
            line += list(seq)
            state = seq
        elif len(state) > markov.size:
            state = state[-1*markov.size:]

        while count < target:
            maxsize = target - count
            score = markov.scores[state]

            # okay, if count + syl(next_token) != target then we need there
            # to be an entry in the symbolstate for that next potential
            # symbol. this lets us restrict further and not fall down holes
            # so often
            def is_not_deadend(id):
                next_count = count + syl(id)
                if next_count == target: return True
                next_state = (state + (id,))[1:]
                next_score = markov.scores.get(next_state)
                return next_score != None

            possible = set(filter(lambda id: syl(id) <= maxsize and is_not_deadend(id), score.scores))
#           print "status:", target, state, score.scores.keys(), possible
            total_possible = sum(map(lambda tok: score.scores[tok], possible))
            if not possible:
                break

            token = pickfrom(possible, total_possible, lambda seq: score.scores[seq])
            count += syl(token)
            state = (state + (token,))[1:]
            line.append(token)

        if count != target:
            return None
        else:
            return line

    rv = []
    last_line= []
    for length in form:
        for i in xrange(config.haiku_line_attempts):
            line = generate_line(length, last_line)
            if line != None:
                break
        if not line:
            return None
        rv.append(line)
        last_line = line
    return rv