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]):
    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:
	print "** target length is:", length

	for i in xrange(config.haiku_line_attempts):
	    line = generate_line(length, last_line)
	    if line != None:
		break

	print "** resulting line is:", line
	if not line:
	    return None
	rv.append(line)
	last_line = line
    return rv