Day 20
Heavily cribbed from https://gist.github.com/AlaskanShade/5c46b96a4b1f08cdb6568c15b6e86341 This one really tripped me up, so I sought help. A lot of help.
This commit is contained in:
@ -62,6 +62,9 @@
|
|||||||
<None Update="19input.txt">
|
<None Update="19input.txt">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
|
<None Update="20input.txt">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
<None Update="21input.txt">
|
<None Update="21input.txt">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
|
1727
20input.txt
Normal file
1727
20input.txt
Normal file
File diff suppressed because it is too large
Load Diff
@ -22,6 +22,7 @@
|
|||||||
Q15.Go();
|
Q15.Go();
|
||||||
Q18.Go();
|
Q18.Go();
|
||||||
Q19.Go();
|
Q19.Go();
|
||||||
|
Q20.Go();
|
||||||
Q21.Go();
|
Q21.Go();
|
||||||
Q22.Go();
|
Q22.Go();
|
||||||
Util.Log($"Total time={(System.DateTime.Now - start).TotalMilliseconds}ms");
|
Util.Log($"Total time={(System.DateTime.Now - start).TotalMilliseconds}ms");
|
||||||
|
345
Q20.cs
Normal file
345
Q20.cs
Normal file
@ -0,0 +1,345 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace _2020
|
||||||
|
{
|
||||||
|
class Q20
|
||||||
|
{
|
||||||
|
static readonly List<Tile> list = new List<Tile>();
|
||||||
|
const int TileSize = 10;
|
||||||
|
|
||||||
|
[DebuggerDisplay("{ID}")]
|
||||||
|
private class Tile
|
||||||
|
{
|
||||||
|
public enum SideName { Top, TopReverse, Left, LeftReverse, Bottom, BottomReverse, Right, RightReverse }
|
||||||
|
public int ID { get; private set; }
|
||||||
|
public char[,] Data = new char[TileSize, TileSize];
|
||||||
|
public bool IsPlaced { get; set; }
|
||||||
|
public IEnumerable<int> Sides => Enum.GetValues(typeof(SideName)).Cast<SideName>().Select(n => GetSide(n));
|
||||||
|
|
||||||
|
public Tile(int id, string[] lines)
|
||||||
|
{
|
||||||
|
ID = id;
|
||||||
|
for (int i = 0; i < lines.Length; i++)
|
||||||
|
{
|
||||||
|
for (int j = 0; j < lines[i].Length; j++)
|
||||||
|
{
|
||||||
|
Data[i, j] = lines[i][j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetSide(SideName name)
|
||||||
|
{
|
||||||
|
return name switch
|
||||||
|
{
|
||||||
|
SideName.Top => ParseNumber(new string(Enumerable.Range(0, TileSize).Select(r => Data[0, r]).ToArray())),
|
||||||
|
SideName.TopReverse => ParseNumber(new string(Enumerable.Range(0, TileSize).Select(r => Data[0, r]).Reverse().ToArray())),
|
||||||
|
SideName.Left => ParseNumber(new string(Enumerable.Range(0, TileSize).Select(r => Data[r, 0]).ToArray())),
|
||||||
|
SideName.LeftReverse => ParseNumber(new string(Enumerable.Range(0, TileSize).Select(r => Data[r, 0]).Reverse().ToArray())),
|
||||||
|
SideName.Bottom => ParseNumber(new string(Enumerable.Range(0, TileSize).Select(r => Data[TileSize - 1, r]).ToArray())),
|
||||||
|
SideName.BottomReverse => ParseNumber(new string(Enumerable.Range(0, TileSize).Select(r => Data[TileSize - 1, r]).Reverse().ToArray())),
|
||||||
|
SideName.Right => ParseNumber(new string(Enumerable.Range(0, TileSize).Select(r => Data[r, TileSize - 1]).ToArray())),
|
||||||
|
SideName.RightReverse => ParseNumber(new string(Enumerable.Range(0, TileSize).Select(r => Data[r, TileSize - 1]).Reverse().ToArray())),
|
||||||
|
_ => -1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Rotate()
|
||||||
|
{
|
||||||
|
RotateSquareCW(ref Data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FlipHorizontally()
|
||||||
|
{
|
||||||
|
Data = FlipH(Data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FlipVertically()
|
||||||
|
{
|
||||||
|
Data = FlipV(Data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Matches(IEnumerable<Tile> tiles)
|
||||||
|
{
|
||||||
|
return tiles.Count(t => t.ID != ID && t.Sides.Any(s => Sides.Contains(s)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int ParseNumber(string text)
|
||||||
|
{
|
||||||
|
return Convert.ToInt32(text.Replace('#', '1').Replace('.', '0'), 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void RotateSquareCW(ref char[,] source)
|
||||||
|
{
|
||||||
|
var n = source.GetLength(0);
|
||||||
|
for (int i = 0; i < n / 2; i++)
|
||||||
|
{
|
||||||
|
for (int j = i; j < n - i - 1; j++)
|
||||||
|
{
|
||||||
|
var temp = source[i, j];
|
||||||
|
source[i, j] = source[n - 1 - j, i];
|
||||||
|
source[n - 1 - j, i] = source[n - 1 - i, n - 1 - j];
|
||||||
|
source[n - 1 - i, n - 1 - j] = source[j, n - 1 - i];
|
||||||
|
source[j, n - 1 - i] = temp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static char[,] FlipH(char[,] source)
|
||||||
|
{
|
||||||
|
var numRows = source.GetLength(0);
|
||||||
|
var numCols = source.GetLength(1);
|
||||||
|
char[,] temp = new char[numRows, numCols];
|
||||||
|
for (int row = 0; row < numRows; row++)
|
||||||
|
{
|
||||||
|
for (int col = 0; col < numCols; col++)
|
||||||
|
{
|
||||||
|
temp[row, numCols - col - 1] = source[row, col];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char[,] FlipV(char[,] source)
|
||||||
|
{
|
||||||
|
var numRows = source.GetLength(0);
|
||||||
|
var numCols = source.GetLength(1);
|
||||||
|
char[,] temp = new char[numRows, numCols];
|
||||||
|
for (int row = 0; row < numRows; row++)
|
||||||
|
{
|
||||||
|
for (int col = 0; col < numCols; col++)
|
||||||
|
{
|
||||||
|
temp[numRows - row - 1, col] = source[row, col];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Go()
|
||||||
|
{
|
||||||
|
var start = DateTime.Now;
|
||||||
|
MakeList();
|
||||||
|
Util.Log($"Q20 MakeList took {(DateTime.Now - start).TotalMilliseconds}ms");
|
||||||
|
var p1start = DateTime.Now;
|
||||||
|
Part1();
|
||||||
|
Util.Log($"Q20 part1 took {(DateTime.Now - p1start).TotalMilliseconds}ms");
|
||||||
|
var p2start = DateTime.Now;
|
||||||
|
Part2();
|
||||||
|
Util.Log($"Q20 part2 took {(DateTime.Now - p2start).TotalMilliseconds}ms");
|
||||||
|
|
||||||
|
Util.Log($"Q20 took {(DateTime.Now - start).TotalMilliseconds}ms");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void MakeList()
|
||||||
|
{
|
||||||
|
string[] currTile = new string[TileSize];
|
||||||
|
int tileNum = 0;
|
||||||
|
int rowNum = 0;
|
||||||
|
foreach (var line in File.ReadAllLines("20input.txt"))
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(line))
|
||||||
|
{
|
||||||
|
list.Add(new Tile(tileNum, currTile));
|
||||||
|
currTile = new string[TileSize];
|
||||||
|
rowNum = 0;
|
||||||
|
}
|
||||||
|
else if (line.StartsWith("Tile"))
|
||||||
|
{
|
||||||
|
tileNum = Convert.ToInt32(line["Tile ".Length..^1]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
currTile[rowNum] = line;
|
||||||
|
rowNum++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
list.Add(new Tile(tileNum, currTile));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int CountMonsters(char[,] input)
|
||||||
|
{
|
||||||
|
var count = 0;
|
||||||
|
for (int col = 0; col < input.GetLength(1) - monster.GetLength(1); col++)
|
||||||
|
{
|
||||||
|
for (int row = 0; row < input.GetLength(0) - monster.GetLength(0); row++)
|
||||||
|
{
|
||||||
|
if (HasMonster(input, row, col))
|
||||||
|
{
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
static readonly char[,] monster = new char[3, 20]
|
||||||
|
{
|
||||||
|
{ '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '#', '.' },
|
||||||
|
{ '#', '.', '.', '.', '.', '#', '#', '.', '.', '.', '.', '#', '#', '.', '.', '.', '.', '#', '#', '#' },
|
||||||
|
{ '.', '#', '.', '.', '#', '.', '.', '#', '.', '.', '#', '.', '.', '#', '.', '.', '#', '.', '.', '.' },
|
||||||
|
};
|
||||||
|
|
||||||
|
static int MonsterSize
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
int size = 0;
|
||||||
|
for (int i = 0; i < monster.GetLength(0); i++)
|
||||||
|
{
|
||||||
|
for (int j = 0; j < monster.GetLength(1); j++)
|
||||||
|
{
|
||||||
|
if (monster[i, j] == '#')
|
||||||
|
{
|
||||||
|
size++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool HasMonster(char[,] grid, int row, int col)
|
||||||
|
{
|
||||||
|
for (int c2 = 0; c2 < 20; c2++)
|
||||||
|
{
|
||||||
|
for (int r2 = 0; r2 < 3; r2++)
|
||||||
|
{
|
||||||
|
if (monster[r2, c2] == '#' && grid[col + c2, row + r2] != '#')
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<Tile> _corners;
|
||||||
|
static List<Tile> Corners
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_corners == null)
|
||||||
|
{
|
||||||
|
_corners = list.Where(t => t.Matches(list) == 2).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return _corners;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Part1()
|
||||||
|
{
|
||||||
|
Util.Log($"Q20Part1: {Corners.Aggregate(1L, (i, v) => i * v.ID)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Part2()
|
||||||
|
{
|
||||||
|
var sides = new Dictionary<int, List<Tile>>(
|
||||||
|
list.SelectMany(t => t.Sides.Select(s => new { s, t }))
|
||||||
|
.GroupBy(s => s.s)
|
||||||
|
.Where(s => s.Count() > 1)
|
||||||
|
.Select(s => new KeyValuePair<int, List<Tile>>(s.Key, s.Select(i => i.t).ToList()))
|
||||||
|
);
|
||||||
|
|
||||||
|
var gridSize = (int)Math.Sqrt(list.Count);
|
||||||
|
var assembled = new Tile[gridSize, gridSize];
|
||||||
|
for (int col = 0; col < gridSize; col++)
|
||||||
|
{
|
||||||
|
for (int row = 0; row < gridSize; row++)
|
||||||
|
{
|
||||||
|
Tile nextTile;
|
||||||
|
if (col == 0 && row == 0) // Find the corner and orient
|
||||||
|
{
|
||||||
|
nextTile = Corners.First();
|
||||||
|
while (!sides.ContainsKey(nextTile.GetSide(Tile.SideName.Right)) || !sides.ContainsKey(nextTile.GetSide(Tile.SideName.Bottom)))
|
||||||
|
{
|
||||||
|
nextTile.Rotate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (row == 0) // top row: find the tile that fits to the right of the last one
|
||||||
|
{
|
||||||
|
var lastRight = assembled[row, col - 1].GetSide(Tile.SideName.Right);
|
||||||
|
nextTile = sides[lastRight].Single(e => !e.IsPlaced);
|
||||||
|
// Rotate until the right side is on the left
|
||||||
|
while (nextTile.GetSide(Tile.SideName.Left) != lastRight && nextTile.GetSide(Tile.SideName.LeftReverse) != lastRight)
|
||||||
|
{
|
||||||
|
nextTile.Rotate();
|
||||||
|
}
|
||||||
|
// Flip if necessary
|
||||||
|
if (nextTile.GetSide(Tile.SideName.Left) != lastRight)
|
||||||
|
{
|
||||||
|
nextTile.FlipVertically();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else // find the tile that fits the one above
|
||||||
|
{
|
||||||
|
var lastBottom = assembled[row - 1, col].GetSide(Tile.SideName.Bottom);
|
||||||
|
nextTile = sides[lastBottom].Single(e => !e.IsPlaced);
|
||||||
|
while (nextTile.GetSide(Tile.SideName.Top) != lastBottom && nextTile.GetSide(Tile.SideName.TopReverse) != lastBottom)
|
||||||
|
{
|
||||||
|
nextTile.Rotate();
|
||||||
|
}
|
||||||
|
if (nextTile.GetSide(Tile.SideName.Top) != lastBottom)
|
||||||
|
{
|
||||||
|
nextTile.FlipHorizontally();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assembled[row, col] = nextTile;
|
||||||
|
nextTile.IsPlaced = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the final image
|
||||||
|
var imgDimensions = (TileSize - 2) * gridSize;
|
||||||
|
var grid = new char[imgDimensions, imgDimensions];
|
||||||
|
var innerWidth = TileSize - 2;
|
||||||
|
for (int col = 0; col < gridSize; col++)
|
||||||
|
{
|
||||||
|
for (int tileColIdx = 1; tileColIdx < innerWidth + 1; tileColIdx++)
|
||||||
|
{
|
||||||
|
for (int row = 0; row < gridSize; row++)
|
||||||
|
{
|
||||||
|
for (int tileRowIdx = 1; tileRowIdx < innerWidth + 1; tileRowIdx++)
|
||||||
|
{
|
||||||
|
grid[(col * innerWidth) + tileColIdx - 1, (row * innerWidth) + tileRowIdx - 1] = assembled[row, col].Data[tileRowIdx, tileColIdx];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var waterCount = grid.Cast<char>().Count(c => c == '#');
|
||||||
|
var roughness = waterCount;
|
||||||
|
var numSides = 4;
|
||||||
|
for (int i = 0; i < numSides * 2; i++)
|
||||||
|
{
|
||||||
|
var count = CountMonsters(grid);
|
||||||
|
if (count > 0)
|
||||||
|
{
|
||||||
|
roughness = waterCount - count * MonsterSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i == numSides)
|
||||||
|
{
|
||||||
|
grid = FlipH(grid);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RotateSquareCW(ref grid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Util.Log($"Q20Part2: roughness={roughness}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user