Another weekly challenge from CodeWars, The Lift. This time slightly more challenging, and took some time to get it right.
class Dinglemouse(object):
def __init__(self, queues, capacity):
self.queues = [list(x) for x in queues]
self.capacity = capacity
def theLift(self):
stops = [0]
elevator = []
people_waiting = True
while people_waiting:
people_waiting = False
# Going up
for floor in range(0, len(self.queues)):
stop_at_floor = False
for person in elevator[:]:
if person == floor:
elevator.remove(person)
stop_at_floor = True
for person in self.queues[floor][:]:
if person > floor:
stop_at_floor = True
if self.capacity > len(elevator):
elevator.append(person)
self.queues[floor].remove(person)
else:
people_waiting = True
if stop_at_floor and not stops[-1] == floor:
stops.append(floor)
# Going down
for floor in range(len(self.queues) -1, -1, -1):
stop_at_floor = False
for person in elevator[:]:
if person == floor:
elevator.remove(person)
stop_at_floor = True
for person in self.queues[floor][:]:
if person < floor:
stop_at_floor = True
if self.capacity > len(elevator):
elevator.append(person)
self.queues[floor].remove(person)
else:
people_waiting = True
if stop_at_floor and not stops[-1] == floor:
stops.append(floor)
if stops[-1] != 0:
stops.append(0)
return stops
# Floors: G 1 2 3 4 5 6 Answers:
tests = [[ ( (), (), (5,5,5), (), (), (), () ), [0, 2, 5, 0] ],
[ ( (), (), (1,1), (), (), (), () ), [0, 2, 1, 0] ],
[ ( (), (3,), (4,), (), (5,), (), () ), [0, 1, 2, 3, 4, 5, 0] ],
[ ( (), (0,), (), (), (2,), (3,), () ), [0, 5, 4, 3, 2, 1, 0] ]]
assert(Dinglemouse(tests[0][0], 5).theLift() == tests[0][1])
assert(Dinglemouse(tests[1][0], 5).theLift() == tests[1][1])
assert(Dinglemouse(tests[2][0], 5).theLift() == tests[2][1])
assert(Dinglemouse(tests[3][0], 5).theLift() == tests[3][1])
Comparing Solution to others
My initial solution felt like spaghetti code, but after looking at others solutions I think it’s quite ok. What I feel would improve the code a lot would be to refactor it a bit to break out the nested conditionals into semantic operations, and in order to reduce code duplication.
One neat thing in order to be able to remove items from a list while iterating through it is to create an anonymous copy in order to avoid unexpected behaviour;
a_list = [1, 2, 3, 4, 5]
for item in a_list:
a_list.remove(item)
print(a_list)
[2, 4]
I am not entirely sure what’s going on here, luckily it’s easily avoided;
a_list = [1, 2, 3, 4, 5]
for item in a_list[:]:
a_list.remove(item)
print(a_list)
[]
Of course, for single line statements like this a list comprehension would be a better solution to filter out the items to keep, but in a more complex function like in the assignment, this trick helped.
class Dinglemouse_v2(object):
def __init__(self, queues, capacity):
self.queues = [list(x) for x in queues]
self.capacity = capacity
self.elevator = []
self.people_waiting = True
def theLift(self):
"""Main function called to execute the elevator."""
stops = [0]
while self.people_waiting:
self.people_waiting = False
stops = self._run_elevator(stops, 'up')
stops = self._run_elevator(stops, 'down')
if stops[-1] != 0:
stops.append(0)
return stops
def _run_elevator(self, stops, direction):
"""Runs the elevator in one direction and returns updated list of all stops."""
floor_order = range(len(self.queues) -1, -1, -1) if direction == 'down' else range(0, len(self.queues))
for floor in floor_order:
stop_to_let_people_off = self._people_getting_off(floor)
stop_to_let_people_on = self._people_getting_on(floor, direction)
if stop_to_let_people_off or stop_to_let_people_on and not stops[-1] == floor:
stops.append(floor)
return stops
def _people_getting_off(self, floor):
"""Removes people waiting in elevator."""
stop_at_floor = False
for person in self.elevator[:]:
if person == floor:
self.elevator.remove(person)
stop_at_floor = True
return stop_at_floor
def _people_getting_on(self, floor, direction):
"""Let new people onto the elevator."""
stop_at_floor = False
for person in self.queues[floor][:]:
person_is_getting_on = person < floor if direction == 'down' else person > floor
if person_is_getting_on:
stop_at_floor = True
if self.capacity > len(self.elevator):
self.elevator.append(person)
self.queues[floor].remove(person)
else:
self.people_waiting = True
return stop_at_floor
assert(Dinglemouse_v2(tests[0][0], 5).theLift() == tests[0][1])
assert(Dinglemouse_v2(tests[1][0], 5).theLift() == tests[1][1])
assert(Dinglemouse_v2(tests[2][0], 5).theLift() == tests[2][1])
assert(Dinglemouse_v2(tests[3][0], 5).theLift() == tests[3][1])
Final Thoughts
- Still room for a lot of improvements. Especially I don’t like passing the historic stops to the run_elevator functions everytime, but it’s currently needed for it to have access to the last floor stopped on during last run up/down. The alternative would be to add the historic stops as an object variable, but since it should be reset everytime the elevator runs that doesn’t feel very clean either.
- There are still nested conditionals that could be broken out into smaller functions, but unless there’s a reason to do it, I think that would be starting to verge on making the code too atomic.
Comments