Sequential Access Optimization (SCAN)¶
Determine the next floor for an elevator using the SCAN (elevator) algorithm -- the same strategy used in disk-head scheduling to minimize seek time.
Problem¶
Given:
- current_floor -- where the elevator is now
- direction -- currently moving UP or DOWN
- buttons -- a list of pressed buttons like
['5D', '3U', '7D']where the number is the floor and the suffix indicates whether the button was pressed going UP, DOWN, or INSIDE the elevator
Determine the next floor the elevator should visit.
Approach¶
This implements the SCAN algorithm (also called the elevator algorithm):
- Classify buttons into UP, DOWN, and INSIDE lists based on their suffix.
- Sort floors in the current direction of travel.
- Check if direction reversal is needed -- if no remaining floors lie ahead in the current direction, flip.
- Build the visit order: Starting from the current floor, take all floors ahead in the current direction, then append floors in the opposite direction (reversed).
- Return the first floor in the resulting ordered list.
The key insight: the elevator continues in one direction, serving all requests along the way, then reverses -- exactly like a disk head doing a SCAN sweep across platters. This minimizes total seek distance compared to naive FCFS scheduling.
Implementation¶
from enum import Enum
from random import choice, sample
class ButtonType(Enum):
UP = "U"
DOWN = "D"
INSIDE = "I"
class Button:
def __init__(self, button_str):
self.floor = int(button_str[:-1])
self.type = ButtonType(button_str[-1])
def map_input_buttons_to_lists(buttons):
by_type = {
bt: [Button(x) for x in buttons if x.endswith(bt.value)]
for bt in ButtonType
}
# INSIDE buttons are reachable regardless of direction
inside = by_type[ButtonType.INSIDE]
by_type[ButtonType.UP] = by_type[ButtonType.UP] + inside
by_type[ButtonType.DOWN] = by_type[ButtonType.DOWN] + inside
return by_type
def check_direction(current_floor, direction, floors_map):
"""Return True if direction should reverse (no reachable floors ahead)."""
return bool(
floors_map[direction] and (
(direction == ButtonType.UP and
max(b.floor for b in floors_map[direction]) < current_floor) or
(direction == ButtonType.DOWN and
min(b.floor for b in floors_map[direction]) > current_floor)
)
)
def sort_floors_to_visit(current_floor, direction, opposite_dir, floors_map):
"""Build ordered visit list: floors ahead first, then opposite direction."""
reverse = (direction == ButtonType.DOWN)
floor_list = sorted(floors_map[direction], key=lambda x: x.floor, reverse=reverse)
next_floor = next(
(x for x in floor_list
if (x.floor >= current_floor and direction == ButtonType.UP) or
(x.floor <= current_floor and direction == ButtonType.DOWN)),
None
)
if next_floor:
idx = floor_list.index(next_floor)
floor_list = floor_list[idx:] + sorted(floors_map[opposite_dir], key=lambda x: x.floor)
else:
floor_list.extend(sorted(floors_map[opposite_dir], key=lambda x: x.floor))
return floor_list
def next_floor(current_floor, direction, buttons):
floors_map = map_input_buttons_to_lists(buttons)
if check_direction(current_floor, direction, floors_map):
direction = ButtonType.DOWN if direction == ButtonType.UP else ButtonType.UP
opposite_dir = ButtonType.DOWN if direction == ButtonType.UP else ButtonType.UP
visit_order = sort_floors_to_visit(current_floor, direction, opposite_dir, floors_map)
return visit_order[0].floor if visit_order else None
# Test harness
class TestData:
TEST_FLOORS = [
['5D', '3U'],
['5D', '3U', '7D'],
['5D', '3U', '2U', '7D', '10U'],
]
def main():
test_cases = [
(choice(range(1, 21)), choice(list(ButtonType)),
sample(TestData.TEST_FLOORS[i % len(TestData.TEST_FLOORS)],
len(TestData.TEST_FLOORS[i % len(TestData.TEST_FLOORS)])))
for i in range(20)
]
for i, (floor, direction, buttons) in enumerate(test_cases):
result = next_floor(floor, direction, buttons)
print(f"Test {i}: floor={floor}, dir={direction.value}, "
f"buttons={buttons}, next={result}")
if __name__ == "__main__":
main()
Complexity¶
- Time: O(n log n) for sorting the button lists, where n = number of buttons
- Space: O(n) for the classified and sorted lists
Takeaway¶
SCAN is one of those algorithms where the real-world analogy (elevator) makes it click immediately. The same sequential-access principle matters in data loaders -- FFCV and WebDataset use sequential sharded reads because random I/O on spinning disks is 10-100x slower.