diff --git a/AdventOfCode/Controllers/AdventOfCodeController.cs b/AdventOfCode/Controllers/AdventOfCodeController.cs index 4743ff4..e599afc 100644 --- a/AdventOfCode/Controllers/AdventOfCodeController.cs +++ b/AdventOfCode/Controllers/AdventOfCodeController.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using System; using System.Linq; +using System.Text.RegularExpressions; namespace AdventOfCode.Controllers { @@ -23,7 +24,8 @@ namespace AdventOfCode.Controllers public AOCResponse Day(int year, int day, AOCVersion version, [FromBody] string input, bool IgnoreLogMessages = false) { AOCRequest request = new AOCRequest() { Input = input, Version = version, IgnoreLogMessages = IgnoreLogMessages }; - return GetAOCDay(year, day).ExecuteDay(request); + var resp = GetAOCDay(year, day).ExecuteDay(request); + return resp; } private AOCDay GetAOCDay(int year, int day) @@ -44,6 +46,29 @@ namespace AdventOfCode.Controllers aocDay.SetLogger(this._logger); } } + else + { + var ns = x.Namespace; + var className = x.Name; + var dayParsed = 0; + _ = int.TryParse(Regex.Replace(className, "[^0-9.]", ""), out dayParsed); + + var match = Regex.Match(ns, @"\d{4}"); + if (match.Success) + { + var yearParsed = 0; + _ = int.TryParse(match.Value, out yearParsed); + if (yearParsed == year && dayParsed == day) + { + aocDay = (AOCDay)(IAOCService)Activator.CreateInstance(x); + aocDay.SetLogger(this._logger); + break; + } + } + + } + + if (aocDay != null) break; //Means we found a match and it was created! } return aocDay; } diff --git a/AdventOfCode/Models/AOCDay.cs b/AdventOfCode/Models/AOCDay.cs index 71265db..9a7f7ab 100644 --- a/AdventOfCode/Models/AOCDay.cs +++ b/AdventOfCode/Models/AOCDay.cs @@ -11,7 +11,7 @@ namespace AdventOfCode.Models { protected AOCRequest _request; protected AOCResponse _response; - protected ILogger _logger; + private ILogger _logger; private List _debugMessages; protected object Answer { set { this._response.Answer = value; } } public AOCDay() diff --git a/AdventOfCode/Properties/launchSettings.json b/AdventOfCode/Properties/launchSettings.json index c26a05f..a5ac3fe 100644 --- a/AdventOfCode/Properties/launchSettings.json +++ b/AdventOfCode/Properties/launchSettings.json @@ -23,7 +23,7 @@ "ASPNETCORE_ENVIRONMENT": "Development" }, "dotnetRunMessages": "true", - "applicationUrl": "https://localhost:5001;http://localhost:5000" + "applicationUrl": "https://localhost:5023;http://localhost:5123" } } } \ No newline at end of file diff --git a/AdventOfCode/_2023/Day1.cs b/AdventOfCode/_2023/Day1.cs new file mode 100644 index 0000000..b440747 --- /dev/null +++ b/AdventOfCode/_2023/Day1.cs @@ -0,0 +1,83 @@ +using AdventOfCode.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace AdventOfCode._2023 +{ + public class Day1 : AOCDay + { + private Dictionary lookup = new Dictionary() + { + {"zero", 0 }, + {"one", 1 }, + {"two", 2 }, + {"three", 3 }, + {"four", 4 }, + {"five", 5 }, + {"six", 6 }, + {"seven", 7 }, + {"eight", 8 }, + {"nine", 9 }, + }; + protected override AOCResponse ExecutePartA() + { + var totalCount = 0; + foreach (var line in this.GetSplitInput()) + { + totalCount += this._request.Version == AOCVersion.A ? ParseLineIntoNumber(line) : ParseTextLineIntoNumber(line); + } + this.Answer = totalCount; + return this._response; + } + + protected override AOCResponse ExecutePartB() + { + return ExecutePartA(); + } + + private int ParseLineIntoNumber(string line) + { + var firstDigit = line.First(x => char.IsDigit(x)); + var lastDigit = line.Reverse().First(x => char.IsDigit(x)); + Log($"{line} => first: {firstDigit} last: {lastDigit}"); + return int.Parse($"{firstDigit}{lastDigit}"); + } + + private int ParseTextLineIntoNumber(string line) + { + var foundDigits = new List(); + for (int c = 0; c < line.Length; c++) + { + if (char.IsDigit(line[c])) + { + foundDigits.Add(int.Parse(line[c].ToString())); + continue; + } + foreach (var key in lookup.Keys) + { + var length = c + key.Length; + if (length <= line.Length) //Check if we exceeded our line length + { + var section = new String(line.Skip(c).Take(key.Length).ToArray()); + if (section.Equals(key)) + { + foundDigits.Add(lookup[key]); + + //We can't skip the length because eighthree => 83 and sevenine => 79. We only advance one char to fix + //c += key.Length-1; //Outer forloop auto increments one already + break; + } + } + } + } + var firstDigit = foundDigits.First(); + var lastDigit = foundDigits.Last(); + if (firstDigit > 9 || lastDigit > 9) throw new Exception("No digit should be higher than 9"); + var total = int.Parse($"{firstDigit}{lastDigit}"); + Log($"{line} => first: {firstDigit} last: {lastDigit} for total of {total}" ); + return total; + } + } +} diff --git a/AdventOfCode/_2023/Day2.cs b/AdventOfCode/_2023/Day2.cs new file mode 100644 index 0000000..aacfab3 --- /dev/null +++ b/AdventOfCode/_2023/Day2.cs @@ -0,0 +1,142 @@ +using AdventOfCode.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace AdventOfCode._2023 +{ + public class Day2 : AOCDay + { + private readonly List ANSWER_CUBES = new List() + { + new Cube("12 red"), + new Cube("13 green"), + new Cube("14 blue") + }; + + protected override AOCResponse ExecutePartA() + { + //Create game objects for each of our lines + var games = this.GetSplitInput().Select(x => new Game(x)).ToList(); + + //Select only our valid games + var validGames = games.Where(x => x.CompareAgainstAnswer(ANSWER_CUBES)); + + this.Answer = validGames.Sum(x => x.Id); + return this._response; + } + + protected override AOCResponse ExecutePartB() + { + //Create game objects for each of our lines + var games = this.GetSplitInput().Select(x => new Game(x)).ToList(); + + if (!this._request.IgnoreLogMessages) + { + games.ForEach(x => + { + var minAnswerCubes = x.GetMinAnswerCubes(); + Log($"Game ID {x.Id} generated the min answer: {string.Join(", ", minAnswerCubes.Select(x => $"{x.Count} {x.Color}"))} with power {x.GetGamePower()}"); + }); + } + this.Answer = games.Sum(x => x.GetGamePower()); + return this._response; + } + } + + class Game + { + public int Id { get; set; } + public List drawings { get; set; } = new List(); + public Game(string input) + { + var splitInput = input.Split(':'); + Id = int.Parse(splitInput[0].Split(' ')[1]); //Extract our game id out of input string + + //Parse our drawings into objects + foreach (var drawing in splitInput[1].Split(';')) + { + drawings.Add(new Drawing(drawing.Trim())); + } + } + + public bool CompareAgainstAnswer(List baggedCubes) + { + foreach (var drawing in drawings) + { + foreach (var cube in drawing.Cubes) + { + var answerCube = baggedCubes.First(x => x.Color == cube.Color); + if (cube.Count > answerCube.Count) + { + //Means the drawed cube count exceeds the number of cubes in our bag + return false; + } + } + } + + return true; + } + + public List GetMinAnswerCubes() + { + var minAnswerCubes = new List(); + foreach (var drawing in drawings) + { + foreach (var cube in drawing.Cubes) + { + var matchedCube = minAnswerCubes.FirstOrDefault(x => x.Color == cube.Color); + if (matchedCube == null) + { + minAnswerCubes.Add(new Cube() + { + Color = cube.Color, + Count = cube.Count + }); + } + else + { + matchedCube.Count = Math.Max(matchedCube.Count, cube.Count); + } + } + } + return minAnswerCubes; + } + + public int GetGamePower() + { + var minAnswerCubes = GetMinAnswerCubes(); + var power = 1; + minAnswerCubes.ForEach(x => power *= x.Count); + return power; + } + } + + class Drawing + { + public List Cubes { get; set; } = new List(); + + public Drawing(string drawing) + { + foreach (var input in drawing.Split(',')) + { + Cubes.Add(new Cube(input.Trim())); + } + } + } + + class Cube + { + public int Count { get; set; } + public string Color { get; set; } + + public Cube() { } + public Cube(string input) + { + var split = input.Split(' '); + Count = int.Parse(split[0]); + Color = split[1]; + } + } +} diff --git a/AdventOfCode/_2023/Day3.cs b/AdventOfCode/_2023/Day3.cs new file mode 100644 index 0000000..115e55f --- /dev/null +++ b/AdventOfCode/_2023/Day3.cs @@ -0,0 +1,196 @@ +using AdventOfCode.Common; +using AdventOfCode.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace AdventOfCode._2023 +{ + public class Day3 : AOCDay + { + + protected override AOCResponse ExecutePartA() + { + var board = new SchematicBoard(this.GetSplitInput().ToList()); + + var validSchematicNumbers = board.GetValidSchematicNumbers(); + + this.Answer = validSchematicNumbers.Sum(x => x.Number); + + return this._response; + } + + protected override AOCResponse ExecutePartB() + { + var board = new SchematicBoard(this.GetSplitInput().ToList()); + + var gearLookup = board.GetGearLookup(); + + if (!this._request.IgnoreLogMessages) + { + foreach (var gl in gearLookup) + { + Log($"Gear at {gl.Key} has numbers: {string.Join(",", gl.Value)}"); + } + } + + //Select valid gears, those with two numbers at a location + var validGears = gearLookup.Where(x => x.Value.Count == 2); + + //Calculate the gear ration (num1 * num2) + var gearRatios = validGears.Select(x => x.Value.First() * x.Value.Last()); + + //Sum up all the gear ratio for an answer + this.Answer = gearRatios.Sum(x => x); + + return this._response; + } + } + + class SchematicBoard + { + private List _schematicNumbers; + private char[,] _board; + public SchematicBoard(List input) + { + _schematicNumbers = new List(); + var rows = input.Count; + var columns = input.First().Length; + _board = new char[rows, columns]; + + for (int y = 0; y < rows; y++) + { + for (int x = 0; x < columns; x++) + { + _board[x,y] = input[x][y]; + } + } + _board.PrintSquareArray(); + + //Pull out our schematic numbers from our board + for (int x = 0; x < _board.GetLength(0); x++) + { + for (int y = 0; y < _board.GetLength(1); y++) + { + if (char.IsDigit(_board[x, y])) + { + var number = ""; + var yPoses = new List(); + for (int l = y; l < _board.GetLength(1); l++) + { + if (char.IsDigit(_board[x, l])) + { + yPoses.Add(l); + number += _board[x, l].ToString(); + } + else + { + y = l; + break; + } + } + _schematicNumbers.Add(new SchematicNumber() + { + Number = int.Parse(number), + Coords = yPoses.Select(yPos => Tuple.Create(x, yPos)).ToList() + }); + + } + } + } + } + + public IEnumerable GetValidSchematicNumbers() + { + return _schematicNumbers.Where(x => x.IsValidNumber(_board)); + } + + public Dictionary> GetGearLookup() + { + var lookup = new Dictionary>(); + foreach (var schematicNumber in _schematicNumbers) + { + var gears = schematicNumber.GetGearCoordinates(_board); + foreach (var gear in gears) + { + if (lookup.ContainsKey(gear)) + { + lookup[gear].Add(schematicNumber.Number); + } + else + { + lookup[gear] = new HashSet() { schematicNumber.Number }; + } + } + } + return lookup; + } + } + + class SchematicNumber + { + public int Number { get; set; } + public List> Coords { get; set; } + + public bool IsValidNumber(char[,] board) + { + foreach (var coord in Coords) + { + for (int x = -1; x <= 1; x++) + { + for (int y = -1; y <= 1; y++) + { + var checkedPosX = coord.Item1 + x; + var checkedPosY = coord.Item2 + y; + + //Check if the points are within the valid size of our board + if (checkedPosX < 0 || checkedPosY < 0) continue; + if (checkedPosX >= board.GetLength(0) || checkedPosY >= board.GetLength(1)) continue; + + //Get our char at the given index + var charAtPoint = board[checkedPosX, checkedPosY]; + //Check if the char is NOT a digit, and NOT a period, then return true its adjacent + if (!char.IsDigit(charAtPoint) && charAtPoint != '.') + { + return true; + } + + } + } + } + return false; + } + + public List GetGearCoordinates(char[,] board) + { + var gearCoords = new List(); + foreach (var coord in Coords) + { + for (int x = -1; x <= 1; x++) + { + for (int y = -1; y <= 1; y++) + { + var checkedPosX = coord.Item1 + x; + var checkedPosY = coord.Item2 + y; + + //Check if the points are within the valid size of our board + if (checkedPosX < 0 || checkedPosY < 0) continue; + if (checkedPosX >= board.GetLength(0) || checkedPosY >= board.GetLength(1)) continue; + + //Get our char at the given index + var charAtPoint = board[checkedPosX, checkedPosY]; + + //Check if it is a gear point + if (charAtPoint == '*') + { + gearCoords.Add($"{checkedPosX},{checkedPosY}"); + } + + } + } + } + return gearCoords; + } + } +} diff --git a/AdventOfCode/_2023/Day4.cs b/AdventOfCode/_2023/Day4.cs new file mode 100644 index 0000000..a082efa --- /dev/null +++ b/AdventOfCode/_2023/Day4.cs @@ -0,0 +1,116 @@ +using AdventOfCode.Common; +using AdventOfCode.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace AdventOfCode._2023 +{ + public class Day4 : AOCDay + { + + protected override AOCResponse ExecutePartA() + { + var cards = new List(); + foreach (var entry in this.GetSplitInput()) + { + cards.Add(new Card(entry)); + } + + foreach (var card in cards) + { + Log($"Card {card.Id} has a score of {card.GenerateScore()}"); + } + + this.Answer = cards.Sum(x => x.GenerateScore()); + return this._response; + } + + protected override AOCResponse ExecutePartB() + { + + var cards = new List(); + foreach (var entry in this.GetSplitInput()) + { + cards.Add(new Card(entry)); + } + + foreach (var card in cards) + { + for (int cardCount = 0; cardCount < card.CardCount; cardCount++) + { + var count = card.GetCountOfWinningNumbers(); + for (int i = 1; i <= count; i++) + { + cards[(card.Id + i)-1].CardCount++; + } + } + } + + this.Answer = cards.Sum(x => x.CardCount); + + return this._response; + } + } + + class Card + { + public int Id { get; set; } + public int CardCount { get; set; } + public List WinningNumbers { get; set; } = new List(); + public List Numbers { get; set; } = new List(); + public Card(string entry) + { + CardCount = 1; + entry = entry.Replace(" ", " ").Replace(" ", " "); //Replace two spaces with 1 + var split = entry.Split(':'); + Id = int.Parse(split[0].Split(' ')[1]); + + //Winning numbers + foreach (var number in split[1].Split('|')[0].Trim().Split(' ')) + { + WinningNumbers.Add(int.Parse(number)); + } + + //Winning numbers + foreach (var number in split[1].Split('|')[1].Trim().Split(' ')) + { + Numbers.Add(int.Parse(number)); + } + } + + public int GenerateScore() + { + var score = 0; + foreach (var number in Numbers) + { + if (WinningNumbers.Contains(number)) + { + if (score == 0) + { + score = 1; + } + else + { + score *= 2; + } + } + } + return score; + } + + public int GetCountOfWinningNumbers() + { + var count = 0; + foreach (var number in Numbers) + { + if (WinningNumbers.Contains(number)) + { + count++; + } + } + return count; + } + } +}