Browse Source

Day 11

master
Chris Smith 7 years ago
parent
commit
d03f2ce03a
2 changed files with 114 additions and 0 deletions
  1. 110
    0
      11.py
  2. 4
    0
      11.txt

+ 110
- 0
11.py View File

@@ -0,0 +1,110 @@
1
+#!/usr/bin/python3
2
+
3
+import itertools, re
4
+
5
+# Marker used to show the position of the lift
6
+lift = '*YOU ARE HERE*'
7
+
8
+# Read the input
9
+with open('11.txt', 'r') as file:
10
+    lines = list(map(str.strip, file.readlines()))
11
+    floors = [re.findall(r'\b(\S+ (?:generator|microchip))\b', line) for line in lines]
12
+    floors[0].append(lift)
13
+
14
+# Return the elements of the chips or generators in the given lists
15
+chips = lambda items: set([item.split('-')[0] for item in items if item.endswith('microchip')])
16
+genrs = lambda items: set([item.split(' ')[0] for item in items if item.endswith('generator')])
17
+
18
+# Verify that if there are generators, then all microchips present are paired
19
+valid_floor = lambda floor: not len(genrs(floor)) or not len(chips(floor) - genrs(floor))
20
+valid_layout = lambda layout: False not in [valid_floor(floor) for floor in layout]
21
+
22
+# We win when everything is on the last floor (i.e., nothing is on the other floors)
23
+target = lambda layout: sum(len(floor) for floor in layout[:-1]) == 0
24
+
25
+# Returns the floor/floor index the lift is currently on
26
+my_floor = lambda layout: next(floor for floor in layout if lift in floor)
27
+my_floor_index = lambda layout: next(i for i, floor in enumerate(layout) if lift in floor)
28
+
29
+# Returns just the items on a floor (not the lift)
30
+items = lambda floor: set(floor) - set([lift])
31
+
32
+# Returns an enumeration of sets of items that could potentially be picked up (any combo of 1 or 2 items)
33
+pickups = lambda items: map(set, itertools.chain(itertools.combinations(items, 2), itertools.combinations(items, 1)))
34
+
35
+# Returns an enumeration of possible destinations for the lift (up or down one floor)
36
+dests = lambda layout: filter(is_floor, [my_floor_index(layout) + 1, my_floor_index(layout) - 1])
37
+is_floor = lambda i: i >= 0 and i < len(floors)
38
+
39
+# Returns an enumeration of possible moves that could be made from the given state
40
+moves = lambda layout: itertools.product(pickups(items(my_floor(layout))), dests(layout))
41
+
42
+# Finds a floor that contains the given item
43
+find = lambda item, layout: next(i for i, floor in enumerate(layout) if item in floor)
44
+
45
+
46
+# Performs a breadth-first search over all moves for the given layout in order to find the number
47
+# of steps needed to get to a winning state.
48
+def run(floors):
49
+
50
+    # The available types depends on the input (and thus differs between calls to the run function),
51
+    # so we have to calculate it here, and make the serialise() and domoves() functions closures
52
+    # over this list.
53
+    types = [chip.split(' ')[0] for chip in itertools.chain.from_iterable(chips(items(floor)) for floor in floors)]
54
+
55
+    # Serialises a layout into a string, for easy storage.
56
+    # Items are replaced with numeric identifiers, determined based on position of the generator and
57
+    # chip of that type. This means that layouts that are identical except for the elements being
58
+    # swapped around serialise to the same string (as the process for moving them to the end will be
59
+    # the) same.
60
+    def serialise(layout):
61
+        keys = sorted(types, key=lambda t: find(t + ' generator', layout) * len(layout)
62
+                                         + find(t + '-compatible microchip', layout))
63
+        mappings = {lift: '*'}
64
+        for i, key in enumerate(keys):
65
+            mappings['%s generator' % key] = '%iG' % i
66
+            mappings['%s-compatible microchip' % key] = '%iM' % i
67
+        return '|'.join(''.join(sorted(mappings[item] for item in floor)) for floor in layout)
68
+
69
+
70
+    # Evaluates each possible move for the given layout.
71
+    # Moves are checked to ensure they're valid and serialised to ensure they haven't been visited
72
+    # Returns a list of new layouts for the next step, or False if a solution was encountered
73
+    def domoves(layout, steps):
74
+        queued = []
75
+        for items, to in moves(layout):
76
+            items = set(items).union(set([lift]))
77
+            new_layout = [set(floor) - items for floor in layout]
78
+            new_layout[to] |= (items)
79
+            if valid_layout(new_layout):
80
+                serialised = serialise(new_layout)
81
+                if serialised not in distances:
82
+                    distances[serialised] = steps
83
+                    queued.append(new_layout)
84
+                    if target(new_layout):
85
+
86
+                        return False
87
+        return queued
88
+
89
+
90
+    # Run repeated iterations until we hit a winning result, then immediately returns the step
91
+    # count.
92
+    distances = {serialise(floors): 0}
93
+    step = 1
94
+    queued = [floors]
95
+    while True:
96
+        next_queue = []
97
+        for layout in queued:
98
+            res = domoves(layout, step)
99
+            if res == False:
100
+                return step
101
+            next_queue.extend(res)
102
+        queued = next_queue
103
+        step += 1
104
+
105
+print("Part 1: %s" % run(floors))
106
+
107
+floors[0].extend(['elerium generator', 'elerium-compatible microchip',
108
+                  'dilithium generator', 'dilithium-compatible microchip'])
109
+
110
+print("Part 2: %s" % run(floors))

+ 4
- 0
11.txt View File

@@ -0,0 +1,4 @@
1
+The first floor contains a thulium generator, a thulium-compatible microchip, a plutonium generator, and a strontium generator.
2
+The second floor contains a plutonium-compatible microchip and a strontium-compatible microchip.
3
+The third floor contains a promethium generator, a promethium-compatible microchip, a ruthenium generator, and a ruthenium-compatible microchip.
4
+The fourth floor contains nothing relevant.

Loading…
Cancel
Save