A Test-Driven Development (TDD) solution in C# for simulating people passing through a university turnstile, following object-oriented design principles.
A university has a turnstile to enter or exit a building. The goal is to determine the exact time each person will pass through the turnstile, where each person takes one second to pass through.
When multiple people arrive at the turnstile simultaneously, the following priority rules apply:
- Idle Priority: If the turnstile was not used in the previous second, priority is given to people exiting.
- Continuous Exit Flow: If the previous person exited, the next person exiting goes first.
- Continuous Enter Flow: If the previous person entered, the next person entering goes first.
- Same Direction: If two people going in the same direction arrive at the same time, the lower-indexed one goes first.
Key Insight: Rules 2 and 3 create a continuous flow pattern - once a direction starts, it continues until that queue is empty or time advances.
Line 1: n (number of people)
Line 2: n space-separated integers representing arrival times
Line 3: n space-separated integers representing directions (0 = enter, 1 = exit)
n space-separated integers representing the time each person passes through (indexed by person 0 to n-1)
1 ? n ? 10^50 ? time[i] ? 10^9for0 ? i < ntime[i] ? time[i+1]for0 ? i < n-1(sorted by arrival time)direction[i]is either0(enter) or1(exit)
Input:
4
0 0 1 5
0 1 1 0
Output:
2 0 1 5
Explanation (by time):
- Time 0: Person 0 (enter) and Person 1 (exit) arrive ? Rule 1 (idle prefers exit) ? Person 1 exits
- Time 1: Person 2 (exit) arrives, Person 0 still waiting ? Rule 2 (continuous exit flow) ? Person 2 exits
- Time 2: Person 0 (enter) still waiting ? Person 0 enters
- Time 5: Person 3 (enter) arrives ? Person 3 enters
Input:
5
0 1 1 3 3
0 1 0 0 1
Output:
0 2 1 4 3
Explanation (by time):
- Time 0: Person 0 (enter) arrives ? Person 0 enters
- Time 1: Person 1 (exit) and Person 2 (enter) arrive ? Rule 3 (continuous enter flow) ? Person 2 enters
- Time 2: Person 1 (exit) still waiting ? Person 1 exits
- Time 3: Person 3 (enter) and Person 4 (exit) arrive ? Rule 2 (continuous exit flow) ? Person 4 exits
- Time 4: Person 3 (enter) still waiting ? Person 3 enters
This solution follows Test-Driven Development (TDD) and SOLID principles with a clean object-oriented design.
The priority rules are implemented as interchangeable strategies, allowing the simulator to select the appropriate rule based on the turnstile state.
IPriorityRule (interface)
??? IdlePreferExitRule (Rule 1)
??? PreviousExitPreferEnterRule (Rule 2)
??? PreviousEnterPreferExitRule (Rule 3)
Clear separation of concerns with distinct layers:
- Domain Layer: Core entities (
Person,TurnstileDirection) - Rules Layer: Priority strategies (
IPriorityRuleimplementations) - Simulation Layer: Orchestration (
TurnstileSimulator) - Application Layer: I/O handling (
Program)
TurnstileDirection (Enum)
- Type-safe representation of directions
Enter = 0,Exit = 1
Person (Class)
- Represents an individual with:
Index: Person identifierArrivalTime: When they arriveDirection: Enter or ExitIsProcessed: Tracking flagPassTime: When they passed through
- Implements
IComparable<Person>for automatic ordering (Rule 4) - Includes validation in constructor and methods
IPriorityRule (Interface)
- Defines the contract:
Person SelectNext(IList<Person> entering, IList<Person> exiting) - Allows runtime selection of priority strategy
Concrete Rules (Stateless)
IdlePreferExitRule: Handles idle state (Rule 1)PreviousExitPreferEnterRule: Continuous exit flow (Rule 2)PreviousEnterPreferExitRule: Continuous enter flow (Rule 3)
TurnstileSimulator
- Main simulation engine
- Tracks current time and last direction
- Manages waiting people directly using lists
- Selects appropriate priority rule
- Coordinates the simulation loop
- Includes comprehensive input validation
? Single Responsibility Principle
- Each class has one clear purpose
Personmanages person state- Rules encapsulate priority logic
TurnstileSimulatororchestrates simulation and manages queues
? Open/Closed Principle
- Open for extension (can add new rules without modifying existing code)
- Closed for modification (existing rules don't change when adding new ones)
? Liskov Substitution Principle
- All
IPriorityRuleimplementations are interchangeable - Simulator works with any rule that implements the interface
? Interface Segregation Principle
- Small, focused interfaces
IPriorityRulehas only one method with clear responsibility
? Dependency Inversion Principle
TurnstileSimulatordepends onIPriorityRuleabstraction- Not coupled to concrete rule implementations
TurnstileSolution/
??? TurnstileDirection.cs # Enum for directions
??? Person.cs # Domain entity
??? IPriorityRule.cs # Strategy interface
??? IdlePreferExitRule.cs # Rule 1 implementation
??? PreviousExitPreferEnterRule.cs # Rule 2 implementation
??? PreviousEnterPreferExitRule.cs # Rule 3 implementation
??? TurnstileSimulator.cs # Simulation engine
??? Program.cs # Console application
TurnstileSolution.Tests/
??? PersonTests.cs # Unit tests for Person
??? PriorityRuleTests.cs # Unit tests for rules
??? TurnstileSimulatorTests.cs # Integration tests
Documentation/
??? README.md # This file - project overview
??? DEVELOPMENT_LOG.md # Complete development history
??? CLEANUP_SUMMARY.md # Code cleanup documentation
??? DEPENDENCY_INVERSION_IMPROVEMENT.md # SOLID improvements
??? PERFORMANCE_OPTIMIZATIONS.md # Performance analysis
??? PERF_SUMMARY.md # Performance quick reference
??? COMMIT_MESSAGE.txt # Cleanup commit message
??? COMMIT_MESSAGE_DI.txt # DI fix commit message
??? COMMIT_MESSAGE_PERF.txt # Performance commit message
This project was built following the Red-Green-Refactor cycle:
- ?? RED Phase: Wrote 20 comprehensive tests before any implementation
- ?? GREEN Phase: Implemented classes incrementally to make tests pass
- ?? REFACTOR Phase: Applied clean code and design patterns
15 tests covering:
- ? Unit tests for
Personclass (4 tests) - ? Unit tests for priority rules (5 tests)
- ? Integration tests for complete simulation (6 tests)
- Both provided examples
- Same direction priority
- Single person scenario
- Time gaps (turnstile becomes idle)
- Large time values (constraint validation)
- .NET 10 SDK
dotnet builddotnet run --project TurnstileSolutionExample input:
4
0 0 1 5
0 1 1 0
Expected output:
2 0 1 5
dotnet testExpected result:
Passed! - Failed: 0, Passed: 15, Skipped: 0, Total: 15
# Example 1
Get-Content TurnstileSolution\example1_input.txt | dotnet run --project TurnstileSolution
# Example 2
Get-Content TurnstileSolution\example2_input.txt | dotnet run --project TurnstileSolutionThe most important insight from this problem is that Rules 2 and 3 create continuous flow:
- Once exiting starts, it continues until no more people are exiting
- Once entering starts, it continues until no more people are entering
- This is not an alternating pattern
This design choice optimizes throughput by minimizing direction changes.
By implementing IComparable<Person> and using SortedSet<Person>, Rule 4 (same direction, lower index first) is automatically enforced without explicit logic. This is an example of using the type system to encode business rules.
The solution includes comprehensive validation:
- Input array length validation
- Duplicate index detection
- Arrival time ordering verification
- Index-position consistency checks
- Pass time validation (cannot pass before arrival)
This is a demonstration project for educational purposes.
This repository includes comprehensive documentation covering the development process, design decisions, and optimization journey:
README.md (This file)
Main project documentation covering:
- Problem description and rules
- Architecture and design patterns
- Building and running instructions
- Key insights and learnings
Complete conversation history documenting:
- Initial problem statement and clarifications
- TDD approach and planning discussions
- All implementation decisions and iterations
- Test-driven development process
- Bug fixes and rule corrections
- Full audit trail of the development session
Code cleanup phase covering:
- Removal of unused
TurnstileQueueabstraction - Elimination of dead code and test artifacts
- Code simplification (13% reduction)
- Before/after metrics and benefits
- YAGNI and KISS principles applied
SOLID principles improvement covering:
- Fixed Dependency Inversion Principle violation
- Changed from concrete types to
IPriorityRuleinterface - Reduced coupling and improved testability
- Design pattern alignment (Strategy Pattern)
- Future extensibility opportunities
Detailed performance analysis covering:
- Identified bottlenecks (O(n² log n) complexity)
- Eliminated redundant operations (LINQ, sorting)
- Optimized algorithms (single-pass processing)
- Complexity reduction and allocation improvements
- Benchmark comparisons and trade-offs
- Code quality impact analysis
Quick performance summary covering:
- Key optimizations at a glance
- Performance metrics comparison table
- Expected real-world impact (20-30% speedup)
- Verification results
- Future optimization opportunities
Pre-written comprehensive commit messages for version control:
Git commit message for cleanup phase:
- Removal of unused code and abstractions
- Simplification changes
- Impact metrics and benefits
Git commit message for dependency inversion fix:
- SOLID principles improvement
- Interface-based design
- Coupling reduction
Git commit message for performance optimizations:
- Complexity improvements
- Allocation reductions
- Benchmark data
| Document | Purpose | Audience |
|---|---|---|
| README.md | Project overview | All users |
| DEVELOPMENT_LOG.md | Complete development history | Developers, reviewers |
| CLEANUP_SUMMARY.md | Code cleanup details | Maintainers |
| DEPENDENCY_INVERSION_IMPROVEMENT.md | SOLID improvements | Architects, senior devs |
| PERFORMANCE_OPTIMIZATIONS.md | Performance deep-dive | Performance engineers |
| PERF_SUMMARY.md | Quick perf overview | Tech leads, managers |
| COMMIT_MESSAGE_*.txt | Version control | Git users |
For New Contributors:
- Start with README.md (this file) for project overview
- Read DEVELOPMENT_LOG.md to understand design decisions
- Review CLEANUP_SUMMARY.md to see code evolution
For Code Reviewers:
- Check COMMIT_MESSAGE_*.txt for change context
- Read specific optimization docs for technical details
- Verify test coverage in README.md
For Performance Analysis:
- Start with PERF_SUMMARY.md for quick overview
- Deep-dive into PERFORMANCE_OPTIMIZATIONS.md for details
- Cross-reference with code changes in commits
For Architecture Review:
- Read DEPENDENCY_INVERSION_IMPROVEMENT.md for SOLID compliance
- Review CLEANUP_SUMMARY.md for design evolution
- Check DEVELOPMENT_LOG.md for decision rationale
All documentation includes:
- ? Clear problem statements
- ? Before/after comparisons
- ? Metrics and measurements
- ? Code examples
- ? Benefits and trade-offs
- ? Verification results
For the complete development journey from initial problem to optimized solution, start with DEVELOPMENT_LOG.md!