Автор оригинала: FreeCodeCamp Community Member.
Давайте рассмотрим веселье, контр-интуитивное мир комбинатористов.
Сочетание значений для формирования наборов отдельных комбинаций может быть сложной вещью. Даже если вы игнорируете заказ, количество возможных множеств возрождается.
Для массива двух значений [1, 2] вы можете генерировать:
- [] (пустой набор)
- [1]
- [2]
- [1,2] (или [2,1])
Если повторы разрешены (например, [2, 2]), увеличение еще больше. По мере увеличения количества входных значений количество соответствующих наборов выходов стреляет через крышу !
Давайте назовем входные значения Предметы и каждая комбинация этих ценностей A выбор Отказ Кроме того, давайте разрешим нескольким элементам, каждый из которых с отличительным вариантом. Хороший рабочий пример будет меню. Мы смоделируем меню Ye Olde мороженое Shoppe , который предлагает своим клиентам комбинации мороженого, начинки и сироп вкусы.
Ароматы мороженого: Шоколад, клубника, ваниль
Начинки: Ананас, клубника, кокосовые хлопья, пекан
Сиропы: Шоколад, зефир, перевозки, клен
Есть некоторые ограничения на выбор: клиенты могут выбрать любой Два мороженое, Два начинки и один сироп. Выбор мороженого и топлива эксклюзивна, что означает, что я не могу выбрать ананас + ананас. Клиент может не иметь начинки и нет сиропа, но должен выбрать хотя бы одно мороженое. С этими ограничениями скорость увеличения экспоненциальна, порядка 2 к Nth Power, что значительно меньше, чем если порядок был значительным, и разрешены дубликаты.
Вкусность
Ye Olde мороженое Shoppe На самом деле довольно современно в своем подходе к бизнесу и разрабатывает экспертную систему искусственного интеллекта, чтобы судить, какие комбинации мороженого, доливания и сиропа вкусные. Серверы будут отображаться предупреждение о своих регистрах, когда клиент выбирает неприятный выбор. Затем серверы проинструктируются двойной проверки с клиентом, что их заказ правильный.
Шаг 1: Создание данных
Код для этой статьи можно найти здесь Отказ Я буду предполагать, что вы знакомы с JavaScript и Node.js. Рабочие знания о Лодаш (или подчеркивании) полезны. Код использует карту/уменьшить базу данных для хранения.
Первым шагом будет создание базы данных всех комбинаций мороженого, топинга и сирона. Входы будут следующими:
var menu = { iceCream: {min: 1, max: 2, values: ["CHOCOLATE", "STRAWBERRY", "VANILLA"]}, topping: {min: 0, max: 2, values: ["pineapple", "strawberry", "coconut flakes", "pecans"]}, syrup: {min:0, max: 1, values: ["chocolate", "marshmallow", "butterscotch", "maple"]} }
С помощью этих данных я могу написать Комбинатор Функция, которая принимает каждый элемент меню и генерирует все возможные разрешенные комбинации. Каждая комбинация хранится как массив. Например, комбинации мороженого будут выглядеть так:
[ [ 'CHOCOLATE', 'STRAWBERRY' ], [ 'CHOCOLATE', 'VANILLA' ], [ 'CHOCOLATE' ], [ 'STRAWBERRY', 'VANILLA' ], [ 'STRAWBERRY' ], [ 'VANILLA' ] ]
После того, как комбинации мороженого, начинки и сиропов определяются, все, что осталось, – это повторить сочетание каждого предмета с другими:
var allChoices = []; _.each(iceCreamChoices, function(ic) { _.each(toppingChoices, function(tp) { _.each(syrupChoices, function(sy) { allChoices.push([ic,tp,sy]); }) }) })
Это дает комбинацию мороженого (ы), топпинги (ы) и сиропа, как:
[ [ 'VANILLA' ], [ 'coconut flakes', 'pecans' ], [] ], [ [ 'VANILLA' ], [ 'coconut flakes' ], [ 'chocolate' ] ], [ [ 'VANILLA' ], [ 'coconut flakes' ], [ 'marshmallow' ] ],...
Выбор показан перевести как:
- Ванильное мороженое с кокосовыми хлопьями и пекан, без сиропа
- Ванильное мороженое с кокосовыми хлопьями и шоколадным сиропом
- Ванильное мороженое с кокосовыми хлопьями и сироп зефир
Даже только с несколькими ограниченными пунктами меню количество разрешенных вариантов 330!
Шаг 2: Хранение данных
С каждой комбинацией упорядоченных предметов теперь определяется дальнейшая работа. Система AI для определения комбинаций вкусных выбора превращается в комплекс и не будет встроен в операционную систему регистров. Вместо этого будет запрашивается запрос AJAX в серверное корпус программы AI. Входы будут варианты меню клиента, и вывод оценивает вкусному правдоподобному желанию этих выборов как один из: [Тьфу, Мех, вкусное, возвышенное]. Рейтинг вкусов тьфу триггеры вышеупомянутой предупреждением.
Нам нужен быстрый ответ на запрос, поэтому рейтинги вкусов будут кэшированы в базе данных. Учитывая природу экспоненциального увеличения, это может развиваться, чтобы стать большой проблемой данных, если в меню в будущем добавляется больше вариантов предметов.
Допустим, решается хранить комбинации и рейтинги по выбору в базе данных NoSQL. Использование Pouchdb, Каждый выбор и значение вкусов хранятся как документы JSON. А вторичный индекс (A.k.a. View ) С каждым выбором в качестве ключа позволит нам быстро посмотреть рейтинг вкусов. Вместо того, чтобы выталкивать данные в allchoices Массив, как показано выше в buildchoices.js Я могу нажимать документы JSON в базу данных для хранения.
Исходя из наивно, я могу сделать пару изменений в Steps1.js, чтобы прибыть на Step2.js: Прежде всего, мне нужно установить PUCHDB через NPM, а затем требуют его. Затем я создаю базу данных NoSQL под названием Выбор Отказ
var PouchDB = require('pouchdb'); var db = new PouchDB('choices');
Теперь каждый выбор размещен в базе данных вариантов:
var count = 0; _.each(iceCreamChoices, function(ic) { _.each(toppingChoices, function(tp) { _.each(syrupChoices, function(sy) { //allChoices.push([ic,tp,sy]); db.post({choice: [ic,tp,sy]}, function(err, doc){ if (err) console.error(err); else console.log(`stored ${++count}`); }); }) }) }); console.log('done??');
Это работает! Вроде, как бы, что-то вроде. Как можно выводить параметр обратного вызова в db.post Эта операция асинхронная. Что мы видим в журнале:
>node Step2.js done?? stored 1 stored 2 stored 3 ...
Таким образом, код говорит, что это сделано до того, как даже запись 1 была сохранена. Это будет проблемой, если у меня есть дальнейшая обработка, чтобы сделать против базы данных, и все записи еще нет там.
Шаг 3: Фиксация и переработка
Существует также более тонкая проблема: потенциальное истощение ресурсов. Если база данных ограничивает количество одновременных соединений, большое количество одновременных запросов постоблоки может привести к тому времени соединения.
Для Step3.js Я сделал немного исправления ошибок, переформатируя и рефакторинг того, что было написано на Step2.js. Одна ошибка заключалась в том, что каждый запуск добавил все больше и больше записей в базу данных, дублируя то, что было там раньше. Решение было уничтожение существующей базы данных, повторно создайте его, а затем запустить основную программу:
// remove old db.destroy(null, function () { db = new PouchDB('choices'); run(); });
Далее было добавить подходящий подсчет хранимых документов и последующих запросов в процессе, так что программа: 1) знает, когда последний документ хранится; 2) позволяет только пять постов действовать в любое время. Метод Run () выглядит так, как сейчас (с некоторыми упущениями):
function run() { var menu = { //... } var iceCreamChoices = new Combinator({ //... }); var toppingChoices = new Combinator({ //... }); var syrupChoices = new Combinator({ //... }); var count = 0; var total = iceCreamChoices.length * toppingChoices.length * syrupChoices.length; var postCount = 0; var postCountMax = 5; _.each(iceCreamChoices, function (ic) { _.each(toppingChoices, function (tp) { _.each(syrupChoices, function (sy) { var si = setInterval(() => { if (postCount < postCountMax) { clearInterval(si); postChoice(ic, tp, sy); } }, 10); }) }) }); function postChoice(ic, tp, sy) { ++postCount; db.post({ choice: [ic, tp, sy] }, function (err, doc) { --postCount; done(err); }); } function done(err) { if (err) { console.error(err); process.exit(1); } console.log(`stored ${++count}`); if (count === total) { console.log('done'); } } }
Основные изменения в примечании:
- А postcount отслеживает, сколько постов выдаются
- Интервал таймер проверяет postcount и будет опубликовать и выйти, когда будут доступны посты
- А сделано () обработчик называется, когда все варианты хранятся
Шаг 4: Добавление приманки
Со всеми возможными вариантами меню на место, теперь мы можем определить AI. AI – это просто макет в данный момент, который назначает случайные значения каждой записи документов в PUCCADB. Эти значения будут храниться в базе данных, обновляя каждый документ со вкусом рейтинга.
var _ = require('lodash'); var PouchDB = require('pouchdb'); var db = new PouchDB('choices'); db.allDocs({ include_docs: true }) .then(docs => { _.each(docs.rows, r => { r.doc.taste = palatability(); db.put(r.doc); }); }); function palatability() { var scale = Math.round(Math.random() * 10); var taste; switch (true) { // this switch is a horrible hack; don't ever do this ;-P case (scale < 2): taste = "ugh"; break; case (scale < 5): taste = "meh"; break; case (scale < 8): taste = "tasty"; break; default: taste = "sublime"; break; } return taste; }
Просто чтобы убедиться, что мы правильно хранили вещи, мы можем бросить документы в базу данных в консоль:
db.allDocs({ include_docs: true }) .then(docs => { _.each(docs.rows, r => { console.log(r.doc.choice, r.doc.taste) }); }); //output looks like: /* [ [ 'STRAWBERRY' ], [ 'coconut flakes' ], [ 'maple' ] ] 'sublime' [ [ 'CHOCOLATE' ], [ 'pecans' ], [ 'chocolate' ] ] 'tasty' [ [ 'CHOCOLATE', 'STRAWBERRY' ], [], [ 'chocolate' ] ] 'sublime' [ [ 'VANILLA' ], [], [ 'marshmallow' ] ] 'meh' [ [ 'CHOCOLATE', 'STRAWBERRY' ], [ 'pineapple' ], [ 'marshmallow' ] ] 'meh' */
Шаг 5: Глядя вверх
Документы находятся в базе данных, но теперь необходимо быть способом определить, что добруемость для выбора клиента. Это делается путем определения представления, которое является функцией, которая возвращает ключ для каждого документа наряду со значением. Что должен быть ключ?
Я мог бы использовать r.doc.Choice в качестве ключа, но массивы имеют заказ, и этот заказ может измениться, если пункты меню, определенные на шаге 1, были позже перегружены. Ключ является только идентификатором выбора выбора и не имеет собственного смысла семантического значения. Что должно работать – это:
- сглаживать каждый массив r.doc.choice,
- Закажите элементы в алфавитном порядке, затем
- объединить их вместе
- Результат является ключом
Если в будущем добавляется больше вариантов, длина ключей может быть более пределы, разрешенным базой данных. Вместо того, чтобы использовать ключ, как построенный, хеш ключ может использоваться в качестве реального ключа. HASH SHA256 в Hex составляет 64 символа, а также вероятность хеш-столкновения, даже для четырехстороннего выбора, по сути ноль. Написание хеш-функции для выбора легко, используя Node.js Крипто Модуль и ножная цепочка:
const crypto = require('crypto'); const _ = require('lodash') function hash(choice) { var str = _.chain(choice) .flatten() .sortBy() .join('|') .value(); return crypto.createHmac('sha256', 'old ice cream') .update(str) .digest('hex'); } module.exports = hash;
Добавление хеша в наших существующих документах – это простое значение iTerating через каждый документ базы данных, вычисляя его хеш и обновляя документ с ключом:
const _ = require('lodash'); const hash = require('./hash'); const PouchDB = require('pouchdb'); const db = new PouchDB('choices'); db.allDocs({ include_docs: true }) .then(docs => { _.each(docs.rows, r => { r.doc.key = hash(r.doc.choice); db.put(r.doc); }); }) .catch(e => { console.error(e) });
Далее представление базы данных построена с использованием поля ключа документа в качестве индекса; Я назову это выбор Отказ
const PouchDB = require('pouchdb'); const db = new PouchDB('choices'); // doc that defines the view var ddoc = { _id: '_design/choice', views: { by_key: { map: function (doc) { emit(doc.key, doc.taste); }.toString() } } }; // remove any existing view, then add new one: db.get(ddoc._id) .then(doc => { return db.remove(doc); }) .then(() => { db.put(ddoc) .catch(function (err) { console.error(err); }); });
Для любого ключа документа (хеш выбор выбора), я могу найти свой вкус через вид выбор. Теперь все на месте, чтобы определить, является ли выбор клиента Тьфу, Мех, вкусно, или возвышенный Отказ Чтобы проверить это, мы делаем некоторые случайные варианты и посмотрим, сможем найти вкус:
const choices = [ [['VANILLA'], ['coconut flakes', 'pecans'], ['marshmallow']], [['CHOCOLATE'], ['pecans'], ['chocolate']], [['STRAWBERRY', 'VANILLA'], ['pineapple', 'coconut flakes'], ['marshmallow']], [['STRAWBERRY'], ['pecans'], ['maple']], [['VANILLA'], ['coconut flakes', 'pineapple'], ['chocolate']], [['CHOCOLATE, STRAWBERRY'], ['pineapple', 'pecans'], ['butterscotch']], ]; const keys = _.map(choices, c => { return hash(c); }); db.query('choice/by_key', { keys: keys, include_docs: false, }, function (err, result) { if (err) { return console.error(err); } _.each(result.rows, (r, i) => { console.log(`${choices[i]} tastes ${r.value}`); }) });
Результаты:
=> node test VANILLA,coconut flakes,pecans,marshmallow tastes ugh CHOCOLATE,pecans,chocolate tastes sublime STRAWBERRY,VANILLA,pineapple,coconut flakes,marshmallow tastes tasty STRAWBERRY,pecans,maple tastes meh VANILLA,coconut flakes,pineapple,chocolate tastes sublime
Вот и все! Все, что осталось, это написать клиентское программное обеспечение, которое подает выбор через ajax и получает вкус (вкусное) значение обратно. Если это тьфу Затем поднимается предупреждение в реестре.
В последующем посте я усовершенствую алгоритм, используемый выше. Проверьте это !
использованная литература
Экспоненциальный рост не круто. Комбинаторное взрыв это. Столько технической промышленности одержима экспоненциальной ростом. Что-нибудь линейное умирает, или было мертвым в течение многих лет … www.torbair.com.
Комбинации и перестановки калькулятор Узнайте, сколько разных способов выберете элементы. Для глубокого объяснения формул, пожалуйста, посетите … www.mathsisfun.com.