コード例 #1
0
def draw(cuboids: Iterable[Cuboid], axis_flat: str = 'z') -> None:
    axis_hor, axis_vert = {
        'x': ('y', 'z'),
        'y': ('x', 'z'),
        'z': ('x', 'y')
    }[axis_flat]
    depths = (((h, v), len(cuboid.get_range(axis_flat))) for cuboid in cuboids
              for h in cuboid.get_range(axis_hor)
              for v in cuboid.get_range(axis_vert))
    total_depths: Counter[tuple[int, int]] = Counter()
    for h_v, depth in depths:
        total_depths[h_v] += depth

    def char(pos: tuple[int, int]) -> str:
        val = total_depths[pos]
        if val == 0:
            return '·'
        elif val < 10:
            return str(val)
        else:
            return '#'

    bounds = Rect.with_all(total_depths.keys())
    left_margin = len(str(bounds.top_y)) + 1
    print(' ' * left_margin + f'{bounds.left_x} -> {axis_hor}')  # h_label
    vert_labels = [
        f'{bounds.top_y} ', '↓'.rjust(left_margin - 1) + ' ',
        axis_vert.rjust(left_margin - 1) + ' '
    ]

    for vert in bounds.range_y():
        vert_off = vert - bounds.top_y
        vert_label = vert_labels[vert_off] if vert_off < len(
            vert_labels) else ' ' * left_margin
        print(vert_label + ''.join(char((h, vert)) for h in bounds.range_x()))
コード例 #2
0
    def drawn(self,
              collisions: Iterable[Pos] = None,
              coordinates: bool = False) -> str:
        collisions_set = set(collisions or ())

        def char(pos: Pos) -> str:
            if pos in self.carts:
                return str(self.carts[pos])
            elif pos in collisions_set:
                return 'X'
            elif pos in self.tracks:
                return self.tracks[pos]
            else:
                return ' '

        bounds = Rect.with_all(self.tracks.keys())

        def lines() -> Iterable[str]:
            if coordinates:
                for coordinates_row in zip(*(str(x).rjust(2)
                                             for x in bounds.range_x())):
                    yield " " + "".join(coordinates_row)
            for y in bounds.range_y():
                y_coor = str(y) if coordinates else ""
                yield y_coor + "".join(char((x, y))
                                       for x in bounds.range_x()).rstrip()

        return "\n".join(lines())
コード例 #3
0
 def __init__(self, board: Board, sources: Iterable[Pos]):
     self.board = board
     self.sources = list(sources)
     self.scoring_bounds = Rect.with_all(self.board.keys())
     self.drawing_bounds = self.scoring_bounds.grow_to_fit(
         self.sources).grow_by(dx=2)
     self.ticks = 0
コード例 #4
0
def watch_for_message(stars: Stars, font: ocr.Font) -> tuple[str, int]:
    return next((font.read_string(drawn(stars, bounds)), tick)
                for tick, stars in tqdm(enumerate(move(stars)),
                                        desc="watching stars",
                                        unit=" ticks",
                                        unit_scale=True,
                                        delay=0.5)
                if (bounds := Rect.with_all(
                    (x, y) for (x, y), _ in stars)).height <= font.char_height)
コード例 #5
0
def drawn(stars: Stars,
          bounds: Rect = None,
          char_star='*',
          char_sky='·') -> str:
    positions = set(tuple(pos) for pos, _ in stars)
    if bounds is None:
        bounds = Rect.with_all((x, y) for x, y in positions)

    return "\n".join(''.join(char_star if (x, y) in positions else char_sky
                             for x in bounds.range_x())
                     for y in bounds.range_y())
コード例 #6
0
ファイル: day06_area.py プロジェクト: cathackk/advent-of-code
def safe_region(coordinates: Iterable[Pos], distance_limit: int) -> set[Pos]:
    # note that this wouldn't work with distance_limit large enough
    # for the safe region to overflow the bounds
    coordinates_list = list(coordinates)
    bounds = Rect.with_all(coordinates_list)
    return {
        pos
        for pos in bounds
        if sum(manhattan_distance(pos, coor)
               for coor in coordinates_list) < distance_limit
    }
コード例 #7
0
def draw_map(vents: Iterable[Vent], allow_diagonal: bool = False) -> None:
    considered_vents = [
        vent for vent in vents
        if allow_diagonal or vent.is_vertical() or vent.is_horizontal()
    ]

    bounds = Rect.with_all(pos for vent in considered_vents
                           for pos in (vent.pos_1, vent.pos_2))
    counts = Counter(p for vent in considered_vents for p in vent.points())
    lines = (''.join(str(counts[(x, y)] or '·') for x in bounds.range_x())
             for y in bounds.range_y())
    print('\n'.join(lines))
コード例 #8
0
    def __init__(self, passages: Iterable[Pos],
                 targets: Iterable[Target] | dict[Pos, str]):
        self.passages = set(passages)
        self.targets: dict[Pos, str] = dict(targets)

        self.bounds = Rect.with_all(self.passages).grow_by(+1, +1)

        assert self.passages
        assert self.targets
        for target_pos, target_code in self.targets.items():
            assert target_pos in self.passages  # target is not in a wall
            assert len(target_code) == 1
コード例 #9
0
    def __init__(self, tiles: Iterable[tuple[Pos, str]]):
        self.tiles = {
            pos: ch
            for pos, ch in tiles
            if ch != self.FLOOR
        }
        self.bounds = Rect.with_all(self.tiles.keys())
        self.rounds = 0

        assert all(
            tile in (self.EMPTY_SEAT, self.OCCUPIED_SEAT)
            for tile in self.tiles.values()
        )
コード例 #10
0
    def draw(self, z: int) -> None:
        flat_scanners = {(s.pos.x, s.pos.y) for s in self.scanners if s.pos.z == z}
        flat_beacons = {(b.x, b.y) for b in self.all_beacons if b.z == z}
        canvas = Rect.with_all(flat_scanners | flat_beacons)

        def char(pos: tuple[int, int]) -> str:
            if pos in flat_scanners:
                return 'S'
            elif pos in flat_beacons:
                return 'B'
            else:
                return '·'

        for y in canvas.range_y():
            print(''.join(char((x, y)) for x in canvas.range_x()))
コード例 #11
0
    def draw(self, z: int) -> None:
        flat_beacons = {(b.x, b.y) for b in self if b.z == z}
        flat_origin = (0, 0)
        canvas = Rect.with_all(flat_beacons | {flat_origin})

        def char(pos: tuple[int, int]) -> str:
            if pos == flat_origin:
                return 'S'
            elif pos in flat_beacons:
                return 'B'
            else:
                return '·'

        for y in canvas.range_y():
            print(''.join(char((x, y)) for x in canvas.range_x()))
コード例 #12
0
def draw(dots: set[Pos],
         instruction: Instruction = None,
         full_char='█',
         empty_char='·') -> None:
    def char(pos: Pos) -> str:
        if pos in dots:
            return full_char
        elif instruction and instruction.is_on_fold(pos):
            return '|' if instruction.is_vertical() else '-'
        else:
            return empty_char

    bounds = Rect.with_all(dots)
    for y in bounds.range_y():
        print(''.join(char((x, y)) for x in bounds.range_x()))
コード例 #13
0
ファイル: day24_hex.py プロジェクト: cathackk/advent-of-code
    def __str__(self):
        def char(pos: Pos) -> str:
            x, y = pos
            if (x + y) % 2 == 1:
                return ' '
            elif pos in self.active_tiles:
                return '#'
            else:
                return '.'

        bounds = Rect.with_all(self.active_tiles)
        return "\n".join(
            "".join(char((x, y)) for x in bounds.range_x())
            for y in bounds.range_y()
        )
コード例 #14
0
    def __init__(self, nodes: Iterable[Node], target_data_pos: Pos = None):
        self.nodes = {node.pos: node for node in nodes}
        self.rect = Rect.with_all(self.nodes.keys())
        assert self.rect.area == len(self.nodes)

        if target_data_pos:
            assert target_data_pos in self.rect
            self.target_data_pos = target_data_pos
        else:
            self.target_data_pos = self.rect.top_right

        self.empty_node_pos = single_value(
            pos
            for pos, node in self.nodes.items()
            if node.used == 0
        )

        self._key = self._create_hash_key()
コード例 #15
0
def draw_trajectory(initial_velocity: Vector, target: Rect) -> None:
    steps = list(shoot(initial_velocity, target))

    origin = (0, 0)
    bounds = Rect.with_all([origin, target.top_left, target.bottom_right] +
                           steps)

    def char(pos: Pos) -> str:
        if pos == origin:
            return 'S'
        elif pos in steps:
            return '#'
        elif pos in target:
            return 'T'
        else:
            return '·'

    for y in reversed(bounds.range_y()):
        print(''.join(char((x, y)) for x in bounds.range_x()))
コード例 #16
0
    def __format__(self, format_spec: str) -> str:
        pad = int(format_spec) if format_spec else 2
        bounds = Rect.with_all(self.grid.nodes.keys()).grow_to_fit([self.pos]).grow_by(pad, pad)

        def char(pos: Pos) -> str:
            x, y = pos
            if pos == self.pos:
                suffix = "]"
            elif (x + 1, y) == self.pos:
                suffix = "["
            else:
                suffix = " "

            return self.grid[pos].char + suffix

        def lines() -> Iterable[str]:
            for y in bounds.range_y():
                status = f" ({self.heading.arrow})" if y == self.pos[1] else ""
                yield ("".join(char((x, y)) for x in bounds.range_x()) + status).rstrip()

        return "\n".join(lines())
コード例 #17
0
ファイル: day06_area.py プロジェクト: cathackk/advent-of-code
def claim_areas(coordinates: Iterable[Pos],
                include_infinite: bool = False) -> dict[Pos, set[Pos]]:
    def neighbors(pos: Pos) -> Iterable[Pos]:
        x, y = pos
        yield x + 1, y
        yield x - 1, y
        yield x, y + 1
        yield x, y - 1

    positions = sorted(coordinates)
    # determine boundaries
    bounds = Rect.with_all(positions).grow_by(+3, +3)
    # position -> claimed by which original coordinate
    claimed_by: dict[Pos, Pos | None] = {pos: pos for pos in positions}
    new_claims: dict[Pos, set[Pos]] = {pos: {pos} for pos in positions}

    while any(len(claimants) == 1 for claimants in new_claims.values()):
        # collect all new claims
        new_claims = dgroupby_pairs_set(
            (neighbor, claimant) for pos, claimants in new_claims.items()
            for neighbor in neighbors(pos) if neighbor not in claimed_by
            if neighbor in bounds for claimant in claimants)
        # mark new claims (with single claimant) and any position with draws
        claimed_by.update({
            pos: single_value(claimants) if len(claimants) == 1 else None
            for pos, claimants in new_claims.items()
        })

    # optionally ignore all claims reaching bounds (infinite?)
    if not include_infinite:
        ignored = set(claimant for pos in bounds.border_ps()
                      if (claimant := claimed_by[pos]))
    else:
        ignored = set()

    # return the areas: claimant -> set of their claimed positions
    return dgroupby_pairs_set((claimant, pos)
                              for pos, claimant in claimed_by.items()
                              if claimant if claimant not in ignored)
コード例 #18
0
 def __init__(self, heights: Iterable[tuple[Pos, int]]):
     self.heights = dict(heights)
     self.bounds = Rect.with_all(self.heights.keys())
コード例 #19
0
ファイル: day15_risk.py プロジェクト: cathackk/advent-of-code
 def __init__(self, values: Iterable[tuple[Pos, int]]):
     self.values = dict(values)
     self.bounds = Rect.with_all(self.values.keys())
     assert len(self.values) == self.bounds.area
コード例 #20
0
 def __str__(self):
     rect = Rect.with_all(self.keys)
     return "\n".join(
         " ".join(self.get((x, y), " ") for x in rect.range_x()).rstrip()
         for y in rect.range_y()
     )
コード例 #21
0
            if claim.id_ not in claim_ids_with_overlaps)


def print_claims(*claims: Claim,
                 empty_color: str = '·',
                 overlapping_color: str = 'X',
                 long_id_color: str = '#',
                 bounds: Rect | None = None):
    canvas: dict[Pos, str] = {}
    for claim in claims:
        color = long_id_color if len(id_str := str(claim.id_)) > 1 else id_str
        canvas.update((pos, overlapping_color if pos in canvas else color)
                      for pos in claim.rect)

    if bounds is None:
        bounds = Rect.with_all(canvas).grow_by(1, 1)

    for y in bounds.range_y():
        print(''.join(
            canvas.get((x, y), empty_color) for x in bounds.range_x()))


def claims_from_text(text: str) -> list[Claim]:
    return list(claims_from_lines(text.strip().splitlines()))


def claims_from_file(fn: str) -> list[Claim]:
    return list(claims_from_lines(open(relative_path(__file__, fn))))


def claims_from_lines(lines: Iterable[str]) -> Iterable[Claim]:
コード例 #22
0
 def __init__(self, pixels: Iterable[Pixel], others: bool = False):
     self.lit = set(pos for pos, lit in pixels if lit != others)
     self.inverted = others
     self.bounds = Rect.with_all(self.lit)
コード例 #23
0
    def assemble(cls, tiles: Iterable[Tile]) -> Optional['Image']:
        tiles = list(tiles)

        # matrix of continuously placed tiles; bordering ones must match
        placed: dict[Pos, Tile] = {}
        # pool of unplaced tiles -> needs to be emptied by the end of the algorithm
        unplaced_tiles: dict[int, Tile] = {tile.tile_id: tile for tile in tiles}
        # empty positions bordering any placed tiles -> needs to be updated continuously
        # start with a single position where the first tile will be placed immediately
        fringe_positions: set[Pos] = {(0, 0)}

        # any further tiles will have to be checked for match with their neighbors
        def is_matching(tile: Tile, pos: Pos) -> bool:
            assert pos not in placed
            top, right, bottom, left = neighbors(pos)
            return (top not in placed or tile.border_top == placed[top].border_bottom) \
                and (right not in placed or tile.border_right == placed[right].border_left) \
                and (bottom not in placed or tile.border_bottom == placed[bottom].border_top) \
                and (left not in placed or tile.border_left == placed[left].border_right)

        # four adjacent positions
        def neighbors(pos: Pos) -> tuple[Pos, Pos, Pos, Pos]:
            x, y = pos
            return (
                (x, y - 1),  # top
                (x + 1, y),  # right
                (x, y + 1),  # bottom
                (x - 1, y)   # left
            )

        # as long as there are any tiles unplaced ...
        while unplaced_tiles:
            try:
                # ... try placing one of them into any empty bordering position
                matching_tile, placed_pos = next(
                    (candidate_orientation, fringe_pos)
                    for candidate_tile in unplaced_tiles.values()
                    for candidate_orientation in candidate_tile.orientations()
                    for fringe_pos in fringe_positions
                    if is_matching(candidate_orientation, fringe_pos)
                )
            except StopIteration:
                # no matching tile found
                return None

            # found a matching tile!
            placed[placed_pos] = matching_tile
            del unplaced_tiles[matching_tile.tile_id]

            # update bordering positions
            fringe_positions.remove(placed_pos)
            fringe_positions.update(
                npos
                for npos in neighbors(placed_pos)
                if npos not in placed
            )

            # draw
            # bounds = Rect.with_all(set(placed.keys()) | (fringe_positions))
            # def chr(pos):
            #     if pos == placed_pos:
            #         return "+"
            #     elif pos in fringe_positions:
            #         return "."
            #     elif pos in placed:
            #         return "O" if pos == (0, 0) else "#"
            #     else:
            #         return " "
            # eprint(f"{len(placed)}/{len(tiles)}")
            # eprint("\n".join(
            #     "".join(chr((x, y)) for x in bounds.range_x())
            #     for y in bounds.range_y()
            # ))
            # eprint()

        # everything placed!
        # make sure all tiles fit into a full rectangle without any gaps
        bounds = Rect.with_all(placed.keys())
        if bounds.area != len(tiles):
            raise ValueError("tiles placed into non-rectangular area")

        # return the assembled rectangle as Image
        return cls(
            [placed[(x, y)] for x in bounds.range_x()]
            for y in bounds.range_y()
        )