This commit is contained in:
nannal
2020-01-26 21:03:32 +02:00
commit 562b320154
700 changed files with 120537 additions and 0 deletions

2
node_modules/markov-chains/.agignore generated vendored Normal file
View File

@@ -0,0 +1,2 @@
dist
node_modules

10
node_modules/markov-chains/.babelrc generated vendored Normal file
View File

@@ -0,0 +1,10 @@
{
"presets": [
"es2015-native-generators"
],
"env": {
"test": {
"plugins": [ "babel-plugin-rewire" ]
}
}
}

28
node_modules/markov-chains/.eslintrc generated vendored Normal file
View File

@@ -0,0 +1,28 @@
{
"parser": "babel-eslint",
"extends": "airbnb/base",
"plugins": [
"babel"
],
"rules": {
"new-cap": [2, { "capIsNewExceptions": ["List", "IMap", "Repeat", "Range"]}],
"no-console": 0,
"no-param-reassign": [2, { "props": false }],
"no-use-before-define": [2, { "functions": false }],
"quote-props": [2, "consistent-as-needed"],
"indent": [2, 2, { "SwitchCase": 1 }],
"arrow-parens": 0,
"babel/arrow-parens": 1,
"object-shorthand": 0,
"babel/object-shorthand": 1,
"no-await-in-loop": 0,
"babel/no-await-in-loop": 1,
},
"globals": {
"Generator": false
}
}

1
node_modules/markov-chains/.npmignore generated vendored Normal file
View File

@@ -0,0 +1 @@
node_modules

49
node_modules/markov-chains/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,49 @@
The MIT License (MIT)
Copyright (c) 2016, Benjamin Chauvette
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-------------------------------------------------------------------------------
markov-chains is based on the Markovify library by Jeremy Singer-Vine, which
can be found at https://github.com/jsvine/markovify. Markovify uses the
following license:
The MIT License (MIT)
Copyright (c) 2015, Jeremy Singer-Vine
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

178
node_modules/markov-chains/README.md generated vendored Normal file
View File

@@ -0,0 +1,178 @@
## markov-chains
**A general purpose markov chain generator for Node and the browser**
[![npm version](https://badge.fury.io/js/markov-chains.svg)](https://badge.fury.io/js/markov-chains)
---
`markov-chains` is a simple, general purpose markov chain generator written in
JavaScript, and designed for both Node and the browser.
Unlike many markov chain generators in JavaScript, `markov-chains` can handle
and generate non-textual data just as easily as it handles text (see
[example](#example)).
## Table of Contents
- [Features](#features)
- [Example](#example)
- [Installation & Usage](#installation--usage)
- [API Reference](#api-reference)
- [Contributing](#contributing)
- [See Also](#see-also)
- [License](#license)
---
## Features
- Simple API that's easy to customize
- Chains can be serialized to and hydrated from JSON
[Back to Top ↑](#readme)
---
## Example
```js
import Chain from 'markov-chains';
// our states (an array of arrays)
const states = [
// week 1
[
{ temp: 'hot', weather: 'sunny' },
{ temp: 'hot', weather: 'cloudy' },
{ temp: 'warm', weather: 'cloudy' },
{ temp: 'warm', weather: 'cloudy' },
{ temp: 'warm', weather: 'rainy' },
{ temp: 'cool', weather: 'cloudy' },
{ temp: 'warm', weather: 'sunny' },
],
// week 2
[
{ temp: 'warm', weather: 'sunny' },
{ temp: 'warm', weather: 'cloudy' },
{ temp: 'warm', weather: 'cloudy' },
{ temp: 'warm', weather: 'sunny' },
{ temp: 'hot', weather: 'sunny' },
{ temp: 'hot', weather: 'sunny' },
{ temp: 'warm', weather: 'sunny' },
],
// etc.
];
// build the chain
const chain = new Chain(states);
// generate a forecast
const forecast = chain.walk();
console.log(forecast);
// Example output:
//
// [ { temp: 'warm', weather: 'sunny' },
// { temp: 'warm', weather: 'cloudy' },
// { temp: 'warm', weather: 'rainy' },
// { temp: 'cool', weather: 'cloudy' },
// { temp: 'warm', weather: 'sunny' } ]
```
[Back to Top ↑](#readme)
---
## Installation & Usage
### Requirements
`markov-chains` relies on [Maps][] and [Generators][], which are available
natively in Node v4.0 and above, and in modern versions of many browsers.
For a list of JavaScript environments that support these features, see the
[ECMAScript Compatability Table][].
[Maps]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
[Generators]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator
[ECMAScript Compatability Table]: http://kangax.github.io/compat-table/es6/
### Downloading
```sh
npm install --save markov-chains
```
### Usage (ES6+)
```js
import Chain from 'markov-chains';
const chain = new Chain(/* corpus: Array<Array<any>> */);
```
### Usage (CommonJS)
```js
var Chain = require('markov-chains').default;
var chain = new Chain(/* corpus: Array<Array<any>> */);
```
[Back to Top ↑](#readme)
---
## API Reference
```
Coming Soon
```
[Back to Top ↑](#readme)
---
## Contributing
Pull requests are always welcome!
### Building
The following `npm` scripts are available for use during development:
Command | Use to...
---------------------------|-----------
`npm run clean` | Remove the `dist/` files
`npm run lint` | Lint the files in `src/`
`npm run build` | Transpile the code with `babel`
### Tests
`markov-chains` uses [`tape`](https://github.com/substack/tape) for testing.
To run the tests, just run `npm test` at the command line.
[Back to Top ↑](#readme)
---
## See Also
- [`markovify`](https://github.com/jsvine/markovify) - The excellent python
library that inspired `markov-chains`
- [`markovchain`](https://www.npmjs.com/package/markovchain)
- [`general-markov`](https://github.com/swang/markovchain)
- [`markov`](https://github.com/substack/node-markov)
[Back to Top ↑](#readme)
---
## License
`markov-chains` is licensed under the MIT License.
For details, please see the [`LICENSE`](https://raw.githubusercontent.com/bdchauvette/markov-chains/master/LICENSE) file.
[Back to Top ↑](#readme)

382
node_modules/markov-chains/dist/markov-chains.js generated vendored Normal file
View File

@@ -0,0 +1,382 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _javascriptStringify = require('javascript-stringify');
var _javascriptStringify2 = _interopRequireDefault(_javascriptStringify);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
/**
* Constants used to pad states from the beginning and end of a corpus
*/
var BEGIN = '@@MARKOV_CHAIN_BEGIN';
var END = '@@MARKOV_CHAIN_END';
/**
* The default state size
*/
var DEFAULT_STATE_SIZE = 1;
// ============================================================================
/**
* A Markov chain representing processes that have both beginnings and ends.
* For example: Sentences.
*/
var Chain = function () {
function Chain(corpusOrModel) {
var _ref = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
var _ref$stateSize = _ref.stateSize;
var stateSize = _ref$stateSize === undefined ? DEFAULT_STATE_SIZE : _ref$stateSize;
_classCallCheck(this, Chain);
this.stateSize = stateSize;
if (corpusOrModel instanceof Map) {
this.model = corpusOrModel;
} else {
this.model = Chain.build(corpusOrModel, { stateSize: stateSize });
}
}
/**
* Creates a Map of Maps where the keys of the outer Map represent all
* possible states, and point to the inner Map. The inner Maps represent all
* possibilities for the 'next' item in the chain, along with the count of
* times it appears.
*/
_createClass(Chain, [{
key: 'toJSON',
/**
* Converts the model to a 2D array, which can then be serialized by
* JSON.stringify
*/
value: function toJSON() {
var serialized = [];
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = this.model[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var _step$value = _slicedToArray(_step.value, 2);
var state = _step$value[0];
var follow = _step$value[1];
serialized.push([state, [].concat(_toConsumableArray(follow))]);
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
return serialized;
}
/**
* Given a state, chooses the next item at random, with a bias towards next
* states with higher weights
*/
}, {
key: 'move',
value: function move(fromState) {
var stateKey = createStateKey(fromState);
var state = this.model.get(stateKey);
if (!state) {
return undefined;
}
var choices = [];
var weights = [];
state.forEach(function (follow) {
choices.push(follow.value);
weights.push(follow.count);
});
var cumulativeDistribution = weights.reduce(function (cumWeights, currWeight) {
var sum = last(cumWeights) || 0;
return [].concat(_toConsumableArray(cumWeights), [sum + currWeight]);
}, []);
var r = Math.random() * last(cumulativeDistribution);
var randomIndex = bisect(cumulativeDistribution, r);
var nextMove = choices[randomIndex];
return nextMove;
}
/**
* Generates successive items until the chain reaches the END state
*/
}, {
key: 'generate',
value: function* generate() {
var beginState = arguments.length <= 0 || arguments[0] === undefined ? createBeginState(this.stateSize) : arguments[0];
var state = beginState;
for (;;) {
var step = this.move(state);
if (step === undefined || step === END) {
break;
} else {
yield step;
state = [].concat(_toConsumableArray(state.slice(1)), [step]);
}
}
}
/**
* Performs a single run of the Markov model, optionally starting from the
* provided `beginState`
*/
}, {
key: 'walk',
value: function walk(beginState) {
var steps = [];
var _iteratorNormalCompletion2 = true;
var _didIteratorError2 = false;
var _iteratorError2 = undefined;
try {
for (var _iterator2 = this.generate(beginState)[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
var step = _step2.value;
steps.push(step);
}
} catch (err) {
_didIteratorError2 = true;
_iteratorError2 = err;
} finally {
try {
if (!_iteratorNormalCompletion2 && _iterator2.return) {
_iterator2.return();
}
} finally {
if (_didIteratorError2) {
throw _iteratorError2;
}
}
}
return steps;
}
}], [{
key: 'build',
value: function build(corpus) {
var _ref2 = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
var _ref2$stateSize = _ref2.stateSize;
var stateSize = _ref2$stateSize === undefined ? DEFAULT_STATE_SIZE : _ref2$stateSize;
if (!Array.isArray(corpus)) {
throw new Error('Corpus must be a List or an Array');
}
var model = new Map();
corpus.forEach(function (run) {
if (!Array.isArray(run)) {
throw new Error('Invalid run in corpus: Must be an array');
}
var paddedRun = [].concat(_toConsumableArray(createBeginState(stateSize)), _toConsumableArray(run), [END]);
// add one to original run size to account for END state
for (var ngramStart = 0; ngramStart < run.length + 1; ngramStart++) {
var ngramEnd = ngramStart + stateSize;
var stateKey = createStateKey(paddedRun.slice(ngramStart, ngramEnd));
var follow = paddedRun[ngramEnd];
var followKey = (0, _javascriptStringify2.default)(follow);
if (!model.has(stateKey)) {
model.set(stateKey, new Map());
}
var stateMap = model.get(stateKey);
if (!stateMap.has(followKey)) {
stateMap.set(followKey, { value: follow, count: 0 });
}
var followMap = stateMap.get(followKey);
followMap.count += 1;
}
});
return model;
}
/**
* Creates a Chain instance by hydrating the model from a JSON string
*/
}, {
key: 'fromJSON',
value: function fromJSON(jsonData) {
var getStateSize = function getStateSize(stateKey) {
return JSON.parse(stateKey).length;
};
var stateSize = void 0;
var states = JSON.parse(jsonData).map(function (_ref3) {
var _ref4 = _slicedToArray(_ref3, 2);
var stateKey = _ref4[0];
var follow = _ref4[1];
var currentStateSize = getStateSize(stateKey);
// Ensure that each state in the chain has a consistent size
if (!stateSize) {
stateSize = currentStateSize;
} else if (currentStateSize !== stateSize) {
throw new Error('Inconsistent state size. ' + ('Expected ' + stateSize + ' but got ' + currentStateSize + ' (' + stateKey + ').'));
}
var followMap = new Map();
// Clone the `followData` object so that the garbage collector doesn't
// keep the temporary hydrated states array laying around because the new
// chain references objects it contains.
var _iteratorNormalCompletion3 = true;
var _didIteratorError3 = false;
var _iteratorError3 = undefined;
try {
for (var _iterator3 = follow[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
var _step3$value = _slicedToArray(_step3.value, 2);
var followKey = _step3$value[0];
var followData = _step3$value[1];
followMap.set(followKey, Object.assign({}, followData));
}
} catch (err) {
_didIteratorError3 = true;
_iteratorError3 = err;
} finally {
try {
if (!_iteratorNormalCompletion3 && _iterator3.return) {
_iterator3.return();
}
} finally {
if (_didIteratorError3) {
throw _iteratorError3;
}
}
}
return [stateKey, followMap];
});
return new Chain(new Map(states), { stateSize: stateSize });
}
}]);
return Chain;
}();
// ============================================================================
/**
* Creates a state that can be used to look up transitions in the model
*/
exports.default = Chain;
function createStateKey(fromState) {
// When the `stateSize` is one, it can seem a bit silly to have to pass in an
// array with a single item. To make things simpler to use, we therefore
// convert any single, non-array argument to arrays.
var state = Array.isArray(fromState) ? fromState : [fromState];
// Using `JSON.stringify` here allows us to programmatically determine the
// original `stateSize` when we restore a chain from JSON. If we were to use
// `serialize`, the stateKey array would be surrounded by single quotes, and
// would therefore need to be parsed by `eval` in order to determine the
// original state size. Using JSON.parse is a lot safer than using `eval`.
return JSON.stringify(state.map(_javascriptStringify2.default));
}
/**
* Creates inital `BEGIN` states to use for padding at the beginning of runs
*/
function createBeginState(stateSize) {
var beginStates = new Array(stateSize);
for (var i = 0; i < stateSize; i++) {
beginStates[i] = BEGIN;
}
return beginStates;
}
/**
* Gets the last item in an array
*/
function last(arr) {
return arr[arr.length - 1];
}
/**
* A port of Python's `bisect.bisect_right`, similar to lodash's `sortedIndex`
*/
function bisect(list, num) {
var high = arguments.length <= 2 || arguments[2] === undefined ? list.length : arguments[2];
var currLow = 0;
var currHigh = high;
while (currLow < currHigh) {
var mid = Math.floor((currLow + currHigh) / 2);
if (num < list[mid]) {
currHigh = mid;
} else {
currLow = mid + 1;
}
}
return currLow;
}

36
node_modules/markov-chains/example.js generated vendored Normal file
View File

@@ -0,0 +1,36 @@
const Chain = require('markov-chains').default;
// our states (an array of arrays)
const states = [
// week 1
[
{ temp: 'hot', weather: 'sunny' },
{ temp: 'hot', weather: 'cloudy' },
{ temp: 'warm', weather: 'cloudy' },
{ temp: 'warm', weather: 'cloudy' },
{ temp: 'warm', weather: 'rainy' },
{ temp: 'cool', weather: 'cloudy' },
{ temp: 'warm', weather: 'sunny' },
],
// week 2
[
{ temp: 'warm', weather: 'sunny' },
{ temp: 'warm', weather: 'cloudy' },
{ temp: 'warm', weather: 'cloudy' },
{ temp: 'warm', weather: 'sunny' },
{ temp: 'hot', weather: 'sunny' },
{ temp: 'hot', weather: 'sunny' },
{ temp: 'warm', weather: 'sunny' },
],
// etc.
];
// build the chain
const chain = new Chain(states);
// generate a forecast
const forecast = chain.walk();
console.log(forecast);

79
node_modules/markov-chains/package.json generated vendored Normal file
View File

@@ -0,0 +1,79 @@
{
"_from": "markov-chains",
"_id": "markov-chains@1.0.2",
"_inBundle": false,
"_integrity": "sha1-SRBTZZgbi5EDCZBEE5graWovfBQ=",
"_location": "/markov-chains",
"_phantomChildren": {},
"_requested": {
"type": "tag",
"registry": true,
"raw": "markov-chains",
"name": "markov-chains",
"escapedName": "markov-chains",
"rawSpec": "",
"saveSpec": null,
"fetchSpec": "latest"
},
"_requiredBy": [
"#USER",
"/"
],
"_resolved": "https://registry.npmjs.org/markov-chains/-/markov-chains-1.0.2.tgz",
"_shasum": "49105365981b8b910309904413982b696a2f7c14",
"_spec": "markov-chains",
"_where": "/home/dabbott/dev/chainy",
"author": {
"name": "Ben Chauvette",
"email": "bdchauvette@gmail.com",
"url": "https://github.com/bdchauvette"
},
"bugs": {
"url": "https://github.com/bdchauvette/markov-chains/issues"
},
"bundleDependencies": false,
"dependencies": {
"javascript-stringify": "^1.1.0"
},
"deprecated": false,
"description": "A general purpose markov chain generator",
"devDependencies": {
"babel": "^6.5.2",
"babel-cli": "^6.6.5",
"babel-eslint": "^6.0.2",
"babel-plugin-rewire": "^1.0.0-rc-2",
"babel-preset-es2015-native-generators": "^6.6.0",
"babel-tape-runner": "^2.0.1",
"eslint": "^2.4.0",
"eslint-config-airbnb": "^6.2.0",
"eslint-plugin-babel": "^3.1.0",
"is-equal": "^1.5.1",
"rimraf": "^2.5.1",
"tape": "^4.5.1"
},
"homepage": "https://github.com/bdchauvette/markov-chains#readme",
"jsnext:main": "src/index.js",
"keywords": [
"markovify",
"markov",
"markov chain",
"omm"
],
"license": "MIT",
"main": "dist/markov-chains.js",
"name": "markov-chains",
"repository": {
"type": "git",
"url": "git://github.com/bdchauvette/markov-chains.git"
},
"scripts": {
"build": "babel src/index.js -o dist/markov-chains.js",
"clean": "rimraf dist/*",
"lint": "eslint src/",
"postversion": "git push && git push --tags",
"preversion": "npm test && npm run lint",
"test": "BABEL_ENV=test babel-tape-runner test/*.spec.js"
},
"tonicExampleFilename": "example.js",
"version": "1.0.2"
}

258
node_modules/markov-chains/src/index.js generated vendored Normal file
View File

@@ -0,0 +1,258 @@
import serialize from 'javascript-stringify';
/**
* Constants used to pad states from the beginning and end of a corpus
*/
const BEGIN = '@@MARKOV_CHAIN_BEGIN';
const END = '@@MARKOV_CHAIN_END';
/**
* The default state size
*/
const DEFAULT_STATE_SIZE = 1;
// ============================================================================
/**
* A Markov chain representing processes that have both beginnings and ends.
* For example: Sentences.
*/
export default class Chain {
constructor(
corpusOrModel,
{ stateSize = DEFAULT_STATE_SIZE } = {}
) {
this.stateSize = stateSize;
if (corpusOrModel instanceof Map) {
this.model = corpusOrModel;
} else {
this.model = Chain.build(corpusOrModel, { stateSize });
}
}
/**
* Creates a Map of Maps where the keys of the outer Map represent all
* possible states, and point to the inner Map. The inner Maps represent all
* possibilities for the 'next' item in the chain, along with the count of
* times it appears.
*/
static build(
corpus,
{ stateSize = DEFAULT_STATE_SIZE } = {}
) {
if (!Array.isArray(corpus)) {
throw new Error('Corpus must be a List or an Array');
}
const model = new Map();
corpus.forEach((run) => {
if (!Array.isArray(run)) {
throw new Error('Invalid run in corpus: Must be an array');
}
const paddedRun = [...createBeginState(stateSize), ...run, END];
// add one to original run size to account for END state
for (let ngramStart = 0; ngramStart < run.length + 1; ngramStart++) {
const ngramEnd = ngramStart + stateSize;
const stateKey = createStateKey(paddedRun.slice(ngramStart, ngramEnd));
const follow = paddedRun[ngramEnd];
const followKey = serialize(follow);
if (!model.has(stateKey)) {
model.set(stateKey, new Map());
}
const stateMap = model.get(stateKey);
if (!stateMap.has(followKey)) {
stateMap.set(followKey, { value: follow, count: 0 });
}
const followMap = stateMap.get(followKey);
followMap.count += 1;
}
});
return model;
}
/**
* Creates a Chain instance by hydrating the model from a JSON string
*/
static fromJSON(jsonData) {
const getStateSize = (stateKey) => JSON.parse(stateKey).length;
let stateSize;
const states = JSON.parse(jsonData).map(([stateKey, follow]) => {
const currentStateSize = getStateSize(stateKey);
// Ensure that each state in the chain has a consistent size
if (!stateSize) {
stateSize = currentStateSize;
} else if (currentStateSize !== stateSize) {
throw new Error(
'Inconsistent state size. ' +
`Expected ${stateSize} but got ${currentStateSize} (${stateKey}).`
);
}
const followMap = new Map();
// Clone the `followData` object so that the garbage collector doesn't
// keep the temporary hydrated states array laying around because the new
// chain references objects it contains.
for (const [followKey, followData] of follow) {
followMap.set(followKey, Object.assign({}, followData));
}
return [stateKey, followMap];
});
return new Chain(new Map(states), { stateSize });
}
/**
* Converts the model to a 2D array, which can then be serialized by
* JSON.stringify
*/
toJSON() {
const serialized = [];
for (const [state, follow] of this.model) {
serialized.push([state, [...follow]]);
}
return serialized;
}
/**
* Given a state, chooses the next item at random, with a bias towards next
* states with higher weights
*/
move(fromState) {
const stateKey = createStateKey(fromState);
const state = this.model.get(stateKey);
if (!state) {
return undefined;
}
const choices = [];
const weights = [];
state.forEach((follow) => {
choices.push(follow.value);
weights.push(follow.count);
});
const cumulativeDistribution = weights.reduce(
(cumWeights, currWeight) => {
const sum = last(cumWeights) || 0;
return [...cumWeights, (sum + currWeight)];
}, []);
const r = Math.random() * last(cumulativeDistribution);
const randomIndex = bisect(cumulativeDistribution, r);
const nextMove = choices[randomIndex];
return nextMove;
}
/**
* Generates successive items until the chain reaches the END state
*/
*generate(beginState = createBeginState(this.stateSize)) {
let state = beginState;
for (;;) {
const step = this.move(state);
if (step === undefined || step === END) {
break;
} else {
yield step;
state = [...state.slice(1), step];
}
}
}
/**
* Performs a single run of the Markov model, optionally starting from the
* provided `beginState`
*/
walk(beginState) {
const steps = [];
for (const step of this.generate(beginState)) {
steps.push(step);
}
return steps;
}
}
// ============================================================================
/**
* Creates a state that can be used to look up transitions in the model
*/
function createStateKey(fromState) {
// When the `stateSize` is one, it can seem a bit silly to have to pass in an
// array with a single item. To make things simpler to use, we therefore
// convert any single, non-array argument to arrays.
const state = (Array.isArray(fromState))
? fromState
: [fromState];
// Using `JSON.stringify` here allows us to programmatically determine the
// original `stateSize` when we restore a chain from JSON. If we were to use
// `serialize`, the stateKey array would be surrounded by single quotes, and
// would therefore need to be parsed by `eval` in order to determine the
// original state size. Using JSON.parse is a lot safer than using `eval`.
return JSON.stringify(state.map(serialize));
}
/**
* Creates inital `BEGIN` states to use for padding at the beginning of runs
*/
function createBeginState(stateSize) {
const beginStates = new Array(stateSize);
for (let i = 0; i < stateSize; i++) {
beginStates[i] = BEGIN;
}
return beginStates;
}
/**
* Gets the last item in an array
*/
function last(arr) {
return arr[arr.length - 1];
}
/**
* A port of Python's `bisect.bisect_right`, similar to lodash's `sortedIndex`
*/
function bisect(list, num, high = list.length) {
let currLow = 0;
let currHigh = high;
while (currLow < currHigh) {
const mid = Math.floor((currLow + currHigh) / 2);
if (num < list[mid]) {
currHigh = mid;
} else {
currLow = mid + 1;
}
}
return currLow;
}

196
node_modules/markov-chains/test/markov-chains.spec.js generated vendored Normal file
View File

@@ -0,0 +1,196 @@
import test from 'tape';
import isEqual from 'is-equal';
import serialize from 'javascript-stringify';
import Chain from '../src';
// private constants & helper functions (imported via babel-plugin-rewire)
const BEGIN = Chain.__get__('BEGIN');
const END = Chain.__get__('END');
const last = Chain.__get__('last');
const createStateKey = Chain.__get__('createStateKey');
// ============================================================================
const corpus = ['foo bar baz qux.', 'foo baz qux bar.'].map((str) => str.split(' '));
const mixedCorpus = [
[[1, 2, 3], { foo: 'bar' }, 'qux', 0, { end: true }],
[[1, 2, 3], { foo: 'baz' }, 'qux', 1, { end: true }],
[[1, 2, 3], { foo: 'bar' }, 'bar', 0, { end: true }],
[[4, 5, 6], { foo: 'baz' }, 'bar', 1, { end: true }],
];
test('building models from text corpora', (t) => {
t.plan(2);
const testModel = Chain.build(corpus, { stateSize: 2 });
const expectedModel = new Map([
[createStateKey([BEGIN, BEGIN]), new Map([[serialize('foo'), { value: 'foo', count: 2 }]])],
[createStateKey([BEGIN, 'foo']), new Map([
[serialize('bar'), { value: 'bar', count: 1 }],
[serialize('baz'), { value: 'baz', count: 1 }],
])],
[createStateKey(['foo', 'bar']), new Map([[serialize('baz'), { value: 'baz', count: 1 }]])],
[createStateKey(['bar', 'baz']), new Map([[serialize('qux.'), { value: 'qux.', count: 1 }]])],
[createStateKey(['baz', 'qux.']), new Map([[serialize(END), { value: END, count: 1 }]])],
[createStateKey(['foo', 'baz']), new Map([[serialize('qux'), { value: 'qux', count: 1 }]])],
[createStateKey(['baz', 'qux']), new Map([[serialize('bar.'), { value: 'bar.', count: 1 }]])],
[createStateKey(['qux', 'bar.']), new Map([[serialize(END), { value: END, count: 1 }]])],
]);
t.ok(
testModel instanceof Map,
'Should return an immutable hash map'
);
t.ok(
isEqual(testModel, expectedModel),
'Returned object should have expected key-value pairs'
);
});
// ============================================================================
test('serializing chains', (t) => {
t.plan(3);
const original = new Chain(corpus, { stateSize: 2 });
const serialized = JSON.stringify(original);
const hydrated = Chain.fromJSON(serialized);
t.ok(
typeof serialized === 'string',
'Chain should be able to be serialized by JSON.stringify'
);
t.equal(
hydrated.stateSize,
original.stateSize,
'Hydrated chain should have same state size as original chain'
);
t.ok(
isEqual(hydrated.model, original.model),
'Hydrated chain should be identical to original chain'
);
});
// ============================================================================
test('moving on chains (stateSize = 1)', (t) => {
t.plan(2);
const testChain = new Chain(corpus);
const expectedWords = ['bar', 'baz'];
const steps = [];
for (let i = 0; i < 255; i++) {
steps.push(testChain.move('foo'));
}
t.ok(
steps.every((step) => expectedWords.includes(step)),
'Should only contain possible follow steps'
);
// check whether each valid step was actually used. This has the potential to
// fail, but the chances of doing so are rather low (2 ** -255).
const wordCounts = steps.reduce((counts, word) => {
const wordCount = counts[word];
counts[word] = (wordCount) ? wordCount + 1 : 1;
return counts;
}, {});
t.ok(
expectedWords.every((word) => wordCounts[word]),
'Should use every expected word at least once'
);
});
// ============================================================================
test('moving on chains (stateSize = 2)', (t) => {
t.plan(2);
const testChain = new Chain(corpus, { stateSize: 2 });
const expectedWords = ['bar', 'baz'];
const steps = [];
for (let i = 0; i < 255; i++) {
steps.push(testChain.move([BEGIN, 'foo']));
}
t.ok(
steps.every((step) => expectedWords.includes(step)),
'Should only contain possible follow steps'
);
// check whether each valid step was actually used. This has the potential to
// fail, but the chances of doing so are rather low (2 ** -255).
const wordCounts = steps.reduce((counts, word) => {
const wordCount = counts[word];
counts[word] = (wordCount) ? wordCount + 1 : 1;
return counts;
}, {});
t.ok(
expectedWords.every((word) => wordCounts[word]),
'Should use every expected word at least once'
);
});
// ============================================================================
test('walking chains (string corpus)', (t) => {
t.plan(3);
const testChain = new Chain(corpus);
const walkResult = testChain.walk();
const firstItems = corpus.map((row) => row[0]);
const lastItems = corpus.map(last);
t.ok(
Array.isArray(walkResult),
'Walking should return an array'
);
t.ok(
firstItems.includes(walkResult[0]),
'First item should be a possible first item in corpus'
);
t.ok(
lastItems.includes(last(walkResult)),
'Last item should be a possible last item in corpus'
);
});
// ============================================================================
test('walking chains (mixed corpus)', (t) => {
t.plan(3);
const testChain = new Chain(mixedCorpus);
const walkResult = testChain.walk();
const firstItems = mixedCorpus.map((row) => row[0]);
const lastItems = mixedCorpus.map(last);
t.ok(
Array.isArray(walkResult),
'Walking should return an array'
);
t.ok(
firstItems.includes(walkResult[0]),
'First item should be a possible first item in corpus'
);
t.ok(
lastItems.includes(last(walkResult)),
'Last item should be a possible last item in corpus'
);
});