104 lines
3.7 KiB
JavaScript
104 lines
3.7 KiB
JavaScript
// Algoritmo di assegnazione persone -> stanze.
|
|
// Vincolo: ogni stanza è mono-genere (M con M, F con F).
|
|
// Strategia: 1) partiziona le stanze tra i due generi in modo che ciascun
|
|
// genere ci stia (capienza sufficiente); 2) distribuisce casualmente le
|
|
// persone nelle stanze del proprio genere, cercando di non lasciare stanze
|
|
// completamente vuote.
|
|
|
|
(function (global) {
|
|
function shuffle(arr, rng) {
|
|
const a = arr.slice();
|
|
for (let i = a.length - 1; i > 0; i--) {
|
|
const j = Math.floor(rng() * (i + 1));
|
|
[a[i], a[j]] = [a[j], a[i]];
|
|
}
|
|
return a;
|
|
}
|
|
|
|
// Sceglie quali stanze vanno alle femmine e quali ai maschi.
|
|
// Preferisce partizioni in cui la capienza femminile combacia esattamente
|
|
// col numero di femmine (zero letti sprecati da quel lato => niente stanze
|
|
// vuote lato femmine). Fallback: qualsiasi partizione valida.
|
|
function chooseGenderPartition(rooms, nF, nM, rng) {
|
|
const n = rooms.length;
|
|
const total = rooms.reduce((s, r) => s + r.posti, 0);
|
|
const valid = [];
|
|
const exact = [];
|
|
// esclude mask 0 e mask "tutte" così ogni genere ha almeno una stanza
|
|
for (let mask = 1; mask < (1 << n) - 1; mask++) {
|
|
let capF = 0;
|
|
for (let i = 0; i < n; i++) if (mask & (1 << i)) capF += rooms[i].posti;
|
|
const capM = total - capF;
|
|
if (capF >= nF && capM >= nM) {
|
|
valid.push(mask);
|
|
if (capF === nF) exact.push(mask);
|
|
}
|
|
}
|
|
const pool = exact.length ? exact : valid;
|
|
if (!pool.length) {
|
|
throw new Error(
|
|
"Nessuna partizione valida: capienza insufficiente per il vincolo di genere."
|
|
);
|
|
}
|
|
return pool[Math.floor(rng() * pool.length)];
|
|
}
|
|
|
|
// Distribuisce le persone nelle stanze del proprio genere.
|
|
// Prima assegna 1 persona a stanza (copertura => niente stanze vuote se
|
|
// le persone sono >= delle stanze), poi riempie i letti restanti a caso.
|
|
function fillRooms(roomsG, peopleG, rng) {
|
|
const rooms = shuffle(roomsG, rng);
|
|
const people = shuffle(peopleG, rng);
|
|
const assign = {};
|
|
rooms.forEach((r) => (assign[r.id] = []));
|
|
|
|
let idx = 0;
|
|
if (people.length >= rooms.length) {
|
|
for (const r of rooms) assign[r.id].push(people[idx++]);
|
|
}
|
|
|
|
const slots = [];
|
|
for (const r of rooms) {
|
|
const free = r.posti - assign[r.id].length;
|
|
for (let k = 0; k < free; k++) slots.push(r.id);
|
|
}
|
|
const freeSlots = shuffle(slots, rng);
|
|
while (idx < people.length) {
|
|
const roomId = freeSlots.shift();
|
|
assign[roomId].push(people[idx++]);
|
|
}
|
|
return assign;
|
|
}
|
|
|
|
function assign(rooms, participants, rng) {
|
|
rng = rng || Math.random;
|
|
const females = participants.filter((p) => p.genere === "F");
|
|
const males = participants.filter((p) => p.genere === "M");
|
|
|
|
const mask = chooseGenderPartition(rooms, females.length, males.length, rng);
|
|
const fRooms = rooms.filter((r, i) => mask & (1 << i));
|
|
const mRooms = rooms.filter((r, i) => !(mask & (1 << i)));
|
|
|
|
const fAssign = fillRooms(fRooms, females.map((p) => p.nome), rng);
|
|
const mAssign = fillRooms(mRooms, males.map((p) => p.nome), rng);
|
|
|
|
const byPerson = {};
|
|
const roomGender = {};
|
|
fRooms.forEach((r) => (roomGender[r.id] = "F"));
|
|
mRooms.forEach((r) => (roomGender[r.id] = "M"));
|
|
for (const rid in fAssign) fAssign[rid].forEach((nome) => (byPerson[nome] = rid));
|
|
for (const rid in mAssign) mAssign[rid].forEach((nome) => (byPerson[nome] = rid));
|
|
|
|
return {
|
|
byPerson, // nome -> roomId
|
|
roomGender, // roomId -> "F" | "M"
|
|
fRooms,
|
|
mRooms,
|
|
};
|
|
}
|
|
|
|
const Matching = { assign, shuffle };
|
|
if (typeof module !== "undefined" && module.exports) module.exports = Matching;
|
|
global.Matching = Matching;
|
|
})(typeof window !== "undefined" ? window : globalThis);
|