Advent of Code

My solutions for the Advent of Code puzzles. View on GitHub

Day 25 - [Combo Breaker]

var cardPk = 9717666;
var doorPk = 20089533;

long Transform(long value, long subjectNumber) => ((value * subjectNumber ) % 20201227);
long TransformLoop(long subjectNumber, long loops) { long value=1; for (long l=0; l<loops; l++) value = Transform(value, subjectNumber); return value; }
long FindLoopCount(long publicKey) {long l = 1, v=1; while ((v = Transform(v, 7)) != publicKey) l++; return l; }

var cardLoops = FindLoopCount(cardPk);
var doorLoops = FindLoopCount(doorPk);

TransformLoop(doorPk, cardLoops).Dump();
TransformLoop(cardPk, doorLoops).Dump();

Day 24 - [Lobby Layout]

(int,int) Move ((int x,int y) position, string direction)
{
	switch (direction)
	{
		case "w": return ( position.x-1, position.y);
		case "sw": return (position.x-Math.Abs((position.y+1)%2), position.y-1);
		case "ne": return (position.x+Math.Abs(position.y%2), position.y+1);
		case "e": return (position.x+1, position.y);
		case "nw": return (position.x-Math.Abs(((position.y+1)%2)), position.y+1);
		case "se": return (position.x+Math.Abs((position.y)%2), position.y-1);
	}
	return (0,0);
}

(int,int) Follow((int x, int y) pos, string directions)
{
	var ptr = 0;
	while (ptr < directions.Length){
		var l = directions[ptr] == 's' || directions[ptr] == 'n' ? 2 : 1;
		pos = Move(pos,directions.Substring(ptr,l))	;
		ptr+=l;
	}
	return pos;
}

var tiles = new Dictionary<(int,int), bool>();

foreach(var i in input){
	var t = Follow((0,0),i);
	if (tiles.ContainsKey(t)){
		tiles[t] = !tiles[t];
	}
	else{
		tiles[t]=true;
	}
}

var part1=tiles.Count(t => t.Value);
part1.Dump();


IEnumerable<(int, int)> GetAdjacents((int, int) tile)
{
	yield return Follow(tile, "w");
	yield return Follow(tile, "nw");
	yield return Follow(tile, "ne");
	yield return Follow(tile, "e");
	yield return Follow(tile, "se");
	yield return Follow(tile, "sw");
}
int CountAdjacentBlackTiles((int,int) tile) => GetAdjacents(tile).Count(t => tiles.TryGetValue(t, out var state) && state);

for (int day=0; day<100; day++){
	var blackTiles = tiles.Where(t => t.Value == true).Select(t => t.Key).ToList();
	var adjacentWhiteTiles = blackTiles.SelectMany(GetAdjacents).Distinct().Except(blackTiles).ToList();
	tiles = blackTiles.Select(t => new { t, n = CountAdjacentBlackTiles(t) }).Where(t => t.n > 0 && t.n <= 2).Select(t => t.t).Concat(adjacentWhiteTiles.Where(t => CountAdjacentBlackTiles(t) == 2)).ToDictionary(t => t, t=> true);
}

var part2 = tiles.Count;
part2.Dump();

Day 23 - [Crab Cups]

var cups = Enumerable.Range(0,input.Length).ToDictionary(e => input[e], e=> input[(e+1)%input.Length]);
var currentPosition = cups.First().Key;

IEnumerable<int> Play(int moves){	
	for (int move = 0; move < moves; move++)
	{
		var pickedUp = new [] { RemoveAfter(currentPosition), RemoveAfter(currentPosition), RemoveAfter(currentPosition), };
		var dest = currentPosition - 1;
		if (dest < 1) dest = cups.Count;
		while (pickedUp.Contains(dest))
		{
			dest--;
			if (dest < 1) dest = cups.Count;
		}
		InsertAfter(dest, pickedUp[2]);
		InsertAfter(dest, pickedUp[1]);
		InsertAfter(dest, pickedUp[0]);
		currentPosition = cups[currentPosition];
	}
	return GetCups(1);
}

IEnumerable<int> GetCups(int start)
{
	var pos = start;
	for (int i = 0; i < cups.Count; i++)
	{
		yield return pos;
		pos = cups[pos];
	}
}

void InsertAfter(int which, int val){
	var nextCup = cups[which];
	cups[which]=val;
	cups[val] = nextCup;
}
int RemoveAfter(int which){
	var nextCup = cups[which];
	var followingCup = cups[nextCup];
	cups[which] = followingCup;
	return nextCup;
}

var part1 = string.Join("",Play(100).Skip(1));
part1.Dump();

input = input.Concat(Enumerable.Range(input.Length+1, 1000000 - input.Length)).ToArray();
cups = Enumerable.Range(0,input.Length).ToDictionary(e => input[e], e=> input[(e+1)%input.Length]);
currentPosition = cups.First().Key;

var part2 = Play(10000000).Skip(1).Take(2).Select(i => (long)i).Aggregate ((x, y) => x * y);
part2.Dump();

Day 22 - [Crab Combat]

IEnumerable<IEnumerable<T>> Split<T>(IEnumerator<T> e, Func<T, bool> splitFunc)
{
	while (e.MoveNext()) { var ret = new List<T>(); while (!splitFunc(e.Current)) { ret.Add(e.Current); e.MoveNext();} yield return ret; }
}
int ScoreHand(List<int> hand) => hand.Select((c,i)=> (c,i)).OrderByDescending(h => h.i).Select((h,i)=> h.c*(i+1)).Sum();

var hands = Split(input.GetEnumerator(), string.IsNullOrEmpty).Select(i => i.Skip(1).Select(int.Parse).ToList()).ToList();
while (hands.All(h => h.Any()))
{
	var nextCards = hands.Select(h => h.First()).OrderByDescending(h => h).ToArray();
	var winningHand = hands.Single(h => h[0] == nextCards[0]);
	hands.ForEach(h => h.RemoveAt(0));
	winningHand.AddRange(nextCards);
}

var part1 = ScoreHand(hands.Single(h => h.Any()));
part1.Dump();


hands = Split(input.GetEnumerator(), string.IsNullOrEmpty).Select(i => i.Skip(1).Select(int.Parse).ToList()).ToList();
int RecursiveCombat(List<int> p1, List<int> p2)
{
	var infiniteGamePrevention = new HashSet<string>();
	while (p1.Any() && p2.Any())
	{
		var key = string.Join(":", string.Join(",", p1),  string.Join(",", p2));
		if (infiniteGamePrevention.Contains(key)) break;
		infiniteGamePrevention.Add(key);
		
		var p1c = p1[0];
		var p2c = p2[0];
		
		p1.RemoveAt(0);
		p2.RemoveAt(0);
		
		var winner = p1c <= p1.Count && p2c <= p2.Count
			? RecursiveCombat (p1.Select(p => p).Take(p1c).ToList(), p2.Select(p => p).Take(p2c).ToList())
			: p1c > p2c ? 1 : 2;

		if (winner == 1)
		{
			p1.Add(p1c);
			p1.Add(p2c);
		}
		else
		{
			p2.Add(p2c);
			p2.Add(p1c);
		}		
	}
	return p1.Any() ? 1 : 2;
}

var win = RecursiveCombat(hands[0], hands[1]);

var part2 = ScoreHand(win == 1 ? hands[0] : hands[1]);
part2.Dump();

Day 21 - [Allergen Assessment]

var re = new Regex(@"(.+) \(contains (.+)\)");

var foods = input.Select(i => re.Match(i)).Select(i => (i.Groups[1].Value.Split(' ').ToArray(), i.Groups[2].Value.Split(new[] { ", "}, StringSplitOptions.None).ToArray())).ToArray();
var allergens = foods.SelectMany(f => f.Item2.Select(a => (a,food:f.Item1))).GroupBy(a=> a.Item1).Select(g => (g.Key, g.SelectMany(i => i.food).Where(f => g.All(o => o.food.Contains(f))).Distinct().ToArray())).ToArray();
var safeIngredients = foods.SelectMany(f => f.Item1).Except(allergens.SelectMany(a => a.Item2)).ToArray();
var part1 = foods.Sum(f => f.Item1.Count(fi => safeIngredients.Contains(fi)));
part1.Dump();

var identified = new Dictionary<string,string>();
while (allergens.Any(a => !identified.ContainsValue(a.Key)))
{
	foreach (var a in allergens.Where(a => !identified.ContainsKey(a.Key))){
		var extra = a.Item2.Except(identified.Keys);
		if (extra.Count() == 1){
			identified[extra.Single()]=a.Key;			
		}
		
	}
}

var part2 = string.Join(",",identified.OrderBy(t => t.Value).Select(t => t.Key));
part2.Dump();

Day 20 - [Jurassic Jigsaw]

void Main()
{
	var input = File.ReadAllLines(Path.Combine(Path.GetDirectoryName(Util.CurrentQueryPath), "..", "day20.txt")).ToList();
	
	var tiles = ReadTiles(input.GetEnumerator()).ToArray();
	
	var corners = tiles.Where(t => tiles.Where(o => o.Id != t.Id).Count(o => t.CanTouch(o)) == 2).ToArray();
	var part1 = corners.Select(t => t.Id).Aggregate ((x, y) => x * y);
	part1.Dump();

	var size = (int)Math.Sqrt(tiles.Count());
	var layout = new Tile[size,size];
	
	for(int row=0; row<size; row++){
		for(int col=0; col<size; col++){
			if (row == 0 && col == 0)
			{
				var c = corners.First();
				c.Flip();
				if (c.SpinAndFlipUntil(() => tiles.Where(o => o.Id != c.Id).Count(o => o.PossibleEdges.Contains(c.BottomEdge)) == 1 &&
					tiles.Where(o => o.Id != c.Id).Count(o => o.PossibleEdges.Contains(c.RightEdge)) == 1))
					layout[row, col] = c;
			}else if (row ==0) {
				var existing = layout[0,col-1];
				foreach( var candidate in  tiles.Where(t => t.Id != existing.Id).Where(o => o.PossibleEdges.Contains(existing.RightEdge))){
					if (candidate.SpinAndFlipUntil (() => existing.RightEdge == candidate.LeftEdge))
						layout[row,col]=candidate;
						break;
					}				
			}else{
				var existing = layout[row - 1,col];
				foreach (var candidate in tiles.Where(t => t.Id != existing.Id).Where(o => o.PossibleEdges.Contains(existing.BottomEdge)))				
					if (candidate.SpinAndFlipUntil(() => existing.BottomEdge == candidate.TopEdge))
					{
						layout[row, col] = candidate;
						break;
					}				
			}
		}		
	}
	
	//assemble the image
	var w = tiles.First().Bits.GetLength(1) - 2;
	var h = tiles.First().Bits.GetLength(0)-2;	
	var image = new char[layout.GetLength(0)*h,layout.GetLength(1)*w ];
	for(int row=0; row<size; row++){
		for(int col=0; col<size; col++){
			Helpers.Copy2DArray(ref image,col*w,row*h,layout[row,col].Bits,1,1,w,h);
		}		
	}

	var seaMonster = new[] { "                  # ", "#    ##    ##    ###", " #  #  #  #  #  #   "}.Select(o => o.ToCharArray()).ToArray().To2DArray();

	bool FindSeaMonster(int row, int col) {
		if (row+seaMonster.GetLength(0) > image.GetLength(0) || col+seaMonster.GetLength(1) > image.GetLength(1))
			return false;
		for (int r = 0; r<seaMonster.GetLength(0); r++)
		{
			for (int c = 0; c < seaMonster.GetLength(1); c++)
			{
				if (seaMonster[r, c] == '#' && image[row + r, col + c] != '#')
					return false;
			}
		}
		return true;
	}
	
	var monsters = Enumerable.Range(0, image.GetLength(0)).Sum(row => Enumerable.Range(0, image.GetLength(1)).Count(col => FindSeaMonster(row,col)));
	var ctr = 0;
	while (monsters == 0){
		image = image.RotateArray();
		if (++ctr == 4) image= image.FlipArray(); 
		monsters = Enumerable.Range(0, image.GetLength(0)).Sum(row => Enumerable.Range(0, image.GetLength(1)).Count(col => FindSeaMonster(row,col)));
	}
	
	var part2 = image.ToEnumerable().Count(i => i=='#') - (monsters * seaMonster.ToEnumerable().Count(i => i=='#'));
	part2.Dump();
}

IEnumerable<Tile> ReadTiles(IEnumerator<string> e)
{
	while (e.MoveNext())
	{
		var tileid = int.Parse(((string)e.Current).Substring(5, 4));
		var lines = new List<string>();
		e.MoveNext();
		while (!string.IsNullOrEmpty(e.Current))
		{
			lines.Add(e.Current);
			e.MoveNext();
		}
		yield return new Tile(tileid, lines.Select(l => l.ToCharArray().ToArray()).ToArray().To2DArray() );
	}
}

public class Tile
{
	public Tile(long id, char[,] bits)
	{
		Id = id;
		Bits = bits;
	}
	
	public long Id { get; private set; }
	public char[,] Bits { get; private set; }

	private List<string> _possibleEdges;
	public List<string> PossibleEdges
	{
		get
		{
			if (_possibleEdges == null)
			{
				_possibleEdges = new List<string> { TopEdge, BottomEdge, LeftEdge, RightEdge, new string(TopEdge.Reverse().ToArray()), new string(BottomEdge.Reverse().ToArray()), new string(LeftEdge.Reverse().ToArray()), new string(RightEdge.Reverse().ToArray()) };
			}
			return _possibleEdges;
		}
	}
	
	public void Rotate() { Bits = Bits.RotateArray(); }
	public void Flip() { Bits = Bits.FlipArray() ; }

	public string TopEdge => new string(Enumerable.Range(0, Bits.GetLength(1)).Select(col => Bits[0, col]).ToArray());
	public string BottomEdge => new string(Enumerable.Range(0, Bits.GetLength(1)).Select(col => Bits[ Bits.GetLength(0)-1, col]).ToArray());
	public string LeftEdge => new string(Enumerable.Range(0, Bits.GetLength(0)).Select(row => Bits[row, 0]).ToArray());
	public string RightEdge => new string(Enumerable.Range(0, Bits.GetLength(0)).Select(row => Bits[row, Bits.GetLength(1)-1]).ToArray());
	
	public bool CanTouch(Tile other) => PossibleEdges.Any(e => other.PossibleEdges.Contains(e));
	public bool SpinAndFlipUntil(Func<bool> condition)
	{
		for (int opt = 0; opt < 9; opt++)
		{
			if (condition())
				return true;

			Rotate();
			if (opt == 4) Flip();
		}
		return false;
	}
}

public static class Helpers
{
	public static T[,] RotateArray<T>(this T[,] src)
	{
		int oRows = src.GetLength(0);
		int oCols = src.GetLength(1);

		int nRows = oCols;
		int nCols = oRows;
		
		T[,] ret = new T[nRows, nCols];

		int oldRow;
		for (int newRow = 0; newRow < nRows; newRow++)
		{
			oldRow = oRows -1;
			for (int newCol = 0; newCol < nCols; newCol++)			
				ret[newRow, newCol] = src[oldRow--, newRow];	
		}

		return ret;
	}

	public static T[,] FlipArray<T>(this T[,] src)
	{
		int rows = src.GetLength(0);
		int cols = src.GetLength(1);

		T[,] ret = new T[rows, cols];

		for (int i = 0; i < rows; i++)
		{
			for (int j = 0; j < cols; j++)
			{
				ret[i, j] = src[i, cols - j - 1];
			}
		}

		return ret;
	}

	public static T[,] To2DArray<T>(this IList<IList<T>> src)
	{
		int max = src.Select(l => l).Max(l => l.Count());
		var ret = new T[src.Count, max];

		for (int i = 0; i < src.Count; i++)
		{
			for (int j = 0; j < src[i].Count(); j++)
			{
				ret[i, j] = src[i][j];
			}
		}
		return ret;
	}

	public static void Copy2DArray<T>(ref T[,] dest, int destCol, int destRow, T[,] source, int? sourceCol = null, int? sourceRow = null, int? cols = null, int? rows = null)
	{
		// no error checking...
		var rowsToCopy = rows ?? source.GetLength(0) - sourceRow ?? source.GetLength(0);
		var colsToCopy = cols ?? source.GetLength(1) - sourceCol ?? source.GetLength(1);

		for (int r = 0; r < rowsToCopy; r++)
		{
			for (int c = 0; c < colsToCopy; c++)
			{
				dest[destRow + r, destCol + c] = source[sourceRow.GetValueOrDefault() + r, sourceCol.GetValueOrDefault() + c];

			}
		}
	}
	
	public static IEnumerable<T> ToEnumerable<T>(this T[,] src)
	{
		for (int r=0; r<src.GetLength(0);r++)
		for (int c=0; c<src.GetLength(1);c++)
			yield return src[r,c];
	}
}

Day 19 - [Monster Messages]

var rules = input.TakeWhile(i => !string.IsNullOrEmpty(i)).Select(i => i.Split(':')).ToDictionary(i => int.Parse(i[0]), i => i[1]);
var messages = input.Skip(rules.Count()).Skip(1).ToList();

var compiled = rules.Where(r => r.Value.Trim().StartsWith("\"")).ToDictionary(r => r.Key, r => new[] { r.Value.Trim().Substring(1, 1) });
rules = rules.Where(r => !compiled.ContainsKey(r.Key)).ToDictionary(r => r.Key, r => r.Value);
while (rules.Any())
{
	foreach (var rule in rules)
	{

		var deps = rule.Value.Split(" |".ToCharArray()).Where(r => !string.IsNullOrEmpty(r)).Select(int.Parse);
		if (deps.All(d => compiled.ContainsKey(d)))
		{
			var options = new List<string>();
			foreach (var segment in rule.Value.Split('|'))
			{
				var dep = segment.Split(' ').Where(s => !string.IsNullOrEmpty(s)).Select(int.Parse).Select(d => compiled[d]).ToArray();
				if (dep.Length > 1)
					options.AddRange(from l in dep[0]
									 from r in dep[1]
									 let o =l+r 
									 select o);
				else options.AddRange(dep[0]);
			}
			compiled[rule.Key] = options.ToArray();
		}

	}
	rules = rules.Where(r => !compiled.ContainsKey(r.Key)).ToDictionary(r => r.Key, r => r.Value);
}

var part1 = messages.Count(m => compiled[0].Contains(m));
part1.Dump();


int part2 = 0;
foreach (var message in messages)
{
	// rule 8
	var segments = compiled[42].Where(o => message.IndexOf(o) > -1).ToArray();
	var rule8 = new List<string>(segments);
	string[] possibles = segments;
	while (possibles.Any() && possibles.First().Length < message.Length)
	{
		possibles = (from s1 in segments
					 from s2 in possibles
					 let s = s1 + s2
					 where message.IndexOf(s) > -1
					 select s)
						.ToArray();
		rule8.AddRange(possibles);
	}

	// rule 11	
	var left = compiled[42].Where(o => message.IndexOf(o) > -1).ToArray();
	var right = compiled[31].Where(o => message.IndexOf(o) > -1).ToArray();
	possibles = (from l in left from r in right let o =l+r where message.IndexOf(o) > -1 select o).ToArray();
	var rule11 = new List<string>(possibles);
	while (possibles.Any() && possibles.First().Length < message.Length){
		
		possibles = (from l in left
					 from p in possibles
					 from r in right
					 let s = l + p + r
					 where message.IndexOf(s) > -1
					 select s)
						.ToArray();
					rule11.AddRange(possibles);
	}

	// rule 0: 8 11
	if ((from a in rule8 from b in rule11 let c=a+b where c.Length == message.Length select c).Any(c => message == c))
		part2++;
}
part2.Dump();

Day 18 - [Operation Order]

var reGroup = new Regex(@"(\(([^\(\)]+)\))");
var reOp = new Regex(@" *(\d+) *([+*]) *(\d+) *(.*)");
var reOpAdd = new Regex(@" *((\d+) *\+ *(\d+)) *");

string evalLeftToRight(string src) 
{
	var g = reGroup.Match(src);
	while (g.Success) {
		src = src.Substring(0,g.Groups[1].Index) + evalLeftToRight(g.Groups[2].Value)+ src.Substring(g.Groups[1].Index + g.Groups[1].Length);
		g = reGroup.Match(src);
	}
	var op = reOp.Match(src); 
	while(op.Success) {
		var o1 = long.Parse(op.Groups[1].Value);
		var o2 = long.Parse(op.Groups[3].Value);
		var val = op.Groups[2].Value == "+" ? o1 + o2 : o1 * o2;
		src = $"{val}{op.Groups[4].Value}";
		op = reOp.Match(src);
	}
	return src; 
}

var part1 = input.Select(evalLeftToRight).Select(long.Parse).Sum();
part1.Dump();

string evalAdditionFirst(string src)
{
	var g = reGroup.Match(src);
	while (g.Success)
	{
		src = src.Substring(0, g.Groups[1].Index) + evalAdditionFirst(g.Groups[2].Value) + src.Substring(g.Groups[1].Index + g.Groups[1].Length);
		g = reGroup.Match(src);
	}


	var opAdd = reOpAdd.Match(src);
	while (opAdd.Success)
	{
		var o1 = long.Parse(opAdd.Groups[2].Value);
		var o2 = long.Parse(opAdd.Groups[3].Value);
		var val = o1 + o2;
		src = src.Substring(0, opAdd.Groups[1].Index) + val.ToString() + src.Substring(opAdd.Groups[1].Index + opAdd.Groups[1].Length);
		opAdd = reOpAdd.Match(src);
	}

	var op = reOp.Match(src);
	while (op.Success)
	{
		var o1 = long.Parse(op.Groups[1].Value);
		var o2 = long.Parse(op.Groups[3].Value);
		var val = op.Groups[2].Value == "+" ? o1 + o2 : o1 * o2;
		src = $"{val}{op.Groups[4].Value}";
		op = reOp.Match(src);
	}
	return src;
}

var part2 = input.Select(evalAdditionFirst).Select(long.Parse).Sum();
part2.Dump();

Day 17 - [Conway Cubes]

var pocketSpace = input.SelectMany((s, li) => s.Select((c, ci) => ((ci, li, 0), c))).Where(i => i.c =='#').Select(i => i.Item1).ToHashSet();

HashSet<(int, int, int)> Cycle(HashSet<(int x, int y, int z)> src)
{
	int CountNeighbors((int x, int y, int z) p) => src.Count(s => Math.Abs(p.x - s.x) < 2 && Math.Abs(p.y - s.y) < 2 && Math.Abs(p.z - s.z) < 2 );

	var ret = new HashSet<(int, int, int)>();
	for (int dx = src.Select(k => k.x).Min() - 1; dx <= src.Select(k => k.x).Max() + 1; dx++)
		for (int dy = src.Select(k => k.y).Min() - 1; dy <= src.Select(k => k.y).Max() + 1; dy++)
			for (int dz = src.Select(k => k.z).Min() - 1; dz <= src.Select(k => k.z).Max() + 1; dz++)
			{
				var k = (dx, dy, dz);
				var n = CountNeighbors(k);
				if (n == 3 || (src.TryGetValue(k, out var _) && n == 4))
					ret.Add(k);
			}
	return ret;
}

for (int cycle = 0; cycle < 6; cycle++)
{
	pocketSpace = Cycle(pocketSpace);
}
var part1 = pocketSpace.Count();
part1.Dump();


var hyperCubePocketSpace = input.SelectMany((s, li) => s.Select((c, ci) => ((x: ci, y: li, z: 0, w: 0), c))).Where(i => i.c =='#').Select(i => i.Item1).ToHashSet();
HashSet<(int, int, int, int)> Cycle2(HashSet<(int x, int y, int z, int w)> src)
{
	int CountNeighbors((int x, int y, int z, int w) p) => src.Count(s => s.x>=p.x-1 && s.x<=p.x+1 && s.y>=p.y-1 && s.y<=p.y+1 && s.z>=p.z-1 && s.z<=p.z+1 && s.w>=p.w-1 && s.w<=p.w+1);
																
	var wmax = src.Select(k => k.w).Max() + 1;
	var wmin = src.Select(k => k.w).Min() - 1;
	var zmax = src.Select(k => k.z).Max() + 1;
	var zmin = src.Select(k => k.z).Min() - 1;
	var ymax = src.Select(k => k.y).Max() + 1;
	var ymin = src.Select(k => k.y).Min() - 1;
	var xmax = src.Select(k => k.x).Max() + 1;
	var xmin = src.Select(k => k.x).Min() - 1;
	var ret = new HashSet<(int, int, int, int)>();
	for (int dx = xmin; dx <= xmax; dx++)
		for (int dy = ymin; dy <= ymax; dy++)
			for (int dz = zmin; dz <= zmax; dz++)
				for (int dw = wmin; dw <= wmax; dw++)
				{
					var k = (dx, dy, dz, dw);
					var n = CountNeighbors(k);
					if (n == 3 || (src.TryGetValue(k, out var _) && n == 4))
						ret.Add(k);
				}
	return ret;
}
for (int cycle = 0; cycle < 6; cycle++)
{
	hyperCubePocketSpace = Cycle2(hyperCubePocketSpace);
}
var part2 = hyperCubePocketSpace.Count();
part2.Dump();

Day 16 - [Ticket Translation]

var fields = input.TakeWhile(i => !string.IsNullOrEmpty(i)).Select(i => Regex.Match(i, @"(.+): (\d+)-(\d+) or (\d+)-(\d+)")).ToDictionary(m => m.Groups[1].Value, m => new List<(int, int)> { (int.Parse(m.Groups[2].Value), int.Parse(m.Groups[3].Value)), (int.Parse(m.Groups[4].Value), int.Parse(m.Groups[5].Value)), });
var myTicket = input.Skip(input.IndexOf("your ticket:")).Skip(1).Select(i => i.Split(',').Select(int.Parse).ToArray()).First();
var nearbyTickets = input.Skip(input.IndexOf("nearby tickets:")).Skip(1).Select(i => i.Split(',').Select(int.Parse).ToArray()).ToArray();

int scanningErrorRate(int[] ticket) => ticket.Where(t => fields.SelectMany(f => f.Value).All(f => !(t >= f.Item1 && t <= f.Item2))).Sum();
var part1 = nearbyTickets.Sum(scanningErrorRate);
part1.Dump();

var validTickets = nearbyTickets.Where(t => scanningErrorRate(t) == 0);
var maps = fields.Select(f => (f.Key, Enumerable.Range(0, myTicket.Length).Where(slot => validTickets.Select(vt => vt[slot]).All(value => f.Value.Any(range => range.Item1 <= value && range.Item2 >= value))).ToList())).ToDictionary(f => f.Key, f => f.Item2); ;
while (maps.Any(m => m.Value.Count() > 1))
{
	var excluded = maps.Where(m => m.Value.Count() == 1).SelectMany(m => m.Value);
	var keys = maps.Where(m => m.Value.Count() > 1).Select(m => m.Key).ToList();
	foreach (var map in keys)
		maps[map] = maps[map].Except(excluded).ToList();
}
var part2 = maps.Where(m => m.Key.StartsWith("departure")).Select(m => (long)myTicket[m.Value[0]]).Aggregate((x, y) => x * y);
part2.Dump();

Day 15 - [Rambunctious Recitation]

var spoken = new Dictionary<int,(int,int)>();
int lastSpoken = 0, part1 = 0;
for (var turn = 0; turn < 30000000; turn++)
{
	if (turn == 2020)
		part1 = lastSpoken;

	void speak(int n) { if (!spoken.ContainsKey(n)) spoken[n]=(-1,turn); else spoken[n]=(spoken[n].Item2, turn); lastSpoken = n; }

	if (turn < input.Length)
		speak(input[turn]);
	else
		if (spoken.TryGetValue(lastSpoken, out var ns) && ns.Item1 > -1 )
			speak(ns.Item2-ns.Item1);
		else
			speak(0);									
}
var part2 = lastSpoken;

part1.Dump();
part2.Dump();

Day 14 - [Docking Data]

var memory = new Dictionary<int,long>();
var mask = string.Empty;

long ApplyMask(long val) => Convert.ToInt64(new string(mask.ToCharArray().Zip(Convert.ToString(val,2).PadLeft(mask.Length,'0').ToCharArray(), (m,v) => m == 'X' ? v : m).ToArray()),2);

foreach (var i in input)
{
	if (i.StartsWith("mask"))
		mask = i.Substring(7);
	else
	{
		var m = Regex.Match(i, @"\[(\d+)\].*?=.*?(\d+)");
		var address = int.Parse(m.Groups[1].Value);
		var val = long.Parse(m.Groups[2].Value);
		memory[address]=ApplyMask(val);
	}
}
var part1 = memory.Sum(m => m.Value);
part1.Dump();


var mem2 = new Dictionary<long, long>();
IEnumerable<long> GetMaskedAddresses(long a) 
{
	var xcount = mask.ToCharArray().Count(o => o =='X');
	var addr = 	Convert.ToString(a,2).PadLeft(mask.Length,'0').ToCharArray();
	for (int i = 0; i < Math.Pow(2,xcount); i++){
		var xbits = new System.Collections.Generic.Queue<char>(Convert.ToString(i,2).PadLeft(xcount,'0').ToCharArray());
		yield return Convert.ToInt64(new string(mask.ToCharArray().Zip(addr, (mb,ab) => mb=='0' ? ab : mb=='1' ? mb : xbits.Dequeue()).ToArray()),2);
	}
}
foreach (var i in input)
{
	if (i.StartsWith("mask"))
		mask = i.Substring(7);
	else
	{
		var m = Regex.Match(i, @"\[(\d+)\].*?=.*?(\d+)");
		var address = long.Parse(m.Groups[1].Value);
		var val = long.Parse(m.Groups[2].Value);
		foreach(var a in GetMaskedAddresses(address))
			mem2[a] = val;
	}
}
var part2 = mem2.Sum(m => m.Value);
part2.Dump();

Day 13 - [Shuttle Search]

var buses = input.Split(',').Select((b,i) => (b,i)).Where(o => o.b != "x").Select(o => (bus:int.Parse(o.b),index:o.i)).ToArray();

var nextDeparture = buses.Select(b => b.bus).Select(b => (b, ((estimate / b) + 1) * b)).OrderBy(b => b.Item2).First();
var part1 = (nextDeparture.Item2-estimate)*nextDeparture.b;
part1.Dump();

long timestamp = buses.First().bus;
long interval = buses.First().bus;
foreach (var b in buses.Skip(1))
{
	while ((timestamp+b.index)%b.bus != 0) timestamp += interval;
	interval *= b.bus;
}
var part2 = timestamp;
part2.Dump();

Day 12 - [Rain Risk]

var x = 0;
var y = 0;
var h = 90;

foreach (var step in input)
{
	switch (step.Item1)
	{
		case 'S': y -= step.Item2; break;
		case 'N': y += step.Item2; break;
		case 'W': x -= step.Item2; break;
		case 'E': x += step.Item2; break;
		case 'L': h += (360 - step.Item2); h %= 360; break;
		case 'R': h += step.Item2; h %= 360; break;
		case 'F':
			switch (h)
			{
				case 90: x += step.Item2; break;
				case 270: x -= step.Item2; break;
				case 0: y += step.Item2; break;
				case 180: y -= step.Item2; break;
			}
			break;
	}
}

var part1 = Math.Abs(x) + Math.Abs(y);
part1.Dump();


var shipx = 0;
var shipy = 0;
var wpx = 10;
var wpy = 1;

foreach (var step in input)
{
	switch (step.Item1)
	{
		case 'S': wpy -= step.Item2; break;
		case 'N': wpy += step.Item2; break;
		case 'W': wpx -= step.Item2; break;
		case 'E': wpx += step.Item2; break;
		case 'L':
		case 'R':
			var rot = step.Item1 == 'L' ? 360 - step.Item2 : step.Item2;
			int temp;
			switch (rot)
			{
				case 90: temp = wpx; wpx = wpy; wpy = -temp; break;
				case 180: wpx = -wpx; wpy = -wpy; break;
				case 270: temp = wpx; wpx = -wpy; wpy = temp; break;
			}
			break;
		case 'F': shipx += (step.Item2 * wpx); shipy += (step.Item2 * wpy); break;
	}
}

var part2 = Math.Abs(shipx) + Math.Abs(shipy);
part2.Dump();

Day 11 - [Seating System]

var current = input;

bool isValid(int y, int x) => y >= 0 && y < current.Length && x >= 0 && x < input[y].Length;
bool isOccupied(int y, int x) => isValid(y, x) && current[y][x] == '#';
int adjacentOccupied(int y, int x)
{
	var count = 0;
	for (int col = x - 1; col <= x + 1; col++)
		for (int row = y - 1; row <= y + 1; row++)
			if ((x != col || y != row) && isOccupied(row, col))
				count++;
	return count;
}
char[][] stepPart1() => current.Select((r, ri) => r.Select((c, ci) => c == 'L' ? adjacentOccupied(ri, ci) == 0 ? '#' : c : c == '#' ? adjacentOccupied(ri, ci) >= 4 ? 'L' : c : c).ToArray()).ToArray();

var nextStep = stepPart1();
while (!current.Select(p => new string(p)).SequenceEqual(nextStep.Select(s => new string(s))))
{
	current = nextStep;
	nextStep = stepPart1();
}

var part1 = current.Sum(r => r.Count(c => c == '#'));
part1.Dump();


bool visible(int y, int x, int dy, int dx)
{
	while (isValid(y+=dy, x+=dx) && current[y][x] != '.')
		 return isOccupied(y, x);
	return false;
}
int visibleOccupied(int y, int x)
{
	var count = 0;
	if (visible(y, x, 1, 0)) count++;
	if (visible(y, x, 1, 1)) count++;
	if (visible(y, x, 0, 1)) count++;
	if (visible(y, x, -1, 1)) count++;
	if (visible(y, x, -1, 0)) count++;
	if (visible(y, x, -1, -1)) count++;
	if (visible(y, x, 0, -1)) count++;
	if (visible(y, x, 1, -1)) count++;
	return count;
}
char[][] stepPart2() => current.Select((r, ri) => r.Select((c, ci) => c == 'L' ? visibleOccupied(ri, ci) == 0 ? '#' : c : c == '#' ? visibleOccupied(ri, ci) >= 5 ? 'L' : c : c).ToArray()).ToArray();


current = input;
nextStep = stepPart2();
while (!current.Select(p => new string(p)).SequenceEqual(nextStep.Select(s => new string(s))))
{
	current = nextStep;
	nextStep = stepPart2();
}
var part2 = current.Sum(r => r.Count(c => c == '#'));
part2.Dump();

Day 10 - [Adapter Array]

var jolts = new[] { 0, input.Max()+3 }.Concat(input).OrderBy(o => o).ToArray();

var diffs = jolts.Zip(jolts.Skip(1), (a,b) =>  b-a).GroupBy(j => j).ToDictionary(g => g.Key, g => g.Count());
var part1 = diffs[1] * diffs[3];
part1.Dump();

var connections = new Dictionary<int, long> { { 0, 1 }};
foreach (var jolt in jolts.Skip(1))
{
	connections[jolt] = (connections.TryGetValue(jolt - 1, out var one) ? one : 0) +
						(connections.TryGetValue(jolt - 2, out var two) ? two : 0) +
						(connections.TryGetValue(jolt - 3, out var three) ? three : 0);
}
var part2 = connections[jolts.Max()];
part2.Dump();

Day 9 - [Encoding Error]

IEnumerable<(T, T)> Pairs<T>(IList<T> items)
{
	for (var outer = 0; outer < items.Count() - 1; outer++)
		for (var inner = outer + 1; inner < items.Count(); inner++)
			if (!items[outer].Equals(items[inner]))
				yield return (items[outer], items[inner]);
}

bool isValid(int pos) => Pairs(input.Skip(pos-25).Take(25).ToList()).Any(p => p.Item1 + p.Item2 == input[pos]);
var part1 = Enumerable.Range(25, input.Length-25).Where(i => !isValid(i)).Select(i => input[i]).First();

long[] findWeakness(int pos){
	var numbers = new List<long>();
	while (numbers.Sum() < part1)
		numbers.Add(input[pos+numbers.Count()]);
	return numbers.Sum() == part1 ? numbers.ToArray() : null;
}

var w = Enumerable.Range(0, input.Length).Select(findWeakness).First(o => o != null);
var part2 = w.Max() + w.Min();
part2.Dump();

Day 8 - [Handheld Halting]

(bool, int) Run(List<List<string>> code)
{
	var executed = new Dictionary<int, int>();

	var ptr = 0;
	var acc = 0;
	while (ptr < code.Count)
	{
		if (executed.ContainsKey(ptr))
			return (false, acc);
		
		executed[ptr] = 1;
		var cmd = code[ptr];
		switch (cmd[0])
		{
			case "acc":
				acc += int.Parse(cmd[1]);
				ptr++;
				break;
			case "nop":
				ptr++;
				break;
			case "jmp":
				ptr += int.Parse(cmd[1]);
				break;
		}
	}
	return (true, acc);
}

(_, var part1) = Run(input);
part1.Dump();

for (int i = 0; i < input.Count(); i++){
	var altered = input.Select(o => o.Select(ii => ii).ToList()).ToList();
	if (altered[i][0] == "nop"){
		altered[i][0] = "jmp";
	}
	else if (altered[i][0] == "jmp"){
		altered[i][0] = "nop";
	}
	else{
		continue;
	}
	(var result, var part2) = Run(altered);
	if (result == true){
		part2.Dump();
	}
}

Day 7 - [Handy Haversacks]

var rules = input.Select(i => Regex.Match(i, @"^(.+) bags contain (no other bags|(([ ]*\d ([^,]+) bag[^,]*[,]?)*)).$")).ToDictionary(m => m.Groups[1].Value, m => m.Groups[3].Value.Split(',').Where(i => !string.IsNullOrEmpty(i)).Select(i => Regex.Match(i, @"(\d+) (.+) bag")).Select(i => (count: int.Parse(i.Groups[1].Value),bag: i.Groups[2].Value)).ToArray());

bool CanContain(string bag, string inner) =>  rules[bag].Any(i => i.bag == inner || CanContain(i.bag, inner));
var part1 = rules.Count(r => CanContain(r.Key, "shiny gold"));
part1.Dump();


int Contains(string bag) => rules[bag].Sum(v => v.count + (v.count * Contains(v.bag)));
var part2 = Contains("shiny gold");
part2.Dump();

Day 6 - [Custom Customs]

IEnumerable<List<string>> GetGroups(string[] raw)
{
	var e = raw.GetEnumerator();
	var s = new List<string>();
	while (e.MoveNext())
	{
		var line = (string)e.Current;
		if (string.IsNullOrEmpty(line))
		{
			yield return s;
			s = new List<string>();
			continue;
		}
		s.Add(line);
	}
	yield return s;
}

var groups = GetGroups(input).ToList();

var part1 = groups.Sum(g => g.Aggregate((x, y) => x + y).ToCharArray().Distinct().Count());
part1.Dump();

var part2 = groups.Sum(g => g.SelectMany(a => a.ToCharArray()).GroupBy(a => a).Count(ga => ga.Count() == g.Count()));
part2.Dump();

Day 5 - [Binary Boarding]

int GetNumber(string id, char lower)
{
	var range = (0, (int)Math.Pow(2, id.Length) - 1);
	for (int i = 0; i < id.Length; i++)
		range = GetHalf(range, id[i] == lower);
	return range.Item1;
}
(int min, int max) GetHalf((int min, int max) r, bool lower) => lower ? (r.min, r.max - (r.max - r.min + 1) / 2) : (r.min + (r.max - r.min + 1) / 2, r.max);
int SeatId(string id) => GetNumber(id.Substring(0,7),'F') * 8 + GetNumber(id.Substring(7,3), 'L');

var part1 = input.Max(i => SeatId(i));
part1.Dump();

var seats = input.Select(SeatId).OrderBy(i => i).ToArray();
var part2 = seats.Zip(seats.Skip(1), (a, b) => b == a + 2 ? a + 1 : 0).Max();
part2.Dump();

Day 4 - [Passport Processing]

IEnumerable<Dictionary<string, string>> Parse(string[] raw) {
	var e = raw.GetEnumerator();
	var dict = new Dictionary<string,string>();
	while(e.MoveNext()){
		var line = (string)e.Current;
		if (string.IsNullOrEmpty(line))
		{
			yield return dict;
			dict = new Dictionary<string, string>();
			continue;
		}
		var items = line.Split(' ');
		foreach (var item in items)
		{
			var parts = item.Split(':');
			dict.Add(parts[0], parts[1]);
		}
	}
	yield return dict;
}
var passports = Parse(input);


var required = new[] { "byr", "iyr", "eyr", "hgt", "hcl", "ecl", "pid" };
bool isValid(Dictionary<string,string> passport) => required.All(r => passport.ContainsKey(r));
var part1 = passports.Count(isValid);
part1.Dump();


var fields = new Dictionary<string, Func<string, bool>> {
	{ "byr", i => { var n = int.Parse(i); return n >= 1920 && n <= 2002; }},
	{ "iyr", i => { var n = int.Parse(i); return n >= 2010 && n <= 2020; }},
	{ "eyr", i => { var n = int.Parse(i); return n >= 2020 && n <= 2030; }},
	{ "hgt", i => { var m = Regex.Match(i, @"^(\d+)(cm|in)$"); return m.Success && int.TryParse(m.Groups[1].Value, out int n) && (m.Groups[2].Value == "cm" ? n >= 150 && n <= 193 : n>=59 && n <=76 ); }},
	{ "hcl", i => { return Regex.IsMatch(i, @"^#[0-9a-f]{6}$"); }},
	{ "ecl", i => { return i == "amb" || i == "blu" || i == "brn" || i == "gry" || i == "grn" || i == "hzl" || i == "oth"; }},
	{ "pid", i => { return Regex.IsMatch(i, @"^\d{9}$"); }},
};
bool isValidPartTwo (Dictionary<string,string> passport) => fields.All(f => passport.ContainsKey(f.Key) && f.Value(passport[f.Key]));
var part2 = passports.Count(isValidPartTwo);
part2.Dump();

Day 3 - [Toboggan Trajectory]

bool isTree(int row, int col) => input[row][col % input[row].Length] == '#';

var part1 = Enumerable.Range(0, input.Length).Count(i => isTree(i, i*3));
part1.Dump();

var part2 = (long)Enumerable.Range(0, input.Length).Count(i => isTree(i, i))
		  * Enumerable.Range(0, input.Length).Count(i => isTree(i, i * 3))
		  * Enumerable.Range(0, input.Length).Count(i => isTree(i, i * 5))
		  * Enumerable.Range(0, input.Length).Count(i => isTree(i, i * 7))
		  * Enumerable.Range(0, input.Length / 2).Count(i => isTree(i * 2, i));
part2.Dump();

Day 2 - [Password Philosophy]

var re = new Regex(@"^(\d+)-(\d+) (.): (.+)$");
var passwords = input.Select(i => re.Match(i)).Select(i => (min: int.Parse(i.Groups[1].Value) , max: int.Parse(i.Groups[2].Value), c: i.Groups[3].Value.Single(), pw: i.Groups[4].Value) ).ToList();

var part1 = passwords.Select(item => (item.min, item.max, count: item.pw.Count(p => p == item.c))).Count(item => item.count >= item.min && item.count <= item.max);
part1.Dump();

var part2 = passwords.Count(item => item.pw[item.min-1]==item.c ^ item.pw[item.max-1]==item.c);
part2.Dump();

Day 1 - [Report Repair]

IEnumerable<(T, T)> Pairs<T>(IList<T> items) {
	for (var outer = 0; outer < input.Count() -1 ; outer++)
		for (var inner = outer + 1; inner < input.Count(); inner++)
			yield return (items[outer], items[inner]);
}

var pair = Pairs(input).First(p => p.Item1 + p.Item2 == 2020);
var part1 = pair.Item1 * pair.Item2;
part1.Dump();


IEnumerable<(T, T, T)> Triples<T>(IList<T> items)
{
	for (var x = 0; x < input.Count() - 2; x++)
		for (var y = x + 1; y < input.Count() - 1; y++)
			for (var z = y + 1; z < input.Count(); z++)
				yield return (items[x], items[y], items[z]);
}

var triple = Triples(input).First(p => p.Item1 + p.Item2 + p.Item3 == 2020);
var part2 = triple.Item1 * triple.Item2 * triple.Item3;
part2.Dump();