PHP code example of niktomo / weighted-sample

1. Go to this page and download the library: Download niktomo/weighted-sample library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

niktomo / weighted-sample example snippets


use WeightedSample\Pool\WeightedPool;

// Associative array — any key names work
$pool = WeightedPool::of(
    [
        ['name' => 'SSR', 'weight' => 1],
        ['name' => 'SR',  'weight' => 9],
        ['name' => 'R',   'weight' => 90],
    ],
    static fn (array $item): int => $item['weight'],
);

$result = $pool->draw(); // returns one item — 90% chance of R, 9% SR, 1% SSR

use WeightedSample\Pool\WeightedPool;

// Object with a method
$pool = WeightedPool::of($prizes, static fn (Prize $p): int => $p->rarityWeight());

// Scalar — item IS the weight
$pool = WeightedPool::of([10, 30, 60], static fn (int $w): int => $w);

use WeightedSample\Pool\BoxPool;

// static fn (array $item): int => weight,  static fn (array $item): int => stock
$pool = BoxPool::of(
    $items,
    static fn (array $item): int => $item['weight'],  // draw probability
    static fn (array $item): int => $item['stock'],   // how many times it can be drawn
);

use WeightedSample\Pool\BoxPool;

$items = [
    ['name' => 'A', 'weight' => 10, 'stock' =>  1],
    ['name' => 'B', 'weight' => 20, 'stock' =>  3],
    ['name' => 'C', 'weight' => 20, 'stock' =>  3],
    ['name' => 'D', 'weight' => 20, 'stock' =>  3],
    ['name' => 'E', 'weight' => 30, 'stock' =>  5],
    ['name' => 'F', 'weight' => 40, 'stock' => 10],
    ['name' => 'G', 'weight' => 30, 'stock' =>  5],
    ['name' => 'H', 'weight' => 30, 'stock' =>  5],
    ['name' => 'I', 'weight' => 40, 'stock' => 10],
    ['name' => 'J', 'weight' => 40, 'stock' => 10],
];

$newBox = static fn (): BoxPool => BoxPool::of(
    $items,
    static fn (array $item): int => $item['weight'],
    static fn (array $item): int => $item['stock'],
);

// The box holds 55 prizes. A player draws 50 at a time, 3 rounds.
//
// Round 1 (draws  1– 50): 50 succeed — 5 remain in the current box
// Round 2 (draws 51– 55): box empties after 5 draws — a fresh box is opened (55 prizes)
//          (draws 56–100): 45 draws from the new box — 10 remain
// Round 3 (draws 101–110): box empties after 10 draws — a fresh box is opened (55 prizes)
//          (draws 111–150): 40 draws from the new box — 15 remain

$pool = $newBox();

for ($round = 1; $round <= 3; $round++) {
    $drawn = [];
    for ($i = 0; $i < 50; $i++) {
        if ($pool->isEmpty()) {
            $pool = $newBox(); // open a fresh box and continue drawing
        }
        $drawn[] = $pool->draw();
    }
    // process $drawn for this round
}

use WeightedSample\Pool\BoxPool;

$pool = BoxPool::of($items, static fn (array $i): int => $i['weight'], static fn (array $i): int => $i['stock']);

$prizes = $pool->drawMany(10); // up to 10 items; fewer if pool runs dry

use WeightedSample\Pool\WeightedPool;

// weight=0 items are excluded automatically — no exception thrown
$pool = WeightedPool::of($items, static fn (array $item): int => $item['weight']);

use WeightedSample\Filter\StrictValueFilter;
use WeightedSample\Pool\WeightedPool;

// Throws InvalidArgumentException if any item has weight ≤ 0
$pool = WeightedPool::of(
    $items,
    static fn (array $item): int => $item['weight'],
    filter: new StrictValueFilter(),
);

use WeightedSample\Filter\CompositeFilter;
use WeightedSample\Filter\ItemFilterInterface;
use WeightedSample\Filter\PositiveValueFilter;
use WeightedSample\Pool\WeightedPool;

$enabledFilter = new class implements ItemFilterInterface {
    public function accepts(mixed $item, int $weight): bool
    {
        return $item['enabled'] === true;
    }
};

$pool = WeightedPool::of(
    $items,
    static fn (array $item): int => $item['weight'],
    filter: new CompositeFilter([new PositiveValueFilter(), $enabledFilter]),
);

use WeightedSample\Filter\CountedItemFilterInterface;
use WeightedSample\Pool\BoxPool;

$activeFilter = new class implements CountedItemFilterInterface {
    public function accepts(mixed $item, int $weight): bool
    {
        return $item['enabled'] === true && $weight > 0;
    }

    public function acceptsWithCount(mixed $item, int $weight, int $count): bool
    {
        return $this->accepts($item, $weight) && $count > 0;
    }
};

$pool = BoxPool::of(
    $items,
    static fn (array $item): int => $item['weight'],
    static fn (array $item): int => $item['stock'],
    filter: $activeFilter,
);

use InvalidArgumentException;
use WeightedSample\Exception\EmptyPoolException;
use WeightedSample\Pool\WeightedPool;

// Construction-time: InvalidArgumentException when no items survive the filter.
// PositiveValueFilter (the default) excludes any item whose weight is 0.
try {
    $pool = WeightedPool::of($items, static fn (array $item): int => $item['weight']);
} catch (InvalidArgumentException $e) {
    // Bad input — every item was excluded. Check data or filter config.
    return;
}

// Runtime: EmptyPoolException when the pool is exhausted.
try {
    while (true) {
        $winner = $pool->draw();
    }
} catch (EmptyPoolException $e) {
    // Pool is exhausted — all items have been drawn.
}

use WeightedSample\Pool\WeightedPool;

// Read from a database cursor without buffering all rows into an array first
function itemsFromDb(\PDOStatement $stmt): \Generator
{
    while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
        yield $row;
    }
}

$pool = WeightedPool::of(itemsFromDb($stmt), static fn (array $row): int => $row['weight']);

use WeightedSample\Pool\WeightedPool;
use WeightedSample\Randomizer\SeededRandomizer;

$pool = WeightedPool::of(
    $items,
    static fn (array $item): int => $item['weight'],
    randomizer: new SeededRandomizer(42), // fixed seed
);

// Always produces the same sequence for seed=42
$result = $pool->draw();

// Bind the seed to a specific entity for per-entity reproducibility.
// The same userId + campaignId always produces the same draw sequence:
$seed = (int) hexdec(substr(hash('sha256', $userId . ':' . $campaignId), 0, 8));

// If you need a one-off reproducible sequence, generate the seed once, persist it,
// then reuse it to replay the exact same draws later:
$seed = unpack('N', random_bytes(4))[1]; // store this value — it is the key to replay
// Note: if replay is not needed, use SecureRandomizer (the default) instead.

use WeightedSample\Builder\FenwickSelectorBundleFactory;
use WeightedSample\Filter\StrictValueFilter;
use WeightedSample\Pool\BoxPool;
use WeightedSample\Pool\WeightedPool;
use WeightedSample\Randomizer\SeededRandomizer;
use WeightedSample\Selector\AliasTableSelectorFactory;

// Change the randomizer (e.g. for tests)
$pool = WeightedPool::of($items, static fn (array $i): int => $i['weight'],
    randomizer: new SeededRandomizer(42),
);

// Change the filter
$pool = WeightedPool::of($items, static fn (array $i): int => $i['weight'],
    filter: new StrictValueFilter(),
);

// Change the selector algorithm (WeightedPool)
$pool = WeightedPool::of($items, static fn (array $i): int => $i['weight'],
    selectorFactory: new AliasTableSelectorFactory(),
);

// Change the builder strategy (BoxPool)
$pool = BoxPool::of($items, static fn (array $i): int => $i['weight'], static fn (array $i): int => $i['stock'],
    bundleFactory: new FenwickSelectorBundleFactory(), // default; shown for clarity
);

use WeightedSample\Pool\WeightedPool;
use WeightedSample\Pool\BoxPool;
use WeightedSample\Selector\AliasTableSelectorFactory;
use WeightedSample\Builder\FenwickSelectorBundleFactory;
use WeightedSample\Builder\RebuildSelectorBundleFactory;

// WeightedPool with many repeated draws — use Alias for O(1) pick
$pool = WeightedPool::of(
    $items,
    static fn (array $item): int => $item['weight'],
    selectorFactory: new AliasTableSelectorFactory(),
);

// BoxPool with large N — FenwickSelectorBundleFactory is the default (O(n log n) total)
$pool = BoxPool::of(
    $items,
    static fn (array $item): int => $item['weight'],
    static fn (array $item): int => $item['stock'],
    bundleFactory: new FenwickSelectorBundleFactory(),
);

// BoxPool with AliasTable pick (O(1)) — use RebuildSelectorBundleFactory
$pool = BoxPool::of(
    $items,
    static fn (array $item): int => $item['weight'],
    static fn (array $item): int => $item['stock'],
    bundleFactory: new RebuildSelectorBundleFactory(new AliasTableSelectorFactory()),
);

use WeightedSample\Pool\WeightedPool;

/** @var WeightedPool<array{name: string, weight: int}> $pool */
$pool = WeightedPool::of($items, static fn (array $item): int => $item['weight']);

$item = $pool->draw(); // PHPStan infers array{name: string, weight: int}
bash
php benchmark/run.php