1
- package ScalaPlayground .Lift
1
+ package ScalaPlayground .Lift . Immutable
2
2
3
3
// https://www.codewars.com/kata/58905bfa1decb981da00009e
4
4
5
- import ScalaPlayground . Lift . Direction .{Down , Up }
5
+ import Direction .{Down , Up }
6
6
7
- import scala .collection .immutable .ListMap
7
+ import scala .annotation .tailrec
8
+ import scala .collection .immutable .{ListMap , Queue }
8
9
import scala .collection .mutable
9
10
10
11
type Floor = Int
@@ -23,26 +24,24 @@ case class Person(position: Floor, destination: Floor) {
23
24
}
24
25
25
26
case class Lift (
26
- var position : Floor ,
27
- var direction : Direction ,
28
- people : mutable. Queue [Person ],
27
+ position : Floor ,
28
+ direction : Direction ,
29
+ people : Queue [Person ],
29
30
capacity : Int
30
31
) {
31
- private def isFull : Boolean = people.size == capacity
32
- def hasRoom : Boolean = ! isFull
33
- def hasPeople : Boolean = people.nonEmpty
34
- def isEmpty : Boolean = people.isEmpty
35
- def accepts (person : Person ): Boolean = hasRoom && person.desiredDirection == direction
32
+ def isFull : Boolean = people.size == capacity
33
+ def hasRoom : Boolean = people.size != capacity
34
+ def hasPeople : Boolean = people.nonEmpty
35
+ def isEmpty : Boolean = people.isEmpty
36
+
37
+ def accepts (person : Person ): Boolean =
38
+ hasRoom && person.desiredDirection == direction
36
39
37
40
def nearestPassengerTarget : Option [Floor ] =
38
41
people.filter(_.matchesDirection(this )).map(_.destination).minByOption(floor => Math .abs(floor - position))
39
-
40
- def turn (): Unit = direction = direction match
41
- case Up => Down
42
- case Down => Up
43
42
}
44
43
45
- case class Building (floors : ListMap [Floor , mutable. Queue [Person ]]) {
44
+ case class Building (floors : ListMap [Floor , Queue [Person ]]) {
46
45
def isEmpty : Boolean = floors.values.forall(_.isEmpty)
47
46
def hasPeople : Boolean = ! isEmpty
48
47
@@ -61,10 +60,13 @@ case class Building(floors: ListMap[Floor, mutable.Queue[Person]]) {
61
60
case Down => peopleGoing(Down ).filter(_.isBelow(lift)).map(_.position).maxOption
62
61
}
63
62
64
- case class State (building : Building , lift : Lift , stops : mutable.ListBuffer [Floor ]) {
63
+ case class State (building : Building , lift : Lift , stops : List [Floor ])
64
+
65
+ extension (state : State ) {
65
66
def toPrintable : String = {
66
- val sb = new StringBuilder ()
67
+ import state .{ building , stops , lift }
67
68
69
+ val sb = new StringBuilder ()
68
70
sb.append(s " ${stops.length} stops: ${stops.mkString(" , " )}\n " )
69
71
70
72
building.floors.toSeq.reverse.foreach { case (floor, queue) =>
@@ -84,17 +86,17 @@ case class State(building: Building, lift: Lift, stops: mutable.ListBuffer[Floor
84
86
// Excuse the name. Dinglemouse.theLift() is how the function is called in the Codewars test suite
85
87
object Dinglemouse {
86
88
def theLift (queues : Array [Array [Int ]], capacity : Int ): Array [Int ] = {
87
- val floors : ListMap [Int , mutable. Queue [Person ]] =
89
+ val floors : ListMap [Int , Queue [Person ]] =
88
90
queues.zipWithIndex
89
91
.map { case (queue, index) =>
90
- (index, queue.map(destination => Person (position = index, destination = destination)).to(mutable. Queue ))
92
+ (index, queue.map(destination => Person (position = index, destination = destination)).to(Queue ))
91
93
}
92
94
.to(ListMap )
93
95
94
- val lift = Lift (position = 0 , Direction .Up , people = mutable. Queue .empty, capacity)
96
+ val lift = Lift (position = 0 , Direction .Up , people = Queue .empty, capacity)
95
97
val building = Building (floors)
96
98
97
- val initialState = State (building = building, lift = lift, stops = mutable. ListBuffer .empty)
99
+ val initialState = State (building = building, lift = lift, stops = List .empty)
98
100
val finalState = LiftLogic .simulate(initialState)
99
101
100
102
finalState.stops.toArray
@@ -103,55 +105,60 @@ object Dinglemouse {
103
105
104
106
object LiftLogic {
105
107
def simulate (initialState : State ): State = {
106
- var state = initialState
108
+ val state = initialState.copy(stops = initialState.stops :+ initialState.lift.position)
107
109
108
- state.stops += state.lift.position // register initial position as the first stop
109
- println(state.toPrintable) // draw the initial state of the lift
110
+ @ tailrec
111
+ def resolve (state : State ): State =
112
+ val newState = step(state)
113
+ val State (building, lift, _) = newState
114
+ if building.isEmpty && lift.isEmpty && lift.position == 0 then newState
115
+ else resolve(newState)
110
116
111
- val State (building, lift, _) = state
112
-
113
- while building.hasPeople || lift.hasPeople || lift.position > 0 do
114
- state = step(state)
115
- println(state.toPrintable)
116
-
117
- state
117
+ resolve(state)
118
118
}
119
119
120
120
private def step (state : State ): State = {
121
- // Destructure state into convenient variables
122
- val State (building, lift, stops) = state
121
+ import state .{building , lift , stops }
123
122
124
- val maxFloor = building.floors.keys.maxOption.getOrElse(0 )
125
-
126
- // Always force the lift into a valid direction
127
- lift.direction = lift.position match
128
- case 0 => Up
129
- case p if p == maxFloor => Down
130
- case _ => lift.direction
123
+ val validDirection = lift.position match
124
+ case 0 => Up
125
+ case p if p == building.floors.size - 1 => Down
126
+ case _ => lift.direction
131
127
132
128
// Off-board people who reached their destination
133
- lift.people.dequeueAll(_.destination == lift.position)
129
+ val lift2 = lift.copy(
130
+ direction = validDirection,
131
+ people = lift.people.filter(_.destination != lift.position)
132
+ )
133
+
134
+ @ tailrec
135
+ def pickup (lift : Lift , queue : Queue [Person ]): (Lift , Queue [Person ]) =
136
+ queue.filter(lift.accepts).dequeueOption match
137
+ case None => (lift, queue)
138
+ case Some (person, _) =>
139
+ val fullerLift = lift.copy(people = lift.people.enqueue(person))
140
+ val emptierQueue = queue.diff(Seq (person))
141
+ pickup(fullerLift, emptierQueue)
134
142
135
- // get current floor queue
136
- val queue = building.floors(lift .position)
143
+ // pick up people from the current floor
144
+ val (lift3, floorQueue) = pickup(lift = lift2, queue = building.floors(lift2 .position) )
137
145
138
- // Transfer people from floor queue into lift
139
- while lift.hasRoom && queue.exists(lift.accepts) do
140
- val person = queue.dequeueFirst(lift.accepts).get
141
- lift.people.enqueue(person)
146
+ // update the building to reflect the updated floor
147
+ val building2 = building.copy(floors = building.floors.updated(lift3.position, floorQueue))
142
148
143
- val oldPosition = lift.position
144
- val (nextPosition, nextDirection) = getNextPositionAndDirection(building, lift )
149
+ // core task: find the new target and direction
150
+ val (nextPosition, nextDirection) = getNextPositionAndDirection(building2, lift3 )
145
151
146
- // Set the new values
147
- lift.direction = nextDirection
148
- lift.position = nextPosition
152
+ // update lift parameters
153
+ val lift4 = lift3.copy(nextPosition, nextDirection)
149
154
150
155
// Register the stop. I added the extra condition because of a bug
151
156
// by which the lift sometimes takes two turns for the very last move 🤔
152
- if oldPosition != nextPosition then stops += nextPosition
157
+ val stops2 = true match
158
+ case _ if lift3.position != lift4.position => stops :+ lift4.position
159
+ case _ => stops
153
160
154
- state
161
+ state.copy(building2, lift4, stops2)
155
162
}
156
163
157
164
private def getNextPositionAndDirection (building : Building , lift : Lift ): (Floor , Direction ) =
0 commit comments