A TypeScript library for weighted random item selection with a flexible and intuitive API.
- 🎲 Weighted random selection
- 🔄 Optional item removal after selection
- ⚡ TypeScript support with full type safety
- 🎯 Configurable default weights
⚠️ Customizable empty list handling- 🧪 Thoroughly tested
- 📦 Support for both scalar and object values
npm install bentools-picker
import { Picker } from 'bentools-picker';
// Create a picker with default options
const picker = new Picker(['A', 'B', 'C']);
const item = picker.pick(); // Random item with equal weights
You can set weights in three different ways:
Best for both object and scalar values:
interface Item {
name: string;
}
const items = [
{ name: 'Common' },
{ name: 'Rare' },
{ name: 'Epic' },
{ name: 'Legendary' }
];
const picker = new Picker(items, {
weights: [
[items[0], 100], // Common: very high chance
[items[1], 50], // Rare: high chance
[items[2], 20], // Epic: medium chance
[items[3], 5] // Legendary: low chance
]
});
Only available for scalar values (strings, numbers):
const namePicker = new Picker(['Common', 'Rare', 'Epic', 'Legendary'], {
weights: {
'Common': 100, // Very high chance
'Rare': 50, // High chance
'Epic': 20, // Medium chance
'Legendary': 5 // Low chance
}
});
Useful for setting weights dynamically:
const chainedPicker = new Picker(items)
.setWeight(items[0], 100) // Common: very high chance
.setWeight(items[1], 50) // Rare: high chance
.setWeight(items[2], 20) // Epic: medium chance
.setWeight(items[3], 5); // Legendary: low chance
// Create a picker that removes items after picking
const consumablePicker = new Picker(items, { shift: true });
// Each pick removes the item from the pool
while (true) {
try {
const item = consumablePicker.pick();
console.log(item.name);
} catch (e) {
if (e.name === 'EmptyPickerError') {
console.log('No more items!');
break;
}
throw e;
}
}
All options are optional with sensible defaults:
interface PickerOptions<T> {
// Remove items after picking (default: false)
shift?: boolean;
// Throw error when picking from empty pool (default: true)
errorIfEmpty?: boolean;
// Default weight for items without specific weight (default: 1)
defaultWeight?: number;
// Optional weight definitions
weights?: Array<[T, number]> | Record<string | number, number>;
}
The picker is fully type-safe:
interface User {
id: number;
name: string;
}
const users: User[] = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
];
// TypeScript knows the picked item is of type User
const picker = new Picker(users);
const user = picker.pick(); // type: User
console.log(user.name); // TypeScript knows .name exists
The main class for weighted random selection.
T
- The type of items to pick from. Can be either a scalar (number, string, etc.) or an object type.
constructor(items: T[], options: PickerOptions<T>)
interface PickerOptions<T> {
shift: boolean; // Remove picked items from the pool
errorIfEmpty: boolean; // Throw error on empty list
defaultWeight: number; // Default weight for items
weights?: Weights<T>; // Optional weights definition (array of tuples or record object)
}
type Weights<T> = Array<[T, Weight]> | Record<string | number, Weight>;
Picks a random item based on weights. May throw EmptyPickerError
if the list is empty and errorIfEmpty
is true.
Sets the weight for a specific item. Returns the picker instance for method chaining.
// Set weights individually
picker.setWeight(items[0], 100);
// Or chain multiple calls
picker
.setWeight(items[0], 100)
.setWeight(items[1], 50)
.setWeight(items[2], 20);
Thrown when attempting to pick from an empty list with errorIfEmpty: true
.
try {
picker.pick();
} catch (error) {
if (error instanceof EmptyPickerError) {
// Handle empty list
}
}
The probability of an item being picked is proportional to its weight relative to the sum of all weights. For example:
const items = [1, 2, 3]; // weights: 100, 50, 25
In this case:
- 1 has a 57.14% chance (100/175)
- 2 has a 28.57% chance (50/175)
- 3 has a 14.29% chance (25/175)
- Memory Management: The library automatically uses
WeakMap
for objects andMap
for scalar values internally. - Weight Formats: Choose the most convenient weight format for your use case:
- Array of tuples: Best for type safety and IDE support
- Record object: Best for configuration files (remember to use
JSON.stringify
for object keys) setWeight
method: Best for dynamic weight updates
- Error Handling: Always handle
EmptyPickerError
whenerrorIfEmpty
is true. - Weight Distribution: Use relative weights that make sense for your use case.
- Type Safety: Leverage TypeScript's type system by properly typing your items.
Contributions are welcome! Please feel free to submit a Pull Request.
MIT.