| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091 |
- #!/usr/bin/env python3
- import re
- from collections import namedtuple
- from itertools import chain
- from queue import PriorityQueue
- State = namedtuple('State', 'floors elevator')
- def get_score(state):
- # - lower accumulated distance of all elements to floor 4 = better
- # - elevator position closer to lowest item = better
- accumulated_distance = 0
- elevator_to_bottom = None
- top_dist = len(state.floors) - 1
- for i, (rtgs, chips) in enumerate(state.floors):
- nitems = len(rtgs) + len(chips)
- accumulated_distance += top_dist * nitems
- top_dist -= 1
- if nitems > 0 and elevator_to_bottom is None:
- elevator_to_bottom = state.elevator - i
- return accumulated_distance, elevator_to_bottom
- def is_valid(state):
- def floor_valid(rtgs, chips):
- return len(rtgs) == 0 or all(ty in rtgs for ty in chips)
- return 0 <= state.elevator < len(state.floors) and \
- all(floor_valid(r, c) for r, c in state.floors)
- def add_to_floor(floor, new_rtgs, new_chips):
- old_rtgs, old_chips = floor
- return old_rtgs + new_rtgs, old_chips + new_chips
- def valid_steps(state):
- floors, el = state
- def select(s):
- if len(s) > 0:
- for i, c in enumerate(s):
- yield c, s[:i] + s[i + 1:]
- def move_up(rtg, rem_rtgs, chip, rem_chips):
- cur = rem_rtgs, rem_chips
- top = add_to_floor(floors[el + 1], rtg, chip)
- return State(floors[:el] + (cur, top) + floors[el + 2:], el + 1)
- def move_down(rtg, rem_rtgs, chip, rem_chips):
- cur = rem_rtgs, rem_chips
- bot = add_to_floor(floors[el - 1], rtg, chip)
- return State(floors[:el - 1] + (bot, cur) + floors[el + 1:], el - 1)
- def gen_steps():
- rtgs, chips = floors[el]
- for rtg1, rem_rtgs1 in select(rtgs):
- if el < len(floors) - 1:
- for rtg2, rem_rtgs2 in select(rem_rtgs1):
- yield move_up(rtg1 + rtg2, rem_rtgs2, '', chips)
- for chip, rem_chips in select(chips):
- yield move_up(rtg1, rem_rtgs1, chip, rem_chips)
- yield move_up(rtg1, rem_rtgs1, '', chips)
- if el > 0:
- yield move_down(rtg1, rem_rtgs1, '', chips)
- for chip1, rem_chips1 in select(chips):
- if el < len(floors) - 1:
- for chip2, rem_chips2 in select(rem_chips1):
- yield move_up('', rtgs, chip1 + chip2, rem_chips2)
- yield move_up('', rtgs, chip1, rem_chips1)
- if el > 0:
- yield move_down('', rtgs, chip1, rem_chips1)
- return filter(is_valid, gen_steps())
- def min_steps(initial):
- worklist = PriorityQueue()
- seen = {initial}
- worklist.put((get_score(initial), 0, initial))
- nprocessed = 0
- while not worklist.empty():
- score, steps, state = worklist.get()
- if score == (0, 0):
- return steps
- for next_state in valid_steps(state):
- if next_state not in seen:
- worklist.put((get_score(next_state), steps + 1, next_state))
- seen.add(next_state)
- assert min_steps(State((('', 'HL'), ('H', ''), ('L', ''), ('', '')), 0)) == 11
- part1 = min_steps(State((('SP', 'SP'), ('TRC', 'RC'), ('', 'T'), ('', '')), 0))
- print(part1)
- print(part1 + min_steps(State((('ED', 'ED'), ('', ''), ('', ''), ('S', 'S')), 3)))
- #print(min_steps(State((('SPED', 'SPED'), ('TRC', 'RC'), ('', 'T'), ('', '')), 0)))
|