Skip to content

Implement percentage functions for random number generation #83

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions src/async-stream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ import {
} from "./summary";
import { AsyncFlatMapper, Comparable, Comparator, Numeric, Pair, ZipTuple } from "./types";
import { infinite } from "./index";
import { percentageAsync } from "./random";

/**
* Provides fluent interface for working with async iterables.
Expand Down Expand Up @@ -1129,4 +1130,17 @@ export class AsyncStream<T> implements AsyncIterable<T> {
protected constructor(iterable: AsyncIterable<T>) {
this.data = iterable;
}
/**
* Generate random percentages between 0 (inclusive) and 1 (exclusive) asynchronously.
*
* If optional param `repetitions` is not given, iterates infinitely.
*
* @param repetitions - Number of values to generate
*
* @see random.percentageAsync
*/
static percentage(repetitions?: number): AsyncStream<number> {
return new AsyncStream(percentageAsync(repetitions));
}

}
10 changes: 10 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,8 @@ import type {
Pipe,
} from "./types";

import { percentage, percentageAsync } from "./random";

export const single = {
chunkwise,
chunkwiseOverlap,
Expand Down Expand Up @@ -380,3 +382,11 @@ export type {
};

export { InvalidArgumentError, LengthError };

export const random = {
percentage,
percentageAsync,
};



43 changes: 43 additions & 0 deletions src/random.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { InvalidArgumentError } from "./exceptions";

/**
* Generates random percentages between 0 (inclusive) and 1 (exclusive).
*
* If optional param `repetitions` is not given, iterates infinitely.
*
* @param repetitions - Number of values to generate
* @throws {InvalidArgumentError} If repetitions is negative
* @see percentageAsync For asynchronous version
*/
export function* percentage(repetitions?: number): Generator<number> {
if (repetitions !== undefined && repetitions < 0) {
throw new InvalidArgumentError(`Number of repetitions cannot be negative: ${repetitions}`);
}

let count = 0;
while (repetitions === undefined || count < repetitions) {
yield Math.random();
if (repetitions !== undefined) count++;
}
}

/**
* Asynchronously generates random percentages between 0 (inclusive) and 1 (exclusive).
*
* If optional param `repetitions` is not given, iterates infinitely.
*
* @param repetitions - Number of values to generate
* @throws {InvalidArgumentError} If repetitions is negative
* @see percentage For synchronous version
*/
export async function* percentageAsync(repetitions?: number): AsyncGenerator<number> {
if (repetitions !== undefined && repetitions < 0) {
throw new InvalidArgumentError(`Number of repetitions cannot be negative: ${repetitions}`);
}

let count = 0;
while (repetitions === undefined || count < repetitions) {
yield Math.random();
if (repetitions !== undefined) count++;
}
}
14 changes: 14 additions & 0 deletions src/stream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ import {
} from "./summary";
import type { Comparable, Comparator, FlatMapper, Numeric, ZipTuple } from "./types";
import { infinite } from "./index";
import { percentage } from "./random";

/**
* Provides fluent interface for working with iterables.
Expand Down Expand Up @@ -1022,4 +1023,17 @@ export class Stream<T> implements Iterable<T> {
protected constructor(iterable: Iterable<T>) {
this.data = iterable;
}

/**
* Generate random percentages between 0 (inclusive) and 1 (exclusive).
*
* If optional param `repetitions` is not given, iterates infinitely.
*
* @param repetitions - Number of values to generate
*
* @see random.percentage
*/
static percentage(repetitions?: number): Stream<number> {
return new Stream(percentage(repetitions));
}
}
63 changes: 63 additions & 0 deletions tests/async-stream/random.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { percentageAsync } from '../../src/random';
import { InvalidArgumentError } from '../../src/exceptions';
import { AsyncStream } from '../../src/async-stream';


describe('Async Stream Integration - percentageAsync()', () => {
it('should generate async values between 0 and 1', async () => {
const values: number[] = [];
for await (const num of percentageAsync(10)) {
values.push(num);
}
expect(values.length).toBe(10);
values.forEach(num => {
expect(num).toBeGreaterThanOrEqual(0);
expect(num).toBeLessThan(1);
});
});

it('should throw an error for negative repetitions', async () => {
const gen = percentageAsync(-1);
await expect((async () => {
for await (const _ of gen) {}
})()).rejects.toThrow(InvalidArgumentError);
});

it('should process values in an async stream pipeline', async () => {
let sum = 0;
let count = 0;
for await (const num of percentageAsync(5)) {
sum += num;
count++;
}
expect(count).toBe(5);
expect(sum).toBeGreaterThanOrEqual(0);
expect(sum).toBeLessThan(5);
});

it('should allow async transformations using map', async () => {
const values: number[] = [];
for await (const num of percentageAsync(5)) {
values.push(num * 100);
}
expect(values.length).toBe(5);
values.forEach(num => {
expect(num).toBeGreaterThanOrEqual(0);
expect(num).toBeLessThan(100);
});
});
});

describe('AsyncStream.percentage()', () => {
it('should generate async values between 0 and 1', async () => {
const values: number[] = [];
for await (const num of AsyncStream.percentage(10)) {
values.push(num);
}
expect(values.length).toBe(10);
values.forEach(num => {
expect(num).toBeGreaterThanOrEqual(0);
expect(num).toBeLessThan(1);
});
});
});
71 changes: 71 additions & 0 deletions tests/random/percentage.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { percentage, percentageAsync } from '../../src/random';
import { InvalidArgumentError } from '../../src/exceptions';

describe('percentage()', () => {
it('should generate values between 0 (inclusive) and 1 (exclusive)', () => {
const gen = percentage(10);
for (const num of gen) {
expect(num).toBeGreaterThanOrEqual(0);
expect(num).toBeLessThan(1);
}
});

it('should generate exactly the number of values specified', () => {
const count = 5;
const values = Array.from(percentage(count));
expect(values.length).toBe(count);
});

it('should throw an error for negative repetitions', () => {
expect(() => Array.from(percentage(-1))).toThrow(InvalidArgumentError);
});

it('should iterate infinitely if repetitions is undefined (testing first 5 values)', () => {
const gen = percentage();
const values: number[] = [];
for (let i = 0; i < 5; i++) {
const { value, done } = gen.next();
if (done) break;
values.push(value);
}
expect(values.length).toBe(5);
values.forEach(num => {
expect(num).toBeGreaterThanOrEqual(0);
expect(num).toBeLessThan(1);
});
});
});

describe('percentageAsync()', () => {
it('should generate async values between 0 and 1', async () => {
const values: number[] = [];
for await (const num of percentageAsync(10)) {
values.push(num);
}
expect(values.length).toBe(10);
values.forEach(num => {
expect(num).toBeGreaterThanOrEqual(0);
expect(num).toBeLessThan(1);
});
});

it('should throw an error for negative repetitions asynchronously', async () => {
const gen = percentageAsync(-1);
await expect((async () => {
for await (const _ of gen) {}
})()).rejects.toThrow(InvalidArgumentError);
});

it('should iterate infinitely if repetitions is undefined (testing first 5 values)', async () => {
const values: number[] = [];
for await (const num of percentageAsync()) {
values.push(num);
if (values.length === 5) break;
}
expect(values.length).toBe(5);
values.forEach(num => {
expect(num).toBeGreaterThanOrEqual(0);
expect(num).toBeLessThan(1);
});
});
});
44 changes: 44 additions & 0 deletions tests/stream/random.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { percentage } from '../../src/random';
import { InvalidArgumentError } from '../../src/exceptions';
import { Stream } from '../../src/stream';

describe('Stream Integration - percentage()', () => {
it('should generate values between 0 and 1 for streams', () => {
const values = Array.from(percentage(10));
expect(values.length).toBe(10);
values.forEach(num => {
expect(num).toBeGreaterThanOrEqual(0);
expect(num).toBeLessThan(1);
});
});

it('should throw an error for negative repetitions', () => {
expect(() => Array.from(percentage(-1))).toThrow(InvalidArgumentError);
});

it('should allow usage in stream processing (example: summing first 5 numbers)', () => {
const sum = Array.from(percentage(5)).reduce((acc, num) => acc + num, 0);
expect(sum).toBeGreaterThanOrEqual(0);
expect(sum).toBeLessThan(5);
});

it('should allow transformation using map function', () => {
const transformed = Array.from(percentage(5)).map(num => num * 100);
expect(transformed.length).toBe(5);
transformed.forEach(num => {
expect(num).toBeGreaterThanOrEqual(0);
expect(num).toBeLessThan(100);
});
});
});

describe('Stream.percentage()', () => {
it('should generate values between 0 and 1', () => {
const values = Array.from(Stream.percentage(10));
expect(values.length).toBe(10);
values.forEach(num => {
expect(num).toBeGreaterThanOrEqual(0);
expect(num).toBeLessThan(1);
});
});
});