-
Notifications
You must be signed in to change notification settings - Fork 1
/
day_20.ts
180 lines (147 loc) · 6.48 KB
/
day_20.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
import assert from "assert";
import { loadInput, Hashable, HashValue, ImmutableSet, minmax } from "./helpers";
const ALGORITHM_STRING_LENGTH = 512;
/* Data Model:
* - Image coordinates use the top-left corner of the original pixelSet as (0,0)
* with x increasing to the right and y increasing downwards.
* - Images are modeled using:
* - a tuple of a Set of coordinates of ON pixels within a set of tracked bounds.
* - a boolean state tracking the state of all pixels out of tracked bounds,
* necessary to model the case where bit 0 is ON in the algorithm string.
*/
type AlgorithmString = string;
type PixelSet = ImmutableSet<Pixel>;
type Bounds = {x: {min: number, max: number}, y: {min: number, max: number}};
class Image {
readonly pixels: PixelSet;
readonly bounds: Bounds;
readonly outOfBoundsOn: boolean;
constructor(pixels: PixelSet, outOfBoundsOn: boolean) {
this.pixels = pixels;
this.bounds = pixelSetBounds(pixels);
this.outOfBoundsOn = outOfBoundsOn;
}
has(pixel: Pixel): boolean {
if (this.bounds.x.min <= pixel.x && pixel.x <= this.bounds.x.max &&
this.bounds.y.min <= pixel.y && pixel.y <= this.bounds.y.max) {
return this.pixels.has(pixel);
} else {
return this.outOfBoundsOn;
}
}
}
const ON_CHAR = '#';
class Pixel implements Hashable {
readonly x: number;
readonly y: number;
static allPixels: Map<HashValue, Pixel> = new Map();
static of(x: number, y: number) {
const hash = Pixel.hash(x, y);
if (this.allPixels.has(hash)) {
return this.allPixels.get(hash);
} else {
const pixel = new Pixel(x, y);
this.allPixels.set(hash, pixel);
return pixel;
}
}
private constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
static hash(x: number, y: number): HashValue { return `(${x}:${y})`; }
hash(): HashValue { return Pixel.hash(this.x, this.y); }
}
function imageFromLines(lines: string[]): Image {
const pixelSet = new ImmutableSet([...lines.flatMap( (rowStr, rowIndex) => {
return rowStr.split('').flatMap( (char, colIndex) => {
return char === ON_CHAR ? [Pixel.of(colIndex, rowIndex)] : [];
});
})]);
// The entire grid begins in the OFF state by default.
return new Image(pixelSet, false);
}
function enhance(image: Image, algorithm: string): Image {
function isEnabled(pixel: Pixel, image: Image, algorithm: AlgorithmString): boolean {
const pixelsInBitOrder = [
Pixel.of(pixel.x - 1, pixel.y - 1), Pixel.of(pixel.x, pixel.y - 1), Pixel.of(pixel.x + 1, pixel.y - 1),
Pixel.of(pixel.x - 1, pixel.y), pixel, Pixel.of(pixel.x + 1, pixel.y),
Pixel.of(pixel.x - 1, pixel.y + 1), Pixel.of(pixel.x, pixel.y + 1), Pixel.of(pixel.x + 1, pixel.y + 1),
];
const bitString = pixelsInBitOrder.map(pixel => image.has(pixel) ? '1' : '0').join('');
const position = parseInt(bitString, 2);
return algorithm.charAt(position) === ON_CHAR;
}
const bounds = pixelSetExtendedBounds(image.pixels);
const newPixels = [];
for (let x = bounds.x.min; x <= bounds.x.max; x++) {
for (let y = bounds.y.min; y <= bounds.y.max; y++) {
const pixel = Pixel.of(x, y);
if (isEnabled(pixel, image, algorithm)) { newPixels.push(pixel); }
}
}
const newOutOfBoundsBitPosition = image.outOfBoundsOn ? 511 : 0;
const newOutOfBounds = algorithm.charAt(newOutOfBoundsBitPosition) === ON_CHAR;
return new Image(new ImmutableSet(newPixels), newOutOfBounds);
}
function pixelSetBounds(pixelSet: PixelSet): Bounds {
const pixels: Pixel[] = [...pixelSet];
const [xMin, xMax]: [number, number] = minmax(pixels.map(p => p.x));
const [yMin, yMax]: [number, number] = minmax(pixels.map(p => p.y));
return {x: {min: xMin, max: xMax}, y: {min: yMin, max: yMax}};
}
// Extends bounds one pixel beyond the min/max in every direction.
// Useful for both formatting output and generating the next cycle of pixels.
function pixelSetExtendedBounds(pixelSet: PixelSet): Bounds {
const {x: {min: xMin, max: xMax}, y: {min: yMin, max: yMax}} = pixelSetBounds(pixelSet);
return {x: {min: xMin - 1, max: xMax + 1}, y: {min: yMin - 1, max: yMax + 1}};
}
function format(image: Image): string {
const buffer = [];
const bounds = pixelSetExtendedBounds(image.pixels);
for (let y = bounds.y.min; y <= bounds.y.max; y++) {
for (let x = bounds.x.min; x <= bounds.x.max; x++) {
const pixel = Pixel.of(x, y);
buffer.push(image.has(pixel) ? '#' : '.');
}
buffer.push("\n");
}
return buffer.join('');
}
function parseInput(input: string): [Image, AlgorithmString] {
const [algorithmString, _, ...inputImageLines] = input.split("\n");
assert.equal(algorithmString.length, ALGORITHM_STRING_LENGTH);
const image: Image = imageFromLines(inputImageLines);
return [image, algorithmString];
}
function part1(input: string) {
const [image, algorithmString] = parseInput(input);
console.log("Starting Image");
console.log(format(image));
console.log("First pass");
const enhancedOnce = enhance(image, algorithmString);
console.log(format(enhancedOnce));
console.log("Second pass");
const enhancedTwice = enhance(enhancedOnce, algorithmString);
console.log(format(enhancedTwice));
console.log("Part 1", [...enhancedTwice.pixels].length);
}
function part2(input: string) {
const [startImage, algorithmString] = parseInput(input);
let image = startImage;
for (let iteration = 0; iteration < 50; iteration++) {
image = enhance(image, algorithmString);
}
console.log("Part 2", [...image.pixels].length);
}
const testInput = `..#.#..#####.#.#.#.###.##.....###.##.#..###.####..#####..#....#..#..##..###..######.###...####..#..#####..##..#.#####...##.#.#..#.##..#.#......#.###.######.###.####...#.##.##..#..#..#####.....#.#....###..#.##......#.....#..#..#..##..#...##.######.####.####.#.#...#.......#..#.#.#...####.##.#......#..#...##.#.##..#...##.#.##..###.#......#.#.......#.#.#.####.###.##...#.....####.#..#..#.##.#....##..#.####....##...##..#...#......#.#.......#.......##..####..#...#.#.#...##..#.#..###..#####........#..####......#..#
#..#.
#....
##..#
..#..
..###`;
const realInput = loadInput("day_20.input");
part1(testInput);
part1(realInput);
part2(testInput);
part2(realInput);