feat: add CLI random number generator supporting 6 distributions
This commit is contained in:
+39
@@ -0,0 +1,39 @@
|
|||||||
|
# dependencies (bun install)
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
# output
|
||||||
|
out
|
||||||
|
dist
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# code coverage
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# logs
|
||||||
|
logs
|
||||||
|
_.log
|
||||||
|
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||||
|
|
||||||
|
# dotenv environment variable files
|
||||||
|
.env
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# caches
|
||||||
|
.eslintcache
|
||||||
|
.cache
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# IntelliJ based IDEs
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# Finder (MacOS) folder config
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# lock
|
||||||
|
|
||||||
|
bun.lock
|
||||||
|
package-lock.json
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
# rand
|
||||||
|
|
||||||
|
一个快速、可组合的命令行随机数生成器,支持 6 种概率分布,原生管道语法。
|
||||||
|
|
||||||
|
## 安装
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 源码运行
|
||||||
|
bun install
|
||||||
|
bun run index.ts
|
||||||
|
|
||||||
|
# 构建独立二进制
|
||||||
|
bun run build # → dist/rand
|
||||||
|
sudo cp dist/rand /usr/local/bin/
|
||||||
|
```
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rand # 0–100 的随机整数
|
||||||
|
rand 50 # 0–50
|
||||||
|
rand 10 20 # 10–20
|
||||||
|
rand -c 5 # 生成 5 个数
|
||||||
|
rand -f2 # 保留 2 位小数
|
||||||
|
```
|
||||||
|
|
||||||
|
## 选项
|
||||||
|
|
||||||
|
| 选项 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| `-c, --count <n>` | 生成个数,默认 1 |
|
||||||
|
| `-f[N]` | 小数位数:`-f` 默认 2 位,`-f1` = 1 位,`-f3` = 3 位 |
|
||||||
|
| `--float [N]` | `-f` 的长形式 |
|
||||||
|
| `-d, --dist <name>` | 概率分布,默认 `uniform` |
|
||||||
|
| `-h, --help` | 帮助信息 |
|
||||||
|
| `--` | 之后所有参数视为位置参数(用于负数) |
|
||||||
|
|
||||||
|
## 概率分布
|
||||||
|
|
||||||
|
| 分布 | 命令 | 参数 | 默认值 |
|
||||||
|
|------|------|------|--------|
|
||||||
|
| uniform | `rand` | `[min] [max]` | 0, 100 |
|
||||||
|
| normal | `rand -d normal` | `[μ] [σ]` | 0, 1 |
|
||||||
|
| binomial | `rand -d binomial` | `[n] [p]` | 10, 0.5 |
|
||||||
|
| poisson | `rand -d poisson` | `[λ]` | 1 |
|
||||||
|
| exponential | `rand -d exponential` | `[λ]` | 1 |
|
||||||
|
| hypergeometric | `rand -d hypergeometric` | `[N] [K] [n]` | 100, 50, 10 |
|
||||||
|
|
||||||
|
### 采样算法
|
||||||
|
|
||||||
|
| 分布 | 算法 |
|
||||||
|
|------|------|
|
||||||
|
| normal | Box-Muller 变换 |
|
||||||
|
| binomial | Bernoulli 试验求和 |
|
||||||
|
| poisson | Knuth 算法 |
|
||||||
|
| exponential | 逆 CDF 变换 |
|
||||||
|
| hypergeometric | 不放回抽样模拟 |
|
||||||
|
|
||||||
|
## 管道
|
||||||
|
|
||||||
|
stdout 输出纯数据,stderr 输出诊断信息,天然支持管道组合:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rand | xargs echo # 管道输出
|
||||||
|
echo 50 | rand # stdin 提供上限
|
||||||
|
echo "5 2" | rand -d normal -f1 # stdin 覆盖分布参数
|
||||||
|
rand -c 100 | sort -n | head -5 # 生成 100 个,取最小的 5 个
|
||||||
|
```
|
||||||
|
|
||||||
|
## 示例
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 正态分布:均值 100,标准差 15
|
||||||
|
rand -d normal 100 15 -f1
|
||||||
|
|
||||||
|
# 二项分布:20 次试验,成功概率 0.3,生成 5 个样本
|
||||||
|
rand -d binomial 20 0.3 -c 5
|
||||||
|
|
||||||
|
# 泊松分布:λ=3
|
||||||
|
rand -d poisson 3
|
||||||
|
|
||||||
|
# 指数分布:λ=0.5,保留 1 位小数
|
||||||
|
rand -d exponential 0.5 -f1
|
||||||
|
|
||||||
|
# 超几何分布:总体 100,成功 30,抽取 5 次
|
||||||
|
rand -d hypergeometric 100 30 5
|
||||||
|
|
||||||
|
# 负数范围(用 -- 分隔标志和参数)
|
||||||
|
rand -- -10 -5
|
||||||
|
rand -d normal -- 0 -1 # stddev 必须 > 0,会报错
|
||||||
|
```
|
||||||
|
|
||||||
|
## 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
index.ts # 入口
|
||||||
|
src/
|
||||||
|
types.ts # 类型定义与默认值
|
||||||
|
help.ts # 帮助文本
|
||||||
|
args.ts # 参数解析与校验
|
||||||
|
stdin.ts # 管道输入读取
|
||||||
|
dist.ts # 分布采样器与调度
|
||||||
|
main.ts # 编排逻辑
|
||||||
|
```
|
||||||
|
|
||||||
|
## 构建
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun run build # → dist/rand(独立二进制,约 74MB)
|
||||||
|
```
|
||||||
|
|
||||||
|
基于 [Bun](https://bun.com) 构建,零外部依赖。
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"name": "rand",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"module": "index.ts",
|
||||||
|
"type": "module",
|
||||||
|
"bin": {
|
||||||
|
"rand": "./index.ts"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "bun build ./index.ts --compile --outfile dist/rand"
|
||||||
|
},
|
||||||
|
"private": true,
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/bun": "latest"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": "^5"
|
||||||
|
}
|
||||||
|
}
|
||||||
+202
@@ -0,0 +1,202 @@
|
|||||||
|
import { type Options, type Dist, DISTS, defaultOptions } from "./types";
|
||||||
|
import { showHelp } from "./help";
|
||||||
|
|
||||||
|
export function showError(msg: string): never {
|
||||||
|
console.error(`rand: ${msg}\nTry 'rand --help' for usage.`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseArgs(raw: string[]): { options: Options; help: boolean } {
|
||||||
|
const positional: string[] = [];
|
||||||
|
let count = 1;
|
||||||
|
let decimals = 0;
|
||||||
|
let dist: Dist = "uniform";
|
||||||
|
let help = false;
|
||||||
|
|
||||||
|
let i = 0;
|
||||||
|
while (i < raw.length) {
|
||||||
|
const arg = raw[i]!;
|
||||||
|
|
||||||
|
if (arg === "-h" || arg === "--help") {
|
||||||
|
help = true;
|
||||||
|
i++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -f (plain) → default 2 decimal places
|
||||||
|
if (arg === "-f") {
|
||||||
|
decimals = 2;
|
||||||
|
i++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -fN combined form: -f1, -f2, -f3, …
|
||||||
|
if (/^-f\d+$/.test(arg)) {
|
||||||
|
decimals = parseInt(arg.slice(2), 10);
|
||||||
|
i++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --float [N]
|
||||||
|
if (arg === "--float") {
|
||||||
|
i++;
|
||||||
|
if (i < raw.length && /^\d+$/.test(raw[i]!)) {
|
||||||
|
decimals = parseInt(raw[i]!, 10);
|
||||||
|
i++;
|
||||||
|
} else {
|
||||||
|
decimals = 2;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -d, --dist <name>
|
||||||
|
if (arg === "-d" || arg === "--dist") {
|
||||||
|
i++;
|
||||||
|
if (i >= raw.length) showError("--dist requires a distribution name");
|
||||||
|
const name = raw[i]!;
|
||||||
|
if (!(DISTS as string[]).includes(name)) {
|
||||||
|
showError(`unknown distribution: ${name}\n valid: ${DISTS.join(", ")}`);
|
||||||
|
}
|
||||||
|
dist = name as Dist;
|
||||||
|
i++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -c, --count <n>
|
||||||
|
if (arg === "-c" || arg === "--count") {
|
||||||
|
i++;
|
||||||
|
if (i >= raw.length) showError("--count requires a value");
|
||||||
|
const n = parseInt(raw[i]!, 10);
|
||||||
|
if (isNaN(n) || n < 1) showError(`invalid count: ${raw[i]}`);
|
||||||
|
count = n;
|
||||||
|
i++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- : everything after is positional
|
||||||
|
if (arg === "--") {
|
||||||
|
i++;
|
||||||
|
while (i < raw.length) {
|
||||||
|
positional.push(raw[i]!);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Negative number as positional (e.g. -1, -0.5)
|
||||||
|
if (/^-\d+(\.\d+)?$/.test(arg)) {
|
||||||
|
positional.push(arg);
|
||||||
|
i++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Catch unknown flags
|
||||||
|
if (arg.startsWith("-")) {
|
||||||
|
showError(`unknown option: ${arg}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
positional.push(arg);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
const opts = defaultOptions(dist);
|
||||||
|
opts.count = count;
|
||||||
|
opts.decimals = decimals;
|
||||||
|
applyPositionals(opts, positional);
|
||||||
|
|
||||||
|
return { options: opts, help };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyPositionals(opts: Options, args: string[]): void {
|
||||||
|
const nums = args.map(parseNum);
|
||||||
|
|
||||||
|
switch (opts.dist) {
|
||||||
|
case "uniform": {
|
||||||
|
if (nums.length === 0) return;
|
||||||
|
if (nums.length === 1) {
|
||||||
|
opts.max = nums[0]!;
|
||||||
|
} else if (nums.length === 2) {
|
||||||
|
opts.min = nums[0]!;
|
||||||
|
opts.max = nums[1]!;
|
||||||
|
} else {
|
||||||
|
showError(`uniform expects 0–2 args, got ${nums.length}: ${args.join(" ")}`);
|
||||||
|
}
|
||||||
|
if (opts.min > opts.max) showError(`min (${opts.min}) > max (${opts.max})`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "normal": {
|
||||||
|
if (nums.length === 0) return;
|
||||||
|
if (nums.length === 1) {
|
||||||
|
opts.mean = nums[0]!;
|
||||||
|
} else if (nums.length === 2) {
|
||||||
|
opts.mean = nums[0]!;
|
||||||
|
opts.stddev = nums[1]!;
|
||||||
|
} else {
|
||||||
|
showError(`normal expects 0–2 args, got ${nums.length}: ${args.join(" ")}`);
|
||||||
|
}
|
||||||
|
if (opts.stddev <= 0) showError(`stddev must be > 0, got ${opts.stddev}`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "binomial": {
|
||||||
|
if (nums.length === 0) return;
|
||||||
|
if (nums.length === 1) {
|
||||||
|
opts.trials = nums[0]!;
|
||||||
|
} else if (nums.length === 2) {
|
||||||
|
opts.trials = nums[0]!;
|
||||||
|
opts.prob = nums[1]!;
|
||||||
|
} else {
|
||||||
|
showError(`binomial expects 0–2 args, got ${nums.length}: ${args.join(" ")}`);
|
||||||
|
}
|
||||||
|
if (opts.trials < 0 || !Number.isInteger(opts.trials)) {
|
||||||
|
showError(`trials must be a non-negative integer, got ${opts.trials}`);
|
||||||
|
}
|
||||||
|
if (opts.prob < 0 || opts.prob > 1) showError(`prob must be 0–1, got ${opts.prob}`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "poisson": {
|
||||||
|
if (nums.length === 0) return;
|
||||||
|
if (nums.length === 1) {
|
||||||
|
opts.lambda = nums[0]!;
|
||||||
|
} else {
|
||||||
|
showError(`poisson expects 0–1 arg, got ${nums.length}: ${args.join(" ")}`);
|
||||||
|
}
|
||||||
|
if (opts.lambda <= 0) showError(`lambda must be > 0, got ${opts.lambda}`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "exponential": {
|
||||||
|
if (nums.length === 0) return;
|
||||||
|
if (nums.length === 1) {
|
||||||
|
opts.lambda = nums[0]!;
|
||||||
|
} else {
|
||||||
|
showError(`exponential expects 0–1 arg, got ${nums.length}: ${args.join(" ")}`);
|
||||||
|
}
|
||||||
|
if (opts.lambda <= 0) showError(`lambda must be > 0, got ${opts.lambda}`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "hypergeometric": {
|
||||||
|
if (nums.length === 0) return;
|
||||||
|
if (nums.length >= 1) opts.popSize = nums[0]!;
|
||||||
|
if (nums.length >= 2) opts.successes = nums[1]!;
|
||||||
|
if (nums.length >= 3) opts.draws = nums[2]!;
|
||||||
|
if (nums.length > 3) {
|
||||||
|
showError(`hypergeometric expects 0–3 args, got ${nums.length}: ${args.join(" ")}`);
|
||||||
|
}
|
||||||
|
if (opts.popSize < 0 || !Number.isInteger(opts.popSize)) {
|
||||||
|
showError(`population size N must be a non-negative integer, got ${opts.popSize}`);
|
||||||
|
}
|
||||||
|
if (opts.successes < 0 || opts.successes > opts.popSize || !Number.isInteger(opts.successes)) {
|
||||||
|
showError(`successes K must be 0–N, got ${opts.successes} (N=${opts.popSize})`);
|
||||||
|
}
|
||||||
|
if (opts.draws < 0 || opts.draws > opts.popSize || !Number.isInteger(opts.draws)) {
|
||||||
|
showError(`draws n must be 0–N, got ${opts.draws} (N=${opts.popSize})`);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseNum(s: string): number {
|
||||||
|
const n = Number(s);
|
||||||
|
if (isNaN(n)) showError(`not a number: ${s}`);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
+88
@@ -0,0 +1,88 @@
|
|||||||
|
import { type Options } from "./types";
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Individual distribution samplers
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** Box-Muller transform. Each call consumes 2 uniform randoms. */
|
||||||
|
function normalRandom(mean: number, stddev: number): number {
|
||||||
|
let u1 = Math.random();
|
||||||
|
while (u1 === 0) u1 = Math.random(); // avoid log(0)
|
||||||
|
const u2 = Math.random();
|
||||||
|
return mean + stddev * Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sum of Bernoulli trials. */
|
||||||
|
function binomialRandom(n: number, p: number): number {
|
||||||
|
let s = 0;
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
if (Math.random() < p) s++;
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Knuth's algorithm. */
|
||||||
|
function poissonRandom(lambda: number): number {
|
||||||
|
const L = Math.exp(-lambda);
|
||||||
|
let k = 0;
|
||||||
|
let p = 1;
|
||||||
|
do {
|
||||||
|
k++;
|
||||||
|
p *= Math.random();
|
||||||
|
} while (p > L);
|
||||||
|
return k - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Inverse CDF. */
|
||||||
|
function exponentialRandom(lambda: number): number {
|
||||||
|
return -Math.log(Math.random()) / lambda;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Urn model — simulate drawing without replacement. */
|
||||||
|
function hypergeometricRandom(N: number, K: number, n: number): number {
|
||||||
|
let s = 0;
|
||||||
|
let remainingK = K;
|
||||||
|
let remainingTotal = N;
|
||||||
|
const draws = Math.min(n, N);
|
||||||
|
for (let i = 0; i < draws; i++) {
|
||||||
|
if (Math.random() < remainingK / remainingTotal) {
|
||||||
|
s++;
|
||||||
|
remainingK--;
|
||||||
|
}
|
||||||
|
remainingTotal--;
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Dispatcher
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export function generate(opts: Options): number[] {
|
||||||
|
const results: number[] = [];
|
||||||
|
for (let i = 0; i < opts.count; i++) {
|
||||||
|
switch (opts.dist) {
|
||||||
|
case "uniform":
|
||||||
|
results.push(Math.random() * (opts.max - opts.min) + opts.min);
|
||||||
|
break;
|
||||||
|
case "normal":
|
||||||
|
results.push(normalRandom(opts.mean, opts.stddev));
|
||||||
|
break;
|
||||||
|
case "binomial":
|
||||||
|
results.push(binomialRandom(opts.trials, opts.prob));
|
||||||
|
break;
|
||||||
|
case "poisson":
|
||||||
|
results.push(poissonRandom(opts.lambda));
|
||||||
|
break;
|
||||||
|
case "exponential":
|
||||||
|
results.push(exponentialRandom(opts.lambda));
|
||||||
|
break;
|
||||||
|
case "hypergeometric":
|
||||||
|
results.push(
|
||||||
|
hypergeometricRandom(opts.popSize, opts.successes, opts.draws),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
export const HELP = `rand — Generate random numbers
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
rand [options] [args]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-c, --count <n> Number of random numbers to output (default: 1)
|
||||||
|
-f[N] Decimal places: -f = -f2 (default: 2)
|
||||||
|
--float [N] Long form of -f
|
||||||
|
-d, --dist <name> Distribution (default: uniform)
|
||||||
|
-h, --help Show this help
|
||||||
|
|
||||||
|
Distributions and their positional args:
|
||||||
|
uniform rand [min] [max] (default: 0 100)
|
||||||
|
normal rand -d normal [mean] [stddev] (default: 0 1)
|
||||||
|
binomial rand -d binomial [n] [p] (default: 10 0.5)
|
||||||
|
poisson rand -d poisson [lambda] (default: 1)
|
||||||
|
exponential rand -d exponential [lambda] (default: 1)
|
||||||
|
hypergeometric rand -d hypergeometric [N] [K] [n]
|
||||||
|
(default: 100 50 10)
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
rand # uniform 0–100
|
||||||
|
rand -d normal # normal μ=0, σ=1
|
||||||
|
rand -d normal 100 15 -f1 # normal μ=100, σ=15, 1 decimal
|
||||||
|
rand -d binomial 20 0.3 -c 5 # 5 binomial(n=20, p=0.3)
|
||||||
|
rand -d poisson 3 # poisson λ=3
|
||||||
|
rand -d exponential 0.5 # exponential λ=0.5
|
||||||
|
echo "5 2" | rand -d normal # stdin overrides positional args
|
||||||
|
rand | xargs echo # pipe output`;
|
||||||
|
|
||||||
|
export function showHelp(): never {
|
||||||
|
console.log(HELP);
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
+29
@@ -0,0 +1,29 @@
|
|||||||
|
import { parseArgs, applyPositionals } from "./args";
|
||||||
|
import { showHelp } from "./help";
|
||||||
|
import { readStdin, parseStdinNumbers } from "./stdin";
|
||||||
|
import { generate } from "./dist";
|
||||||
|
|
||||||
|
export async function main(): Promise<void> {
|
||||||
|
const argv = Bun.argv.slice(2);
|
||||||
|
const { options, help } = parseArgs(argv);
|
||||||
|
if (help) showHelp();
|
||||||
|
|
||||||
|
// If stdin has data, parse numbers and apply as positional overrides
|
||||||
|
if (!process.stdin.isTTY) {
|
||||||
|
const input = await readStdin();
|
||||||
|
const nums = parseStdinNumbers(input);
|
||||||
|
if (nums.length > 0) {
|
||||||
|
applyPositionals(options, nums.map(String));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const results = generate(options);
|
||||||
|
for (const r of results) {
|
||||||
|
console.log(options.decimals > 0 ? r.toFixed(options.decimals) : Math.floor(r));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((err: Error) => {
|
||||||
|
console.error(`rand: ${err.message}`);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
export async function readStdin(): Promise<string> {
|
||||||
|
const decoder = new TextDecoder();
|
||||||
|
let buf = "";
|
||||||
|
for await (const chunk of Bun.stdin.stream()) {
|
||||||
|
buf += decoder.decode(chunk, { stream: true });
|
||||||
|
}
|
||||||
|
buf += decoder.decode(); // flush
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Parse space-separated numbers from stdin. Returns number[] (may be empty). */
|
||||||
|
export function parseStdinNumbers(input: string): number[] {
|
||||||
|
const trimmed = input.trim();
|
||||||
|
if (!trimmed) return [];
|
||||||
|
const parts = trimmed.split(/\s+/);
|
||||||
|
const nums: number[] = [];
|
||||||
|
for (const p of parts) {
|
||||||
|
const n = Number(p);
|
||||||
|
if (isNaN(n)) return []; // unrecognizable → ignore stdin
|
||||||
|
nums.push(n);
|
||||||
|
}
|
||||||
|
return nums;
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
export type Dist =
|
||||||
|
| "uniform"
|
||||||
|
| "normal"
|
||||||
|
| "binomial"
|
||||||
|
| "poisson"
|
||||||
|
| "exponential"
|
||||||
|
| "hypergeometric";
|
||||||
|
|
||||||
|
export const DISTS: Dist[] = [
|
||||||
|
"uniform",
|
||||||
|
"normal",
|
||||||
|
"binomial",
|
||||||
|
"poisson",
|
||||||
|
"exponential",
|
||||||
|
"hypergeometric",
|
||||||
|
];
|
||||||
|
|
||||||
|
export interface Options {
|
||||||
|
dist: Dist;
|
||||||
|
count: number;
|
||||||
|
decimals: number;
|
||||||
|
// uniform
|
||||||
|
min: number;
|
||||||
|
max: number;
|
||||||
|
// normal
|
||||||
|
mean: number;
|
||||||
|
stddev: number;
|
||||||
|
// binomial
|
||||||
|
trials: number;
|
||||||
|
prob: number;
|
||||||
|
// poisson
|
||||||
|
lambda: number;
|
||||||
|
// hypergeometric
|
||||||
|
popSize: number;
|
||||||
|
successes: number;
|
||||||
|
draws: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function defaultOptions(dist: Dist): Options {
|
||||||
|
return {
|
||||||
|
dist,
|
||||||
|
count: 1,
|
||||||
|
decimals: 0,
|
||||||
|
min: 0,
|
||||||
|
max: 100,
|
||||||
|
mean: 0,
|
||||||
|
stddev: 1,
|
||||||
|
trials: 10,
|
||||||
|
prob: 0.5,
|
||||||
|
lambda: 1,
|
||||||
|
popSize: 100,
|
||||||
|
successes: 50,
|
||||||
|
draws: 10,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
// Environment setup & latest features
|
||||||
|
"lib": ["ESNext"],
|
||||||
|
"target": "ESNext",
|
||||||
|
"module": "Preserve",
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"allowJs": true,
|
||||||
|
"types": ["bun"],
|
||||||
|
|
||||||
|
// Bundler mode
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"noEmit": true,
|
||||||
|
|
||||||
|
// Best practices
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedIndexedAccess": true,
|
||||||
|
"noImplicitOverride": true,
|
||||||
|
|
||||||
|
// Some stricter flags (disabled by default)
|
||||||
|
"noUnusedLocals": false,
|
||||||
|
"noUnusedParameters": false,
|
||||||
|
"noPropertyAccessFromIndexSignature": false
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user