From f585b103c14abbf4b2d7e4c5f9e274e88f887ebc Mon Sep 17 00:00:00 2001 From: Anton Vakhrushev Date: Sun, 14 Apr 2019 21:20:59 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9A=D0=BE=D0=B4=20=D0=B3=D0=B0=D0=B4=D0=B0?= =?UTF-8?q?=D0=BB=D0=BA=D0=B8=20=D0=A8=D0=B5=D0=BD=D0=BD=D0=BE=D0=BD=D0=B0?= =?UTF-8?q?=20=D0=BF=D0=B5=D1=80=D0=B5=D0=BD=D0=B5=D1=81=D0=B5=D0=BD=20?= =?UTF-8?q?=D0=B2=20=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- npm-shrinkwrap.json | 32 +++++- package.json | 3 +- source/_articles/php-serialization.md | 4 +- source/_assets/predictor/Daemon.js | 145 +++++++++++++++++++++++++ source/_assets/predictor/Journal.js | 47 ++++++++ source/_assets/predictor/Move.js | 15 +++ source/_assets/predictor/Predictor.js | 90 +++++++++++++++ source/_assets/predictor/Supervisor.js | 102 +++++++++++++++++ source/_assets/predictor/demo.vue | 2 +- webpack.config.js | 1 + 10 files changed, 432 insertions(+), 9 deletions(-) create mode 100644 source/_assets/predictor/Daemon.js create mode 100644 source/_assets/predictor/Journal.js create mode 100644 source/_assets/predictor/Move.js create mode 100644 source/_assets/predictor/Predictor.js create mode 100644 source/_assets/predictor/Supervisor.js diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 41163f7..1759ea6 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -776,6 +776,12 @@ "integrity": "sha1-ytnK0RkbWtY0vzCuCHI5HgZHvpU=", "dev": true }, + "babel-plugin-syntax-class-properties": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz", + "integrity": "sha1-1+sjt5oxf4VDlixQW4J8fWysJ94=", + "dev": true + }, "babel-plugin-syntax-exponentiation-operator": { "version": "6.13.0", "resolved": "https://registry.npmjs.org/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz", @@ -799,6 +805,18 @@ "babel-runtime": "^6.22.0" } }, + "babel-plugin-transform-class-properties": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.24.1.tgz", + "integrity": "sha1-anl2PqYdM9NvN7YRqp3vgagbRqw=", + "dev": true, + "requires": { + "babel-helper-function-name": "^6.24.1", + "babel-plugin-syntax-class-properties": "^6.8.0", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, "babel-plugin-transform-es2015-arrow-functions": { "version": "6.22.0", "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz", @@ -1053,6 +1071,15 @@ "regenerator-transform": "^0.10.0" } }, + "babel-plugin-transform-runtime": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-runtime/-/babel-plugin-transform-runtime-6.23.0.tgz", + "integrity": "sha1-iEkNRGUC6puOfvsP4J7E2ZR5se4=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, "babel-plugin-transform-strict-mode": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz", @@ -6654,11 +6681,6 @@ } } }, - "predictor": { - "version": "git+https://github.com/anwinged/predictor.git#c7636b74d4a710b6874e703cd2a2d430c6aedf2e", - "from": "git+https://github.com/anwinged/predictor.git", - "dev": true - }, "prepend-http": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", diff --git a/package.json b/package.json index 3d5d9ef..64e50c7 100644 --- a/package.json +++ b/package.json @@ -8,13 +8,14 @@ "autoprefixer": "^8.6.5", "babel-core": "^6.26.3", "babel-loader": "^7.1.5", + "babel-plugin-transform-class-properties": "^6.24.1", + "babel-plugin-transform-runtime": "^6.23.0", "babel-preset-env": "^1.7.0", "css-loader": "^0.28.11", "glob": "^7.1.3", "mini-css-extract-plugin": "^0.4.5", "node-sass": "^4.11.0", "postcss-loader": "^2.1.6", - "predictor": "git+https://github.com/anwinged/predictor.git", "prettier": "^1.16.4", "sass-loader": "^7.1.0", "style-loader": "^0.21.0", diff --git a/source/_articles/php-serialization.md b/source/_articles/php-serialization.md index f67e709..8ccbf3c 100644 --- a/source/_articles/php-serialization.md +++ b/source/_articles/php-serialization.md @@ -9,7 +9,7 @@ keywords: [php, serialization] В PHP есть две функции для сериализации и десериализации данных: `serialize()` и `unserialize()`. Функции встроены в язык, не требуют дополнительных модулей. -В один момент кто-то решает использовать их для долговременного хранения объектов. +В один момент кто-то решает использовать их для долговременного хранения объектов. В базе данных, на диске, еще где-то. ``` @@ -38,5 +38,5 @@ O:16:"Test\Serialize\A":0:{}O:16:"Test\Serialize\A":0:{} Не делайте так. -Контролируйте процесс сериализации. Например используйте JSON и специальные +Контролируйте процесс сериализации. Например используйте JSON и специальные функции для превращения объекта в массив и обратно. diff --git a/source/_assets/predictor/Daemon.js b/source/_assets/predictor/Daemon.js new file mode 100644 index 0000000..f81deb2 --- /dev/null +++ b/source/_assets/predictor/Daemon.js @@ -0,0 +1,145 @@ +const DEFAULT_EPSILON = 0.01; + +/** + * @param {Number[]} steps + * + * @returns {String} + */ +function create_key(steps) { + return steps.join(':'); +} + +class Daemon { + /** + * @type {Number} + */ + base; + + /** + * @type {Number} + */ + humanCount; + + /** + * @type {Number} + */ + robotCount; + + /** + * @type {Number} + */ + epsilon; + + /** + * @type {Object} + */ + weights = {}; + + /** + * @param {Number} base + * @param {Number} humanCount + * @param {Number} robotCount + * @param {Number} epsilon + */ + constructor(base, humanCount, robotCount, epsilon = DEFAULT_EPSILON) { + this.base = base; + this.humanCount = humanCount; + this.robotCount = robotCount; + this.epsilon = epsilon; + } + + /** + * @returns {Number} + */ + get power() { + return this.humanCount + this.robotCount; + } + + /** + * @param {Journal} journal + * + * @returns {Number} + */ + predict(journal) { + const steps = this._getStepSlice(journal); + + const proposals = []; + for (let i = 0; i < this.base; ++i) { + proposals[i] = this._getWeight([...steps, i]); + } + + const maxWeight = Math.max(...proposals); + + return proposals.indexOf(maxWeight); + } + + /** + * @param {Journal} journal + * @param {Number} humanValue + */ + adjust(journal, humanValue) { + const steps = this._getStepSlice(journal); + const adjustmentWeight = this._getAdjustmentWeight(journal.length); + this._adjustWeight([...steps, humanValue], adjustmentWeight); + } + + /** + * @param {Journal} journal + * + * @returns {Number[]} + */ + _getStepSlice(journal) { + return journal.getLastMovements(this.humanCount, this.robotCount); + } + + /** + * @param {Number} stepNumber + * + * @returns {Number} + * + * @private + */ + _getAdjustmentWeight(stepNumber) { + return Math.pow(1 + this.epsilon, stepNumber); + } + + /** + * @param {Number[]} steps + * + * @returns {Number} + * + * @private + */ + _getWeight(steps) { + const key = create_key(steps); + const weight = this.weights[key]; + return weight === undefined ? 0 : weight; + } + + /** + * @param {Number[]} steps + * @param {Number} value + * + * @returns {Number} + * + * @private + */ + _setWeight(steps, value) { + const key = create_key(steps); + this.weights[key] = value; + } + + /** + * @param {Number[]} steps + * @param {Number} weight + * + * @private + */ + _adjustWeight(steps, weight) { + const currentWeight = this._getWeight(steps); + const newWeight = currentWeight + weight; + this._setWeight(steps, newWeight); + } +} + +export default Daemon; diff --git a/source/_assets/predictor/Journal.js b/source/_assets/predictor/Journal.js new file mode 100644 index 0000000..86584bd --- /dev/null +++ b/source/_assets/predictor/Journal.js @@ -0,0 +1,47 @@ +import Move from './Move'; + +class Journal { + /** + * @type {Move[]} + */ + moves = []; + + /** + * @param {Move[]} moves + */ + constructor(moves = []) { + this.moves = moves; + } + + /** + * @param {Number} human + * @param {Number} robot + */ + makeMove(human, robot) { + this.moves.push(new Move(human, robot)); + } + + /** + * @param {Number} humanCount + * @param {Number} robotCount + * + * @returns {Number[]} + */ + getLastMovements(humanCount, robotCount) { + const humanMoves = this.moves.map(m => m.human); + const robotMoves = this.moves.map(m => m.robot); + return [].concat( + robotMoves.slice(-robotCount), + humanMoves.slice(-humanCount) + ); + } + + /** + * @returns {Number} + */ + get length() { + return this.moves.length; + } +} + +export default Journal; diff --git a/source/_assets/predictor/Move.js b/source/_assets/predictor/Move.js new file mode 100644 index 0000000..292735c --- /dev/null +++ b/source/_assets/predictor/Move.js @@ -0,0 +1,15 @@ +/** + * Represents one game move. + */ +class Move { + /** + * @param {Number} human + * @param {Number} robot + */ + constructor(human, robot) { + this.human = human; + this.robot = robot; + } +} + +export default Move; diff --git a/source/_assets/predictor/Predictor.js b/source/_assets/predictor/Predictor.js new file mode 100644 index 0000000..fe3b397 --- /dev/null +++ b/source/_assets/predictor/Predictor.js @@ -0,0 +1,90 @@ +import Daemon from './Daemon'; +import Journal from './Journal'; +import Supervisor from './Supervisor'; + +const DEFAULT_CONFIG = { + base: 2, + supervisor_epsilon: 0.01, + daemons: [ + { human: 2, robot: 2, epsilon: 0.01 }, + { human: 3, robot: 3, epsilon: 0.01 }, + { human: 4, robot: 4, epsilon: 0.01 }, + { human: 5, robot: 5, epsilon: 0.01 }, + { human: 6, robot: 6, epsilon: 0.01 }, + ], +}; + +export default class Predictor { + /** + * @type {Number} + */ + base; + + /** + * @type {Number} + */ + score; + + /** + * @type {Journal} + */ + journal; + + /** + * @type {Supervisor} + */ + supervisor; + + /** + * @param {Object} config + */ + constructor(config = DEFAULT_CONFIG) { + this.base = config.base; + this.score = 0; + this.journal = new Journal(); + const daemons = this._createDaemons(config.daemons); + this.supervisor = new Supervisor(daemons, config.supervisor_epsilon); + } + + /** + * @param {Number|String} humanValue + * + * @returns {Number} + */ + pass(humanValue) { + const value = parseInt(humanValue, 10); + if (value < 0 || value >= this.base) { + throw new Error(`Passed value must be in [0, ${this.base})`); + } + const prediction = this.supervisor.predict(this.journal); + this.score += prediction === value ? -1 : 1; + this.supervisor.adjust(this.journal, value); + this.journal.makeMove(value, prediction); + return prediction; + } + + /** + * @param {Object} daemonConfigs + * + * @returns {Daemon[]} + * + * @private + */ + _createDaemons(daemonConfigs) { + return daemonConfigs.map(config => { + return new Daemon( + this.base, + config.human, + config.robot, + config.epsilon || 0.01 + ); + }); + } + + /** + * @returns {Number} + */ + stepCount() { + return this.journal.length; + } +} diff --git a/source/_assets/predictor/Supervisor.js b/source/_assets/predictor/Supervisor.js new file mode 100644 index 0000000..faaa0cd --- /dev/null +++ b/source/_assets/predictor/Supervisor.js @@ -0,0 +1,102 @@ +const DEFAULT_EPSILON = 0.01; + +class Supervisor { + /** + * @type {{daemon: Daemon, rate: Number}[]} + */ + daemons = []; + + /** + * @type {Number} + */ + epsilon; + + /** + * @param {Daemon[]} daemons + * @param {Number} epsilon + */ + constructor(daemons, epsilon = DEFAULT_EPSILON) { + if (!daemons || daemons.length === 0) { + throw Error('Empty daemon list'); + } + this.daemons = daemons.map(daemon => ({ + daemon: daemon, + rate: 0, + })); + this.epsilon = epsilon; + } + + /** + * @param {Journal} journal + * + * @returns {Number} + */ + predict(journal) { + const predictions = this._createPredictions(journal); + const ordered = this._sortPredictions(predictions); + + return ordered[0].value; + } + + /** + * @param {Journal} journal + * @param {Number} humanValue + */ + adjust(journal, humanValue) { + const predictions = this._createPredictions(journal); + for (const prediction of predictions) { + if (prediction.value === humanValue) { + prediction.daemon.rate += this._getAdjustmentWeight( + journal.length + ); + } + prediction.daemon.daemon.adjust(journal, humanValue); + } + } + + /** + * @param {Journal} journal + * + * @returns {Array} + * + * @private + */ + _createPredictions(journal) { + return this.daemons.map(daemon => ({ + daemon: daemon, + power: daemon.daemon.power, + rate: daemon.rate, + value: daemon.daemon.predict(journal), + })); + } + + /** + * @param {Array} predictions + * + * @returns {Array} + * + * @private + */ + _sortPredictions(predictions) { + return predictions.sort((result1, result2) => { + const rateDiff = result2.rate - result1.rate; + if (Math.abs(rateDiff) > 0.000001) { + return rateDiff; + } + return result1.power - result2.power; + }); + } + + /** + * @param {Number} stepNumber + * + * @returns {Number} + * + * @private + */ + _getAdjustmentWeight(stepNumber) { + return Math.pow(1 + this.epsilon, stepNumber); + } +} + +export default Supervisor; diff --git a/source/_assets/predictor/demo.vue b/source/_assets/predictor/demo.vue index 60a008e..e4b1256 100644 --- a/source/_assets/predictor/demo.vue +++ b/source/_assets/predictor/demo.vue @@ -39,7 +39,7 @@