UNPKG

markovprocess

Version:

Markov Chain text generator, and generic markov chain

159 lines (146 loc) 3.81 kB
let fs; try { fs = require('fs'); } catch (e) { } const randomElem = arr=>arr[(~~(Math.random()*arr.length))]; class WeightedArray { constructor () { this.storage = []; this.weights = []; this.totalWeight = 0; } push(item, weight) { this.totalWeight += weight; this.weights.push(this.totalWeight); this.storage.push(item); } randomElem () { let randomPosition = Math.random()*this.totalWeight; let i = 0; while(true) { if (this.weights[i] > randomPosition && (this.weights[i-1] || 0) < randomPosition) { return this.storage[i]; } i++; } } } class Chain { constructor () { this.states = {}; } addState(state, next) { this.states[state] = this.states[state] ? this.states[state].concat(next) : [next]; } getNext(state) { return randomElem(this.states[state] || []); } getRandomStart () { return randomElem(Object.keys(this.states)); } } class State { constructor (value) { this.value = value this.possibilities = new WeightedArray(); this.isAbsoring = false; this.visitCount = 0; this._isAperiodic; } addPossibility(nextState, chance) { this.possibilities.push(nextState, chance); } addPossibilities(uniformChance, ...states) { for(let state of states) { this.addPossibility(state, uniformChance); } } get isAperiodic () { if (this._isAperiodic !== undefined) { return this._isAperiodic } else { let isAPeriodic = this.possibilities.includes(this); this._isAperiodic = isAperiodic; return isAperiodic; } } static absorbingState (value) { let absorbing = new this(value); absorbing.isAbsoring = true; return absorbing; } get next() { let next = this.isAbsoring ? this : this.possibilities.randomElem(); next.visitCount ++; return next; } } class MarkovChain { constructor (...states) { this.states = [...states]; this.current; } addState(state) { this.states.push(state); } randomStart() { this.current = randomElem(this.states); } next() { if (!this.current) { this.randomStart(); } else { this.current = this.current.next; } } } class MarkovGenerator { constructor (order = 1) { this.order = order; } iterateOverStates(grams, cb) { for (let i = 0; i < grams.length-this.order; i ++) { let ngram = grams.slice(i, i+this.order); let followingNGram = grams.slice(i+this.order, i + 2 * this.order); cb(ngram, followingNGram); } } //generates a markov test based on separator generate(text, length=text.length, separator="") { let chain = new Chain(); let chars = text.toLowerCase().split(separator); this.iterateOverStates(chars, (state, next) => { let ngram = state.join(separator); let nextNGram = next.join(separator); chain.addState(ngram, nextNGram); }); let result = chain.getRandomStart(); let current = result; if (this.order > 0) { if (typeof length === "number") { while(result.length < length) { current = chain.getNext(current) || chain.getRandomStart(); result += separator + current; } } else { while(true) { let next = chain.getNext(current); if (!next) return result; current = next; result += separator + current; } } } return result; } //generates a markov text based on characters generateChars(text, length=text.length) { return this.generate(text, length, ""); } //generates a markov text based on words generateWords(text, length=text.length) { return this.generate(text, length, " "); } } module.exports = {State, WeightedArray, MarkovGenerator, MarkovChain};