All files / src/math index.ts

100% Statements 36/36
100% Branches 8/8
100% Functions 10/10
100% Lines 35/35

Press n or j to go to the next uncovered block, b, p or k for the previous block.

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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 1271x 1x                           1x 11x 20x     135x   11x   135x 11x     124x 597x   124x   53x             53x     71x             11x     1x           8x   61x         8x   61x 8x     53x 158x   53x   18x             18x     35x             8x                                   1x       175x           1x 1x 1x 1x  
import { arrayEquals, replace } from "../utils";
import { cartesianProduct } from "./combinatorics";
import { RandomDevice } from "./RandomDevice";
import { WithOdds } from "./WithOdds";
 
/**
 * Computes all possible sets from the provided list of random devices along
 * with the odds of obtaining this particular set.
 *
 * @param {Array<RandomDevice<any>>} devices
 *   The list of random devices from which to generate possible sets.
 * @returns {Array<SetWithOdds<any>>}
 *   All possible sets that can be obtainded from this lsit of random devices
 *   along with the odds of obtaining this particular set.
 */
export function odds<T>(devices: RandomDevice<T>[]): WithOdds<T[]>[] {
  const product = cartesianProduct(
    ...devices.map((d) => d.getOutcomesWithOdds())
  );
 
  const combined = product.map((sets) => sets.reduce(combineSetWithOdds));
 
  const grouped = combined.reduce(
    (acc: WithOdds<T[]>[], current: WithOdds<T[]>) => {
      if (acc.length === 0) {
        return [current];
      } else {
        // Find an equivalent set
        const matchingSetIndex = acc.findIndex(({ value }) =>
          arrayEquals(value.sort(), current.value.sort())
        );
        if (matchingSetIndex > -1) {
          // Update the existing set by adding the odds
          const updatedSet = {
            oddsOfValue: acc[matchingSetIndex].oddsOfValue.add(
              current.oddsOfValue
            ),
            value: acc[matchingSetIndex].value,
          };
 
          return replace(acc, matchingSetIndex, updatedSet);
        } else {
          // Add the set
          return [...acc, current];
        }
      }
    },
    []
  );
 
  return grouped;
}
 
export function roll<O, R>(
  device: RandomDevice<O>,
  numberOfDevices: number,
  resultTransformationFn: (outcomes: O[]) => R,
  resultEqualFn: (a: R, b: R) => boolean
): WithOdds<R>[] {
  const raw: WithOdds<R>[] = odds<O>([
    ...Array(numberOfDevices).fill(device),
  ]).map(({ value, oddsOfValue }) => ({
    oddsOfValue,
    value: resultTransformationFn(value),
  }));
 
  const sameResultsCombined = raw.reduce(
    (acc: WithOdds<R>[], current: WithOdds<R>) => {
      if (acc.length === 0) {
        return [current];
      } else {
        // Find an equivalent result
        const matchingResultIndex = acc.findIndex(({ value }) =>
          resultEqualFn(value, current.value)
        );
        if (matchingResultIndex > -1) {
          // Update the existing result by adding the odds
          const updatedResult = {
            oddsOfValue: acc[matchingResultIndex].oddsOfValue.add(
              current.oddsOfValue
            ),
            value: acc[matchingResultIndex].value,
          };
 
          return replace(acc, matchingResultIndex, updatedResult);
        } else {
          // Add the set
          return [...acc, current];
        }
      }
    },
    []
  );
 
  return sameResultsCombined;
}
 
/**
 * Combine two sets with odds into a single one. Sets are merged and odds are
 * multiplied.
 *
 * @param O
 *   Type for the first set.
 * @param P
 *   Type for the second set.
 * @param {WithOdds<O[]>} first
 *   The first set to be combined.
 * @param {WithOdds<P[]>} second
 *   The second set to be combined.
 * @returns {WithOdds<(O | P)[]>}
 *   The result of combining the two sets.
 */
export function combineSetWithOdds<O, P>(
  first: WithOdds<O[]>,
  second: WithOdds<P[]>
): WithOdds<(O | P)[]> {
  return {
    oddsOfValue: first.oddsOfValue.multiply(second.oddsOfValue),
    value: [...first.value, ...second.value],
  };
}
 
export * from "./combinatorics";
export * from "./Fraction";
export * from "./RandomDevice";
export * from "./WithOdds";