A SwiftUI iOS application that solves the electric fleet charging optimization problem. The app intelligently schedules charging sessions for electric mail trucks across multiple chargers within a specified time window, maximizing the number of fully charged vehicles.
Given:
- A fleet of electric trucks with varying battery capacities and current charge levels
- Multiple chargers with different charging rates (measured in kW)
- A time constraint (overnight charging window in hours)
Goal: Schedule charging sessions to maximize the number of fully charged trucks within the time limit.
Real-world Example: You manage a mail delivery fleet with 4 trucks that need overnight charging in an 8-hour window. You have 2 chargers available - one fast charger (22kW) and one standard charger (11kW). The question is: which trucks should charge when to maximize fleet readiness for the next day?
The application follows a clean architecture with clear separation of concerns:
- Models: Domain models and screen-specific data structures
- Views: SwiftUI views with minimal logic
- ViewModels: Business logic and state management
- Router: Navigation management following the Coordinator pattern
- Algorithms: Pluggable scheduling strategies
Single Responsibility Principle (SRP)
- Each class has one reason to change
- ViewModels handle only their specific screen logic
- Models contain only data and computed properties
- Views handle only UI presentation
Open/Closed Principle (OCP)
- New scheduling algorithms can be added without modifying existing code
- Router can be extended with new destinations
- ViewModels can be extended with new functionality
Liskov Substitution Principle (LSP)
- Any
SchedulingAlgorithmProtocolimplementation can replace another RouterProtocolimplementations are interchangeable
Interface Segregation Principle (ISP)
- Protocols are focused and specific (e.g.,
RouterProtocol,DetailScheduleViewModelProtocol) - No client depends on methods it doesn't use
Dependency Inversion Principle (DIP)
- High-level modules depend on abstractions, not concretions
- ViewModels depend on protocol abstractions
- Dependency injection used throughout
The app uses separate model files for better organization:
Model/ScheduleModel.swift - Core Domain Models:
struct Truck: Hashable {
let id: String
let capacityKWh: Double
let currentPercent: Double
}
struct Charger: Hashable {
let id: String
let rateKW: Double
}
struct Assignment: Hashable {
let id = UUID() // Unique identifier for SwiftUI
let chargerId: String
let truckId: String
let startHour: Double
let endHour: Double
}Model/DetailScheduleModel.swift - Detail Screen Models:
struct ScheduleResult: Hashable {
let assignments: [Assignment]
let fullyChargedTruckIds: Set<String>
let hoursLimit: Double
}
struct DetailScheduleModel {
let scheduleResult: ScheduleResult
let allTrucks: [Truck]
let allChargers: [Charger]
}Model/Models+Extension.swift - Computed Properties:
extension Truck {
var energyNeededKWh: Double {
capacityKWh * max(0, (100 - currentPercent) / 100.0)
}
}
extension ScheduleResult {
var trucksChargedCount: Int {
fullyChargedTruckIds.count
}
}
extension DetailScheduleModel {
var unscheduledTrucks: [Truck] {
allTrucks.filter { !scheduleResult.fullyChargedTruckIds.contains($0.id) }
}
}Three scheduling strategies implementing SchedulingAlgorithmProtocol:
1. Greedy Scheduling Algorithm (Shortest Time First) This algorithm prioritizes trucks that need the least charging time. The logic is:
- Calculate charging time for each truck:
energyNeeded / chargerRate - Sort trucks by ascending charging time
- Assign trucks to chargers in this order
How it solves the problem: By always choosing the truck that finishes fastest, it leaves maximum time remaining for other trucks. This "greedy" approach of making the locally optimal choice (shortest time) often leads to good global results.
Example: Truck A needs 2 hours, Truck B needs 4 hours → Truck A charges first Advantage: Maximizes throughput by handling quick jobs first Best for: When you want to charge as many trucks as possible
2. Max Energy First Scheduling Algorithm This algorithm prioritizes trucks that need the most energy (longest charging time). The logic is:
- Calculate energy needed for each truck:
capacity × (100 - currentPercent) / 100 - Sort trucks by descending energy needed
- Assign trucks to chargers in this order
How it solves the problem: By tackling the most energy-hungry trucks first, it ensures that if time runs out, at least the trucks with the greatest charging needs have been handled. This prevents a scenario where many small trucks are charged but critical high-capacity trucks are left uncharged.
Example: Truck A needs 80 kWh, Truck B needs 30 kWh → Truck A charges first Advantage: Ensures high-capacity trucks get priority Best for: When fleet readiness and range are critical
3. Dynamic Programming Algorithm (Optimal) This algorithm finds the mathematically optimal solution by exploring all possible assignments. The logic is:
- Uses memoization to cache subproblem results
- For each truck, considers: skip OR assign to each available charger
- Recursively solves for remaining trucks with updated charger availability
- Returns the assignment that maximizes the number of fully charged trucks
How it solves the problem: Unlike heuristic approaches, this algorithm exhaustively explores the entire solution space. It considers every possible combination of truck-to-charger assignments and timing, then selects the combination that charges the maximum number of trucks. The memoization prevents recalculating the same subproblems, making it feasible for moderate-sized inputs.
Example: Considers all combinations to find the absolute best solution Advantage: Guaranteed optimal result - maximum trucks charged Best for: When you need the mathematically best solution regardless of computation time
The first two algorithms use a shared implementation with a min-heap for efficient charger assignment.
Consider this scenario: 3 trucks, 1 charger (20kW), 5-hour time limit:
- Truck A: 100kWh capacity, 75% charged → needs 25kWh (1.25h)
- Truck B: 80kWh capacity, 25% charged → needs 60kWh (3h)
- Truck C: 60kWh capacity, 50% charged → needs 30kWh (1.5h)
Greedy (Shortest Time First): A→C→B (charges A+C, B doesn't fit) Max Energy First: B→C→A (charges B+C, A doesn't fit) Dynamic Programming: A→C→B or B→C (explores both, picks best)
Each algorithm makes different trade-offs based on its strategy for solving the optimization problem.
Generic min-heap implementation for efficient charger availability tracking.
chargingTime = energyNeededKWh / chargerRateKW
energyNeeded = capacityKWh × (100 - currentPercent) / 100For Greedy and Max Energy First algorithms:
-
Sort trucks based on the selected strategy:
- Greedy: shortest charging time first
- Max Energy: highest energy needed first
-
Initialize charger heap - all chargers start available at time 0
-
For each truck (in sorted order):
- Get the earliest available charger from the heap
- Calculate how long this truck needs to charge
- Check if charging would complete within the time limit
- If yes: create assignment and update charger's next available time
- If no: skip this truck (it won't fit)
- Put the charger back in the heap with its updated availability time
-
Return results with all successful assignments and list of charged trucks
For Dynamic Programming algorithm:
-
Initialize memoization - cache for storing subproblem results
-
Recursive function
dp(truckIndex, chargerTimes):- Base case: if no trucks left, return (0 trucks charged, empty assignments)
- For current truck, try two options:
- Skip truck: solve for remaining trucks with same charger availability
- Assign to each charger: if time permits, solve for remaining trucks with updated availability
- Return the option that charges the most trucks
-
Memoization key: combines truck index and charger availability states
-
Return optimal result with maximum trucks charged and their assignments
This approach ensures optimal solutions while the first two provide efficient heuristic solutions.
Main Screen (ContentView)
- Algorithm Selection: Switch between three scheduling strategies (Greedy, Max Energy, Optimal DP)
- Input Display: Shows truck capacities, current charge levels, and charger rates
- Time Window: Adjustable hours limit with stepper control
- Navigation: Tap "Compute Schedule" to navigate to detailed results
Detail Screen (DetailScheduleView)
- Schedule Summary: High-level metrics showing trucks charged and time limit
- Per-Charger Schedule: Detailed timeline of assignments for each charger
- Unscheduled Trucks: Clear indication of trucks that couldn't be accommodated
- Navigation: Clean back navigation with Router pattern
- Build and run on iOS simulator or device
- Use the algorithm picker to compare all three strategies:
- Shortest Time First: Fast heuristic, good throughput
- Max Energy First: Prioritizes high-capacity trucks
- Optimal (DP): Guaranteed best solution, slower for large inputs
- Adjust time limit using the stepper control
- Tap "Compute Schedule" to generate and view results
Edit Config/SampleData.swift:
enum SampleData {
static let trucks: [Truck] = [
Truck(id: "T1", capacityKWh: 100, currentPercent: 20),
// Add more trucks...
]
static let chargers: [Charger] = [
Charger(id: "C1", rateKW: 22),
// Add more chargers...
]
static let hoursLimit: Double = 8
}- Implement
SchedulingAlgorithmProtocol - Add to
ScheduleViewModel.AlgorithmKeyenum - Update
updateAlgorithm(for:)method - Add localized name to
LocalizedStrings.swift
Comprehensive unit tests cover:
- Model Logic: Energy calculation edge cases (empty, full, partial charge)
- Algorithm Behavior: Basic scheduling, multiple chargers, algorithm differences
- Edge Cases: No trucks/chargers, insufficient time, partial scheduling
- Performance: Large-scale scheduling scenarios (100 trucks, 10 chargers)
- Data Structures: Priority queue operations and heap behavior
- Unit Tests:
Cmd+Uin Xcode orxcodebuild test - UI Tests: Not yet implemented (see Future Enhancements)
func testGreedyAlgorithmBasicCase() {
// Tests that greedy algorithm prioritizes shortest charging time
let trucks = [
Truck(id: "T1", capacityKWh: 100, currentPercent: 50), // 2h needed
Truck(id: "T2", capacityKWh: 60, currentPercent: 0) // 2.4h needed
]
// Verifies T1 charges first (shorter time)
}ChargePoint_Task/
├── App/
│ └── ChargePoint_TaskApp.swift
├── Model/
│ ├── ScheduleModel.swift
│ └── DetailScheduleModels.swift
├── View/
│ ├── ContentView.swift
│ ├── ScheduleView.swift
│ └── DetailScheduleView.swift
├── ViewModel/
│ ├── ScheduleViewModel.swift
│ └── DetailScheduleViewModel.swift
├── Navigation/
│ └── AppRouter.swift
├── Algorithms/
│ ├── SchedulingAlgorithmProtocol.swift
│ ├── GreedySchedulingAlgorithm.swift
│ ├── MaxEnergyFirstSchedulingAlgorithm.swift
│ └── DynamicProgrammingSchedulingAlgorithm.swift
├── Utilities/
│ └── PriorityQueue.swift
├── Config/
│ └── SampleData.swift
├── Localization/
│ └── LocalizedStrings.swift
└── Tests/
└── ChargePoint_TaskTests.swift
Presentation Layer
- Views: SwiftUI views with minimal logic
- ViewModels: Business logic and state management
- Router: Navigation coordination
Business Layer
- Algorithms: Scheduling strategy implementations
- Models: Domain entities and business rules
Infrastructure Layer
- Utilities: Reusable components (PriorityQueue)
- Configuration: Sample data and localization
When to use Greedy (Shortest Time First):
- You have limited time and want to charge as many trucks as possible
- Fleet utilization rate is more important than individual truck range
- Most trucks have similar charging needs
- Example scenario: Urban delivery fleet with short routes
When to use Max Energy First:
- Long-range capability is critical for your fleet operations
- You prefer fewer, fully-charged trucks over many partially-charged ones
- Trucks have significantly different charging requirements
- Example scenario: Long-haul freight or emergency services
When to use Dynamic Programming (Optimal):
- You need the mathematically best possible solution
- Computation time is less critical than optimality
- You want to compare other algorithms against the optimal baseline
- Small to medium fleet sizes (performance degrades with large inputs)
- Example scenario: Critical operations where maximum fleet readiness is essential
Architecture Patterns
- MVVM + Router: Clean separation with centralized navigation
- Protocol-Oriented Design: Easy algorithm swapping and testing
- Dependency Injection: Loose coupling and better testability
- SOLID Principles: Maintainable and extensible codebase
Data Structures & Algorithms
- Min-Heap: Efficient O(log n) charger assignment for heuristic algorithms
- Greedy Algorithms: Fast practical solutions with good performance
- Dynamic Programming: Optimal solutions with memoization for efficiency
UI/UX Design
- SwiftUI: Modern declarative UI framework
- Navigation Stack: iOS 16+ navigation with type safety
- Centralized Localization: Future-ready for internationalization
- Accessibility: VoiceOver support and semantic labels
- Linear charging curve (no tapering)
- Zero switchover time between trucks
- Once started, trucks must charge to 100%
- Chargers can only handle one truck at a time
- Priority-Based: Weight trucks by importance/route criticality
- Predictive: Consider historical charging patterns and truck usage
- Constraint Optimization: Handle complex real-world constraints (temperature, battery degradation)
- Multi-Objective: Balance multiple goals (cost, time, battery health)
- Interactive Timeline: Gantt chart visualization of charging schedule
- Editable Inputs: Add/remove/modify trucks and chargers directly in the UI
- Real-time Updates: Live scheduling as inputs change
- Export Options: Share schedules as PDF or calendar events
- UI Tests: Comprehensive UI test suite with accessibility identifiers
- Accessibility Identifiers: Add testable identifiers to all interactive elements for automated testing
- Persistence: Core Data or CloudKit for fleet management
- Networking: Remote fleet monitoring and control
- Background Processing: Automatic re-scheduling based on truck arrivals
- Advanced Analytics: Charging efficiency metrics and optimization suggestions
- UI Testing Framework: Implement XCUITest suite for end-to-end testing
- Multi-location: Handle multiple charging depots
- Fleet Types: Different vehicle types with varying charging profiles
- Integration: Connect with fleet management systems and charging networks
Greedy and Max Energy First:
- Time Complexity: O(n log m) where n = trucks, m = chargers
- Space Complexity: O(n + m) for assignments and charger heap
- Tested Scale: 100 trucks, 10 chargers in <100ms
Dynamic Programming:
- Time Complexity: O(n × c^n) where c = chargers (with memoization optimization)
- Space Complexity: O(n × c^n) for memoization cache
- Practical Limit: ~10-15 trucks for reasonable performance
- Advantage: Guaranteed optimal solution