probability-picker 1.2.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -14
- package/dist/index.cjs +72 -48
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +15 -3
- package/dist/index.d.ts +15 -3
- package/dist/index.js +71 -48
- package/dist/index.js.map +1 -1
- package/package.json +52 -41
package/README.md
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
1
|
+
# probability-picker
|
|
2
|
+
|
|
3
|
+
A small, type-safe utility for random selection based on weighted probabilities. It handles simple maps, nested objects, and selection without replacement.
|
|
3
4
|
|
|
4
5
|
## Installation
|
|
5
6
|
|
|
@@ -7,20 +8,65 @@ A lightweight library for selecting random values based on weighted probabilitie
|
|
|
7
8
|
npm i probability-picker
|
|
8
9
|
```
|
|
9
10
|
|
|
10
|
-
## Usage
|
|
11
|
+
## Basic Usage
|
|
12
|
+
|
|
13
|
+
The `picker` function takes an object where keys are your options and values are their weights.
|
|
11
14
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
```javascript
|
|
15
|
-
import picker from 'probability-picker';
|
|
15
|
+
```typescript
|
|
16
|
+
import { picker } from 'probability-picker';
|
|
16
17
|
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
});
|
|
18
|
+
const drop = picker({
|
|
19
|
+
common: 80,
|
|
20
|
+
rare: 15,
|
|
21
|
+
legendary: 5
|
|
22
|
+
}).one();
|
|
22
23
|
```
|
|
23
24
|
|
|
24
|
-
##
|
|
25
|
+
## Advanced Features
|
|
26
|
+
|
|
27
|
+
### Picking multiple items
|
|
28
|
+
Use `.take(n)` to get an array of results.
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
const items = picker({ grass: 95, clover: 5 }).take(5);
|
|
32
|
+
// returns something like ['grass', 'grass', 'clover', 'grass', 'grass']
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Luck Modifier
|
|
36
|
+
The `luck()` method adjusts probabilities using an exponential curve. A luck value > 1 increases the weight of all items (favoring rarer ones relatively), while < 1 does the opposite.
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
const loot = picker({ sword: 1, gold: 99 })
|
|
40
|
+
.luck(5)
|
|
41
|
+
.one();
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Nested Maps
|
|
45
|
+
If a value is another object, the picker will recursively select until it hits a leaf node (a string key).
|
|
25
46
|
|
|
26
|
-
|
|
47
|
+
```typescript
|
|
48
|
+
const map = {
|
|
49
|
+
weapons: {
|
|
50
|
+
sword: 10,
|
|
51
|
+
axe: 5
|
|
52
|
+
},
|
|
53
|
+
consumables: 85
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const result = picker(map).one(); // returns 'sword', 'axe', or 'consumables'
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### The "Bag" System
|
|
60
|
+
If you need to pick items without repeating them until the set is exhausted, use `bag`.
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
import { bag } from 'probability-picker';
|
|
64
|
+
|
|
65
|
+
const deck = bag({ Ace: 1, King: 1, Queen: 1 });
|
|
66
|
+
|
|
67
|
+
console.log(deck.next()); // 'King'
|
|
68
|
+
console.log(deck.next()); // 'Ace' (won't repeat until the deck is empty)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## License
|
|
72
|
+
MIT
|
package/dist/index.cjs
CHANGED
|
@@ -1,59 +1,83 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
// src/
|
|
4
|
-
|
|
5
|
-
return entries.filter((e) => typeof e[1] === "number" && !Number.isNaN(e[1]));
|
|
6
|
-
}
|
|
7
|
-
function sortEntries(entries) {
|
|
8
|
-
return entries.sort((a, b) => a[1] - b[1]);
|
|
9
|
-
}
|
|
10
|
-
function sumProbabilities(entries) {
|
|
11
|
-
return entries.reduce((acc, e) => acc + e[1], 0);
|
|
12
|
-
}
|
|
13
|
-
function normalizeProbabilities(entries, total) {
|
|
14
|
-
return entries.map(([k, v]) => [k, Math.round(v / total * 100)]);
|
|
15
|
-
}
|
|
16
|
-
function prepareEntries(entries) {
|
|
17
|
-
const filtered = filterNumbers(entries);
|
|
18
|
-
if (filtered.length === 0) return;
|
|
19
|
-
const sorted = sortEntries(filtered);
|
|
20
|
-
const sum = sumProbabilities(sorted);
|
|
21
|
-
if (sum === 0) return;
|
|
22
|
-
if (sum === 100) return sorted;
|
|
23
|
-
return normalizeProbabilities(sorted, sum);
|
|
24
|
-
}
|
|
25
|
-
function secureRandom() {
|
|
26
|
-
if (typeof globalThis.crypto === "undefined" || typeof globalThis.crypto.getRandomValues !== "function") {
|
|
27
|
-
return Math.random();
|
|
28
|
-
}
|
|
3
|
+
// src/utils.ts
|
|
4
|
+
var secureRandom = () => {
|
|
29
5
|
const array = new Uint32Array(1);
|
|
30
6
|
globalThis.crypto.getRandomValues(array);
|
|
31
7
|
return array[0] / 4294967296;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
count += entry[1];
|
|
41
|
-
if (num <= count) return entry[0];
|
|
8
|
+
};
|
|
9
|
+
var normalize = (map, luck) => {
|
|
10
|
+
const valid = [];
|
|
11
|
+
for (const [key, value] of Object.entries(map)) {
|
|
12
|
+
const weight = typeof value === "number" ? value : 1;
|
|
13
|
+
if (!Number.isNaN(weight) && weight > 0) {
|
|
14
|
+
valid.push([key, Math.pow(weight, 1 / luck)]);
|
|
15
|
+
}
|
|
42
16
|
}
|
|
43
|
-
return
|
|
17
|
+
return valid;
|
|
18
|
+
};
|
|
19
|
+
var select = (items) => {
|
|
20
|
+
const total = items.reduce((acc, [, v]) => acc + v, 0);
|
|
21
|
+
let target = secureRandom() * total;
|
|
22
|
+
for (const [k, v] of items) {
|
|
23
|
+
target -= v;
|
|
24
|
+
if (target <= 0) return k;
|
|
25
|
+
}
|
|
26
|
+
return items[items.length - 1][0];
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// src/picker.ts
|
|
30
|
+
var ProbabilityEngine = class _ProbabilityEngine {
|
|
31
|
+
_luck = 1;
|
|
32
|
+
_map;
|
|
33
|
+
constructor(map) {
|
|
34
|
+
this._map = map;
|
|
35
|
+
}
|
|
36
|
+
luck(value) {
|
|
37
|
+
this._luck = value;
|
|
38
|
+
return this;
|
|
39
|
+
}
|
|
40
|
+
one() {
|
|
41
|
+
const entries = normalize(this._map, this._luck);
|
|
42
|
+
if (entries.length === 0) return;
|
|
43
|
+
const key = select(entries);
|
|
44
|
+
const value = this._map[key];
|
|
45
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
46
|
+
return new _ProbabilityEngine(value).luck(this._luck).one();
|
|
47
|
+
}
|
|
48
|
+
return key;
|
|
49
|
+
}
|
|
50
|
+
take(n) {
|
|
51
|
+
const results = [];
|
|
52
|
+
for (let i = 0; i < n; i++) {
|
|
53
|
+
const picked = this.one();
|
|
54
|
+
if (picked) results.push(picked);
|
|
55
|
+
}
|
|
56
|
+
return results;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
function picker(map) {
|
|
60
|
+
if (!map || typeof map !== "object") {
|
|
61
|
+
return new ProbabilityEngine({});
|
|
62
|
+
}
|
|
63
|
+
return new ProbabilityEngine(map);
|
|
44
64
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
65
|
+
|
|
66
|
+
// src/bag.ts
|
|
67
|
+
function bag(map) {
|
|
68
|
+
let remaining = { ...map };
|
|
69
|
+
return {
|
|
70
|
+
next() {
|
|
71
|
+
const keys = Object.keys(remaining);
|
|
72
|
+
if (keys.length === 0) remaining = { ...map };
|
|
73
|
+
const picked = picker(remaining).one();
|
|
74
|
+
if (picked) delete remaining[picked];
|
|
75
|
+
return picked;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
54
78
|
}
|
|
55
|
-
var index_default = probabilityPicker;
|
|
56
79
|
|
|
57
|
-
|
|
80
|
+
exports.bag = bag;
|
|
81
|
+
exports.picker = picker;
|
|
58
82
|
//# sourceMappingURL=index.cjs.map
|
|
59
83
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/
|
|
1
|
+
{"version":3,"sources":["../src/utils.ts","../src/picker.ts","../src/bag.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["import type { NestedMap } from './types'\r\n\r\nexport const secureRandom = (): number => {\r\n const array = new Uint32Array(1)\r\n globalThis.crypto.getRandomValues(array)\r\n return array[0]! / 0x100000000\r\n}\r\n\r\nexport const normalize = (map: NestedMap, luck: number): [string, number][] => {\r\n const valid: [string, number][] = []\r\n\r\n for (const [key, value] of Object.entries(map)) {\r\n const weight = typeof value === 'number' ? value : 1\r\n if (!Number.isNaN(weight) && weight > 0) {\r\n valid.push([key, Math.pow(weight, 1 / luck)])\r\n }\r\n }\r\n\r\n return valid\r\n}\r\n\r\nexport const select = (items: [string, number][]): string => {\r\n const total = items.reduce((acc, [, v]) => acc + v, 0)\r\n let target = secureRandom() * total\r\n\r\n for (const [k, v] of items) {\r\n target -= v\r\n if (target <= 0) return k\r\n }\r\n\r\n return items[items.length - 1]![0]\r\n}\r\n","import type { NestedMap, PickerInstance } from './types'\r\nimport { normalize, select } from './utils'\r\n\r\nclass ProbabilityEngine<T extends string> implements PickerInstance<T> {\r\n private _luck = 1\r\n private _map: NestedMap\r\n\r\n constructor(map: NestedMap) {\r\n this._map = map\r\n }\r\n\r\n luck(value: number) {\r\n this._luck = value\r\n return this\r\n }\r\n\r\n one(): T | undefined {\r\n const entries = normalize(this._map, this._luck)\r\n if (entries.length === 0) return\r\n\r\n const key = select(entries)\r\n const value = this._map[key]\r\n\r\n if (value && typeof value === 'object' && !Array.isArray(value)) {\r\n return new ProbabilityEngine(value).luck(this._luck).one() as T\r\n }\r\n\r\n return key as T\r\n }\r\n\r\n take(n: number): T[] {\r\n const results: T[] = []\r\n for (let i = 0; i < n; i++) {\r\n const picked = this.one()\r\n if (picked) results.push(picked)\r\n }\r\n return results\r\n }\r\n}\r\n\r\nexport function picker<T extends string>(map: Record<T, number | NestedMap>): PickerInstance<T> {\r\n if (!map || typeof map !== 'object') {\r\n return new ProbabilityEngine<T>({} as NestedMap)\r\n }\r\n return new ProbabilityEngine<T>(map as NestedMap)\r\n}\r\n","import { picker } from './picker'\r\n\r\nexport function bag<T extends string>(map: Record<T, number>) {\r\n let remaining = { ...map }\r\n\r\n return {\r\n next(): T | undefined {\r\n const keys = Object.keys(remaining)\r\n if (keys.length === 0) remaining = { ...map }\r\n const picked = picker(remaining).one()\r\n if (picked) delete remaining[picked as T]\r\n return picked as T\r\n }\r\n }\r\n}\r\n"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
|
-
type
|
|
2
|
-
|
|
1
|
+
type NestedMap = {
|
|
2
|
+
[key: string]: number | NestedMap;
|
|
3
|
+
};
|
|
4
|
+
interface PickerInstance<T extends string> {
|
|
5
|
+
luck(value: number): this;
|
|
6
|
+
one(): T | undefined;
|
|
7
|
+
take(n: number): T[];
|
|
8
|
+
}
|
|
3
9
|
|
|
4
|
-
|
|
10
|
+
declare function picker<T extends string>(map: Record<T, number | NestedMap>): PickerInstance<T>;
|
|
11
|
+
|
|
12
|
+
declare function bag<T extends string>(map: Record<T, number>): {
|
|
13
|
+
next(): T | undefined;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export { type NestedMap, type PickerInstance, bag, picker };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
|
-
type
|
|
2
|
-
|
|
1
|
+
type NestedMap = {
|
|
2
|
+
[key: string]: number | NestedMap;
|
|
3
|
+
};
|
|
4
|
+
interface PickerInstance<T extends string> {
|
|
5
|
+
luck(value: number): this;
|
|
6
|
+
one(): T | undefined;
|
|
7
|
+
take(n: number): T[];
|
|
8
|
+
}
|
|
3
9
|
|
|
4
|
-
|
|
10
|
+
declare function picker<T extends string>(map: Record<T, number | NestedMap>): PickerInstance<T>;
|
|
11
|
+
|
|
12
|
+
declare function bag<T extends string>(map: Record<T, number>): {
|
|
13
|
+
next(): T | undefined;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export { type NestedMap, type PickerInstance, bag, picker };
|
package/dist/index.js
CHANGED
|
@@ -1,57 +1,80 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
|
|
3
|
-
return entries.filter((e) => typeof e[1] === "number" && !Number.isNaN(e[1]));
|
|
4
|
-
}
|
|
5
|
-
function sortEntries(entries) {
|
|
6
|
-
return entries.sort((a, b) => a[1] - b[1]);
|
|
7
|
-
}
|
|
8
|
-
function sumProbabilities(entries) {
|
|
9
|
-
return entries.reduce((acc, e) => acc + e[1], 0);
|
|
10
|
-
}
|
|
11
|
-
function normalizeProbabilities(entries, total) {
|
|
12
|
-
return entries.map(([k, v]) => [k, Math.round(v / total * 100)]);
|
|
13
|
-
}
|
|
14
|
-
function prepareEntries(entries) {
|
|
15
|
-
const filtered = filterNumbers(entries);
|
|
16
|
-
if (filtered.length === 0) return;
|
|
17
|
-
const sorted = sortEntries(filtered);
|
|
18
|
-
const sum = sumProbabilities(sorted);
|
|
19
|
-
if (sum === 0) return;
|
|
20
|
-
if (sum === 100) return sorted;
|
|
21
|
-
return normalizeProbabilities(sorted, sum);
|
|
22
|
-
}
|
|
23
|
-
function secureRandom() {
|
|
24
|
-
if (typeof globalThis.crypto === "undefined" || typeof globalThis.crypto.getRandomValues !== "function") {
|
|
25
|
-
return Math.random();
|
|
26
|
-
}
|
|
1
|
+
// src/utils.ts
|
|
2
|
+
var secureRandom = () => {
|
|
27
3
|
const array = new Uint32Array(1);
|
|
28
4
|
globalThis.crypto.getRandomValues(array);
|
|
29
5
|
return array[0] / 4294967296;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
count += entry[1];
|
|
39
|
-
if (num <= count) return entry[0];
|
|
6
|
+
};
|
|
7
|
+
var normalize = (map, luck) => {
|
|
8
|
+
const valid = [];
|
|
9
|
+
for (const [key, value] of Object.entries(map)) {
|
|
10
|
+
const weight = typeof value === "number" ? value : 1;
|
|
11
|
+
if (!Number.isNaN(weight) && weight > 0) {
|
|
12
|
+
valid.push([key, Math.pow(weight, 1 / luck)]);
|
|
13
|
+
}
|
|
40
14
|
}
|
|
41
|
-
return
|
|
15
|
+
return valid;
|
|
16
|
+
};
|
|
17
|
+
var select = (items) => {
|
|
18
|
+
const total = items.reduce((acc, [, v]) => acc + v, 0);
|
|
19
|
+
let target = secureRandom() * total;
|
|
20
|
+
for (const [k, v] of items) {
|
|
21
|
+
target -= v;
|
|
22
|
+
if (target <= 0) return k;
|
|
23
|
+
}
|
|
24
|
+
return items[items.length - 1][0];
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// src/picker.ts
|
|
28
|
+
var ProbabilityEngine = class _ProbabilityEngine {
|
|
29
|
+
_luck = 1;
|
|
30
|
+
_map;
|
|
31
|
+
constructor(map) {
|
|
32
|
+
this._map = map;
|
|
33
|
+
}
|
|
34
|
+
luck(value) {
|
|
35
|
+
this._luck = value;
|
|
36
|
+
return this;
|
|
37
|
+
}
|
|
38
|
+
one() {
|
|
39
|
+
const entries = normalize(this._map, this._luck);
|
|
40
|
+
if (entries.length === 0) return;
|
|
41
|
+
const key = select(entries);
|
|
42
|
+
const value = this._map[key];
|
|
43
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
44
|
+
return new _ProbabilityEngine(value).luck(this._luck).one();
|
|
45
|
+
}
|
|
46
|
+
return key;
|
|
47
|
+
}
|
|
48
|
+
take(n) {
|
|
49
|
+
const results = [];
|
|
50
|
+
for (let i = 0; i < n; i++) {
|
|
51
|
+
const picked = this.one();
|
|
52
|
+
if (picked) results.push(picked);
|
|
53
|
+
}
|
|
54
|
+
return results;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
function picker(map) {
|
|
58
|
+
if (!map || typeof map !== "object") {
|
|
59
|
+
return new ProbabilityEngine({});
|
|
60
|
+
}
|
|
61
|
+
return new ProbabilityEngine(map);
|
|
42
62
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
63
|
+
|
|
64
|
+
// src/bag.ts
|
|
65
|
+
function bag(map) {
|
|
66
|
+
let remaining = { ...map };
|
|
67
|
+
return {
|
|
68
|
+
next() {
|
|
69
|
+
const keys = Object.keys(remaining);
|
|
70
|
+
if (keys.length === 0) remaining = { ...map };
|
|
71
|
+
const picked = picker(remaining).one();
|
|
72
|
+
if (picked) delete remaining[picked];
|
|
73
|
+
return picked;
|
|
74
|
+
}
|
|
75
|
+
};
|
|
52
76
|
}
|
|
53
|
-
var index_default = probabilityPicker;
|
|
54
77
|
|
|
55
|
-
export {
|
|
78
|
+
export { bag, picker };
|
|
56
79
|
//# sourceMappingURL=index.js.map
|
|
57
80
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/
|
|
1
|
+
{"version":3,"sources":["../src/utils.ts","../src/picker.ts","../src/bag.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { NestedMap } from './types'\r\n\r\nexport const secureRandom = (): number => {\r\n const array = new Uint32Array(1)\r\n globalThis.crypto.getRandomValues(array)\r\n return array[0]! / 0x100000000\r\n}\r\n\r\nexport const normalize = (map: NestedMap, luck: number): [string, number][] => {\r\n const valid: [string, number][] = []\r\n\r\n for (const [key, value] of Object.entries(map)) {\r\n const weight = typeof value === 'number' ? value : 1\r\n if (!Number.isNaN(weight) && weight > 0) {\r\n valid.push([key, Math.pow(weight, 1 / luck)])\r\n }\r\n }\r\n\r\n return valid\r\n}\r\n\r\nexport const select = (items: [string, number][]): string => {\r\n const total = items.reduce((acc, [, v]) => acc + v, 0)\r\n let target = secureRandom() * total\r\n\r\n for (const [k, v] of items) {\r\n target -= v\r\n if (target <= 0) return k\r\n }\r\n\r\n return items[items.length - 1]![0]\r\n}\r\n","import type { NestedMap, PickerInstance } from './types'\r\nimport { normalize, select } from './utils'\r\n\r\nclass ProbabilityEngine<T extends string> implements PickerInstance<T> {\r\n private _luck = 1\r\n private _map: NestedMap\r\n\r\n constructor(map: NestedMap) {\r\n this._map = map\r\n }\r\n\r\n luck(value: number) {\r\n this._luck = value\r\n return this\r\n }\r\n\r\n one(): T | undefined {\r\n const entries = normalize(this._map, this._luck)\r\n if (entries.length === 0) return\r\n\r\n const key = select(entries)\r\n const value = this._map[key]\r\n\r\n if (value && typeof value === 'object' && !Array.isArray(value)) {\r\n return new ProbabilityEngine(value).luck(this._luck).one() as T\r\n }\r\n\r\n return key as T\r\n }\r\n\r\n take(n: number): T[] {\r\n const results: T[] = []\r\n for (let i = 0; i < n; i++) {\r\n const picked = this.one()\r\n if (picked) results.push(picked)\r\n }\r\n return results\r\n }\r\n}\r\n\r\nexport function picker<T extends string>(map: Record<T, number | NestedMap>): PickerInstance<T> {\r\n if (!map || typeof map !== 'object') {\r\n return new ProbabilityEngine<T>({} as NestedMap)\r\n }\r\n return new ProbabilityEngine<T>(map as NestedMap)\r\n}\r\n","import { picker } from './picker'\r\n\r\nexport function bag<T extends string>(map: Record<T, number>) {\r\n let remaining = { ...map }\r\n\r\n return {\r\n next(): T | undefined {\r\n const keys = Object.keys(remaining)\r\n if (keys.length === 0) remaining = { ...map }\r\n const picked = picker(remaining).one()\r\n if (picked) delete remaining[picked as T]\r\n return picked as T\r\n }\r\n }\r\n}\r\n"]}
|
package/package.json
CHANGED
|
@@ -1,42 +1,53 @@
|
|
|
1
|
-
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
"
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
"
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
"
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "probability-picker",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "A lightweight library for selecting random values based on weighted probabilities.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"README.md",
|
|
19
|
+
"LICENSE"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsup",
|
|
23
|
+
"dev": "tsup --watch",
|
|
24
|
+
"lint": "tsc --noEmit",
|
|
25
|
+
"test": "vitest run"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"probability",
|
|
29
|
+
"weighted",
|
|
30
|
+
"random"
|
|
31
|
+
],
|
|
32
|
+
"author": {
|
|
33
|
+
"name": "qgave",
|
|
34
|
+
"url": "https://github.com/qgave"
|
|
35
|
+
},
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "git+https://github.com/qgave/probability-picker"
|
|
40
|
+
},
|
|
41
|
+
"sideEffects": false,
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=18"
|
|
44
|
+
},
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"typescript": "^5.9.3",
|
|
50
|
+
"tsup": "^8.5.1",
|
|
51
|
+
"vitest": "^4.0.17"
|
|
52
|
+
}
|
|
42
53
|
}
|