groupster-engine
Version:
Randomly group objects using do-group and don't-group rules.
84 lines (73 loc) • 2.61 kB
JavaScript
import Joi from 'joi-browser';
import { uniq, difference } from 'lodash';
import { IDsSchema, GroupSizesSchema, RulesSchema } from './schema';
// Verify parameters conform to schema
export function validateSchemas(IDs, groupSizes, rules) {
[[IDs, IDsSchema], [groupSizes, GroupSizesSchema], [rules, RulesSchema]]
.forEach(pair => Joi.assert(...pair));
}
// Verify group sizes add up to IDs count
export function validateGroupSizes(IDs, groupSizes) {
const totalOfGroupSizes = groupSizes.reduce((sum, size) => sum + size, 0);
if (IDs.length !== totalOfGroupSizes) {
throw new Error(`Total of group sizes (${totalOfGroupSizes}) did not equal member count (${IDs.length}).`);
}
}
// Verify Ids are unique
export function validateIds(IDs) {
const uniqueIds = uniq(IDs);
if (IDs.length !== uniqueIds.length) {
const duplicateIds = difference(IDs, uniqueIds);
throw new Error(`One or more IDs had the same ID: [${duplicateIds.join(', ')}]`);
}
}
// Build rules map
export default function buildRuleMap(IDs, groupSizes, rules) {
validateSchemas(IDs, groupSizes, rules);
validateIds(IDs);
validateGroupSizes(IDs, groupSizes);
const ruleMap = IDs.reduce((acc, id) => ({ ...acc, [id]: {} }), {});
if (rules) {
// For each rule...
rules.forEach(rule => {
const { shouldGroup, IDs: ruleIDs } = rule;
// Ensure ruleIDs correspond to actual IDs
ruleIDs.forEach(ruleID => {
if (!ruleMap[ruleID]) {
throw new Error(`Rule ${JSON.stringify(rule)} contained id not present in base IDs array ${JSON.stringify(Object.keys(ruleMap))}`);
}
});
// For each pairing within the rule's IDs...
for (let i = 0; i < ruleIDs.length - 1; i += 1) {
for (let j = i + 1; j < ruleIDs.length; j += 1) {
const m1 = ruleIDs[i];
const m2 = ruleIDs[j];
// Check that rule doesn't conflict with previous rules
const ruleExisted = ruleMap[m1][m2] !== undefined;
if (ruleExisted && ((!shouldGroup && ruleMap[m1][m2]) || (shouldGroup && ruleMap[m1][m2] === false))) {
throw new Error(`Rules conflicted, the following IDs were instructed to both group and not group with eachother: [${m1}, ${m2}]`);
}
// And add rule into map (both ways)
ruleMap[m1][m2] = shouldGroup;
ruleMap[m2][m1] = shouldGroup;
}
}
});
}
return ruleMap;
}
// {
// a: {
// b: true,
// c: true,
// },
// b: {
// a: true,
// c: false,
// },
// c: {
// a: true,
// b: false,
// },
// d: {},
// }