Task: Markov chain text generator




Go: package main

import (
	"bufio"
	"flag"
	"fmt"
	"io"
	"log"
	"math/rand"
	"os"
	"strings"
	"time"
	"unicode"
	"unicode/utf8"
)

func main() {
	log.SetFlags(0)
	log.SetPrefix("markov: ")
	input := flag.String("in", "alice_oz.txt", "input file")
	n := flag.Int("n", 2, "number of words to use as prefix")
	runs := flag.Int("runs", 1, "number of runs to generate")
	wordsPerRun := flag.Int("words", 300, "number of words per run")
	startOnCapital := flag.Bool("capital", false, "start output with a capitalized prefix")
	stopAtSentence := flag.Bool("sentence", false, "end output at a sentence ending punctuation mark (after n words)")
	flag.Parse()

	rand.Seed(time.Now().UnixNano())

	m, err := NewMarkovFromFile(*input, *n)
	if err != nil {
		log.Fatal(err)
	}

	for i := 0; i < *runs; i++ {
		err = m.Output(os.Stdout, *wordsPerRun, *startOnCapital, *stopAtSentence)
		if err != nil {
			log.Fatal(err)
		}
		fmt.Println()
	}
}
















type Markov struct {
	n           int
	capitalized int 
	suffix      map[string][]string
}



func NewMarkovFromFile(filename string, n int) (*Markov, error) {
	f, err := os.Open(filename)
	if err != nil {
		return nil, err
	}
	defer f.Close() 
	return NewMarkov(f, n)
}



func NewMarkov(r io.Reader, n int) (*Markov, error) {
	m := &Markov{
		n:      n,
		suffix: make(map[string][]string),
	}
	sc := bufio.NewScanner(r)
	sc.Split(bufio.ScanWords)
	window := make([]string, 0, n)
	for sc.Scan() {
		word := sc.Text()
		if len(window) > 0 {
			prefix := strings.Join(window, " ")
			m.suffix[prefix] = append(m.suffix[prefix], word)
			
			if isCapitalized(prefix) {
				m.capitalized++
			}
		}
		window = appendMax(n, window, word)
	}
	if err := sc.Err(); err != nil {
		return nil, err
	}
	return m, nil
}





func (m *Markov) Output(w io.Writer, n int, startCapital, stopSentence bool) error {
	
	
	
	bw := bufio.NewWriter(w)

	var i int
	if startCapital {
		i = rand.Intn(m.capitalized)
	} else {
		i = rand.Intn(len(m.suffix))
	}
	var prefix string
	for prefix = range m.suffix {
		if startCapital && !isCapitalized(prefix) {
			continue
		}
		if i == 0 {
			break
		}
		i--
	}

	bw.WriteString(prefix) 
	prefixWords := strings.Fields(prefix)
	n -= len(prefixWords)

	for {
		suffixChoices := m.suffix[prefix]
		if len(suffixChoices) == 0 {
			break
		}
		i = rand.Intn(len(suffixChoices))
		suffix := suffixChoices[i]
		
		bw.WriteByte(' ') 
		if _, err := bw.WriteString(suffix); err != nil {
			break
		}
		n--
		if n < 0 && (!stopSentence || isSentenceEnd(suffix)) {
			break
		}

		prefixWords = appendMax(m.n, prefixWords, suffix)
		prefix = strings.Join(prefixWords, " ")
	}
	return bw.Flush()
}

func isCapitalized(s string) bool {
	
	
	
	r, _ := utf8.DecodeRuneInString(s)
	return unicode.IsUpper(r)
}

func isSentenceEnd(s string) bool {
	r, _ := utf8.DecodeLastRuneInString(s)
	
	
	
	return r == '.' || r == '?' || r == '!'
}

func appendMax(max int, slice []string, value string) []string {
	
	
	
	
	
	
	
	
	
	
	if len(slice)+1 > max {
		n := copy(slice, slice[1:])
		slice = slice[:n]
	}
	return append(slice, value)
}



PHP: <?php

function markovChainTextGenerator($text, $keySize, $maxWords) {

    $token = array();
    $position = 0;
    $maxPosition = strlen($text);
    while ($position < $maxPosition) {
        if (preg_match('/^(\S+)/', substr($text, $position, 25), $matches)) {
            $token[] = $matches[1];
            $position += strlen($matches[1]);
        }
        elseif (preg_match('/^(\s+)/', substr($text, $position, 25), $matches)) {
            $position += strlen($matches[1]);
        }
        else {
            die(
                'Unknown token found at position ' . $position . ' : ' . 
                substr($text, $position, 25) . '...' . PHP_EOL
            );
        }
    }

    $dictionary = array();
    for ($i = 0 ; $i < count($token) - $keySize ; $i++) {
        $prefix = '';
        $separator = '';
        for ($c = 0 ; $c < $keySize ; $c++) {
            $prefix .= $separator . $token[$i + $c];
            $separator = '.';
        }
        $dictionary[$prefix][] = $token[$i + $keySize];
    }

    $rand = rand(0, count($token) - $keySize);
    $startToken = array();
    for ($c = 0 ; $c < $keySize ; $c++) {
        array_push($startToken, $token[$rand + $c]);
    }

    $text = implode(' ', $startToken);
    $words = $keySize;
    do {
        $tokenKey = implode('.', $startToken);
        $rand = rand(0, count($dictionary[$tokenKey]) - 1);
        $newToken = $dictionary[$tokenKey][$rand];
        $text .= ' ' . $newToken;
        $words++;
        array_shift($startToken);
        array_push($startToken, $newToken);
    } while($words < $maxWords);
    return $text;

}

srand(5678);

$text = markovChainTextGenerator(
    file_get_contents(__DIR__ . '/inc/alice_oz.txt'),
    3,
    308
);

echo wordwrap($text, 100, PHP_EOL) . PHP_EOL;