Spaces:
Sleeping
Sleeping
| """ | |
| Gale-Shapley Algorithm Implementation | |
| Python port of the C implementation from the original project. | |
| Implements the Stable Marriage Problem for roommate matching and room allocation. | |
| Original C version: https://github.com/Harshwardhan-Deshmukh03/Roommate-allocation-using-Gale-Shapley-Algorithm.git | |
| """ | |
| from typing import Dict, List, Tuple, Optional | |
| def gale_shapley(pref_matrix: List[List[int]], n: int) -> Dict[int, int]: | |
| """ | |
| Run the Gale-Shapley algorithm for stable matching. | |
| This is a direct Python translation of the C galeShapley() function. | |
| Students 0..n-1 are "proposers" and students n..2n-1 are "acceptors". | |
| Args: | |
| pref_matrix: 2D preference list. pref_matrix[i] is the ordered preference | |
| list for person i. For proposers (0..n-1), preferences are | |
| indices in the acceptor range (n..2n-1). For acceptors, | |
| preferences are proposer indices (0..n-1). | |
| n: Number of pairs (half the total participants). | |
| Returns: | |
| Dictionary mapping each person to their matched partner. | |
| """ | |
| # match[i] = partner of person i, -1 means unmatched | |
| match = [-1] * (2 * n) | |
| free_count = n | |
| # Track which preference index each proposer is up to | |
| next_proposal = [0] * n | |
| while free_count > 0: | |
| # Find the first free proposer | |
| m = -1 | |
| for i in range(n): | |
| if match[i] == -1: | |
| m = i | |
| break | |
| if m == -1: | |
| break | |
| # Proposer m proposes to their next preferred acceptor | |
| while next_proposal[m] < n: | |
| w = pref_matrix[m][next_proposal[m]] | |
| next_proposal[m] += 1 | |
| if match[w] == -1: | |
| # w is free, accept the proposal | |
| match[w] = m | |
| match[m] = w | |
| free_count -= 1 | |
| break | |
| else: | |
| # w is already matched, check if w prefers m over current partner | |
| m1 = match[w] | |
| if _prefers_new_over_current(pref_matrix, w, m, m1): | |
| # w prefers m over m1 — switch | |
| match[w] = m | |
| match[m] = w | |
| match[m1] = -1 # m1 becomes free | |
| break | |
| # else w rejects m, m tries next preference | |
| return match | |
| def _prefers_new_over_current( | |
| pref_matrix: List[List[int]], w: int, m_new: int, m_current: int | |
| ) -> bool: | |
| """ | |
| Check if acceptor w prefers m_new over m_current. | |
| Mirrors the C function wPrefersm1Overm() but with clearer naming. | |
| Returns True if w prefers m_new over m_current. | |
| """ | |
| for pref in pref_matrix[w]: | |
| if pref == m_new: | |
| return True | |
| if pref == m_current: | |
| return False | |
| return False | |
| def run_roommate_matching(students: List[dict]) -> List[Tuple[int, int]]: | |
| """ | |
| Stage 1: Match students into roommate pairs using Gale-Shapley. | |
| Args: | |
| students: List of student dicts with 'id', 'name', 'cgpa', 'pref_roommate'. | |
| Returns: | |
| List of (student_id_1, student_id_2) roommate pairs. | |
| """ | |
| n_students = len(students) | |
| if n_students < 2 or n_students % 2 != 0: | |
| raise ValueError( | |
| f"Need an even number of students (≥ 2). Got {n_students}." | |
| ) | |
| n = n_students // 2 # Number of pairs | |
| # Build preference matrix: proposers are 0..n-1, acceptors are n..2n-1 | |
| # Each student's pref_roommate contains IDs of other students they prefer | |
| pref_matrix = [] | |
| for student in students: | |
| prefs = student["pref_roommate"] | |
| # Filter out only valid preferences (should be IDs of other students) | |
| # For proposers (0..n-1): their prefs should reference acceptors (n..2n-1) | |
| # For acceptors (n..2n-1): their prefs should reference proposers (0..n-1) | |
| sid = student["id"] | |
| if sid < n: | |
| # Proposer: filter prefs to only include acceptor IDs (n..2n-1) | |
| filtered = [p for p in prefs if n <= p < 2 * n] | |
| else: | |
| # Acceptor: filter prefs to only include proposer IDs (0..n-1) | |
| filtered = [p for p in prefs if 0 <= p < n] | |
| pref_matrix.append(filtered) | |
| # Run Gale-Shapley | |
| match = gale_shapley(pref_matrix, n) | |
| # Extract unique pairs (only from proposer side to avoid duplicates) | |
| pairs = [] | |
| seen = set() | |
| for i in range(n): | |
| partner = match[i] | |
| if partner != -1 and i not in seen and partner not in seen: | |
| pairs.append((i, partner)) | |
| seen.add(i) | |
| seen.add(partner) | |
| return pairs | |
| def run_room_allocation( | |
| students: List[dict], | |
| roommate_pairs: List[Tuple[int, int]], | |
| rooms: List[dict], | |
| ) -> List[dict]: | |
| """ | |
| Stage 2: Allocate rooms to roommate pairs based on CGPA ranking. | |
| Higher CGPA pairs get priority in room selection (Gale-Shapley on rooms). | |
| Args: | |
| students: List of student dicts with 'id', 'name', 'cgpa', 'pref_room'. | |
| roommate_pairs: List of (id1, id2) roommate pairs from Stage 1. | |
| rooms: List of room dicts with 'room_id' and 'room_number'. | |
| Returns: | |
| List of allocation dicts: {roommate1, roommate2, room_number, room_id, pair_cgpa} | |
| """ | |
| n = len(roommate_pairs) | |
| n_rooms = len(rooms) | |
| if n_rooms < n: | |
| raise ValueError( | |
| f"Not enough rooms ({n_rooms}) for {n} pairs." | |
| ) | |
| # Build student lookup | |
| student_map = {s["id"]: s for s in students} | |
| # Rank pairs by the higher CGPA in each pair (descending) | |
| pair_cgpas = [] | |
| for id1, id2 in roommate_pairs: | |
| cgpa1 = student_map[id1]["cgpa"] | |
| cgpa2 = student_map[id2]["cgpa"] | |
| max_cgpa = max(cgpa1, cgpa2) | |
| pair_cgpas.append((max_cgpa, id1, id2)) | |
| # Sort descending by max CGPA | |
| pair_cgpas.sort(key=lambda x: x[0], reverse=True) | |
| # Build room preference matrix for Gale-Shapley | |
| # Proposers: ranked pairs (0..n-1) → these map to pair_cgpas indices | |
| # Acceptors: rooms (n..2n-1) → these map to rooms indices (offset by n) | |
| room_id_to_index = {rooms[j]["room_id"]: j + n for j in range(n)} | |
| pref_matrix = [[] for _ in range(2 * n)] | |
| # For each ranked pair, get the higher-CGPA student's room preferences | |
| for rank_idx, (max_cgpa, id1, id2) in enumerate(pair_cgpas): | |
| # Use the higher CGPA student's room preferences | |
| if student_map[id1]["cgpa"] >= student_map[id2]["cgpa"]: | |
| prefs = student_map[id1].get("pref_room", []) | |
| else: | |
| prefs = student_map[id2].get("pref_room", []) | |
| # Map room IDs to room indices (offset by n for acceptor range) | |
| mapped_prefs = [] | |
| for room_id in prefs: | |
| if room_id in room_id_to_index: | |
| mapped_prefs.append(room_id_to_index[room_id]) | |
| pref_matrix[rank_idx] = mapped_prefs | |
| # Rooms accept any student in order (no real preference) | |
| for j in range(n): | |
| pref_matrix[n + j] = list(range(n)) | |
| # Run Gale-Shapley for room allocation | |
| match = gale_shapley(pref_matrix, n) | |
| # Build index-to-room mapping | |
| index_to_room = {j + n: rooms[j] for j in range(n)} | |
| # Build final allocation | |
| allocations = [] | |
| for rank_idx, (max_cgpa, id1, id2) in enumerate(pair_cgpas): | |
| room_index = match[rank_idx] | |
| room = index_to_room.get(room_index, {"room_number": "N/A", "room_id": -1}) | |
| allocations.append({ | |
| "roommate1_id": id1, | |
| "roommate1_name": student_map[id1]["name"], | |
| "roommate1_cgpa": student_map[id1]["cgpa"], | |
| "roommate2_id": id2, | |
| "roommate2_name": student_map[id2]["name"], | |
| "roommate2_cgpa": student_map[id2]["cgpa"], | |
| "room_number": room["room_number"], | |
| "room_id": room["room_id"], | |
| "pair_max_cgpa": max_cgpa, | |
| }) | |
| return allocations | |
| def run_full_allocation(students: List[dict], rooms: List[dict]) -> List[dict]: | |
| """ | |
| Run the complete two-stage allocation pipeline. | |
| Stage 1: Gale-Shapley for roommate matching. | |
| Stage 2: CGPA-ranked Gale-Shapley for room allocation. | |
| Args: | |
| students: List of student dicts. | |
| rooms: List of room dicts. | |
| Returns: | |
| List of allocation result dicts. | |
| """ | |
| # Stage 1: Roommate matching | |
| roommate_pairs = run_roommate_matching(students) | |
| # Stage 2: Room allocation | |
| allocations = run_room_allocation(students, roommate_pairs, rooms) | |
| return allocations | |