Skip to content

Commit

Permalink
Add a frequency arbitrary
Browse files Browse the repository at this point in the history
  • Loading branch information
dubzzz committed Jan 24, 2018
1 parent 8b8fc89 commit 651b7a2
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 1 deletion.
36 changes: 36 additions & 0 deletions src/check/arbitrary/FrequencyArbitrary.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import Arbitrary from './definition/Arbitrary'
import Shrinkable from './definition/Shrinkable'
import MutableRandomGenerator from '../../random/generator/MutableRandomGenerator'
import { nat } from './IntegerArbitrary'

export interface WeightedArbitrary<T> {
weight: number,
arbitrary: Arbitrary<T>
};

class FrequencyArbitrary<T> extends Arbitrary<T> {
readonly summedWarbs: WeightedArbitrary<T>[];
readonly idArb: Arbitrary<number>;
constructor(readonly warbs: WeightedArbitrary<T>[]) {
super();
this.summedWarbs = warbs
.reduce((p: WeightedArbitrary<T>[],c) =>
p.concat({weight: p[p.length -1].weight + c.weight, arbitrary: c.arbitrary}),
[{weight: 0, arbitrary: warbs[0].arbitrary}]
)
.slice(1);
this.idArb = nat(this.summedWarbs[this.summedWarbs.length -1].weight -1);
}
generate(mrng: MutableRandomGenerator): Shrinkable<T> {
const selected = this.idArb.generate(mrng).value;
return this.summedWarbs
.find(warb => selected < warb.weight)!.arbitrary
.generate(mrng);
}
}

function frequency<T>(warb1: WeightedArbitrary<T>, ...warbs: WeightedArbitrary<T>[]): Arbitrary<T> {
return new FrequencyArbitrary([warb1, ...warbs]);
}

export { frequency };
3 changes: 2 additions & 1 deletion src/fast-check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { boolean } from './check/arbitrary/BooleanArbitrary'
import { char, ascii, unicode, hexa, base64 } from './check/arbitrary/CharacterArbitrary'
import { constant } from './check/arbitrary/ConstantArbitrary'
import { float, double } from './check/arbitrary/FloatingPointArbitrary'
import { frequency } from './check/arbitrary/FrequencyArbitrary'
import { integer, nat } from './check/arbitrary/IntegerArbitrary'
import { lorem } from './check/arbitrary/LoremArbitrary'
import { oneof } from './check/arbitrary/OneOfArbitrary'
Expand All @@ -29,7 +30,7 @@ export {
integer, nat, // integer types
char, ascii, unicode, hexa, base64, // single character
string, asciiString, unicodeString, hexaString, base64String, lorem, // strings
constant, option, oneof, array, tuple, // combination of others
constant, option, oneof, frequency, array, tuple, // combination of others
// extend the framework
Arbitrary, Shrinkable
};
48 changes: 48 additions & 0 deletions test/unit/check/arbitrary/FrequencyArbitrary.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import * as assert from 'power-assert';
import * as fc from '../../../../lib/fast-check';

import { frequency } from '../../../../src/check/arbitrary/FrequencyArbitrary';
import { oneof } from '../../../../src/check/arbitrary/OneOfArbitrary';

import * as stubArb from '../../stubs/arbitraries';
import * as stubRng from '../../stubs/generators';

describe('FrequencyArbitrary', () => {
describe('frequency', () => {
const MAX_WEIGHT = 100;
const weightArb = () => fc.tuple(fc.integer(), fc.integer(1, MAX_WEIGHT));
const rng = (seed) => stubRng.mutable.fastincrease(seed);
it('Should produce the same as oneof when called on weights of 1', () => fc.assert(
fc.property(fc.integer(), fc.integer(), fc.array(fc.integer()), (seed, choice1, others) => {
const gFreq = frequency(
{weight: 1, arbitrary: stubArb.counter(choice1)},
...others.map(c => Object({weight: 1, arbitrary: stubArb.counter(c)}))
).generate(rng(seed)).value;
const gOneOf = oneof(
stubArb.counter(choice1),
...others.map(stubArb.counter)
).generate(rng(seed)).value;
return gFreq == gOneOf;
})
));
it('Should produce the same as oneof with sum of weights elements', () => fc.assert(
fc.property(fc.integer(), weightArb(), fc.array(weightArb()), (seed, choice1, others) => {
const expand = (value: number, num: number): number[] => [...Array(num)].map(() => value);

const othersOneOf = [choice1, ...others]
.reduce((p,c) => p.concat(...expand(c[0], c[1])), [])
.slice(1);

const gFreq = frequency(
{weight: choice1[1], arbitrary: stubArb.counter(choice1[0])},
...others.map(c => Object({weight: c[1], arbitrary: stubArb.counter(c[0])}))
).generate(rng(seed)).value;
const gOneOf = oneof(
stubArb.counter(choice1[0]),
...othersOneOf.map(stubArb.counter)
).generate(rng(seed)).value;
return gFreq == gOneOf;
})
));
});
});

0 comments on commit 651b7a2

Please sign in to comment.