weightedrand

repository·main·Indexed 19 days ago

https://github.com/mroth/weightedrand

A high-performance Go library for weighted random selection, optimized for repeated sampling from the same distribution using a presorted cache and binary search. It supports concurrent usage across multiple CPU cores and requires Go 1.22 or greater for v3.

Tokens
1.4K
Snippets
6
Records
8
Agent score
15%

What's inside weightedrand

  1. Understand the performance characteristics of weightedrand

    main

    The weightedrand library is optimized for repeated selections from the same set of choices. It achieves this by creating a presorted cache that allows for fast binary search selection.

    When to use weightedrand vs randutil

    • Use weightedrand when you need to perform many repeated samplings from a large collection. It is significantly faster for repeated calls but incurs higher initialization time and memory usage.
    • Use github.com/jmcvetta/randutil if you are only performing a single selection from a distribution. It is optimized for the single-operation case.

    Parallelism

    Starting from v0.3.0, weightedrand can efficiently utilize a single Chooser across multiple CPU cores in parallel, increasing overall throughput for high-concurrency workloads.

  2. Perform weighted random selection with weightedrand

    main

    Use weightedrand to randomly select elements from a list where each element has a specific relative weight (probability).

    To use the library:

    1. Import github.com/mroth/weightedrand/v3.
    2. Create individual choices using weightedrand.NewChoice(value, weight).
    3. Initialize a Chooser using weightedrand.NewChooser(...) with your choices.
    4. Call chooser.Pick() to retrieve a selected element.

    Note that weights do not need to sum to 1; they are treated as relative probabilities. A weight of 0 means the element will never be selected.

    import (
        "fmt"
        "github.com/mroth/weightedrand/v3"
    )
    
    func main() {
        chooser, _ := weightedrand.NewChooser(
            weightedrand.NewChoice('🍒', 0),
            weightedrand.NewChoice('🍋', 1),
            weightedrand.NewChoice('🍊', 1),
            weightedrand.NewChoice('🍉', 3),
            weightedrand.NewChoice('🥑', 5),
        )
        // The following will print 🍋 and 🍊 with 0.1 probability, 🍉 with 0.3
        // probability, and 🥑 with 0.5 probability. 🍒 will never be printed.
        result := chooser.Pick()
        fmt.Println(result)
    }
  3. Reference: Chooser Errors

    main

    The following errors are returned by NewChooser to prevent unsafe runtime states or imbalanced distributions.

    var (
    	// If the sum of provided Choice weights exceed the maximum integer value
    	// for the current platform, then the internal running total will overflow.
    	errWeightOverflow = errors.New("sum of Choice Weights exceeds max int")
    
    	// If there are no Choices available to the Chooser with a weight >= 1,
    	// there are no valid choices and Pick would produce a runtime panic.
    	errNoValidChoices = errors.New("zero Choices with Weight >= 1")
    )
  4. Initialize a Chooser with NewChooser

    main

    The NewChooser function initializes a Chooser optimized for repeated weighted random selections. It sorts the provided choices by weight and builds a prefix sum array (totals) to enable efficient binary search during selection.

    Errors:

    • errWeightOverflow: Returned if the sum of all weights exceeds math.MaxUint64.
    • errNoValidChoices: Returned if no choices have a weight $\ge 1$.
    choices := []weightedrand.Choice[string, int]{
    	weightedrand.NewChoice("apple", 10),
    	weightedrand.NewChoice("banana", 20),
    	weightedrand.NewChoice("cherry", 70),
    }
    
    chooser, err := weightedrand.NewChooser(choices...)
    if err != nil {
    	// handle error
    }
  5. Pick a random item using PickWith

    main

    The PickWith method allows you to provide a specific *rand.Rand source for randomness. This is useful for reproducible results (e.g., using a seeded source) or when you want to manage your own randomness source.

    // Using a custom source
    src := rand.New(rand.NewPCG(1, 2))
    item := chooser.PickWith(src)