Here's an algorithm written with my functional programming library. It is not ideal roundwise (takes 7 rounds for 6 people), but it gets you 15 combinations. It is not impossible to do exactly 5 rounds, please see the accepted answer. First the results
people [ 'A', 'B', 'C', 'D', 'E', 'F' ]
round 0 [ [ 'A', 'B' ], [ 'C', 'D' ], [ 'E', 'F' ] ]
round 1 [ [ 'A', 'C' ], [ 'B', 'D' ] ]
round 2 [ [ 'A', 'D' ], [ 'B', 'C' ] ]
round 3 [ [ 'A', 'E' ], [ 'B', 'F' ] ]
round 4 [ [ 'A', 'F' ], [ 'B', 'E' ] ]
round 5 [ [ 'C', 'E' ], [ 'D', 'F' ] ]
round 6 [ [ 'C', 'F' ], [ 'D', 'E' ] ]
Here's the implementation
const {
pipe, fork, tap, switchCase,
map, filter, transform,
and, not, eq, get,
} = require('rubico')
const identity = x => x
// [person] => Map { person => (Set { person }) }
const makeTracker = transform(
map(fork([
person => person,
() => new Set(),
])),
new Map(),
)
const db = new Map()
const saveTracker = async tracker => { db.set('tracker', tracker) }
const hasTracker = async () => db.has('tracker')
const getTracker = async tracker => db.get('tracker')
const isNotIn = set => x => !set.has(x)
// { people, tracker } => { people, tracker, pairs }
const algorithm = ({ people, tracker }) => {
const matchedThisRound = new Set()
return fork({
people: identity,
tracker: () => tracker,
pairs: transform(
pipe([
filter(isNotIn(matchedThisRound)),
map(person => {
for (const other of people) {
// console.log('person:other', `${person}:${other}`)
if (other === person) continue
if (tracker.get(person).has(other)) continue
if (matchedThisRound.has(other)) continue
tracker.get(person).add(other)
tracker.get(other).add(person)
matchedThisRound.add(person)
matchedThisRound.add(other)
return [person, other]
}
}),
filter(Array.isArray),
]),
[],
)
})(people)
}
// [person, person, person, person, ...] => [[person, person], [person, person], ...]
const matchmake = pipe([
fork({
people: identity,
tracker: switchCase([
hasTracker, getTracker,
makeTracker,
]),
}),
algorithm,
tap(pipe([get('tracker'), saveTracker])),
])
const didEveryoneMatch = (tracker, people) => {
for (const [, set] of tracker) {
if (set.size < people.length - 1) return false // 1 for yourself
}
return true
}
const main = async () => {
const people = ['A', 'B', 'C', 'D', 'E', 'F']
console.log('people', people)
let i = 0
while (true) {
const round = await matchmake(people)
const { tracker, pairs } = round
console.log('round', i, pairs)
if (didEveryoneMatch(tracker, people)) break
i += 1
}
}
main()
some fun stats
- 100 people takes 127 rounds
- 53 people takes 63 rounds
- 1000 people takes 1023 rounds
You can play with the amount of people yourself if you drop this in for people
const people = Array.from((function*() {
for (let i = 0; i < 53; i++) yield `${i + 1}`
})())