@italia-tools/faker
Version:
Italian-specific fake data generator based on Faker.js
85 lines (82 loc) • 2.96 kB
JavaScript
'use strict';
class WeightedRandomSelector {
/**
* Creates a WeightedRandomSelector that respects actual weights
* @param items Array of items with weights
* @param isSorted boolean indicating if items are pre-sorted
*/
constructor(items, isSorted = false) {
this.SMALL_DATASET_THRESHOLD = 100;
this.totalWeight = 0;
if (items.length === 0) {
throw new Error("Items array cannot be empty");
}
this.isSmallDataset = items.length <= this.SMALL_DATASET_THRESHOLD;
if (this.isSmallDataset) {
this.initializeSmallDataset(items);
}
else {
this.initializeLargeDataset(items, isSorted);
}
}
initializeSmallDataset(items) {
this.fastAccessArray = [];
items.forEach(item => {
// For small datasets, we keep direct representation
this.fastAccessArray.push(...Array(item.weight).fill(item.value));
});
}
/**
* Initialization for large datasets using cumulative weights
*/
initializeLargeDataset(items, isSorted) {
this.items = [];
let accumulatedWeight = 0;
// If not sorted, sort by descending weight to optimize search
const workingItems = isSorted ? items : [...items].sort((a, b) => b.weight - a.weight);
workingItems.forEach(item => {
accumulatedWeight += item.weight;
this.items.push({
value: item.value,
weight: item.weight,
accumulatedWeight
});
});
this.totalWeight = accumulatedWeight;
}
/**
* Returns a random item respecting actual weights
*/
select() {
if (this.isSmallDataset && this.fastAccessArray) {
return this.fastAccessArray[Math.floor(Math.random() * this.fastAccessArray.length)];
}
const randomWeight = Math.random() * this.totalWeight;
// Binary search to find element with correct cumulative weight
return this.binarySearch(randomWeight);
}
binarySearch(targetWeight) {
let left = 0;
let right = this.items.length - 1;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
const item = this.items[mid];
if (mid === 0 || (this.items[mid - 1].accumulatedWeight <= targetWeight &&
item.accumulatedWeight > targetWeight)) {
return item.value;
}
if (item.accumulatedWeight <= targetWeight) {
left = mid + 1;
}
else {
right = mid - 1;
}
}
// Fallback to first element (this should never happen)
return this.items[0].value;
}
selectMultiple(count) {
return Array.from({ length: count }, () => this.select());
}
}
exports.WeightedRandomSelector = WeightedRandomSelector;