diff --git a/src/components/ForbiddenPair.svelte b/src/components/ForbiddenPair.svelte
index ce1978a..4d90471 100644
--- a/src/components/ForbiddenPair.svelte
+++ b/src/components/ForbiddenPair.svelte
@@ -1,5 +1,5 @@
diff --git a/src/lib/utils/groupService.ts b/src/lib/utils/groupService.ts
index af01182..06b9b76 100644
--- a/src/lib/utils/groupService.ts
+++ b/src/lib/utils/groupService.ts
@@ -1,5 +1,6 @@
import type { Person } from '../types';
+// TODO: force men to be 1-st index and women to 2-nd index in every basis group to ensure every group has all genders
export class GroupService {
readonly data: Person[];
readonly forbiddenPairs: string[][];
@@ -10,6 +11,7 @@ export class GroupService {
leftMaxGroupSize: number; // Count number of groups that can have maximum group size
constructor(data: Person[], forbiddenPairs: string[][], DAY: number, TOTAL_GROUP: number) {
+ shuffle(data);
this.data = data;
this.forbiddenPairs = forbiddenPairs;
this.DAY = DAY;
@@ -21,7 +23,6 @@ export class GroupService {
}
randomGroup() {
- // TODO: shuffle data?
const basis = this.generateBasisGroup();
const groups = Array.from({ length: this.TOTAL_GROUP }, () =>
@@ -36,7 +37,7 @@ export class GroupService {
basis[g].forEach((person, idx) => {
for (let d = 0; d < this.DAY; d++) {
// increase actual DAY by 1
- const group_id = this.calculateGroupId(g, idx, this.DAY + 1);
+ const group_id = this.calculateGroupId(g, idx, d + 1);
groups[group_id - 1][d].push(person);
groupOfMembers[person.id - 1][d] = group_id;
}
@@ -50,7 +51,6 @@ export class GroupService {
}
generateBasisGroup(): Person[][] {
- // TODO: implement
this.newData = this.data.map((person: Person) => ({ ...person, rand: Math.random() }));
const basis = Array.from({ length: this.TOTAL_GROUP }, () => []) as Person[][];
@@ -73,6 +73,7 @@ export class GroupService {
while (this.checkInsertSize(1, basis[g])) {
const person = this.getRandomPerson();
basis[g].push(person);
+ this.newData = this.newData.filter((p) => p.id !== person.id);
}
}
}
@@ -150,10 +151,6 @@ export class GroupService {
return false;
}
- checkInsertGender(values: Person[], group: Person[]) {
- // TODO: check gender before insert
- }
-
countGender(group: Person[]) {
return group.reduce(
(agg, person) => {
@@ -181,24 +178,107 @@ export class GroupService {
if (round === 0) round = this.MAX_GROUP_SIZE;
return ((base_group + round * day) % this.TOTAL_GROUP) + 1;
}
-}
-export function getGroupError(groups: Person[][][]) {
- // TODO: validate group by the following conditions:
- // 1. Every group must have at least 1 male and 1 female
- // 2. Number of people in a group must be in range [MAX_GROUP_SIZE - 1, MAX_GROUP_SIZE]
- // 3. No pair of member in a group meet twice
- return undefined;
-}
+ getGroupError(groups: Person[][][]) {
+ // TODO: validate group by the following conditions:
+ // 1. Every group must have at least 1 male and 1 female
+ // 2. Number of people in a group must be in range [MAX_GROUP_SIZE - 1, MAX_GROUP_SIZE]
+ // 3. No pair of member in a group meet twice
+ const errors: string[] = [];
+ this.getGenderError(errors, groups);
+ this.getMeetError(errors, groups);
+ this.getSizeError(errors, groups);
+ this.getForbiddenPairError(errors, groups);
+ return errors;
+ }
-function getGenderError() {
- return undefined;
-}
+ getGenderError(errors: string[], groups: Person[][][]) {
+ for (let g = 0; g < groups.length; g++) {
+ for (let d = 0; d < groups[g].length; d++) {
+ const gender_cnt = this.countGender(groups[g][d]);
+ for (const gender in gender_cnt) {
+ if (gender_cnt[gender] === 0) {
+ errors.push(`Some group do not have all gender, group ${g + 1} day ${d + 1}`);
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ getMeetError(errors: string[], groups: Person[][][]) {
+ const meet_cnt = Array.from({ length: this.data.length }, () =>
+ Array.from({ length: this.data.length }, () => 0)
+ );
+ for (let g = 0; g < groups.length; g++) {
+ for (let d = 0; d < groups[g].length; d++) {
+ for (let i = 0; i < groups[g][d].length; i++) {
+ for (let j = i + 1; j < groups[g][d].length; j++) {
+ meet_cnt[groups[g][d][i].id - 1][groups[g][d][j].id - 1]++;
+ meet_cnt[groups[g][d][j].id - 1][groups[g][d][i].id - 1]++;
+ if (meet_cnt[groups[g][d][i].id - 1][groups[g][d][j].id - 1] > 1) {
+ errors.push(
+ `Some group meet twice: ${groups[g][d][i].name} and ${groups[g][d][j].name}`
+ );
+ return;
+ }
+ }
+ }
+ }
+ }
+ }
-function getMeetError() {
- return undefined;
+ getSizeError(errors: string[], groups: Person[][][]) {
+ for (let g = 0; g < groups.length; g++) {
+ for (let d = 0; d < groups[g].length; d++) {
+ if (
+ groups[g][d].length < this.MAX_GROUP_SIZE - 1 ||
+ groups[g][d].length > this.MAX_GROUP_SIZE
+ ) {
+ errors.push(`Some group is too small`);
+ return;
+ }
+ }
+ }
+ }
+
+ getForbiddenPairError(errors: string[], groups: Person[][][]) {
+ for (let g = 0; g < groups.length; g++) {
+ for (let d = 0; d < groups[g].length; d++) {
+ for (let i = 0; i < groups[g][d].length; i++) {
+ for (let j = i + 1; j < groups[g][d].length; j++) {
+ if (this.isForbiddenPair(groups[g][d][i], groups[g][d][j])) {
+ errors.push(`Some group has forbidden pair`);
+ return;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ isForbiddenPair(person1: Person, person2: Person) {
+ return this.forbiddenPairs.some((pair) => {
+ const idx1 = pair.indexOf(person1.name);
+ const idx2 = pair.indexOf(person2.name);
+ return idx1 !== -1 && idx2 !== -1 && Math.abs(idx1 - idx2) === 1;
+ });
+ }
}
-function getSizeError() {
- return undefined;
+function shuffle(array: unknown[]) {
+ let currentIndex = array.length,
+ randomIndex;
+
+ // While there remain elements to shuffle.
+ while (currentIndex > 0) {
+ // Pick a remaining element.
+ randomIndex = Math.floor(Math.random() * currentIndex);
+ currentIndex--;
+
+ // And swap it with the current element.
+ [array[currentIndex], array[randomIndex]] = [array[randomIndex], array[currentIndex]];
+ }
+
+ return array;
}