Auto
This commit is contained in:
2
node_modules/markov-chains/.agignore
generated
vendored
Normal file
2
node_modules/markov-chains/.agignore
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
dist
|
||||
node_modules
|
10
node_modules/markov-chains/.babelrc
generated
vendored
Normal file
10
node_modules/markov-chains/.babelrc
generated
vendored
Normal 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
28
node_modules/markov-chains/.eslintrc
generated
vendored
Normal 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
1
node_modules/markov-chains/.npmignore
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
node_modules
|
49
node_modules/markov-chains/LICENSE
generated
vendored
Normal file
49
node_modules/markov-chains/LICENSE
generated
vendored
Normal 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
178
node_modules/markov-chains/README.md
generated
vendored
Normal file
@@ -0,0 +1,178 @@
|
||||
## markov-chains
|
||||
**A general purpose markov chain generator for Node and the browser**
|
||||
|
||||
[](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
382
node_modules/markov-chains/dist/markov-chains.js
generated
vendored
Normal 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
36
node_modules/markov-chains/example.js
generated
vendored
Normal 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
79
node_modules/markov-chains/package.json
generated
vendored
Normal 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
258
node_modules/markov-chains/src/index.js
generated
vendored
Normal 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
196
node_modules/markov-chains/test/markov-chains.spec.js
generated
vendored
Normal 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'
|
||||
);
|
||||
});
|
Reference in New Issue
Block a user