An open API service indexing awesome lists of open source software.

https://github.com/bpolaszek/picker-js


https://github.com/bpolaszek/picker-js

Last synced: 7 months ago
JSON representation

Awesome Lists containing this project

README

          

# bentools-picker

A TypeScript library for weighted random item selection with a flexible and intuitive API.

## Features

- ๐ŸŽฒ 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

## Installation

```bash
npm install bentools-picker
```

## Usage

### Basic Usage

```typescript
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
```

### With Weights

You can set weights in three different ways:

#### 1. Using Array of Tuples

Best for both object and scalar values:

```typescript
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
]
});
```

#### 2. Using Record Object

Only available for scalar values (strings, numbers):

```typescript
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
}
});
```

#### 3. Using Method Chaining

Useful for setting weights dynamically:

```typescript
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
```

### With Item Removal

```typescript
// 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;
}
}
```

## Configuration Options

All options are optional with sensible defaults:

```typescript
interface PickerOptions {
// 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;
}
```

## Type Safety

The picker is fully type-safe:

```typescript
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
```

## API Reference

### `Picker`

The main class for weighted random selection.

#### Type Parameters

- `T` - The type of items to pick from. Can be either a scalar (number, string, etc.) or an object type.

#### Constructor

```typescript
constructor(items: T[], options: PickerOptions)
```

#### Options

```typescript
interface PickerOptions {
shift: boolean; // Remove picked items from the pool
errorIfEmpty: boolean; // Throw error on empty list
defaultWeight: number; // Default weight for items
weights?: Weights; // Optional weights definition (array of tuples or record object)
}

type Weights = Array<[T, Weight]> | Record;
```

#### Methods

##### `pick(): T | never`

Picks a random item based on weights. May throw `EmptyPickerError` if the list is empty and `errorIfEmpty` is true.

##### `setWeight(item: T, weight: number): this`

Sets the weight for a specific item. Returns the picker instance for method chaining.

```typescript
// 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);
```

### Errors

#### `EmptyPickerError`

Thrown when attempting to pick from an empty list with `errorIfEmpty: true`.

```typescript
try {
picker.pick();
} catch (error) {
if (error instanceof EmptyPickerError) {
// Handle empty list
}
}
```

## How Weights Work

The probability of an item being picked is proportional to its weight relative to the sum of all weights. For example:

```typescript
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)

## Best Practices

1. **Memory Management**: The library automatically uses `WeakMap` for objects and `Map` for scalar values internally.
2. **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
3. **Error Handling**: Always handle `EmptyPickerError` when `errorIfEmpty` is true.
4. **Weight Distribution**: Use relative weights that make sense for your use case.
5. **Type Safety**: Leverage TypeScript's type system by properly typing your items.

## Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

## License

MIT.