/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { pathToFileURL } from 'node:url'; import l from 'lodash'; export default create; // naive implementation of selection with replacement function create(list: T[]): () => [number, T] { const dist = l.reduce( list, (acc: number[], el, i) => { for (let j = 0; j < el.weight * 100; j++) { acc.push(i); } return acc; }, [] ); return () => { const i = dist[l.random(0, dist.length - 1)]; return [i, list[i]]; }; } function bench() { const items = [ { weight: 0, value: 'zero' }, { weight: 1, value: 'a' }, { weight: 2, value: 'b' }, { weight: 3, value: 'c' }, { weight: 4, value: 'd' }, { weight: 5, value: 'e' }, { weight: 1, value: 'f' }, { weight: 2, value: 'g' }, { weight: 3, value: 'h' }, { weight: 4, value: 'i' }, { weight: 5, value: 'j' }, { weight: 10, value: 'k' } ]; const picker = create(items); const ITERS = 10 ** 6; const startedAt = Date.now(); const picks = l.map(l.range(ITERS), () => { const x = picker()[1]; return x; }); const delta = Date.now() - startedAt; console.log( 'ITERS = %s\ndelta = %s\nITERS/delta = %s', ITERS, delta, Math.round(ITERS / delta) ); const sumWeights = l.reduce(items, (acc, item) => acc + item.weight, 0); console.log('sumWeights = %s', sumWeights); l.each(items, (p) => { const count = l.filter(picks, { value: p.value }).length; console.log( 'Count of %s = %s (should be: %s%, is: %s%)', p.value, count, Math.round((p.weight / sumWeights) * 1000) / 1000, Math.round((count / ITERS) * 1000) / 1000 ); }); } if ( process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href ) { bench(); }