const Schmervice = require('@hapipal/schmervice') function engageEveryone(guys) { let done do { done = true for (let i = 0; i < guys.length; i++) { const guy = guys[i] if (!guy.fiance) { done = false const gal = guy.nextCandidate() if (!gal.fiance || gal.prefers(guy)) { guy.engageTo(gal) } } } } while (!done) } class Person { constructor(name) { let candidateIndex = 0 this.name = name this.fiance = null this.candidates = [] this.rank = p => { for (let i = 0; i < this.candidates.length; i++) { if (this.candidates[i] === p) return i } return this.candidates.length + 1 } this.prefers = p => { return this.rank(p) < this.rank(this.fiance) } this.nextCandidate = () => { if (candidateIndex >= this.candidates.length) { return null } return this.candidates[candidateIndex++] } this.engageTo = p => { if (p.fiance) { p.fiance.fiance = null } p.fiance = this if (this.fiance) { this.fiance.fiance = null } this.fiance = p } this.swapWith = p => { console.log('%s & %s swap partners', this.name, p.name) const thisFiance = this.fiance const pFiance = p.fiance this.engageTo(pFiance) p.engageTo(thisFiance) } } } module.exports = class MatchService extends Schmervice.Service { constructor(...args) { super(...args) } async calcMatches(allQueuesByType) { const seekers = Object.keys(allQueuesByType['seeker']).map(id => ({ profile_id: parseInt(id), queue: allQueuesByType['seeker'][id], })) const posters = Object.keys(allQueuesByType['poster']).map(id => ({ profile_id: parseInt(id), queue: allQueuesByType['poster'][id], })) const diff = Math.abs(posters.length - seekers.length) const smallerList = posters.length < seekers.length ? posters : seekers // ADD DUMMY IDS TO THE SMALLER LIST for (let d = 0; d < diff; d++) { smallerList.push({ profile_id: `${d}-dummy`, queue: [] }) } console.log('---') let allPeople = [...seekers, ...posters] const queuesById = allPeople.reduce((queuesById, profile) => { queuesById[profile.profile_id] = profile.queue return queuesById }, {}) let allIds = allPeople.map(profile => profile.profile_id) seekers.forEach(profile => { profile.queue.forEach(id => allIds.push(id)) }) posters.forEach(profile => { profile.queue.forEach(id => allIds.push(id)) }) allIds = [...new Set(allIds)] const peopleById = allIds.reduce((peopleById, id) => { peopleById[id] = new Person(id) if (queuesById[id]) { peopleById[id].candidates = queuesById[id].map( queueId => new Person(queueId), ) } return peopleById }, {}) const seekerPeople = seekers.map( profile => peopleById[profile.profile_id], ) const posterPeople = posters.map( profile => peopleById[profile.profile_id], ) // You only need to engage from one side engageEveryone(seekerPeople) engageEveryone(posterPeople) // TODO: Test this, probably bug Object.values(peopleById).forEach(person => { if (person.fiance) { person.fiance.fiance = null } }) return Object.values(peopleById).map(person => ({ id: person.name, otp: person?.fiance?.name ?? null, })) } }