diff --git a/13/PointOfIncidence.Tests/PatternTests.cs b/13/PointOfIncidence.Tests/PatternTests.cs index 5022129..2000d85 100644 --- a/13/PointOfIncidence.Tests/PatternTests.cs +++ b/13/PointOfIncidence.Tests/PatternTests.cs @@ -19,6 +19,15 @@ public class PatternTests result.Should().BeEquivalentTo(expected); } + [Theory] + [MemberData(nameof(TestData.FindPointOfReflectionWithoutSmudgeTestData), MemberType = typeof(TestData))] + public void FindPointOfReflection_GivenAPatternAndCleanSmudgeIsTrue_ItShouldReturnPointOfReflection(string[] input, PointOfReflection expected) + { + var pattern = Pattern.Parse(input); + var result = pattern.FindPointOfReflection(true); + result.Should().BeEquivalentTo(expected); + } + [Fact] public void SummarizePatternNotes_GivenExampleInput_ItShouldReturnExpectedValue() { @@ -49,14 +58,35 @@ public class PatternTests .Split(Environment.NewLine + Environment.NewLine) .Select(p => p.Split(Environment.NewLine)); - var result = patterns + var summaries = patterns .Select(Pattern.Parse) - .Select(p => p.SummarizePatternNotes()) - .Sum(); + .Select(p => p.SummarizePatternNotes()); + + var result = summaries.Sum(); result.Should().Be(33728); } + [Fact] + public void SummarizePatternNotes_GivenInputAndIsPartTwo_ItShouldReturnExpectedValue() + { + var input = File + .ReadAllText("INPUT.txt") + .ReplaceLineEndings(); + + var patterns = input + .Split(Environment.NewLine + Environment.NewLine) + .Select(p => p.Split(Environment.NewLine)); + + var summaries = patterns + .Select(Pattern.Parse) + .Select(p => p.SummarizePatternNotes(true)); + + var result = summaries.Sum(); + + result.Should().Be(28235); + } + public static class TestData { public static IEnumerable ParseTestData => @@ -89,6 +119,97 @@ public class PatternTests } }; + public static IEnumerable FindPointOfReflectionWithoutSmudgeTestData => + new List + { + new object[] + { + new string[] + { + "#...##..#", + "#....#..#", + "..##..###", + "#####.##.", + "#####.##.", + "..##..###", + "#....#..#", + }, + new PointOfReflection + { + Type = ReflectionType.Horizontal, + StartIndex = 0, + EndIndex = 1, + }, + }, + new object[] + { + new string[] + { + "#.##..##.", + "..#.##.#.", + "##......#", + "##......#", + "..#.##.#.", + "..##..##.", + "#.#.##.#.", + }, + new PointOfReflection + { + Type = ReflectionType.Horizontal, + StartIndex = 2, + EndIndex = 3, + } + }, + new object[] + { + new string[] + { + "#..#.#........#", + "#..######..####", + ".##..#.#.##.#.#", + "#..##..........", + "######........#", + "#..####......##", + ".##.##.#...##.#", + }, + new PointOfReflection + { + Type = ReflectionType.Vertical, + StartIndex = 9, + EndIndex = 10, + } + }, + new object[] + { + new string[] + { + "####.##.....###.#", + "#####.......#..#.", + "#####.......#..#.", + "####..#.....###.#", + "#..####..#.#.##.#", + ".##..#..........#", + "######..#.##....#", + "#..####...###...#", + "####..#..###.....", + ".......#.##.##...", + "....##.###.##..#.", + "#..##..###...#.#.", + "....#...#.##...##", + ".##........##....", + ".....#.####..##.#", + "#..#..#..#.#.#..#", + "#######.#.#.#....", + }, + new PointOfReflection + { + Type = ReflectionType.Horizontal, + StartIndex = 1, + EndIndex = 2, + }, + } + }; + public static IEnumerable FindPointOfReflectionTestData => new List { diff --git a/13/PointOfIncidence/Program.cs b/13/PointOfIncidence/Program.cs index 0c67213..9b26656 100644 --- a/13/PointOfIncidence/Program.cs +++ b/13/PointOfIncidence/Program.cs @@ -18,6 +18,7 @@ public class Program return -2; } + var isPart2 = args.Length > 1 && args[1] == "part2"; var input = await File.ReadAllTextAsync(args[0]); var patterns = input @@ -30,7 +31,7 @@ public class Program var result = patterns .Select(Pattern.Parse) - .Sum(p => p.SummarizePatternNotes()); + .Sum(p => p.SummarizePatternNotes(isPart2)); stopwatch.Stop(); @@ -40,105 +41,279 @@ public class Program } } +/// +/// Represents a pattern. +/// +/// The rows of the pattern. +/// The columns of the pattern. +/// A instance. public class Pattern( List> rows, List> columns ) { + /// + /// Gets the rows of the pattern. + /// public List> Rows { get; init; } = rows; + + /// + /// Gets the columns of the pattern. + /// public List> Columns { get; init; } = columns; - public PointOfReflection FindPointOfReflection() + private bool HasSmudge(List row, List otherRow) { - var pointOfReflection = new PointOfReflection(); - var startRowIndex = 0; - var endRowIndex = 1; + var numOfDiffs = 0; - while (endRowIndex < Rows.Count) + for (var i = 0; i < row.Count; i++) { - var startRow = Rows[startRowIndex]; - var endRow = Rows[endRowIndex]; - - if (startRow.SequenceEqual(endRow)) + if (row[i] != otherRow[i]) { - var isHorizontalReflection = true; - var possibleReflectionPoint = new PointOfReflection - { - StartIndex = startRowIndex, - EndIndex = endRowIndex, - Type = ReflectionType.Horizontal, - }; + numOfDiffs++; + } + } - while (startRowIndex > 0 && endRowIndex < Rows.Count - 1) - { - var previousRow = Rows[startRowIndex - 1]; - var nextRow = Rows[endRowIndex + 1]; + return numOfDiffs is 1; + } - if (previousRow.SequenceEqual(nextRow) is false) + private bool CheckForReflection( + int startIndex, + int endIndex, + ReflectionType reflectionType, + out PointOfReflection pointOfReflection + ) + { + List start; + List end; + List> collection; + var isReflection = false; + pointOfReflection = new PointOfReflection(); + + // maintain the original indices for the point of reflection + var originalStartIndex = startIndex; + var originalEndIndex = endIndex; + + // set the start and end points and the collection to check + // based on the reflection type + if (reflectionType == ReflectionType.Horizontal) + { + start = Rows[startIndex]; + end = Rows[endIndex]; + collection = Rows; + } + else + { + start = Columns[startIndex]; + end = Columns[endIndex]; + collection = Columns; + } + + // check if the start and end collections are equal + if (start.SequenceEqual(end)) + { + isReflection = true; + + // move the start and end indices outwards + // until the start index reaches start of the collection + // or the end index reaches the end of the collection + while (startIndex > 0 && endIndex < collection.Count - 1) + { + var previous = collection[startIndex - 1]; + var next = collection[endIndex + 1]; + + // if the previous and next collections + // are not equal then the reflection is + // not complete + if (previous.SequenceEqual(next) is false) + { + isReflection = false; + break; + } + + startIndex--; + endIndex++; + } + } + + // if the reflection is complete + // set the point of reflection + if (isReflection) + { + pointOfReflection = new PointOfReflection + { + StartIndex = originalStartIndex, + EndIndex = originalEndIndex, + Type = reflectionType, + }; + } + + return isReflection; + } + + private bool CheckForReflectionWithoutSmudges( + PointOfReflection originalPointOfReflection, + int startIndex, + int endIndex, + ReflectionType reflectionType, + out PointOfReflection pointOfReflection + ) + { + List start; + List end; + List> collection; + var isReflection = false; + var smudgeCleaned = false; + pointOfReflection = new PointOfReflection(); + + // maintain the original indices for the point of reflection + var originalStartIndex = startIndex; + var originalEndIndex = endIndex; + + // set the start and end points and the collection to check + // based on the reflection type + if (reflectionType == ReflectionType.Horizontal) + { + start = Rows[startIndex]; + end = Rows[endIndex]; + collection = Rows; + } + else + { + start = Columns[startIndex]; + end = Columns[endIndex]; + collection = Columns; + } + + var isNotOriginalPointOfReflection = + ( + startIndex != originalPointOfReflection.StartIndex && + endIndex != originalPointOfReflection.EndIndex + ) || originalPointOfReflection.Type != reflectionType; + + do + { + // check if the start and end collections are equal + // and that they are not the original point of reflection + // or that the smudge has been cleaned + if ((start.SequenceEqual(end) && isNotOriginalPointOfReflection) || smudgeCleaned) + { + isReflection = true; + + // move the start and end indices outwards + // until the start index reaches start of the collection + // or the end index reaches the end of the collection + while (startIndex > 0 && endIndex < collection.Count - 1) + { + var previous = collection[startIndex - 1]; + var next = collection[endIndex + 1]; + + // if the previous and next collections + // are not equal then the reflection is + // not complete + if (previous.SequenceEqual(next) is false) { - isHorizontalReflection = false; - startRowIndex = possibleReflectionPoint.StartIndex; - endRowIndex = possibleReflectionPoint.EndIndex; + // if we can clean the smudge and + // keep reflection intact continue + // checking for reflection at this point + if (HasSmudge(previous, next) && smudgeCleaned is false) + { + smudgeCleaned = true; + startIndex--; + endIndex++; + continue; + } + + isReflection = false; break; } - startRowIndex--; - endRowIndex++; + startIndex--; + endIndex++; } - if (isHorizontalReflection) + smudgeCleaned = false; + } + else + { + // if we can clean the smudge and + // then we need to check for reflection + // at this point + if (HasSmudge(start, end)) { - pointOfReflection = possibleReflectionPoint; - break; + smudgeCleaned = true; + continue; } } + } while (smudgeCleaned); + + // if the reflection is complete + // set the point of reflection + if (isReflection) + { + pointOfReflection = new PointOfReflection + { + StartIndex = originalStartIndex, + EndIndex = originalEndIndex, + Type = reflectionType, + }; + } + + return isReflection; + } + + /// + /// Finds the point of reflection. + /// + /// Indicates whether to clean smudges. + /// A instance at which the reflection occurs. + public PointOfReflection FindPointOfReflection(bool cleanSmudge = false) + { + var orgPointOfReflection = new PointOfReflection(); + + var startRowIndex = 0; + var endRowIndex = 1; + + // check for horizontal reflection + while (endRowIndex < Rows.Count) + { + var isReflection = CheckForReflection( + startRowIndex, + endRowIndex, + ReflectionType.Horizontal, + out var reflectionPoint + ); + + if (isReflection) + { + orgPointOfReflection = reflectionPoint; + break; + } startRowIndex++; endRowIndex++; } - if (pointOfReflection.Type == ReflectionType.None) + // if no horizontal reflection was found + if (orgPointOfReflection.Type == ReflectionType.None) { var startColumnIndex = 0; var endColumnIndex = 1; + // check for vertical reflection while (endColumnIndex < Columns.Count) { - var startColumn = Columns[startColumnIndex]; - var endColumn = Columns[endColumnIndex]; + var isReflection = CheckForReflection( + startColumnIndex, + endColumnIndex, + ReflectionType.Vertical, + out var reflectionPoint + ); - if (startColumn.SequenceEqual(endColumn)) + if (isReflection) { - var isVerticalReflection = true; - var possibleReflectionPoint = new PointOfReflection - { - StartIndex = startColumnIndex, - EndIndex = endColumnIndex, - Type = ReflectionType.Vertical, - }; - - while (startColumnIndex > 0 && endColumnIndex < Columns.Count - 1) - { - var previousColumn = Columns[startColumnIndex - 1]; - var nextColumn = Columns[endColumnIndex + 1]; - - if (previousColumn.SequenceEqual(nextColumn) is false) - { - isVerticalReflection = false; - startColumnIndex = possibleReflectionPoint.StartIndex; - endColumnIndex = possibleReflectionPoint.EndIndex; - break; - } - - startColumnIndex--; - endColumnIndex++; - } - - if (isVerticalReflection) - { - pointOfReflection = possibleReflectionPoint; - break; - } + orgPointOfReflection = reflectionPoint; + break; } startColumnIndex++; @@ -146,26 +321,99 @@ public class Pattern( } } - return pointOfReflection; + // if we are not factoring in smudges + // return the original point of reflection + if (cleanSmudge is false) + { + return orgPointOfReflection; + } + + var newPointsOfReflection = new List(); + startRowIndex = 0; + endRowIndex = 1; + + // check for horizontal reflection + // without smudges + while (endRowIndex < Rows.Count) + { + var isReflection = CheckForReflectionWithoutSmudges( + orgPointOfReflection, + startRowIndex, + endRowIndex, + ReflectionType.Horizontal, + out var reflectionPoint + ); + + if (isReflection) + { + newPointsOfReflection.Add(reflectionPoint); + break; + } + + startRowIndex++; + endRowIndex++; + } + + // if no horizontal reflection was found + // without smudges or the original point of reflection + // was found again + if (newPointsOfReflection.Count is 0 || newPointsOfReflection[0].Equals(orgPointOfReflection)) + { + var startColumnIndex = 0; + var endColumnIndex = 1; + + // check for vertical reflection + // without smudges + while (endColumnIndex < Columns.Count) + { + var isReflection = CheckForReflectionWithoutSmudges( + orgPointOfReflection, + startColumnIndex, + endColumnIndex, + ReflectionType.Vertical, + out var reflectionPoint + ); + + if (isReflection) + { + newPointsOfReflection.Add(reflectionPoint); + break; + } + + startColumnIndex++; + endColumnIndex++; + } + } + + // return the first point of reflection + // that is not the original point of reflection + return newPointsOfReflection.First( + p => p.Equals(orgPointOfReflection) is false + ); } - public long SummarizePatternNotes() + /// + /// Summarizes the pattern notes. + /// + /// Indicates whether to clean smudges. + /// A number that represents the summary of the pattern notes. + public long SummarizePatternNotes(bool cleanSmudge = false) { - var pointOfReflection = FindPointOfReflection(); + var pointOfReflection = FindPointOfReflection(cleanSmudge); - if (pointOfReflection.Type is ReflectionType.None) + return pointOfReflection.Type switch { - return 0; - } - - if (pointOfReflection.Type is ReflectionType.Vertical) - { - return pointOfReflection.StartIndex + 1; - } - - return (pointOfReflection.StartIndex + 1) * 100; + ReflectionType.Horizontal => (pointOfReflection.StartIndex + 1) * 100, + ReflectionType.Vertical => pointOfReflection.StartIndex + 1, + _ => 0, + }; } + /// + /// Parses the input into a instance. + /// + /// The input to parse. + /// A instance. public static Pattern Parse(string[] input) { var columns = new List>(); @@ -197,16 +445,74 @@ public class Pattern( } } -public class PointOfReflection +/// +/// Represents a point of reflection +/// +public class PointOfReflection : IEquatable { - public ReflectionType Type = ReflectionType.None; + /// + /// Gets or sets the type of the reflection. + /// + public ReflectionType Type { get; set; } = ReflectionType.None; + + /// + /// Gets or sets the start index of the reflection. + /// public int StartIndex { get; set; } = -1; + + /// + /// Gets or sets the end index of the reflection. + /// public int EndIndex { get; set; } = -1; + + /// + /// Indicates whether the current instance is equal to another instance. + /// + /// An instance of to compare with this instance. + /// true if the current instance is equal to the other parameter; otherwise, false. + public bool Equals(PointOfReflection? other) => + other is not null && + Type == other.Type && + StartIndex == other.StartIndex && + EndIndex == other.EndIndex; + + /// + /// Determines whether the specified object is equal to the current object. + /// + /// The object to compare with the current object. + /// true if the specified object is equal to the current object; otherwise, false. + public override bool Equals(object? obj) + { + return Equals(obj as PointOfReflection); + } + + /// + /// Returns the hash code for this instance. + /// + /// An integer that is the hash code for this instance. + public override int GetHashCode() + { + return HashCode.Combine(Type, StartIndex, EndIndex); + } } +/// +/// Represents the type of reflection +/// public enum ReflectionType { + /// + /// Reflection that occurs on the horizontal axis + /// Horizontal, + + /// + /// Reflection that occurs on the vertical axis + /// Vertical, + + /// + /// No reflection + /// None, } \ No newline at end of file