A native iOS client for Codeforces with an on-device ML recommendation engine that ranks problems by learning opportunity, live contest tracking, and real-time submission verdicts via WebSocket.
Splash Screen
|
Login Screen
|
Contests
|
Problems
|
For You
|
Profile
|
- For You - On-device ML model ranks unsolved problems by personal weak spots and optimal difficulty gap
- Live Verdicts - WebSocket pushes submission results in real time without polling
- Contest Tracker - Upcoming and running contests with countdown timers
- Problem Explorer - Full problem set with search and tag filtering
- Profile Insights - Rating history chart, rank, and solve statistics
The recommendation engine runs fully on-device. No user data leaves the phone.
Raw Codeforces submissions (HuggingFace dataset)
|
v
Feature Engineering -- 55,750 (user, problem) pairs · 1,115 users · 80 features
|
v
Create ML -- Random Forest Classifier (100 trees, 100 iterations)
|
v
ProblemRecommender1.mlmodel -- 2.1 MB · on-device via Core ML
| Feature | Description |
|---|---|
diff_delta |
Problem rating minus user's current rating |
solved_before |
Whether the user has previously attempted this problem |
solvers_norm |
Normalised global acceptance count |
tag_* (x38) |
One-hot encoding of CF topic tags |
success_* (x38) |
User's per-tag acceptance rate from submission history |
| Metric | Value |
|---|---|
| Training samples | 55,750 |
| Users in dataset | 1,115 |
| Validation accuracy | 99% |
| Model size | 2.1 MB |
| Inference | On-device, <1s for full catalog |
The tag success-rate features are the personalisation signal. A user with low success_graphs acceptance gets more graph problems recommended to close that gap.
┌──────────────────────────────────────────────────────────────┐
│ SwiftUI Views │
│ @MainActor · Combine @Published bindings │
└─────────────────────────────────┬────────────────────────────┘
│ async/await + Combine
┌─────────────────────────────────▼────────────────────────────┐
│ ViewModels │
│ @MainActor final class · ObservableObject │
└────────┬─────────────────────────────────────────────────────┘
│ actor-isolated calls
┌────────▼─────────────────────────────────────────────────────┐
│ Repositories │
│ Swift actor · TTL cache · in-flight deduplication │
│ Combine PassthroughSubject for real-time events │
└────────┬──────────────────────────────────┬──────────────────┘
│ │
┌────────▼────────────────────┐ ┌─────────▼────────────────────┐
│ Services │ │ WebSocket Service │
│ URLSession │ │ URLSessionWebSocketTask │
│ RequestScheduler │ │ typed event stream │
└────────┬────────────────────┘ └─────────┬────────────────────┘
│ │
┌────────▼────────────────────┐ ┌─────────▼────────────────────┐
│ Codeforces API │ │ SwiftData Store │
│ (REST JSON) │ │ problems · rating · contest │
└─────────────────────────────┘ └──────────────────────────────┘
On-device ML
┌──────────────────────────────────────────────────────┐
│ RecommendationEngine │
│ Input: [Problem] catalog + [Submission] history │
│ Engine: MLDictionaryFeatureProvider → Core ML │
│ Output: [ScoredProblem] ranked by P(recommended=1) │
└──────────────────────────────────────────────────────┘
All async work in CForge is structured around Swift's actor model and async/await. There are no raw DispatchQueue calls or completion handler chains.
- ViewModels are
@MainActor final class: UI state mutations are always on the main thread without manual dispatch - Repositories are Swift
actors: all internal state (cache,ongoingTasks) is actor-isolated, making concurrent reads safe by construction RequestScheduleris a dedicated actor that serialises outgoing API calls to respect the Codeforces rate limit (1 request per 2 seconds). All network calls go through it.- In-flight deduplication: if two callers request the same resource simultaneously, the Repository creates one
Task<[T], Error>and bothawaitits value. No duplicate network calls.
Repositories expose AnyPublisher streams for events that need to propagate without the caller polling:
| Publisher | Type | Consumed by |
|---|---|---|
ratingUpdated |
AnyPublisher<(String, Int), Never> |
ProfileViewModel overlays live rating |
wsConnectionState |
AnyPublisher<WebSocketConnectionState, Never> |
ProfileViewModel drives connection pill |
dataLastUpdated |
AnyPublisher<Date, Never> |
ProfileViewModel drives staleness banner |
verdictReceived |
AnyPublisher<Submission, Never> |
ProfileViewModel triggers verdict toast |
The WebSocket service emits a typed WebSocketEvent enum (submissionVerdict, connectionStateChanged). The SubmissionRepository subscribes, throttles at 500ms to prevent UI stutter during judge-queue flushes, and republishes through the above subjects.
| Resource | TTL | Rationale |
|---|---|---|
| Problem catalog | 10 minutes | Updated infrequently by Codeforces |
| Rating history | 60 minutes | Staleness banner shown after expiry |
| Contest submissions | 30 seconds | Verdict status changes during judging |
| User profile | On-demand | Refreshed explicitly or on WebSocket event |
SwiftData provides a persistent backing store for PersistedProblem, PersistedRatingChange, and PersistedContest. On first launch or cache miss, data is fetched from the API; on subsequent launches within TTL, the local store is used, thereby making the app functional with no network.
The RecommendationEngine runs entirely on the device using Core ML. It bypasses the network stack completely, thus having no inference API and transmitting no user data.
At runtime, RecommendationViewModel fetches the problem catalog and the user's submission history concurrently, then calls RecommendationEngine.recommend() which:
- Builds a solved-problem key set from accepted submissions
- Computes per-tag success rates from the full history
- Constructs an 80-feature
MLDictionaryFeatureProviderper unsolved problem - Runs
ProblemRecommender1(Random Forest, 100 trees) viaMLModel.prediction(from:) - Sorts by
P(recommended=1)and returns the top 20
MLDictionaryFeatureProvider is used instead of the auto-generated ProblemRecommender1Input struct because one CF tag (*special) contains a character that is not a valid Swift identifier, which breaks the Xcode-generated input class.
| Layer | Technology |
|---|---|
| Language | Swift 5.9 |
| UI | SwiftUI |
| ML | Core ML · Create ML · Random Forest |
| Concurrency | async/await · Actor model |
| Real-time | WebSocket (URLSessionWebSocketTask) |
| Persistence | SwiftData |
| Image Loading | SDWebImage |
| Code Quality | SwiftLint |
| API | Codeforces REST API |
- Clone the repository:
git clone https://github.com/Sandesh282/CForge.git cd CForge - Open in Xcode:
open CForge.xcodeproj
- Go to Signing & Capabilities, select your team, and update the bundle identifier if needed.
- Run on a simulator or connected iOS device (iOS 17+).
| Endpoint | Usage |
|---|---|
contest.list |
Upcoming and running contests |
user.info |
Profile, rating, and rank |
user.status |
Full submission history for recommendations |
user.rating |
Rating change history for the chart |
problemset.problems |
Full problem catalog with tags and ratings |





