Solve 2020/20
parent
171d55e87a
commit
a591b3adf5
@ -0,0 +1,107 @@
|
||||
Tile 2311:
|
||||
..##.#..#.
|
||||
##..#.....
|
||||
#...##..#.
|
||||
####.#...#
|
||||
##.##.###.
|
||||
##...#.###
|
||||
.#.#.#..##
|
||||
..#....#..
|
||||
###...#.#.
|
||||
..###..###
|
||||
|
||||
Tile 1951:
|
||||
#.##...##.
|
||||
#.####...#
|
||||
.....#..##
|
||||
#...######
|
||||
.##.#....#
|
||||
.###.#####
|
||||
###.##.##.
|
||||
.###....#.
|
||||
..#.#..#.#
|
||||
#...##.#..
|
||||
|
||||
Tile 1171:
|
||||
####...##.
|
||||
#..##.#..#
|
||||
##.#..#.#.
|
||||
.###.####.
|
||||
..###.####
|
||||
.##....##.
|
||||
.#...####.
|
||||
#.##.####.
|
||||
####..#...
|
||||
.....##...
|
||||
|
||||
Tile 1427:
|
||||
###.##.#..
|
||||
.#..#.##..
|
||||
.#.##.#..#
|
||||
#.#.#.##.#
|
||||
....#...##
|
||||
...##..##.
|
||||
...#.#####
|
||||
.#.####.#.
|
||||
..#..###.#
|
||||
..##.#..#.
|
||||
|
||||
Tile 1489:
|
||||
##.#.#....
|
||||
..##...#..
|
||||
.##..##...
|
||||
..#...#...
|
||||
#####...#.
|
||||
#..#.#.#.#
|
||||
...#.#.#..
|
||||
##.#...##.
|
||||
..##.##.##
|
||||
###.##.#..
|
||||
|
||||
Tile 2473:
|
||||
#....####.
|
||||
#..#.##...
|
||||
#.##..#...
|
||||
######.#.#
|
||||
.#...#.#.#
|
||||
.#########
|
||||
.###.#..#.
|
||||
########.#
|
||||
##...##.#.
|
||||
..###.#.#.
|
||||
|
||||
Tile 2971:
|
||||
..#.#....#
|
||||
#...###...
|
||||
#.#.###...
|
||||
##.##..#..
|
||||
.#####..##
|
||||
.#..####.#
|
||||
#..#.#..#.
|
||||
..####.###
|
||||
..#.#.###.
|
||||
...#.#.#.#
|
||||
|
||||
Tile 2729:
|
||||
...#.#.#.#
|
||||
####.#....
|
||||
..#.#.....
|
||||
....#..#.#
|
||||
.##..##.#.
|
||||
.#.####...
|
||||
####.#.#..
|
||||
##.####...
|
||||
##..#.##..
|
||||
#.##...##.
|
||||
|
||||
Tile 3079:
|
||||
#.#.#####.
|
||||
.#..######
|
||||
..#.......
|
||||
######....
|
||||
####.#..#.
|
||||
.#...#.##.
|
||||
#.#####.##
|
||||
..#.###...
|
||||
..#.......
|
||||
..#.###...
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,63 @@
|
||||
const fs = require("fs");
|
||||
|
||||
const input = fs.readFileSync("input", "utf-8").trim();
|
||||
|
||||
const tiles = Object.fromEntries(
|
||||
input
|
||||
.split("\n\n")
|
||||
.map((tile) => tile.split("\n"))
|
||||
.map((line) => [line[0].match("[0-9]+")[0], line.slice(1)])
|
||||
);
|
||||
|
||||
// console.log(tiles);
|
||||
|
||||
const tileEdges = {};
|
||||
|
||||
for (const [id, tile] of Object.entries(tiles)) {
|
||||
const n = tile[0];
|
||||
const w = tile.map((line) => line[0]).join("");
|
||||
const e = tile.map((line) => line[line.length - 1]).join("");
|
||||
const s = tile[tile.length - 1];
|
||||
tileEdges[id] = [n, w, e, s];
|
||||
}
|
||||
|
||||
// console.log(tileEdges);
|
||||
|
||||
const edgeMap = {};
|
||||
|
||||
const reverse = (str) => str.split("").reverse().join("");
|
||||
const isCanonical = (edge) => edge < reverse(edge);
|
||||
|
||||
for (const [id, edges] of Object.entries(tileEdges)) {
|
||||
for (const edge of edges) {
|
||||
const key = isCanonical(edge) ? edge : reverse(edge);
|
||||
if (typeof edgeMap[key] === "undefined") {
|
||||
edgeMap[key] = [];
|
||||
}
|
||||
edgeMap[key].push(id);
|
||||
if (edgeMap[key].length > 2) {
|
||||
console.warn(
|
||||
"Assumption does not hold: One edge is present in more than two tiles"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// console.log(edgeMap);
|
||||
|
||||
const edgeTiles = new Set();
|
||||
const cornerTiles = new Set();
|
||||
|
||||
for (const ids of Object.values(edgeMap)) {
|
||||
if (ids.length === 1) {
|
||||
const id = ids[0];
|
||||
if (edgeTiles.has(id)) {
|
||||
edgeTiles.delete(id);
|
||||
cornerTiles.add(id);
|
||||
} else {
|
||||
edgeTiles.add(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(Array.from(cornerTiles).reduce((a, b) => a * b, 1));
|
@ -0,0 +1,310 @@
|
||||
/**
|
||||
* TODO: optimize for reduced complexity
|
||||
* TODO: optimize for cleaner code
|
||||
*/
|
||||
|
||||
const fs = require("fs");
|
||||
|
||||
function main() {
|
||||
const input = fs.readFileSync("input", "utf-8").trim();
|
||||
const tiles = input.split("\n\n").map((tile) => new Tile(tile));
|
||||
const tilesMap = Object.fromEntries(tiles.map((tile) => [tile.id, tile]));
|
||||
const tileSet = new Set(tiles);
|
||||
// console.log(tiles);
|
||||
// console.log(tileSet);
|
||||
|
||||
const borderMap = {};
|
||||
for (const tile of tiles) {
|
||||
for (const border of Object.values(tile.borders)) {
|
||||
const key = canonical(border);
|
||||
if (typeof borderMap[key] === "undefined") {
|
||||
borderMap[key] = [];
|
||||
}
|
||||
borderMap[key].push(tile.id);
|
||||
if (borderMap[key].length > 2) {
|
||||
console.warn(
|
||||
"Assumption does not hold: One edge is present in more than two tiles"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
// console.log(borderMap);
|
||||
|
||||
const edgeTiles = new Set();
|
||||
const cornerTiles = new Set();
|
||||
const neighbors = {};
|
||||
|
||||
for (const ids of Object.values(borderMap)) {
|
||||
if (ids.length === 1) {
|
||||
const id = ids[0];
|
||||
if (edgeTiles.has(id)) {
|
||||
edgeTiles.delete(id);
|
||||
cornerTiles.add(id);
|
||||
} else {
|
||||
edgeTiles.add(id);
|
||||
}
|
||||
} else {
|
||||
const [tileA, tileB] = ids;
|
||||
if (typeof neighbors[tileA] === "undefined") neighbors[tileA] = [];
|
||||
if (typeof neighbors[tileB] === "undefined") neighbors[tileB] = [];
|
||||
neighbors[tileA].push(tileB);
|
||||
neighbors[tileB].push(tileA);
|
||||
}
|
||||
}
|
||||
console.log(neighbors);
|
||||
let topLeft = tilesMap[Array.from(cornerTiles)[0]];
|
||||
console.log("TopLeft:", topLeft.id);
|
||||
|
||||
const isEdge = (border) => borderMap[canonical(border)].length === 1;
|
||||
|
||||
console.log(topLeft.pprint());
|
||||
const reprs = [];
|
||||
for (const t of allOrientations(topLeft)) {
|
||||
reprs.push(t.pprint().split("\n"));
|
||||
}
|
||||
for (let i = 0; i < reprs[0].length; i++) {
|
||||
for (let j = 0; j < reprs.length; j++) {
|
||||
process.stdout.write(reprs[j][i]);
|
||||
process.stdout.write(" ");
|
||||
}
|
||||
process.stdout.write("\n");
|
||||
}
|
||||
|
||||
while (isEdge(topLeft.borders.e) || isEdge(topLeft.borders.s)) {
|
||||
console.log("rotating corner tile");
|
||||
topLeft.rot90();
|
||||
}
|
||||
// console.log(topLeft)
|
||||
tileSet.delete(topLeft);
|
||||
|
||||
let curRow = [topLeft];
|
||||
const orderedTiles = [];
|
||||
const tileBuffer = Array.from(tileSet);
|
||||
|
||||
let lastTile = topLeft;
|
||||
let loopDetection = 0;
|
||||
while (tileBuffer.length > 0) {
|
||||
// console.log("Tiles in buffer:", tileBuffer.length);
|
||||
let cur = tileBuffer.shift();
|
||||
|
||||
if (isEdge(lastTile.borders.e)) {
|
||||
// console.log("Looking for southener of", topLeft.id, cur.id);
|
||||
for (cur of allOrientations(cur)) {
|
||||
if (reverse(topLeft.borders.s) === cur.borders.n) {
|
||||
orderedTiles.push(curRow);
|
||||
curRow = [];
|
||||
lastTile = cur;
|
||||
topLeft = cur;
|
||||
curRow.push(cur);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// console.log("Looking for eastern of", lastTile.id, cur.id);
|
||||
for (cur of allOrientations(cur)) {
|
||||
if (lastTile.borders.e === reverse(cur.borders.w)) {
|
||||
lastTile = cur;
|
||||
curRow.push(cur);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cur !== lastTile) {
|
||||
tileBuffer.push(cur);
|
||||
} else {
|
||||
// console.log("Tile found! Its", cur.id);
|
||||
loopDetection = 0;
|
||||
}
|
||||
// sleep(50);
|
||||
if (loopDetection > tileBuffer.length) {
|
||||
console.log("loop detected");
|
||||
break;
|
||||
}
|
||||
loopDetection++;
|
||||
}
|
||||
orderedTiles.push(curRow);
|
||||
console.log("Found map arrangement");
|
||||
console.log(orderedTiles);
|
||||
|
||||
let map = "";
|
||||
for (const row of orderedTiles) {
|
||||
for (let i = 0; i < row[0].content.length; i++) {
|
||||
for (let j = 0; j < row.length; j++) {
|
||||
map += row[j].content[i].join("");
|
||||
// map += " ";
|
||||
}
|
||||
map += "\n";
|
||||
}
|
||||
// map += "\n";
|
||||
}
|
||||
const hashCount = map.match(/#/g).length;
|
||||
console.log("hashCount", hashCount);
|
||||
// console.log(map);
|
||||
map = map.split("\n").map((row) => row.split(""));
|
||||
|
||||
let sumSeamonsters;
|
||||
for (let i = 0; i < 8; i++) {
|
||||
if (i === 4) {
|
||||
console.log("flip map");
|
||||
map = flipH(map);
|
||||
} else {
|
||||
console.log("rotate map");
|
||||
map = rotSquare(map);
|
||||
}
|
||||
|
||||
sumSeamonsters = findSeamonster(map);
|
||||
console.log("Seamonsters found:", sumSeamonsters);
|
||||
if (sumSeamonsters) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
const seamonsterHashes = 15 * sumSeamonsters;
|
||||
console.log(hashCount - seamonsterHashes);
|
||||
}
|
||||
|
||||
const reverse = (str) => str.split("").reverse().join("");
|
||||
const isCanonical = (edge) => edge < reverse(edge);
|
||||
const canonical = (edge) => (isCanonical(edge) ? edge : reverse(edge));
|
||||
const trimEnds = (arr) => arr.slice(1, arr.length - 1);
|
||||
function rotSquare(mat) {
|
||||
const result = [];
|
||||
const length = mat.length;
|
||||
for (let i = 0; i < length; i++) {
|
||||
const row = [];
|
||||
for (let j = 0; j < length; j++) {
|
||||
row.push(mat[length - 1 - j][i]);
|
||||
}
|
||||
result.push(row);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
const flipH = (mat) => mat.map((row) => row.reverse());
|
||||
const flipV = (mat) => mat.reverse();
|
||||
function* allOrientations(tile) {
|
||||
tile.rot90();
|
||||
yield tile;
|
||||
tile.rot90();
|
||||
yield tile;
|
||||
tile.rot90();
|
||||
yield tile;
|
||||
tile.rot90();
|
||||
yield tile;
|
||||
tile.flipH();
|
||||
yield tile;
|
||||
tile.rot90();
|
||||
yield tile;
|
||||
tile.rot90();
|
||||
yield tile;
|
||||
tile.rot90();
|
||||
yield tile;
|
||||
}
|
||||
|
||||
function findSeamonster(map) {
|
||||
map = map.map((row) => row.join(""));
|
||||
const sm1 = " # ";
|
||||
const sm2 = "# ## ## ###";
|
||||
const sm3 = " # # # # # # ";
|
||||
const pattern1 = new RegExp(`(?=${sm1.replaceAll(" ", ".")})`, "g");
|
||||
const pattern2 = new RegExp(`(?=${sm2.replaceAll(" ", ".")})`, "g");
|
||||
const pattern3 = new RegExp(`(?=${sm3.replaceAll(" ", ".")})`, "g");
|
||||
|
||||
let sum = 0;
|
||||
for (let lineIdx = 0; lineIdx < map.length - 3; lineIdx++) {
|
||||
const [line1, line2, line3] = map.slice(lineIdx, lineIdx + 3);
|
||||
let solutions1 = new Set();
|
||||
let solutions2 = new Set();
|
||||
let solutions3 = new Set();
|
||||
// console.log("Processing line", lineIdx, line1);
|
||||
for (const match of line1.matchAll(pattern1)) {
|
||||
solutions1.add(match.index);
|
||||
}
|
||||
// console.log(solutions1);
|
||||
for (const match of line2.matchAll(pattern2)) {
|
||||
if (solutions1.has(match.index)) solutions2.add(match.index);
|
||||
}
|
||||
// console.log(solutions2);
|
||||
for (const match of line3.matchAll(pattern3)) {
|
||||
if (solutions2.has(match.index)) solutions3.add(match.index);
|
||||
}
|
||||
// console.log(solutions3);
|
||||
if (solutions3.size) {
|
||||
for (const idx of solutions3) {
|
||||
console.log(`Seamonster found at [${lineIdx}, ${idx}]`);
|
||||
}
|
||||
}
|
||||
sum += solutions3.size;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
const rot90Map = { n: "w", w: "s", s: "e", e: "n" };
|
||||
const flipHMap = { w: "e", e: "w" };
|
||||
const flipVMap = { n: "s", s: "n" };
|
||||
|
||||
function applyMap(map, obj, doReverse = false) {
|
||||
const result = {};
|
||||
for (const [target, source] of Object.entries(map)) {
|
||||
if (obj[source] === undefined) {
|
||||
console.log(source, obj);
|
||||
throw Error("aa");
|
||||
}
|
||||
result[target] = doReverse ? reverse(obj[source]) : obj[source];
|
||||
}
|
||||
return { ...obj, ...result };
|
||||
}
|
||||
|
||||
class Tile {
|
||||
constructor(rawInput) {
|
||||
const lines = rawInput.split("\n");
|
||||
this.id = lines.shift().match("[0-9]+")[0];
|
||||
this.borders = {
|
||||
n: lines[0],
|
||||
e: lines.map((line) => line[line.length - 1]).join(""),
|
||||
s: reverse(lines[lines.length - 1]),
|
||||
w: reverse(lines.map((line) => line[0]).join("")),
|
||||
};
|
||||
this.content = trimEnds(lines)
|
||||
.map(trimEnds)
|
||||
.map((line) => line.split(""));
|
||||
}
|
||||
|
||||
rot90(times = 1) {
|
||||
while (times--) {
|
||||
this.borders = applyMap(rot90Map, this.borders);
|
||||
this.content = rotSquare(this.content);
|
||||
}
|
||||
}
|
||||
|
||||
flipH() {
|
||||
this.borders = applyMap(flipHMap, this.borders, true);
|
||||
this.borders.n = reverse(this.borders.n);
|
||||
this.borders.s = reverse(this.borders.s);
|
||||
this.content.map((row) => row.reverse());
|
||||
}
|
||||
|
||||
flipV() {
|
||||
this.borders = applyMap(flipVMap, this.borders, true);
|
||||
this.borders.w = reverse(this.borders.w);
|
||||
this.borders.e = reverse(this.borders.e);
|
||||
this.content.reverse();
|
||||
}
|
||||
|
||||
pprint() {
|
||||
let str = "";
|
||||
str += " " + this.borders.n + " \n";
|
||||
for (let i = 0; i < this.borders.w.length; i++) {
|
||||
str += reverse(this.borders.w)[i];
|
||||
if (i === 0 || i === this.borders.w.length - 1) {
|
||||
str += " ".repeat(this.borders.w.length);
|
||||
} else {
|
||||
str += " " + this.content[i - 1].join("") + " ";
|
||||
}
|
||||
str += this.borders.e[i];
|
||||
str += "\n";
|
||||
}
|
||||
str += " " + reverse(this.borders.s) + " ";
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
Loading…
Reference in New Issue