A heuristic algorithm for solving a
generalised stable roommate problem, i.e. forming k
sized groups
out of n
people when each person has some preference for others. This was inspired by the
difficulty in forming groups for college projects. While this algorithm doesn't guarantee a stable
solution, it does provide a good approximation in reasonable time.
Traditional stable roommate algorithms use ordinal preferences (rankings). This implementation uses cardinal preferences (numerical ratings) because they:
- Carry more information about preference intensity
- Are easier to collect from participants
- Enable more sophisticated preference aggregation
- Preference Matrix: Square matrix
P
whereP[i,j]
represents memberi
's rating of memberj
- Group Size: Target number of members per group (
k
)
Group Formation: Given n
members, create ⌈n/k⌉
groups
Preference Aggregation:
- Member
i
's preference for groupG
:$\text{pref}(i, G) = \frac{1}{|G|} \sum_{j \in G} P[i,j]$ - Group
G
's preference for memberi
:$\text{pref}(G, i) = \frac{1}{|G|} \sum_{j \in G} P[j,i]$ - Group
G
's own satisfaction with members${m_1, m_2, ..., m_k}$ :
Objective: Maximize average satisfaction across all groups
- List of groups that maximize average satisfaction across all groups
- Calculate number of groups:
num_groups = ⌈n/k⌉
- Randomly select
num_groups
members as group seeds - Initialize each group with one seed member
Phase 2: Member Assignment (Gale-Shapley Inspired)
For each round until all members are assigned:
- Proposal: Each ungrouped member proposes to their most preferred available group
- Selection: Each group accepts the member they prefer most among proposers
- Optimization: Perform
optim_steps
rounds of hill-climbing swaps of members between groups
- Run
final_optim_steps
rounds of member swaps across all groups - Accept swaps that improve overall score
- Time: O(n³) worst case
- Space: O(n²) for preference matrix storage
import numpy as np
from algorithm import Matching
# Create preference matrix (example: 8 members)
prefs = np.random.rand(8, 8) * 10 # Random ratings 0-10
# Initialize and solve
matcher = Matching(prefs, group_size=4, optim_steps=2, final_optim_steps=4)
final_score, groups = matcher.solve()
print(f"Final score: {final_score:.2f}")
print(f"Groups: {groups}")
Parameter | Description | Default | Impact |
---|---|---|---|
group_size |
Target members per group | 4 | Affects group count and dynamics |
optim_steps |
Optimization rounds per assignment | 2 | Higher = better quality, slower |
final_optim_steps |
Final optimization rounds | 4 | Higher = better final result, slower |
- Non-deterministic: Results vary due to random initialization
- Local optima: Hill-climbing may miss global optimum
- No stability guarantee: Groups may contain members who prefer other arrangements
- Computational cost: Scales poorly with large groups and high optimisation counts
Try the interactive version: Group Us