A solution in python, with a lot of code to clean up.
import collections import re Line = collections.namedtuple('Line', 'min_x max_x min_y max_y') range_regexp = re.compile(r'\s*(?P<axis>[x|y])=(?P<min>\d+)(\.\.(?P<max>\d+))?\s*') class Ground: def __init__(self, lines): self.parsed_lines = [parse_line(line) for line in lines] self._get_limits() self._create_slice() self._fill_with_clay() self._create_spring() def _get_limits(self): self.min_x = min(l.min_x for l in self.parsed_lines) - 1 self.max_x = max(l.max_x for l in self.parsed_lines) + 1 self.real_min_y = min(l.min_y for l in self.parsed_lines) self.min_y = min(self.real_min_y, 0) self.max_y = max(l.max_y for l in self.parsed_lines) self.width = self.max_x - self.min_x + 1 self.height = self.max_y - self.min_y + 1 def _create_slice(self): self.slice = [['.' for _ in range(self.width)] for _ in range(self.height)] def _fill_with_clay(self): for min_x, max_x, min_y, max_y in self.parsed_lines: for x in range(min_x, max_x + 1): for y in range(min_y, max_y + 1): self.set(x, y, '#') def _create_spring(self): self.set(500, 0, '+') self.drops = [(500, 0)] def get(self, x, y): return self.slice[y - self.min_y][x - self.min_x] def set(self, x, y, val): self.slice[y - self.min_y][x - self.min_x] = val def show(self): return '\n'.join(''.join(self.get(x, y) for x in range(self.min_x, self.max_x + 1)) for y in range(self.min_y, self.max_y + 1)) def find_bottom(self, x, y, bottom): xx_min = x while self.get(xx_min - 1, y) == bottom: xx_min -= 1 xx_max = x while self.get(xx_max + 1, y) == bottom: xx_max += 1 return xx_min, xx_max def find_sand(self, x, y): xx_min = x while xx_min > self.min_x + 1 and self.get(xx_min - 1, y) == '.': xx_min -= 1 xx_max = x while xx_max < self.max_x - 1 and self.get(xx_max + 1, y) == '.': xx_max += 1 if self.get(xx_min - 1, y) == '#' and self.get(xx_max + 1, y) == '#': return xx_min, xx_max else: return None def steps(self): while True: if len(self.drops) == 0: return (x, y) = self.drops.pop() if y == self.max_y: continue if self.get(x, y) == '+': if self.get(x, y + 1) == '.': self.set(x, y + 1, '|') yield self.drops.append((x, y + 1)) elif self.get(x, y) == '|': if self.get(x, y + 1) == '.': self.set(x, y + 1, '|') yield self.drops.append((x, y)) self.drops.append((x, y + 1)) elif self.get(x, y + 1) in ('#', '~'): if self.get(x - 1, y) == '.': self.set(x - 1, y, '|') yield self.drops.append((x - 1, y)) elif self.get(x - 1, y) == '#' and self.get(x,y) == '|': right = self.find_right(x, y, '|') if right: self.rest_water(x, right, y) yield if self.get(x + 1, y) == '.': self.set(x + 1, y, '|') yield self.drops.append((x + 1, y)) elif self.get(x + 1, y) == '#' and self.get(x,y) == '|': left = self.find_left(x, y, '|') if left: self.rest_water(left, x, y) yield def run(self, debug=False): if debug: self.print() for _ in self.steps(): if debug: self.print() def print(self): print() print(self.show()) def count(self): return sum(1 for row in self.slice for c in row if c in ('|', '~')) def count_dry(self): return sum(1 for row in self.slice for c in row if c == '~') def find_right(self, x, y, char): assert self.get(x, y) == char assert self.get(x, y + 1) in ('#', '~') x_max = x while self.get(x_max + 1, y) == char and self.get(x_max + 1, y + 1) in ('#', '~'): x_max += 1 if self.get(x_max + 1, y) == '#' and self.get(x_max + 1, y + 1) in ('#', '~'): return x_max else: return None def find_left(self, x, y, char): assert self.get(x, y) == char assert self.get(x, y + 1) in ('#', '~') x_min = x while self.get(x_min - 1, y) == char and self.get(x_min - 1, y + 1) in ('#', '~'): x_min -= 1 if self.get(x_min - 1, y) == '#' and self.get(x_min - 1, y + 1) in ('#', '~'): return x_min else: return None def rest_water(self, x_min, x_max, y): for x in range(x_min, x_max + 1): self.set(x, y, '~') def parse_line(line): limits = [0] * 4 for part in line.split(','): groups = range_regexp.match(part).groupdict() offset = 0 if groups['axis'] == 'x' else 2 min_ = int(groups['min']) max_ = int(groups['max']) if groups['max'] else min_ limits[offset] = min_ limits[offset + 1] = max_ return Line(*limits) def test_parse_line(): assert Line(495, 495, 2, 7) == parse_line('x=495, y=2..7') assert Line(495, 501, 7, 7) == parse_line('y=7, x=495..501') assert Line(501, 501, 3, 7) == parse_line('x=501, y=3..7') assert Line(498, 498, 2, 4) == parse_line('x=498, y=2..4') assert Line(506, 506, 1, 2) == parse_line('x=506, y=1..2') assert Line(498, 498, 10, 13) == parse_line('x=498, y=10..13') assert Line(504, 504, 10, 13) == parse_line('x=504, y=10..13') assert Line(498, 504, 13, 13) == parse_line('y=13, x=498..504') def test_show(): expected = """\ ......+....... ............#. .#..#.......#. .#..#..#...... .#..#..#...... .#.....#...... .#.....#...... .#######...... .............. .............. ....#.....#... ....#.....#... ....#.....#... ....#######...""" with open('test_input.txt', 'r') as test: test_ground = Ground(line.strip() for line in test) assert expected == test_ground.show() def test_run(): expected = """\ ......+....... ......|.....#. .#..#||||...#. .#..#~~#|..... .#..#~~#|..... .#~~~~~#|..... .#~~~~~#|..... .#######|..... ........|..... ...|||||||||.. ...|#~~~~~#|.. ...|#~~~~~#|.. ...|#~~~~~#|.. ...|#######|..""" with open('test_input.txt', 'r') as test: test_ground = Ground(line.strip() for line in test) test_ground.run() assert expected == test_ground.show() def test_count(): with open('test_input.txt', 'r') as test: test_ground = Ground(line.strip() for line in test) test_ground.run() assert 57 == test_ground.count() # Problematic case def test_problem(): expected = """\ ..........+.......... ..................... .#.................#. .#.................#. .#.................#. .#.................#. .#.................#. .#.................#. .#......######.....#. .#......#....#.....#. .#......#....#.....#. .#......######.....#. .#.................#. .#.................#. .###################.""" with open('problem_input.txt', 'r') as test: test_ground = Ground(line.strip() for line in test) test_ground.print() assert expected == test_ground.show() test_ground.run() test_ground.print() if __name__ == '__main__': with open('input.txt', 'r') as file: ground = Ground(line.strip() for line in file) ground.run() print("Part1:", ground.count() - ground.real_min_y + 1) print("Part2:", ground.count_dry())
After I failed twice your solution inspired me, so thanks! I'll post it as a separate reply.
Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink.
Hide child comments as well
Confirm
For further actions, you may consider blocking this person and/or reporting abuse
We're a place where coders share, stay up-to-date and grow their careers.
A solution in python, with a lot of code to clean up.
After I failed twice your solution inspired me, so thanks! I'll post it as a separate reply.