Spaces:
Sleeping
Sleeping
Commit ·
f804bd5
0
Parent(s):
Initial commit: Streamlit UI for Gale-Shapley Algorithm
Browse files- .gitignore +153 -0
- .streamlit/config.toml +10 -0
- README.md +90 -0
- app.py +456 -0
- data/allocations.json +57 -0
- data/rooms.json +1 -0
- data/students.json +1 -0
- db.py +266 -0
- gale_shapley.py +254 -0
- requirements.txt +3 -0
- sample_csv/sample_rooms_13.csv +14 -0
- sample_csv/sample_rooms_5.csv +6 -0
- sample_csv/sample_students_10.csv +11 -0
- sample_csv/sample_students_26.csv +27 -0
.gitignore
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Byte-compiled / optimized / DLL files
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.py[cod]
|
| 4 |
+
*$py.class
|
| 5 |
+
|
| 6 |
+
# C extensions
|
| 7 |
+
*.so
|
| 8 |
+
|
| 9 |
+
# Distribution / packaging
|
| 10 |
+
.Python
|
| 11 |
+
build/
|
| 12 |
+
develop-eggs/
|
| 13 |
+
dist/
|
| 14 |
+
downloads/
|
| 15 |
+
eggs/
|
| 16 |
+
.eggs/
|
| 17 |
+
lib/
|
| 18 |
+
lib64/
|
| 19 |
+
parts/
|
| 20 |
+
sdist/
|
| 21 |
+
var/
|
| 22 |
+
wheels/
|
| 23 |
+
share/python-wheels/
|
| 24 |
+
*.egg-info/
|
| 25 |
+
.installed.cfg
|
| 26 |
+
*.egg
|
| 27 |
+
MANIFEST
|
| 28 |
+
|
| 29 |
+
# PyInstaller
|
| 30 |
+
# Usually these files are written by a python script from a template
|
| 31 |
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
| 32 |
+
*.manifest
|
| 33 |
+
*.spec
|
| 34 |
+
|
| 35 |
+
# Installer logs
|
| 36 |
+
pip-log.txt
|
| 37 |
+
pip-delete-this-directory.txt
|
| 38 |
+
|
| 39 |
+
# Unit test / coverage reports
|
| 40 |
+
htmlcov/
|
| 41 |
+
.tox/
|
| 42 |
+
.nox/
|
| 43 |
+
.coverage
|
| 44 |
+
.coverage.*
|
| 45 |
+
.cache
|
| 46 |
+
nosetests.xml
|
| 47 |
+
coverage.xml
|
| 48 |
+
*.cover
|
| 49 |
+
*.py,cover
|
| 50 |
+
.hypothesis/
|
| 51 |
+
.pytest_cache/
|
| 52 |
+
cover/
|
| 53 |
+
|
| 54 |
+
# Environments
|
| 55 |
+
.env
|
| 56 |
+
.venv
|
| 57 |
+
env/
|
| 58 |
+
venv/
|
| 59 |
+
ENV/
|
| 60 |
+
env.bak/
|
| 61 |
+
venv.bak/
|
| 62 |
+
|
| 63 |
+
# Jupyter Notebook
|
| 64 |
+
.ipynb_checkpoints
|
| 65 |
+
|
| 66 |
+
# IPython
|
| 67 |
+
profile_default/
|
| 68 |
+
ipython_config.py
|
| 69 |
+
|
| 70 |
+
# pyenv
|
| 71 |
+
# For a library or package, you might want to ignore these files since the code is
|
| 72 |
+
# intended to run in multiple environments; otherwise, check them in:
|
| 73 |
+
# .python-version
|
| 74 |
+
|
| 75 |
+
# pipenv
|
| 76 |
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
| 77 |
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
| 78 |
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
| 79 |
+
# install all needed dependencies.
|
| 80 |
+
#Pipfile.lock
|
| 81 |
+
|
| 82 |
+
# poetry
|
| 83 |
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
| 84 |
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
| 85 |
+
# commonly ignored for libraries.
|
| 86 |
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
| 87 |
+
#poetry.lock
|
| 88 |
+
|
| 89 |
+
# pdm
|
| 90 |
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
| 91 |
+
#pdm.lock
|
| 92 |
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
| 93 |
+
# in version control.
|
| 94 |
+
# https://pdm.fming.dev/#use-with-ide
|
| 95 |
+
.pdm.toml
|
| 96 |
+
|
| 97 |
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
| 98 |
+
__pypackages__/
|
| 99 |
+
|
| 100 |
+
# Celery stuff
|
| 101 |
+
celerybeat-schedule
|
| 102 |
+
celerybeat.pid
|
| 103 |
+
|
| 104 |
+
# SageMath parsed files
|
| 105 |
+
*.sage.py
|
| 106 |
+
|
| 107 |
+
# Environments
|
| 108 |
+
.env
|
| 109 |
+
.venv
|
| 110 |
+
env/
|
| 111 |
+
venv/
|
| 112 |
+
ENV/
|
| 113 |
+
env.bak/
|
| 114 |
+
venv.bak/
|
| 115 |
+
|
| 116 |
+
# Spyder project settings
|
| 117 |
+
.spyderproject
|
| 118 |
+
.spyproject
|
| 119 |
+
|
| 120 |
+
# Rope project settings
|
| 121 |
+
.ropeproject
|
| 122 |
+
|
| 123 |
+
# mkdocs documentation
|
| 124 |
+
/site
|
| 125 |
+
|
| 126 |
+
# mypy
|
| 127 |
+
.mypy_cache/
|
| 128 |
+
.dmypy.json
|
| 129 |
+
dmypy.json
|
| 130 |
+
|
| 131 |
+
# Pyre type checker
|
| 132 |
+
.pyre/
|
| 133 |
+
|
| 134 |
+
# pytype static type analyzer
|
| 135 |
+
.pytype/
|
| 136 |
+
|
| 137 |
+
# Cython debug symbols
|
| 138 |
+
cython_debug/
|
| 139 |
+
|
| 140 |
+
# OS generated files
|
| 141 |
+
.DS_Store
|
| 142 |
+
.DS_Store?
|
| 143 |
+
._*
|
| 144 |
+
.Spotlight-V100
|
| 145 |
+
.Trashes
|
| 146 |
+
ehthumbs.db
|
| 147 |
+
Thumbs.db
|
| 148 |
+
|
| 149 |
+
# Editors
|
| 150 |
+
.vscode/
|
| 151 |
+
.idea/
|
| 152 |
+
*.swp
|
| 153 |
+
*.swo
|
.streamlit/config.toml
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[theme]
|
| 2 |
+
primaryColor = "#6C63FF"
|
| 3 |
+
backgroundColor = "#0E1117"
|
| 4 |
+
secondaryBackgroundColor = "#1A1D29"
|
| 5 |
+
textColor = "#E0E0E0"
|
| 6 |
+
font = "sans serif"
|
| 7 |
+
|
| 8 |
+
[server]
|
| 9 |
+
headless = true
|
| 10 |
+
port = 8501
|
README.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 🏠 Roommate Allocation System — Python & Streamlit Edition
|
| 2 |
+
|
| 3 |
+
[](https://python.org)
|
| 4 |
+
[](https://streamlit.io)
|
| 5 |
+
[](https://en.wikipedia.org/wiki/Gale%E2%80%93Shapley_algorithm)
|
| 6 |
+
|
| 7 |
+
A modern **Streamlit UI** for the Gale-Shapley Roommate Allocation algorithm.
|
| 8 |
+
This version uses **Python** for the algorithm and **file-system storage** (JSON) instead of MySQL.
|
| 9 |
+
|
| 10 |
+
> 🔗 **For the original C + MySQL version**, see:
|
| 11 |
+
> [https://github.com/Harshwardhan-Deshmukh03/Roommate-allocation-using-Gale-Shapley-Algorithm.git](https://github.com/Harshwardhan-Deshmukh03/Roommate-allocation-using-Gale-Shapley-Algorithm.git)
|
| 12 |
+
|
| 13 |
+
---
|
| 14 |
+
|
| 15 |
+
## ✨ Features
|
| 16 |
+
|
| 17 |
+
- **Stable Matching** via the Nobel Prize-winning Gale-Shapley algorithm
|
| 18 |
+
- **Two-Stage Allocation**: Roommate matching → CGPA-ranked room assignment
|
| 19 |
+
- **CSV Import**: Bulk-upload students & rooms via CSV files
|
| 20 |
+
- **File-System Storage**: No database needed — data stored as JSON
|
| 21 |
+
- **Interactive Charts**: Plotly visualizations of CGPA distributions
|
| 22 |
+
- **Premium UI**: Dark theme with glassmorphism, gradients, and animations
|
| 23 |
+
|
| 24 |
+
---
|
| 25 |
+
|
| 26 |
+
## 🚀 Quick Start
|
| 27 |
+
|
| 28 |
+
```bash
|
| 29 |
+
# 1. Create virtual environment
|
| 30 |
+
python -m venv venv
|
| 31 |
+
venv\Scripts\activate # Windows
|
| 32 |
+
# source venv/bin/activate # macOS/Linux
|
| 33 |
+
|
| 34 |
+
# 2. Install dependencies
|
| 35 |
+
pip install -r requirements.txt
|
| 36 |
+
|
| 37 |
+
# 3. Run the app
|
| 38 |
+
streamlit run app.py
|
| 39 |
+
```
|
| 40 |
+
|
| 41 |
+
---
|
| 42 |
+
|
| 43 |
+
## 📁 Project Structure
|
| 44 |
+
|
| 45 |
+
```
|
| 46 |
+
streamlit_gale_shapely/
|
| 47 |
+
├── app.py # Main Streamlit UI application
|
| 48 |
+
├── gale_shapley.py # Gale-Shapley algorithm (Python port)
|
| 49 |
+
├── db.py # File-system database layer (JSON)
|
| 50 |
+
├── requirements.txt # Python dependencies
|
| 51 |
+
├── .streamlit/
|
| 52 |
+
│ └── config.toml # Streamlit theme configuration
|
| 53 |
+
├── data/
|
| 54 |
+
│ ├── students.json # Student records (replaces MySQL 'main' table)
|
| 55 |
+
│ ├── rooms.json # Room records (replaces MySQL 'RoomNum' table)
|
| 56 |
+
│ └── allocations.json # Allocation results
|
| 57 |
+
└── sample_csv/
|
| 58 |
+
├── sample_students_10.csv # 10 students (5 pairs)
|
| 59 |
+
├── sample_rooms_5.csv # 5 rooms for 10 students
|
| 60 |
+
├── sample_students_26.csv # 26 students (13 pairs)
|
| 61 |
+
└── sample_rooms_13.csv # 13 rooms for 26 students
|
| 62 |
+
```
|
| 63 |
+
|
| 64 |
+
---
|
| 65 |
+
|
| 66 |
+
## 📊 CSV Format
|
| 67 |
+
|
| 68 |
+
### Students CSV
|
| 69 |
+
| Column | Type | Description |
|
| 70 |
+
|--------|------|-------------|
|
| 71 |
+
| id | int | Unique student ID (0-indexed) |
|
| 72 |
+
| name | str | Student name |
|
| 73 |
+
| cgpa | float | CGPA (0.0–10.0) |
|
| 74 |
+
| pref_roommate | str | Space-separated preferred roommate IDs |
|
| 75 |
+
| pref_room | str | Space-separated preferred room IDs |
|
| 76 |
+
|
| 77 |
+
### Rooms CSV
|
| 78 |
+
| Column | Type | Description |
|
| 79 |
+
|--------|------|-------------|
|
| 80 |
+
| room_id | int | Unique room ID (0-indexed) |
|
| 81 |
+
| room_number | str | Room label (e.g., "A101") |
|
| 82 |
+
|
| 83 |
+
---
|
| 84 |
+
|
| 85 |
+
## 🧠 Algorithm
|
| 86 |
+
|
| 87 |
+
1. **Stage 1 — Roommate Matching**: Gale-Shapley pairs students into stable roommate matches.
|
| 88 |
+
2. **Stage 2 — Room Allocation**: Pairs ranked by max CGPA select rooms via Gale-Shapley.
|
| 89 |
+
|
| 90 |
+
Higher CGPA pairs get priority in room selection.
|
app.py
ADDED
|
@@ -0,0 +1,456 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Roommate Allocation System — Streamlit UI
|
| 3 |
+
Uses the Gale-Shapley algorithm (Python implementation).
|
| 4 |
+
For the original C + MySQL version, see:
|
| 5 |
+
https://github.com/Harshwardhan-Deshmukh03/Roommate-allocation-using-Gale-Shapley-Algorithm.git
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import streamlit as st
|
| 9 |
+
import pandas as pd
|
| 10 |
+
import os, json
|
| 11 |
+
|
| 12 |
+
from db import (
|
| 13 |
+
get_all_students, get_all_rooms, get_all_allocations,
|
| 14 |
+
save_all_students, save_all_rooms, save_allocations,
|
| 15 |
+
clear_all_students, clear_all_rooms, clear_allocations,
|
| 16 |
+
add_student, delete_student, add_room, delete_room,
|
| 17 |
+
import_students_from_csv, import_rooms_from_csv,
|
| 18 |
+
get_student_count, get_room_count,
|
| 19 |
+
)
|
| 20 |
+
from gale_shapley import run_full_allocation
|
| 21 |
+
|
| 22 |
+
# ── Page Config ───────────────────────────────────────────────────────────────
|
| 23 |
+
st.set_page_config(
|
| 24 |
+
page_title="Roommate Allocation · Gale-Shapley",
|
| 25 |
+
page_icon="🏠",
|
| 26 |
+
layout="wide",
|
| 27 |
+
initial_sidebar_state="expanded",
|
| 28 |
+
)
|
| 29 |
+
|
| 30 |
+
# ── Custom CSS ────────────────────────────────────────────────────────────────
|
| 31 |
+
st.markdown("""
|
| 32 |
+
<style>
|
| 33 |
+
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap');
|
| 34 |
+
:root {
|
| 35 |
+
--accent: #6C63FF; --accent2: #00D2FF; --bg: #0E1117;
|
| 36 |
+
--card: #161B22; --card2: #1A1D29; --text: #E0E0E0;
|
| 37 |
+
--success: #00E676; --warn: #FFD600; --danger: #FF5252;
|
| 38 |
+
}
|
| 39 |
+
html, body, [class*="css"] { font-family: 'Inter', sans-serif; }
|
| 40 |
+
|
| 41 |
+
/* Hero banner */
|
| 42 |
+
.hero {
|
| 43 |
+
background: linear-gradient(135deg, #6C63FF 0%, #00D2FF 100%);
|
| 44 |
+
border-radius: 16px; padding: 2.5rem 2rem; margin-bottom: 1.5rem;
|
| 45 |
+
text-align: center; position: relative; overflow: hidden;
|
| 46 |
+
}
|
| 47 |
+
.hero::before {
|
| 48 |
+
content: ''; position: absolute; inset: 0;
|
| 49 |
+
background: radial-gradient(circle at 30% 50%, rgba(255,255,255,.12) 0%, transparent 60%);
|
| 50 |
+
}
|
| 51 |
+
.hero h1 { color: #fff; font-size: 2.2rem; font-weight: 800; margin: 0; position: relative; }
|
| 52 |
+
.hero p { color: rgba(255,255,255,.85); font-size: 1rem; margin: .5rem 0 0; position: relative; }
|
| 53 |
+
|
| 54 |
+
/* Stat cards */
|
| 55 |
+
.stat-row { display: flex; gap: 1rem; margin-bottom: 1.5rem; flex-wrap: wrap; }
|
| 56 |
+
.stat-card {
|
| 57 |
+
flex: 1; min-width: 160px; background: var(--card); border-radius: 14px;
|
| 58 |
+
padding: 1.4rem; text-align: center; border: 1px solid rgba(108,99,255,.25);
|
| 59 |
+
transition: transform .2s, box-shadow .2s;
|
| 60 |
+
}
|
| 61 |
+
.stat-card:hover { transform: translateY(-4px); box-shadow: 0 8px 24px rgba(108,99,255,.2); }
|
| 62 |
+
.stat-card .num { font-size: 2rem; font-weight: 700; background: linear-gradient(135deg,#6C63FF,#00D2FF);
|
| 63 |
+
-webkit-background-clip: text; -webkit-text-fill-color: transparent; }
|
| 64 |
+
.stat-card .lbl { color: #9CA3AF; font-size: .85rem; margin-top: .3rem; }
|
| 65 |
+
|
| 66 |
+
/* Section headers */
|
| 67 |
+
.sec-hdr { font-size: 1.35rem; font-weight: 700; margin: 1.5rem 0 .8rem;
|
| 68 |
+
padding-left: .6rem; border-left: 4px solid var(--accent); }
|
| 69 |
+
|
| 70 |
+
/* Info banner */
|
| 71 |
+
.info-banner {
|
| 72 |
+
background: linear-gradient(135deg, rgba(108,99,255,.12), rgba(0,210,255,.08));
|
| 73 |
+
border: 1px solid rgba(108,99,255,.3); border-radius: 12px;
|
| 74 |
+
padding: 1rem 1.2rem; margin: 1rem 0; font-size: .9rem; color: var(--text);
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
/* Footer */
|
| 78 |
+
.footer { text-align: center; padding: 2rem 0 1rem; color: #6B7280; font-size: .82rem; }
|
| 79 |
+
.footer a { color: var(--accent); text-decoration: none; }
|
| 80 |
+
.footer a:hover { text-decoration: underline; }
|
| 81 |
+
|
| 82 |
+
/* Table tweaks */
|
| 83 |
+
.stDataFrame { border-radius: 12px; overflow: hidden; }
|
| 84 |
+
</style>
|
| 85 |
+
""", unsafe_allow_html=True)
|
| 86 |
+
|
| 87 |
+
# ── Sidebar ───────────────────────────────────────────────────────────────────
|
| 88 |
+
with st.sidebar:
|
| 89 |
+
st.markdown("## 🧭 Navigation")
|
| 90 |
+
page = st.radio(
|
| 91 |
+
"Go to",
|
| 92 |
+
["🏠 Dashboard", "👥 Manage Students", "🚪 Manage Rooms",
|
| 93 |
+
"📂 CSV Import", "⚙️ Run Allocation", "📊 Results"],
|
| 94 |
+
label_visibility="collapsed",
|
| 95 |
+
)
|
| 96 |
+
st.markdown("---")
|
| 97 |
+
st.markdown(
|
| 98 |
+
'<div class="info-banner">'
|
| 99 |
+
'<b>🐍 Python + Streamlit Edition</b><br>'
|
| 100 |
+
'File-system storage (no MySQL needed).<br><br>'
|
| 101 |
+
'For the <b>C + MySQL</b> version:<br>'
|
| 102 |
+
'<a href="https://github.com/Harshwardhan-Deshmukh03/Roommate-allocation-using-Gale-Shapley-Algorithm.git" '
|
| 103 |
+
'target="_blank">View on GitHub ↗</a></div>',
|
| 104 |
+
unsafe_allow_html=True,
|
| 105 |
+
)
|
| 106 |
+
|
| 107 |
+
# ── Helper ────────────────────────────────────────────────────────────────────
|
| 108 |
+
def stat_cards(items):
|
| 109 |
+
cols = st.columns(len(items))
|
| 110 |
+
for col, (num, lbl) in zip(cols, items):
|
| 111 |
+
col.markdown(
|
| 112 |
+
f'<div class="stat-card"><div class="num">{num}</div>'
|
| 113 |
+
f'<div class="lbl">{lbl}</div></div>',
|
| 114 |
+
unsafe_allow_html=True,
|
| 115 |
+
)
|
| 116 |
+
|
| 117 |
+
# ══════════════════════════════════════════════════════════════════════════════
|
| 118 |
+
# PAGES
|
| 119 |
+
# ══════════════════════════════════════════════════════════════════════════════
|
| 120 |
+
|
| 121 |
+
# ── Dashboard ─────────────────────────────────────────────────────────────────
|
| 122 |
+
if page == "🏠 Dashboard":
|
| 123 |
+
st.markdown(
|
| 124 |
+
'<div class="hero"><h1>🏠 Roommate Allocation System</h1>'
|
| 125 |
+
'<p>Gale-Shapley Stable-Matching Algorithm · Python & Streamlit</p></div>',
|
| 126 |
+
unsafe_allow_html=True,
|
| 127 |
+
)
|
| 128 |
+
n_stu = get_student_count()
|
| 129 |
+
n_rm = get_room_count()
|
| 130 |
+
allocs = get_all_allocations()
|
| 131 |
+
stat_cards([
|
| 132 |
+
(n_stu, "Students"), (n_rm, "Rooms"),
|
| 133 |
+
(n_stu // 2 if n_stu else 0, "Possible Pairs"),
|
| 134 |
+
(len(allocs), "Allocations"),
|
| 135 |
+
])
|
| 136 |
+
|
| 137 |
+
st.markdown('<div class="sec-hdr">How It Works</div>', unsafe_allow_html=True)
|
| 138 |
+
c1, c2 = st.columns(2)
|
| 139 |
+
with c1:
|
| 140 |
+
st.markdown("""
|
| 141 |
+
**Stage 1 — Roommate Matching**
|
| 142 |
+
1. Each student submits an ordered preference list of roommates.
|
| 143 |
+
2. The Gale-Shapley algorithm pairs students into **stable matches**
|
| 144 |
+
(no two students would rather swap partners).
|
| 145 |
+
""")
|
| 146 |
+
with c2:
|
| 147 |
+
st.markdown("""
|
| 148 |
+
**Stage 2 — Room Allocation**
|
| 149 |
+
1. Pairs are ranked by the **higher CGPA** in each pair.
|
| 150 |
+
2. Ranked pairs select rooms via Gale-Shapley, so top performers
|
| 151 |
+
get priority for their preferred rooms.
|
| 152 |
+
""")
|
| 153 |
+
|
| 154 |
+
st.markdown('<div class="sec-hdr">Quick Start</div>', unsafe_allow_html=True)
|
| 155 |
+
st.markdown("""
|
| 156 |
+
1. **Add Students** — manually or via CSV upload.
|
| 157 |
+
2. **Add Rooms** — manually or via CSV upload.
|
| 158 |
+
3. **Run Allocation** — click one button to get stable assignments.
|
| 159 |
+
4. **View Results** — see the final roommate + room table & charts.
|
| 160 |
+
""")
|
| 161 |
+
|
| 162 |
+
st.markdown(
|
| 163 |
+
'<div class="footer">Built with Python & Streamlit · '
|
| 164 |
+
'Algorithm by Gale & Shapley (1962) · '
|
| 165 |
+
'<a href="https://github.com/Harshwardhan-Deshmukh03/Roommate-allocation-using-Gale-Shapley-Algorithm.git" '
|
| 166 |
+
'target="_blank">Original C + MySQL version</a></div>',
|
| 167 |
+
unsafe_allow_html=True,
|
| 168 |
+
)
|
| 169 |
+
|
| 170 |
+
# ── Manage Students ──────────────────────────────────────────────────────────
|
| 171 |
+
elif page == "👥 Manage Students":
|
| 172 |
+
st.markdown('<div class="sec-hdr">👥 Manage Students</div>', unsafe_allow_html=True)
|
| 173 |
+
|
| 174 |
+
students = get_all_students()
|
| 175 |
+
stat_cards([(len(students), "Total Students")])
|
| 176 |
+
|
| 177 |
+
# Show current students
|
| 178 |
+
if students:
|
| 179 |
+
df = pd.DataFrame(students)
|
| 180 |
+
df["pref_roommate"] = df["pref_roommate"].apply(lambda x: " ".join(map(str, x)))
|
| 181 |
+
df["pref_room"] = df["pref_room"].apply(lambda x: " ".join(map(str, x)))
|
| 182 |
+
st.dataframe(df, use_container_width=True, hide_index=True)
|
| 183 |
+
else:
|
| 184 |
+
st.info("No students yet. Add below or import via CSV.")
|
| 185 |
+
|
| 186 |
+
st.markdown("---")
|
| 187 |
+
st.markdown("#### ➕ Add a Student")
|
| 188 |
+
with st.form("add_student", clear_on_submit=True):
|
| 189 |
+
ac1, ac2, ac3 = st.columns(3)
|
| 190 |
+
sid = ac1.number_input("Student ID", min_value=0, step=1)
|
| 191 |
+
name = ac2.text_input("Name")
|
| 192 |
+
cgpa = ac3.number_input("CGPA", min_value=0.0, max_value=10.0, step=0.1)
|
| 193 |
+
pref_r = st.text_input("Roommate Preferences (space-separated IDs)", placeholder="5 6 7 8 9 ...")
|
| 194 |
+
pref_rm = st.text_input("Room Preferences (space-separated room IDs)", placeholder="0 1 2 3 4 ...")
|
| 195 |
+
submitted = st.form_submit_button("Add Student", type="primary")
|
| 196 |
+
if submitted:
|
| 197 |
+
if not name.strip():
|
| 198 |
+
st.error("Name cannot be empty.")
|
| 199 |
+
else:
|
| 200 |
+
try:
|
| 201 |
+
pr = [int(x) for x in pref_r.strip().split()] if pref_r.strip() else []
|
| 202 |
+
pm = [int(x) for x in pref_rm.strip().split()] if pref_rm.strip() else []
|
| 203 |
+
ok = add_student({"id": int(sid), "name": name.strip(), "cgpa": float(cgpa),
|
| 204 |
+
"pref_roommate": pr, "pref_room": pm})
|
| 205 |
+
if ok:
|
| 206 |
+
st.success(f"✅ Added **{name}** (ID {sid})")
|
| 207 |
+
st.rerun()
|
| 208 |
+
else:
|
| 209 |
+
st.error(f"Student ID {sid} already exists.")
|
| 210 |
+
except ValueError:
|
| 211 |
+
st.error("Preferences must be space-separated integers.")
|
| 212 |
+
|
| 213 |
+
# Delete
|
| 214 |
+
if students:
|
| 215 |
+
st.markdown("#### 🗑️ Remove a Student")
|
| 216 |
+
dc1, dc2 = st.columns([3, 1])
|
| 217 |
+
del_id = dc1.selectbox("Select student to remove",
|
| 218 |
+
[(s["id"], s["name"]) for s in students],
|
| 219 |
+
format_func=lambda x: f"ID {x[0]} — {x[1]}")
|
| 220 |
+
if dc2.button("Delete", type="secondary"):
|
| 221 |
+
delete_student(del_id[0])
|
| 222 |
+
st.success(f"Removed student ID {del_id[0]}")
|
| 223 |
+
st.rerun()
|
| 224 |
+
|
| 225 |
+
if st.button("🗑️ Clear ALL Students", type="secondary"):
|
| 226 |
+
clear_all_students()
|
| 227 |
+
st.warning("All students cleared.")
|
| 228 |
+
st.rerun()
|
| 229 |
+
|
| 230 |
+
# ── Manage Rooms ──────────────────────────────────────────────────────────────
|
| 231 |
+
elif page == "🚪 Manage Rooms":
|
| 232 |
+
st.markdown('<div class="sec-hdr">🚪 Manage Rooms</div>', unsafe_allow_html=True)
|
| 233 |
+
|
| 234 |
+
rooms = get_all_rooms()
|
| 235 |
+
stat_cards([(len(rooms), "Total Rooms")])
|
| 236 |
+
|
| 237 |
+
if rooms:
|
| 238 |
+
st.dataframe(pd.DataFrame(rooms), use_container_width=True, hide_index=True)
|
| 239 |
+
else:
|
| 240 |
+
st.info("No rooms yet. Add below or import via CSV.")
|
| 241 |
+
|
| 242 |
+
st.markdown("---")
|
| 243 |
+
st.markdown("#### ➕ Add a Room")
|
| 244 |
+
with st.form("add_room", clear_on_submit=True):
|
| 245 |
+
rc1, rc2 = st.columns(2)
|
| 246 |
+
rid = rc1.number_input("Room ID", min_value=0, step=1)
|
| 247 |
+
rnum = rc2.text_input("Room Number", placeholder="e.g. A101")
|
| 248 |
+
if st.form_submit_button("Add Room", type="primary"):
|
| 249 |
+
if not rnum.strip():
|
| 250 |
+
st.error("Room number cannot be empty.")
|
| 251 |
+
else:
|
| 252 |
+
ok = add_room({"room_id": int(rid), "room_number": rnum.strip()})
|
| 253 |
+
if ok:
|
| 254 |
+
st.success(f"✅ Added room **{rnum}** (ID {rid})")
|
| 255 |
+
st.rerun()
|
| 256 |
+
else:
|
| 257 |
+
st.error(f"Room ID {rid} already exists.")
|
| 258 |
+
|
| 259 |
+
if rooms:
|
| 260 |
+
st.markdown("#### 🗑️ Remove a Room")
|
| 261 |
+
drc1, drc2 = st.columns([3, 1])
|
| 262 |
+
del_rid = drc1.selectbox("Select room to remove",
|
| 263 |
+
[(r["room_id"], r["room_number"]) for r in rooms],
|
| 264 |
+
format_func=lambda x: f"ID {x[0]} — {x[1]}")
|
| 265 |
+
if drc2.button("Delete", type="secondary"):
|
| 266 |
+
delete_room(del_rid[0])
|
| 267 |
+
st.success(f"Removed room ID {del_rid[0]}")
|
| 268 |
+
st.rerun()
|
| 269 |
+
|
| 270 |
+
if st.button("🗑️ Clear ALL Rooms", type="secondary"):
|
| 271 |
+
clear_all_rooms()
|
| 272 |
+
st.warning("All rooms cleared.")
|
| 273 |
+
st.rerun()
|
| 274 |
+
|
| 275 |
+
# ── CSV Import ────────────────────────────────────────────────────────────────
|
| 276 |
+
elif page == "📂 CSV Import":
|
| 277 |
+
st.markdown('<div class="sec-hdr">📂 CSV Import</div>', unsafe_allow_html=True)
|
| 278 |
+
st.markdown(
|
| 279 |
+
'<div class="info-banner">Upload CSV files to bulk-import students and rooms. '
|
| 280 |
+
'This is useful when manual entry is tedious or when the allocation is complex.</div>',
|
| 281 |
+
unsafe_allow_html=True,
|
| 282 |
+
)
|
| 283 |
+
|
| 284 |
+
tab1, tab2, tab3 = st.tabs(["📥 Import Students", "📥 Import Rooms", "📄 Sample CSVs"])
|
| 285 |
+
|
| 286 |
+
with tab1:
|
| 287 |
+
st.markdown("**Expected columns:** `id, name, cgpa, pref_roommate, pref_room`")
|
| 288 |
+
st.caption("Preferences are space-separated integer IDs.")
|
| 289 |
+
f = st.file_uploader("Upload Students CSV", type=["csv"], key="stu_csv")
|
| 290 |
+
if f:
|
| 291 |
+
content = f.getvalue().decode("utf-8")
|
| 292 |
+
st.markdown("**Preview:**")
|
| 293 |
+
st.dataframe(pd.read_csv(f), use_container_width=True, hide_index=True)
|
| 294 |
+
f.seek(0)
|
| 295 |
+
if st.button("✅ Import Students", type="primary"):
|
| 296 |
+
cnt, errs = import_students_from_csv(content)
|
| 297 |
+
if errs:
|
| 298 |
+
for e in errs:
|
| 299 |
+
st.error(e)
|
| 300 |
+
st.success(f"Imported **{cnt}** students.")
|
| 301 |
+
st.rerun()
|
| 302 |
+
|
| 303 |
+
with tab2:
|
| 304 |
+
st.markdown("**Expected columns:** `room_id, room_number`")
|
| 305 |
+
f2 = st.file_uploader("Upload Rooms CSV", type=["csv"], key="room_csv")
|
| 306 |
+
if f2:
|
| 307 |
+
content2 = f2.getvalue().decode("utf-8")
|
| 308 |
+
st.markdown("**Preview:**")
|
| 309 |
+
st.dataframe(pd.read_csv(f2), use_container_width=True, hide_index=True)
|
| 310 |
+
f2.seek(0)
|
| 311 |
+
if st.button("✅ Import Rooms", type="primary"):
|
| 312 |
+
cnt2, errs2 = import_rooms_from_csv(content2)
|
| 313 |
+
if errs2:
|
| 314 |
+
for e in errs2:
|
| 315 |
+
st.error(e)
|
| 316 |
+
st.success(f"Imported **{cnt2}** rooms.")
|
| 317 |
+
st.rerun()
|
| 318 |
+
|
| 319 |
+
with tab3:
|
| 320 |
+
st.markdown("#### Sample CSV Files")
|
| 321 |
+
st.markdown("Download these to understand the expected format, then modify and re-upload.")
|
| 322 |
+
sample_dir = os.path.join(os.path.dirname(__file__), "sample_csv")
|
| 323 |
+
for fname in sorted(os.listdir(sample_dir)):
|
| 324 |
+
fpath = os.path.join(sample_dir, fname)
|
| 325 |
+
with open(fpath, "r") as sf:
|
| 326 |
+
st.download_button(f"⬇️ {fname}", sf.read(), file_name=fname, mime="text/csv")
|
| 327 |
+
with open(fpath, "r") as sf:
|
| 328 |
+
st.markdown(f"**`{fname}` preview:**")
|
| 329 |
+
st.code(sf.read(), language="csv")
|
| 330 |
+
|
| 331 |
+
# ── Run Allocation ────────────────────────────────────────────────────────────
|
| 332 |
+
elif page == "⚙️ Run Allocation":
|
| 333 |
+
st.markdown('<div class="sec-hdr">⚙️ Run Allocation</div>', unsafe_allow_html=True)
|
| 334 |
+
|
| 335 |
+
students = get_all_students()
|
| 336 |
+
rooms = get_all_rooms()
|
| 337 |
+
n_stu = len(students)
|
| 338 |
+
n_rm = len(rooms)
|
| 339 |
+
|
| 340 |
+
stat_cards([(n_stu, "Students"), (n_rm, "Rooms"), (n_stu // 2, "Pairs Needed")])
|
| 341 |
+
|
| 342 |
+
# Validation
|
| 343 |
+
issues = []
|
| 344 |
+
if n_stu < 2:
|
| 345 |
+
issues.append("Need at least 2 students.")
|
| 346 |
+
if n_stu % 2 != 0:
|
| 347 |
+
issues.append("Number of students must be even.")
|
| 348 |
+
if n_rm < n_stu // 2:
|
| 349 |
+
issues.append(f"Need at least {n_stu // 2} rooms (have {n_rm}).")
|
| 350 |
+
|
| 351 |
+
if issues:
|
| 352 |
+
for iss in issues:
|
| 353 |
+
st.error(f"❌ {iss}")
|
| 354 |
+
st.info("Fix the above issues before running the algorithm.")
|
| 355 |
+
else:
|
| 356 |
+
st.success("✅ All checks passed — ready to allocate!")
|
| 357 |
+
st.markdown(
|
| 358 |
+
'<div class="info-banner">'
|
| 359 |
+
'<b>Stage 1:</b> Gale-Shapley matches students into roommate pairs.<br>'
|
| 360 |
+
'<b>Stage 2:</b> Pairs ranked by CGPA select rooms via Gale-Shapley.</div>',
|
| 361 |
+
unsafe_allow_html=True,
|
| 362 |
+
)
|
| 363 |
+
|
| 364 |
+
if st.button("🚀 Run Gale-Shapley Allocation", type="primary", use_container_width=True):
|
| 365 |
+
with st.spinner("Running Gale-Shapley algorithm..."):
|
| 366 |
+
try:
|
| 367 |
+
allocs = run_full_allocation(students, rooms)
|
| 368 |
+
save_allocations(allocs)
|
| 369 |
+
st.success(f"🎉 Allocation complete — **{len(allocs)} pairs** assigned!")
|
| 370 |
+
st.balloons()
|
| 371 |
+
|
| 372 |
+
df = pd.DataFrame(allocs)
|
| 373 |
+
display_cols = ["roommate1_name", "roommate1_cgpa",
|
| 374 |
+
"roommate2_name", "roommate2_cgpa",
|
| 375 |
+
"room_number", "pair_max_cgpa"]
|
| 376 |
+
st.dataframe(df[display_cols], use_container_width=True, hide_index=True)
|
| 377 |
+
except Exception as e:
|
| 378 |
+
st.error(f"Allocation failed: {e}")
|
| 379 |
+
st.info("Try importing data via CSV if manual entry is causing issues.")
|
| 380 |
+
|
| 381 |
+
# ── Results ───────────────────────────────────────────────────────────────────
|
| 382 |
+
elif page == "📊 Results":
|
| 383 |
+
st.markdown('<div class="sec-hdr">📊 Allocation Results</div>', unsafe_allow_html=True)
|
| 384 |
+
|
| 385 |
+
allocs = get_all_allocations()
|
| 386 |
+
if not allocs:
|
| 387 |
+
st.info("No allocation results yet. Go to **⚙️ Run Allocation** first.")
|
| 388 |
+
else:
|
| 389 |
+
df = pd.DataFrame(allocs)
|
| 390 |
+
|
| 391 |
+
stat_cards([
|
| 392 |
+
(len(df), "Pairs Allocated"),
|
| 393 |
+
(f"{df['pair_max_cgpa'].mean():.2f}", "Avg Pair CGPA"),
|
| 394 |
+
(df["room_number"].nunique(), "Rooms Used"),
|
| 395 |
+
])
|
| 396 |
+
|
| 397 |
+
# Main table
|
| 398 |
+
st.markdown("#### 📋 Final Allocation Table")
|
| 399 |
+
display = df[["roommate1_name", "roommate1_cgpa",
|
| 400 |
+
"roommate2_name", "roommate2_cgpa",
|
| 401 |
+
"room_number", "pair_max_cgpa"]].copy()
|
| 402 |
+
display.columns = ["Roommate 1", "CGPA 1", "Roommate 2", "CGPA 2", "Room", "Pair CGPA"]
|
| 403 |
+
st.dataframe(display, use_container_width=True, hide_index=True)
|
| 404 |
+
|
| 405 |
+
# Download
|
| 406 |
+
csv_out = display.to_csv(index=False)
|
| 407 |
+
st.download_button("⬇️ Download Results CSV", csv_out,
|
| 408 |
+
file_name="allocation_results.csv", mime="text/csv")
|
| 409 |
+
|
| 410 |
+
# Charts
|
| 411 |
+
st.markdown("#### 📈 CGPA Distribution")
|
| 412 |
+
import plotly.express as px
|
| 413 |
+
|
| 414 |
+
fig = px.bar(
|
| 415 |
+
display, x="Room", y="Pair CGPA",
|
| 416 |
+
color="Pair CGPA",
|
| 417 |
+
color_continuous_scale=["#6C63FF", "#00D2FF"],
|
| 418 |
+
title="Pair CGPA by Room Assignment",
|
| 419 |
+
)
|
| 420 |
+
fig.update_layout(
|
| 421 |
+
plot_bgcolor="rgba(0,0,0,0)", paper_bgcolor="rgba(0,0,0,0)",
|
| 422 |
+
font_color="#E0E0E0", title_font_size=16,
|
| 423 |
+
)
|
| 424 |
+
st.plotly_chart(fig, use_container_width=True)
|
| 425 |
+
|
| 426 |
+
# Roommate comparison
|
| 427 |
+
st.markdown("#### 🤝 Roommate CGPA Comparison")
|
| 428 |
+
comp = pd.DataFrame({
|
| 429 |
+
"Room": display["Room"],
|
| 430 |
+
"Roommate 1": display["CGPA 1"],
|
| 431 |
+
"Roommate 2": display["CGPA 2"],
|
| 432 |
+
})
|
| 433 |
+
fig2 = px.bar(
|
| 434 |
+
comp.melt(id_vars="Room", var_name="Roommate", value_name="CGPA"),
|
| 435 |
+
x="Room", y="CGPA", color="Roommate", barmode="group",
|
| 436 |
+
color_discrete_sequence=["#6C63FF", "#00D2FF"],
|
| 437 |
+
title="CGPA Comparison per Room",
|
| 438 |
+
)
|
| 439 |
+
fig2.update_layout(
|
| 440 |
+
plot_bgcolor="rgba(0,0,0,0)", paper_bgcolor="rgba(0,0,0,0)",
|
| 441 |
+
font_color="#E0E0E0", title_font_size=16,
|
| 442 |
+
)
|
| 443 |
+
st.plotly_chart(fig2, use_container_width=True)
|
| 444 |
+
|
| 445 |
+
if st.button("🗑️ Clear Results", type="secondary"):
|
| 446 |
+
clear_allocations()
|
| 447 |
+
st.warning("Results cleared.")
|
| 448 |
+
st.rerun()
|
| 449 |
+
|
| 450 |
+
st.markdown(
|
| 451 |
+
'<div class="footer">Built with <b>Python & Streamlit</b> · '
|
| 452 |
+
'For the <b>C + MySQL</b> version, visit '
|
| 453 |
+
'<a href="https://github.com/Harshwardhan-Deshmukh03/Roommate-allocation-using-Gale-Shapley-Algorithm.git" '
|
| 454 |
+
'target="_blank">GitHub ↗</a></div>',
|
| 455 |
+
unsafe_allow_html=True,
|
| 456 |
+
)
|
data/allocations.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
{
|
| 3 |
+
"roommate1_id": 2,
|
| 4 |
+
"roommate1_name": "Charlie",
|
| 5 |
+
"roommate1_cgpa": 9.5,
|
| 6 |
+
"roommate2_id": 7,
|
| 7 |
+
"roommate2_name": "Henry",
|
| 8 |
+
"roommate2_cgpa": 8.6,
|
| 9 |
+
"room_number": "A103",
|
| 10 |
+
"room_id": 2,
|
| 11 |
+
"pair_max_cgpa": 9.5
|
| 12 |
+
},
|
| 13 |
+
{
|
| 14 |
+
"roommate1_id": 1,
|
| 15 |
+
"roommate1_name": "Bob",
|
| 16 |
+
"roommate1_cgpa": 8.9,
|
| 17 |
+
"roommate2_id": 6,
|
| 18 |
+
"roommate2_name": "Grace",
|
| 19 |
+
"roommate2_cgpa": 9.3,
|
| 20 |
+
"room_number": "A102",
|
| 21 |
+
"room_id": 1,
|
| 22 |
+
"pair_max_cgpa": 9.3
|
| 23 |
+
},
|
| 24 |
+
{
|
| 25 |
+
"roommate1_id": 0,
|
| 26 |
+
"roommate1_name": "Alice",
|
| 27 |
+
"roommate1_cgpa": 9.2,
|
| 28 |
+
"roommate2_id": 5,
|
| 29 |
+
"roommate2_name": "Frank",
|
| 30 |
+
"roommate2_cgpa": 8.8,
|
| 31 |
+
"room_number": "A101",
|
| 32 |
+
"room_id": 0,
|
| 33 |
+
"pair_max_cgpa": 9.2
|
| 34 |
+
},
|
| 35 |
+
{
|
| 36 |
+
"roommate1_id": 3,
|
| 37 |
+
"roommate1_name": "Diana",
|
| 38 |
+
"roommate1_cgpa": 8.7,
|
| 39 |
+
"roommate2_id": 8,
|
| 40 |
+
"roommate2_name": "Ivy",
|
| 41 |
+
"roommate2_cgpa": 9.1,
|
| 42 |
+
"room_number": "B201",
|
| 43 |
+
"room_id": 3,
|
| 44 |
+
"pair_max_cgpa": 9.1
|
| 45 |
+
},
|
| 46 |
+
{
|
| 47 |
+
"roommate1_id": 4,
|
| 48 |
+
"roommate1_name": "Eve",
|
| 49 |
+
"roommate1_cgpa": 9.0,
|
| 50 |
+
"roommate2_id": 9,
|
| 51 |
+
"roommate2_name": "Jack",
|
| 52 |
+
"roommate2_cgpa": 8.5,
|
| 53 |
+
"room_number": "B202",
|
| 54 |
+
"room_id": 4,
|
| 55 |
+
"pair_max_cgpa": 9.0
|
| 56 |
+
}
|
| 57 |
+
]
|
data/rooms.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
[]
|
data/students.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
[]
|
db.py
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
File-System Database Layer
|
| 3 |
+
|
| 4 |
+
Replaces MySQL with JSON file storage for students, rooms, and allocations.
|
| 5 |
+
Mirrors the original MySQL schema:
|
| 6 |
+
- main table → data/students.json
|
| 7 |
+
- RoomNum → data/rooms.json
|
| 8 |
+
- results → data/allocations.json
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
import json
|
| 12 |
+
import os
|
| 13 |
+
from typing import List, Optional
|
| 14 |
+
|
| 15 |
+
# ─── Paths ────────────────────────────────────────────────────────────────────
|
| 16 |
+
|
| 17 |
+
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
| 18 |
+
DATA_DIR = os.path.join(BASE_DIR, "data")
|
| 19 |
+
|
| 20 |
+
STUDENTS_FILE = os.path.join(DATA_DIR, "students.json")
|
| 21 |
+
ROOMS_FILE = os.path.join(DATA_DIR, "rooms.json")
|
| 22 |
+
ALLOCATIONS_FILE = os.path.join(DATA_DIR, "allocations.json")
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def _ensure_data_dir():
|
| 26 |
+
"""Create data directory if it doesn't exist."""
|
| 27 |
+
os.makedirs(DATA_DIR, exist_ok=True)
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def _read_json(filepath: str) -> list:
|
| 31 |
+
"""Read a JSON file and return the parsed list."""
|
| 32 |
+
_ensure_data_dir()
|
| 33 |
+
if not os.path.exists(filepath):
|
| 34 |
+
with open(filepath, "w") as f:
|
| 35 |
+
json.dump([], f)
|
| 36 |
+
return []
|
| 37 |
+
try:
|
| 38 |
+
with open(filepath, "r") as f:
|
| 39 |
+
data = json.load(f)
|
| 40 |
+
return data if isinstance(data, list) else []
|
| 41 |
+
except (json.JSONDecodeError, IOError):
|
| 42 |
+
return []
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def _write_json(filepath: str, data: list):
|
| 46 |
+
"""Write a list to a JSON file."""
|
| 47 |
+
_ensure_data_dir()
|
| 48 |
+
with open(filepath, "w") as f:
|
| 49 |
+
json.dump(data, f, indent=2)
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
# ─── Student Operations ──────────────────────────────────────────────────────
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def get_all_students() -> List[dict]:
|
| 56 |
+
"""Get all students from the database."""
|
| 57 |
+
return _read_json(STUDENTS_FILE)
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
def get_student_by_id(student_id: int) -> Optional[dict]:
|
| 61 |
+
"""Get a specific student by ID."""
|
| 62 |
+
students = get_all_students()
|
| 63 |
+
for s in students:
|
| 64 |
+
if s["id"] == student_id:
|
| 65 |
+
return s
|
| 66 |
+
return None
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def add_student(student: dict) -> bool:
|
| 70 |
+
"""
|
| 71 |
+
Add a new student.
|
| 72 |
+
|
| 73 |
+
student dict should contain:
|
| 74 |
+
- id: int
|
| 75 |
+
- name: str
|
| 76 |
+
- cgpa: float
|
| 77 |
+
- pref_roommate: List[int] (ordered list of preferred roommate IDs)
|
| 78 |
+
- pref_room: List[int] (ordered list of preferred room IDs)
|
| 79 |
+
"""
|
| 80 |
+
students = get_all_students()
|
| 81 |
+
# Check for duplicate ID
|
| 82 |
+
for s in students:
|
| 83 |
+
if s["id"] == student["id"]:
|
| 84 |
+
return False
|
| 85 |
+
students.append(student)
|
| 86 |
+
_write_json(STUDENTS_FILE, students)
|
| 87 |
+
return True
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
def update_student(student_id: int, updated_data: dict) -> bool:
|
| 91 |
+
"""Update an existing student's data."""
|
| 92 |
+
students = get_all_students()
|
| 93 |
+
for i, s in enumerate(students):
|
| 94 |
+
if s["id"] == student_id:
|
| 95 |
+
students[i].update(updated_data)
|
| 96 |
+
_write_json(STUDENTS_FILE, students)
|
| 97 |
+
return True
|
| 98 |
+
return False
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
def delete_student(student_id: int) -> bool:
|
| 102 |
+
"""Delete a student by ID."""
|
| 103 |
+
students = get_all_students()
|
| 104 |
+
new_students = [s for s in students if s["id"] != student_id]
|
| 105 |
+
if len(new_students) == len(students):
|
| 106 |
+
return False
|
| 107 |
+
_write_json(STUDENTS_FILE, new_students)
|
| 108 |
+
return True
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
def clear_all_students():
|
| 112 |
+
"""Remove all students."""
|
| 113 |
+
_write_json(STUDENTS_FILE, [])
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
def save_all_students(students: List[dict]):
|
| 117 |
+
"""Overwrite the entire students database."""
|
| 118 |
+
_write_json(STUDENTS_FILE, students)
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
def get_student_count() -> int:
|
| 122 |
+
"""Get the total number of students."""
|
| 123 |
+
return len(get_all_students())
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
# ─── Room Operations ─────────────────────────────────────────────────────────
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
def get_all_rooms() -> List[dict]:
|
| 130 |
+
"""Get all rooms from the database."""
|
| 131 |
+
return _read_json(ROOMS_FILE)
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
def add_room(room: dict) -> bool:
|
| 135 |
+
"""
|
| 136 |
+
Add a new room.
|
| 137 |
+
|
| 138 |
+
room dict should contain:
|
| 139 |
+
- room_id: int
|
| 140 |
+
- room_number: str (e.g., 'A101')
|
| 141 |
+
"""
|
| 142 |
+
rooms = get_all_rooms()
|
| 143 |
+
for r in rooms:
|
| 144 |
+
if r["room_id"] == room["room_id"]:
|
| 145 |
+
return False
|
| 146 |
+
rooms.append(room)
|
| 147 |
+
_write_json(ROOMS_FILE, rooms)
|
| 148 |
+
return True
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
def delete_room(room_id: int) -> bool:
|
| 152 |
+
"""Delete a room by ID."""
|
| 153 |
+
rooms = get_all_rooms()
|
| 154 |
+
new_rooms = [r for r in rooms if r["room_id"] != room_id]
|
| 155 |
+
if len(new_rooms) == len(rooms):
|
| 156 |
+
return False
|
| 157 |
+
_write_json(ROOMS_FILE, new_rooms)
|
| 158 |
+
return True
|
| 159 |
+
|
| 160 |
+
|
| 161 |
+
def clear_all_rooms():
|
| 162 |
+
"""Remove all rooms."""
|
| 163 |
+
_write_json(ROOMS_FILE, [])
|
| 164 |
+
|
| 165 |
+
|
| 166 |
+
def save_all_rooms(rooms: List[dict]):
|
| 167 |
+
"""Overwrite the entire rooms database."""
|
| 168 |
+
_write_json(ROOMS_FILE, rooms)
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
def get_room_count() -> int:
|
| 172 |
+
"""Get the total number of rooms."""
|
| 173 |
+
return len(get_all_rooms())
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
# ─── Allocation Operations ───────────────────────────────────────────────────
|
| 177 |
+
|
| 178 |
+
|
| 179 |
+
def get_all_allocations() -> List[dict]:
|
| 180 |
+
"""Get all allocation results."""
|
| 181 |
+
return _read_json(ALLOCATIONS_FILE)
|
| 182 |
+
|
| 183 |
+
|
| 184 |
+
def save_allocations(allocations: List[dict]):
|
| 185 |
+
"""Save allocation results (overwrites previous results)."""
|
| 186 |
+
_write_json(ALLOCATIONS_FILE, allocations)
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
def clear_allocations():
|
| 190 |
+
"""Clear all allocation results."""
|
| 191 |
+
_write_json(ALLOCATIONS_FILE, [])
|
| 192 |
+
|
| 193 |
+
|
| 194 |
+
# ─── Bulk Import from CSV ────────────────────────────────────────────────────
|
| 195 |
+
|
| 196 |
+
|
| 197 |
+
def import_students_from_csv(csv_content: str) -> tuple:
|
| 198 |
+
"""
|
| 199 |
+
Import students from CSV content.
|
| 200 |
+
|
| 201 |
+
Expected columns: id, name, cgpa, pref_roommate, pref_room
|
| 202 |
+
pref_roommate and pref_room should be space-separated integers.
|
| 203 |
+
|
| 204 |
+
Returns:
|
| 205 |
+
(success_count, error_messages)
|
| 206 |
+
"""
|
| 207 |
+
import csv
|
| 208 |
+
import io
|
| 209 |
+
|
| 210 |
+
reader = csv.DictReader(io.StringIO(csv_content))
|
| 211 |
+
students = []
|
| 212 |
+
errors = []
|
| 213 |
+
|
| 214 |
+
for row_num, row in enumerate(reader, start=2):
|
| 215 |
+
try:
|
| 216 |
+
student = {
|
| 217 |
+
"id": int(row["id"].strip()),
|
| 218 |
+
"name": row["name"].strip(),
|
| 219 |
+
"cgpa": float(row["cgpa"].strip()),
|
| 220 |
+
"pref_roommate": [
|
| 221 |
+
int(x) for x in row["pref_roommate"].strip().split()
|
| 222 |
+
],
|
| 223 |
+
"pref_room": [
|
| 224 |
+
int(x) for x in row["pref_room"].strip().split()
|
| 225 |
+
],
|
| 226 |
+
}
|
| 227 |
+
students.append(student)
|
| 228 |
+
except (KeyError, ValueError) as e:
|
| 229 |
+
errors.append(f"Row {row_num}: {str(e)}")
|
| 230 |
+
|
| 231 |
+
if students:
|
| 232 |
+
save_all_students(students)
|
| 233 |
+
|
| 234 |
+
return len(students), errors
|
| 235 |
+
|
| 236 |
+
|
| 237 |
+
def import_rooms_from_csv(csv_content: str) -> tuple:
|
| 238 |
+
"""
|
| 239 |
+
Import rooms from CSV content.
|
| 240 |
+
|
| 241 |
+
Expected columns: room_id, room_number
|
| 242 |
+
|
| 243 |
+
Returns:
|
| 244 |
+
(success_count, error_messages)
|
| 245 |
+
"""
|
| 246 |
+
import csv
|
| 247 |
+
import io
|
| 248 |
+
|
| 249 |
+
reader = csv.DictReader(io.StringIO(csv_content))
|
| 250 |
+
rooms = []
|
| 251 |
+
errors = []
|
| 252 |
+
|
| 253 |
+
for row_num, row in enumerate(reader, start=2):
|
| 254 |
+
try:
|
| 255 |
+
room = {
|
| 256 |
+
"room_id": int(row["room_id"].strip()),
|
| 257 |
+
"room_number": row["room_number"].strip(),
|
| 258 |
+
}
|
| 259 |
+
rooms.append(room)
|
| 260 |
+
except (KeyError, ValueError) as e:
|
| 261 |
+
errors.append(f"Row {row_num}: {str(e)}")
|
| 262 |
+
|
| 263 |
+
if rooms:
|
| 264 |
+
save_all_rooms(rooms)
|
| 265 |
+
|
| 266 |
+
return len(rooms), errors
|
gale_shapley.py
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Gale-Shapley Algorithm Implementation
|
| 3 |
+
|
| 4 |
+
Python port of the C implementation from the original project.
|
| 5 |
+
Implements the Stable Marriage Problem for roommate matching and room allocation.
|
| 6 |
+
|
| 7 |
+
Original C version: https://github.com/Harshwardhan-Deshmukh03/Roommate-allocation-using-Gale-Shapley-Algorithm.git
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
from typing import Dict, List, Tuple, Optional
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def gale_shapley(pref_matrix: List[List[int]], n: int) -> Dict[int, int]:
|
| 14 |
+
"""
|
| 15 |
+
Run the Gale-Shapley algorithm for stable matching.
|
| 16 |
+
|
| 17 |
+
This is a direct Python translation of the C galeShapley() function.
|
| 18 |
+
Students 0..n-1 are "proposers" and students n..2n-1 are "acceptors".
|
| 19 |
+
|
| 20 |
+
Args:
|
| 21 |
+
pref_matrix: 2D preference list. pref_matrix[i] is the ordered preference
|
| 22 |
+
list for person i. For proposers (0..n-1), preferences are
|
| 23 |
+
indices in the acceptor range (n..2n-1). For acceptors,
|
| 24 |
+
preferences are proposer indices (0..n-1).
|
| 25 |
+
n: Number of pairs (half the total participants).
|
| 26 |
+
|
| 27 |
+
Returns:
|
| 28 |
+
Dictionary mapping each person to their matched partner.
|
| 29 |
+
"""
|
| 30 |
+
# match[i] = partner of person i, -1 means unmatched
|
| 31 |
+
match = [-1] * (2 * n)
|
| 32 |
+
free_count = n
|
| 33 |
+
|
| 34 |
+
# Track which preference index each proposer is up to
|
| 35 |
+
next_proposal = [0] * n
|
| 36 |
+
|
| 37 |
+
while free_count > 0:
|
| 38 |
+
# Find the first free proposer
|
| 39 |
+
m = -1
|
| 40 |
+
for i in range(n):
|
| 41 |
+
if match[i] == -1:
|
| 42 |
+
m = i
|
| 43 |
+
break
|
| 44 |
+
|
| 45 |
+
if m == -1:
|
| 46 |
+
break
|
| 47 |
+
|
| 48 |
+
# Proposer m proposes to their next preferred acceptor
|
| 49 |
+
while next_proposal[m] < n:
|
| 50 |
+
w = pref_matrix[m][next_proposal[m]]
|
| 51 |
+
next_proposal[m] += 1
|
| 52 |
+
|
| 53 |
+
if match[w] == -1:
|
| 54 |
+
# w is free, accept the proposal
|
| 55 |
+
match[w] = m
|
| 56 |
+
match[m] = w
|
| 57 |
+
free_count -= 1
|
| 58 |
+
break
|
| 59 |
+
else:
|
| 60 |
+
# w is already matched, check if w prefers m over current partner
|
| 61 |
+
m1 = match[w]
|
| 62 |
+
if _prefers_new_over_current(pref_matrix, w, m, m1):
|
| 63 |
+
# w prefers m over m1 — switch
|
| 64 |
+
match[w] = m
|
| 65 |
+
match[m] = w
|
| 66 |
+
match[m1] = -1 # m1 becomes free
|
| 67 |
+
break
|
| 68 |
+
# else w rejects m, m tries next preference
|
| 69 |
+
|
| 70 |
+
return match
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def _prefers_new_over_current(
|
| 74 |
+
pref_matrix: List[List[int]], w: int, m_new: int, m_current: int
|
| 75 |
+
) -> bool:
|
| 76 |
+
"""
|
| 77 |
+
Check if acceptor w prefers m_new over m_current.
|
| 78 |
+
|
| 79 |
+
Mirrors the C function wPrefersm1Overm() but with clearer naming.
|
| 80 |
+
Returns True if w prefers m_new over m_current.
|
| 81 |
+
"""
|
| 82 |
+
for pref in pref_matrix[w]:
|
| 83 |
+
if pref == m_new:
|
| 84 |
+
return True
|
| 85 |
+
if pref == m_current:
|
| 86 |
+
return False
|
| 87 |
+
return False
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
def run_roommate_matching(students: List[dict]) -> List[Tuple[int, int]]:
|
| 91 |
+
"""
|
| 92 |
+
Stage 1: Match students into roommate pairs using Gale-Shapley.
|
| 93 |
+
|
| 94 |
+
Args:
|
| 95 |
+
students: List of student dicts with 'id', 'name', 'cgpa', 'pref_roommate'.
|
| 96 |
+
|
| 97 |
+
Returns:
|
| 98 |
+
List of (student_id_1, student_id_2) roommate pairs.
|
| 99 |
+
"""
|
| 100 |
+
n_students = len(students)
|
| 101 |
+
if n_students < 2 or n_students % 2 != 0:
|
| 102 |
+
raise ValueError(
|
| 103 |
+
f"Need an even number of students (≥ 2). Got {n_students}."
|
| 104 |
+
)
|
| 105 |
+
|
| 106 |
+
n = n_students // 2 # Number of pairs
|
| 107 |
+
|
| 108 |
+
# Build preference matrix: proposers are 0..n-1, acceptors are n..2n-1
|
| 109 |
+
# Each student's pref_roommate contains IDs of other students they prefer
|
| 110 |
+
pref_matrix = []
|
| 111 |
+
for student in students:
|
| 112 |
+
prefs = student["pref_roommate"]
|
| 113 |
+
# Filter out only valid preferences (should be IDs of other students)
|
| 114 |
+
# For proposers (0..n-1): their prefs should reference acceptors (n..2n-1)
|
| 115 |
+
# For acceptors (n..2n-1): their prefs should reference proposers (0..n-1)
|
| 116 |
+
sid = student["id"]
|
| 117 |
+
if sid < n:
|
| 118 |
+
# Proposer: filter prefs to only include acceptor IDs (n..2n-1)
|
| 119 |
+
filtered = [p for p in prefs if n <= p < 2 * n]
|
| 120 |
+
else:
|
| 121 |
+
# Acceptor: filter prefs to only include proposer IDs (0..n-1)
|
| 122 |
+
filtered = [p for p in prefs if 0 <= p < n]
|
| 123 |
+
pref_matrix.append(filtered)
|
| 124 |
+
|
| 125 |
+
# Run Gale-Shapley
|
| 126 |
+
match = gale_shapley(pref_matrix, n)
|
| 127 |
+
|
| 128 |
+
# Extract unique pairs (only from proposer side to avoid duplicates)
|
| 129 |
+
pairs = []
|
| 130 |
+
seen = set()
|
| 131 |
+
for i in range(n):
|
| 132 |
+
partner = match[i]
|
| 133 |
+
if partner != -1 and i not in seen and partner not in seen:
|
| 134 |
+
pairs.append((i, partner))
|
| 135 |
+
seen.add(i)
|
| 136 |
+
seen.add(partner)
|
| 137 |
+
|
| 138 |
+
return pairs
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
def run_room_allocation(
|
| 142 |
+
students: List[dict],
|
| 143 |
+
roommate_pairs: List[Tuple[int, int]],
|
| 144 |
+
rooms: List[dict],
|
| 145 |
+
) -> List[dict]:
|
| 146 |
+
"""
|
| 147 |
+
Stage 2: Allocate rooms to roommate pairs based on CGPA ranking.
|
| 148 |
+
|
| 149 |
+
Higher CGPA pairs get priority in room selection (Gale-Shapley on rooms).
|
| 150 |
+
|
| 151 |
+
Args:
|
| 152 |
+
students: List of student dicts with 'id', 'name', 'cgpa', 'pref_room'.
|
| 153 |
+
roommate_pairs: List of (id1, id2) roommate pairs from Stage 1.
|
| 154 |
+
rooms: List of room dicts with 'room_id' and 'room_number'.
|
| 155 |
+
|
| 156 |
+
Returns:
|
| 157 |
+
List of allocation dicts: {roommate1, roommate2, room_number, room_id, pair_cgpa}
|
| 158 |
+
"""
|
| 159 |
+
n = len(roommate_pairs)
|
| 160 |
+
n_rooms = len(rooms)
|
| 161 |
+
|
| 162 |
+
if n_rooms < n:
|
| 163 |
+
raise ValueError(
|
| 164 |
+
f"Not enough rooms ({n_rooms}) for {n} pairs."
|
| 165 |
+
)
|
| 166 |
+
|
| 167 |
+
# Build student lookup
|
| 168 |
+
student_map = {s["id"]: s for s in students}
|
| 169 |
+
|
| 170 |
+
# Rank pairs by the higher CGPA in each pair (descending)
|
| 171 |
+
pair_cgpas = []
|
| 172 |
+
for id1, id2 in roommate_pairs:
|
| 173 |
+
cgpa1 = student_map[id1]["cgpa"]
|
| 174 |
+
cgpa2 = student_map[id2]["cgpa"]
|
| 175 |
+
max_cgpa = max(cgpa1, cgpa2)
|
| 176 |
+
pair_cgpas.append((max_cgpa, id1, id2))
|
| 177 |
+
|
| 178 |
+
# Sort descending by max CGPA
|
| 179 |
+
pair_cgpas.sort(key=lambda x: x[0], reverse=True)
|
| 180 |
+
|
| 181 |
+
# Build room preference matrix for Gale-Shapley
|
| 182 |
+
# Proposers: ranked pairs (0..n-1) → these map to pair_cgpas indices
|
| 183 |
+
# Acceptors: rooms (n..2n-1) → these map to rooms indices (offset by n)
|
| 184 |
+
room_id_to_index = {rooms[j]["room_id"]: j + n for j in range(n)}
|
| 185 |
+
|
| 186 |
+
pref_matrix = [[] for _ in range(2 * n)]
|
| 187 |
+
|
| 188 |
+
# For each ranked pair, get the higher-CGPA student's room preferences
|
| 189 |
+
for rank_idx, (max_cgpa, id1, id2) in enumerate(pair_cgpas):
|
| 190 |
+
# Use the higher CGPA student's room preferences
|
| 191 |
+
if student_map[id1]["cgpa"] >= student_map[id2]["cgpa"]:
|
| 192 |
+
prefs = student_map[id1].get("pref_room", [])
|
| 193 |
+
else:
|
| 194 |
+
prefs = student_map[id2].get("pref_room", [])
|
| 195 |
+
|
| 196 |
+
# Map room IDs to room indices (offset by n for acceptor range)
|
| 197 |
+
mapped_prefs = []
|
| 198 |
+
for room_id in prefs:
|
| 199 |
+
if room_id in room_id_to_index:
|
| 200 |
+
mapped_prefs.append(room_id_to_index[room_id])
|
| 201 |
+
pref_matrix[rank_idx] = mapped_prefs
|
| 202 |
+
|
| 203 |
+
# Rooms accept any student in order (no real preference)
|
| 204 |
+
for j in range(n):
|
| 205 |
+
pref_matrix[n + j] = list(range(n))
|
| 206 |
+
|
| 207 |
+
# Run Gale-Shapley for room allocation
|
| 208 |
+
match = gale_shapley(pref_matrix, n)
|
| 209 |
+
|
| 210 |
+
# Build index-to-room mapping
|
| 211 |
+
index_to_room = {j + n: rooms[j] for j in range(n)}
|
| 212 |
+
|
| 213 |
+
# Build final allocation
|
| 214 |
+
allocations = []
|
| 215 |
+
for rank_idx, (max_cgpa, id1, id2) in enumerate(pair_cgpas):
|
| 216 |
+
room_index = match[rank_idx]
|
| 217 |
+
room = index_to_room.get(room_index, {"room_number": "N/A", "room_id": -1})
|
| 218 |
+
|
| 219 |
+
allocations.append({
|
| 220 |
+
"roommate1_id": id1,
|
| 221 |
+
"roommate1_name": student_map[id1]["name"],
|
| 222 |
+
"roommate1_cgpa": student_map[id1]["cgpa"],
|
| 223 |
+
"roommate2_id": id2,
|
| 224 |
+
"roommate2_name": student_map[id2]["name"],
|
| 225 |
+
"roommate2_cgpa": student_map[id2]["cgpa"],
|
| 226 |
+
"room_number": room["room_number"],
|
| 227 |
+
"room_id": room["room_id"],
|
| 228 |
+
"pair_max_cgpa": max_cgpa,
|
| 229 |
+
})
|
| 230 |
+
|
| 231 |
+
return allocations
|
| 232 |
+
|
| 233 |
+
|
| 234 |
+
def run_full_allocation(students: List[dict], rooms: List[dict]) -> List[dict]:
|
| 235 |
+
"""
|
| 236 |
+
Run the complete two-stage allocation pipeline.
|
| 237 |
+
|
| 238 |
+
Stage 1: Gale-Shapley for roommate matching.
|
| 239 |
+
Stage 2: CGPA-ranked Gale-Shapley for room allocation.
|
| 240 |
+
|
| 241 |
+
Args:
|
| 242 |
+
students: List of student dicts.
|
| 243 |
+
rooms: List of room dicts.
|
| 244 |
+
|
| 245 |
+
Returns:
|
| 246 |
+
List of allocation result dicts.
|
| 247 |
+
"""
|
| 248 |
+
# Stage 1: Roommate matching
|
| 249 |
+
roommate_pairs = run_roommate_matching(students)
|
| 250 |
+
|
| 251 |
+
# Stage 2: Room allocation
|
| 252 |
+
allocations = run_room_allocation(students, roommate_pairs, rooms)
|
| 253 |
+
|
| 254 |
+
return allocations
|
requirements.txt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
streamlit>=1.30.0
|
| 2 |
+
pandas>=2.0.0
|
| 3 |
+
plotly>=5.18.0
|
sample_csv/sample_rooms_13.csv
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
room_id,room_number
|
| 2 |
+
0,A101
|
| 3 |
+
1,A102
|
| 4 |
+
2,A103
|
| 5 |
+
3,A104
|
| 6 |
+
4,A105
|
| 7 |
+
5,B201
|
| 8 |
+
6,B202
|
| 9 |
+
7,B203
|
| 10 |
+
8,B204
|
| 11 |
+
9,B205
|
| 12 |
+
10,C301
|
| 13 |
+
11,C302
|
| 14 |
+
12,C303
|
sample_csv/sample_rooms_5.csv
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
room_id,room_number
|
| 2 |
+
0,A101
|
| 3 |
+
1,A102
|
| 4 |
+
2,A103
|
| 5 |
+
3,B201
|
| 6 |
+
4,B202
|
sample_csv/sample_students_10.csv
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
id,name,cgpa,pref_roommate,pref_room
|
| 2 |
+
0,Alice,9.2,5 6 7 8 9 1 2 3 4,0 1 2 3 4
|
| 3 |
+
1,Bob,8.9,5 6 7 8 9 0 2 3 4,1 0 2 3 4
|
| 4 |
+
2,Charlie,9.5,5 6 7 8 9 3 1 0 4,2 1 0 3 4
|
| 5 |
+
3,Diana,8.7,5 6 7 8 9 2 0 1 4,3 2 1 0 4
|
| 6 |
+
4,Eve,9.0,5 6 7 8 9 0 1 2 3,4 3 2 1 0
|
| 7 |
+
5,Frank,8.8,0 1 2 3 4 6 7 8 9,0 1 2 3 4
|
| 8 |
+
6,Grace,9.3,0 1 2 3 4 7 5 8 9,1 0 2 3 4
|
| 9 |
+
7,Henry,8.6,0 1 2 3 4 6 5 8 9,2 1 0 3 4
|
| 10 |
+
8,Ivy,9.1,0 1 2 3 4 9 5 6 7,3 2 1 0 4
|
| 11 |
+
9,Jack,8.5,0 1 2 3 4 8 5 6 7,4 3 2 1 0
|
sample_csv/sample_students_26.csv
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
id,name,cgpa,pref_roommate,pref_room
|
| 2 |
+
0,Aarav Sharma,9.4,13 14 15 16 17 18 19 20 21 22 23 24 25 1 2 3 4 5 6 7 8 9 10 11 12,0 1 2 3 4 5 6 7 8 9 10 11 12
|
| 3 |
+
1,Priya Patel,8.8,13 14 15 16 17 18 19 20 21 22 23 24 25 0 2 3 4 5 6 7 8 9 10 11 12,1 0 2 3 4 5 6 7 8 9 10 11 12
|
| 4 |
+
2,Rohan Mehta,9.1,13 14 15 16 17 18 19 20 21 22 23 24 25 3 0 1 4 5 6 7 8 9 10 11 12,2 1 0 3 4 5 6 7 8 9 10 11 12
|
| 5 |
+
3,Sneha Kulkarni,8.5,13 14 15 16 17 18 19 20 21 22 23 24 25 2 0 1 4 5 6 7 8 9 10 11 12,3 2 1 0 4 5 6 7 8 9 10 11 12
|
| 6 |
+
4,Vikram Singh,9.0,13 14 15 16 17 18 19 20 21 22 23 24 25 5 6 7 0 1 2 3 8 9 10 11 12,4 5 6 7 0 1 2 3 8 9 10 11 12
|
| 7 |
+
5,Ananya Desai,8.7,13 14 15 16 17 18 19 20 21 22 23 24 25 4 6 7 0 1 2 3 8 9 10 11 12,5 4 6 7 0 1 2 3 8 9 10 11 12
|
| 8 |
+
6,Karan Joshi,9.3,13 14 15 16 17 18 19 20 21 22 23 24 25 7 5 4 0 1 2 3 8 9 10 11 12,6 5 4 7 0 1 2 3 8 9 10 11 12
|
| 9 |
+
7,Meera Nair,8.9,13 14 15 16 17 18 19 20 21 22 23 24 25 6 5 4 0 1 2 3 8 9 10 11 12,7 6 5 4 0 1 2 3 8 9 10 11 12
|
| 10 |
+
8,Arjun Reddy,9.2,13 14 15 16 17 18 19 20 21 22 23 24 25 9 10 11 0 1 2 3 4 5 6 7 12,8 9 10 11 0 1 2 3 4 5 6 7 12
|
| 11 |
+
9,Divya Iyer,8.6,13 14 15 16 17 18 19 20 21 22 23 24 25 8 10 11 0 1 2 3 4 5 6 7 12,9 8 10 11 0 1 2 3 4 5 6 7 12
|
| 12 |
+
10,Nikhil Gupta,9.5,13 14 15 16 17 18 19 20 21 22 23 24 25 11 8 9 0 1 2 3 4 5 6 7 12,10 11 8 9 0 1 2 3 4 5 6 7 12
|
| 13 |
+
11,Ritu Agarwal,8.4,13 14 15 16 17 18 19 20 21 22 23 24 25 10 8 9 0 1 2 3 4 5 6 7 12,11 10 8 9 0 1 2 3 4 5 6 7 12
|
| 14 |
+
12,Siddharth Rao,8.3,13 14 15 16 17 18 19 20 21 22 23 24 25 0 1 2 3 4 5 6 7 8 9 10 11,12 0 1 2 3 4 5 6 7 8 9 10 11
|
| 15 |
+
13,Pooja Verma,9.0,0 1 2 3 4 5 6 7 8 9 10 11 12 14 15 16 17 18 19 20 21 22 23 24 25,0 1 2 3 4 5 6 7 8 9 10 11 12
|
| 16 |
+
14,Harsh Deshmukh,8.7,0 1 2 3 4 5 6 7 8 9 10 11 12 13 15 16 17 18 19 20 21 22 23 24 25,1 0 2 3 4 5 6 7 8 9 10 11 12
|
| 17 |
+
15,Kavya Menon,9.2,0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 16 17 18 19 20 21 22 23 24 25,2 1 0 3 4 5 6 7 8 9 10 11 12
|
| 18 |
+
16,Aditya Bhatt,8.5,0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 17 18 19 20 21 22 23 24 25,3 2 1 0 4 5 6 7 8 9 10 11 12
|
| 19 |
+
17,Tanvi Shah,8.8,0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 18 19 20 21 22 23 24 25,4 3 2 1 0 5 6 7 8 9 10 11 12
|
| 20 |
+
18,Parth Mane,9.1,0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 19 20 21 22 23 24 25,5 4 3 2 1 0 6 7 8 9 10 11 12
|
| 21 |
+
19,Ishita Roy,8.6,0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 20 21 22 23 24 25,6 5 4 3 2 1 0 7 8 9 10 11 12
|
| 22 |
+
20,Jia Johnson,9.4,0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 21 22 23 24 25,7 6 5 4 3 2 1 0 8 9 10 11 12
|
| 23 |
+
21,Rahul Kapoor,8.3,0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 22 23 24 25,8 7 6 5 4 3 2 1 0 9 10 11 12
|
| 24 |
+
22,Neha Saxena,8.9,0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 23 24 25,9 8 7 6 5 4 3 2 1 0 10 11 12
|
| 25 |
+
23,Yash Tiwari,8.2,0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 24 25,10 9 8 7 6 5 4 3 2 1 0 11 12
|
| 26 |
+
24,Shweta Mishra,9.3,0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 25,11 10 9 8 7 6 5 4 3 2 1 0 12
|
| 27 |
+
25,Amit Pandey,8.0,0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24,12 11 10 9 8 7 6 5 4 3 2 1 0
|