markovprocess
Version:
Markov Chain text generator, and generic markov chain
159 lines (146 loc) • 3.81 kB
JavaScript
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};