0.2.1 • Published 9 months ago

@lrkit/weighted v0.2.1

Weekly downloads
-
License
MPL-2.0
Repository
github
Last release
9 months ago

Weighted

Weighted is a TypeScript project that provides utility functions for creating a weighted table. The table can be used to randomly select an item from a list of items where each item has a corresponding weight. Weights influence the likelihood that an item will be picked: a higher weight means a higher relative likelihood of being being picked.

ItemWeight
Apple0.75
Orange0.25

In the example above, you'd be 3 times more likely to get an Apple than you'd be to get an Orange when picking a random item from the list.

Installation

Weighted is scoped to lrkit. To install the package, run the following command:

npm i @lrkit/weighted

lrkit stands for light radius kit.

Usage

To use the package, import the weighted function to create a weighted table from a WeightedItem<T> list. The returned object can be used to randomly select an item from the list at will.

import { weighted } from "@lrkit/weighted";

const items: WeightedItem<T> = [
  {
    item: "a",
    weight: 3,
  },
  {
    item: "b",
    weight: 1,
  },
];

const table = weighted(items);

const result = table.pick(); // mostly "a", sometimes "b"

Table options

weighted accepts an options object as its second argument. Currently the only option available is seed, which allows you to set a seed for the random number generator used to pick items from the table. This is useful for testing purposes, or if you want to be able to reproduce the same results more than once for whatever reason.

const table = weighted(items, { seed: "my-seed" });

Pick options

The pick method itself also accepts an options object as its first argument. There are two options available: quantity and exclusive.

quantity allows you to pick more than one item from the table at once, while exclusive ensures that no item is picked more than once.

const items = [
  {
    item: "a",
    weight: 1,
  },
  {
    item: "b",
    weight: 1,
  },
  {
    item: "c",
    weight: 1,
  },
];

const table = weighted(items);

const result = table.pick({ quantity: 2 }); // ["a", "b"] or ["a", "c"] or ["b", "c"], but never ["a", "a"] or ["b", "b"] or ["c", "c"]

const result = table.pick({ quantity: 3, exclusive: true }); // guaranteed ["a", "b", "c"]

exclusive can only be used when quantity is also set, as it would be meaningless to use it for only one item.

Auxiliary functions

weighted also exports one auxiliary function called addWeight which you can use to add a weight property to any list of items. This is useful when you want to create a weighted table from a list of items that don't have a weight property, for example:

enum WEIGHTS { // remember, heavier items are more likely to be picked
  common = 3,
  uncommon = 2,
  rare = 1,
}

const weightedCommon = addWeight(commonItems, WEIGHTS.common);

const weightedUncommon = addWeight(uncommonItems, WEIGHTS.uncommon);

const weightedRare = addWeight(rareItems, WEIGHTS.rare);

const weightedItems = weighted([...weightedCommon, ...weightedUncommon, ...weightedRare]);

Note: addWeight attributes the same weight to every item passed as part of the first argument.

The WeightedItem type

Beforing using weighted to create a weighted table, you must first create a WeightedItem<T> list to pass as its first argument:

type WeightedItem<T> = { item: T; weight: number };

This shape offers some advantages over the alternatives. Let's go over a couple of possible alterantive implementations:

Items and weights as separate arguments

declare const createWeightedTable: <T>(items: T[], weights: number[]) => WeightedTable<T>;
  • Makes it easier to accidently pass mismatched lists, either because some weight is missing, or because the weight information is simply wrong, since the only way to match an item to its corresponding weight would be to match the elements at the same index on both arrays.
  • Much harder to make changes to large lists.
  • No type safety, you'll only notice missing or mismatched weights on runtime, or worse, never.

Record

type WeightedItem<T> = Record<[number, T]>;
  • Keys as items:
    • We'd be limiting ourselves tremendously in possible item types, and we want weighted to be as flexible as possible.
  • Keys as weights:
    • It's not immediately clear what that number is supposed to represent.
    • Extra gymnastics would be required to pick a value T out of Record<[number, T]> (extracting total weight for example). Not impossible, but still awkward.

WeightedItem

WeightedItem<T> provides type checking and makes it clear that every item requires a corresponding weight, keeping the weight property tightly coupled to its corresponding item and eliminating the risk of accidentally passing in an array of items without a corresponding weight.

const weightedItems = [{ item: "a" }];

const table = createWeightedTable(weightedItems); // Property 'weight' is missing in type '{ item: string; }' but required in type 'WeightedItem<string>'

It also guarantees that all your items must be of the same type, which, depending on your use case, might look like an advantage or a disadvantage, but if you ever see yourself needing different item types on a weighted table, consider creating more than one table instead.

Remember: a weighted table can return another weighted table!

Contributing

Contributions are welcome! If you find a bug or have a feature request, please open an issue on the GitHub repository. If you would like to contribute code, please fork the repository and submit a pull request.

License

This project is licensed under the MPL 2.0 License. See the LICENSE file for details.

0.2.1

9 months ago

0.2.0

9 months ago

0.1.0

11 months ago

0.0.2

11 months ago

0.0.1

11 months ago