At first this problem concerned me. I started immediately thinking that I’d need to start duplicating the map, but then it dawned on me that I could actually just reset the line point back to the start, retaining the offset that it had gone pass the end of line.
Day 3 Part A
With that in mind, I created the test with the sample input:
[TestMethod]
public void SampleInputTest()
{
// Example Input
var inputMap = new List<string>()
{
"..##.......",
"#...#...#..",
".#....#..#.",
"..#.#...#.#",
".#...##..#.",
"..#.##.....",
".#.#.#....#",
".#........#",
"#.##...#...",
"#...##....#",
".#..#...#.#"
};
var numberOfTreesHit = new Day3PartA().GetNumberOfHits(inputMap, 3, 1);
Assert.AreEqual(7, numberOfTreesHit);
}
(NB. I got lucky with declaring constants for the moves for X and Y in the initial implementation, so ended up altering the signature for Part B for the method – so this code isn’t exactly the same as it was when solving the problem).
Turning to the code itself, I felt it needed to do a couple of things:
- Declare constants for right (X) and down (Y) moves.
- Due to the 1st line not needing to be considered, the last entry could be ignored as this is when the <whatever travelling device> had got through the map.
- Needed to consider when X when passed the end of the line, and do a reset back to the start (applying the offset)
- NB. Subtle change to the for loop coming up, which was originally just incrementing by 1, but subsequently changed to handle the varying down (Y) moves, as part of Part B.
- I was considering if there was an algorithm to just calculate the series of co-ordinates and then retrieve those entries from the line entries – that may be an more efficient approach.
So the code looked very similar to this:
public int GetNumberOfHits(List<string> inputMap, int moveX, int moveY)
{
var numberOfHits = 0;
var currentX = 0;
var currentY = 0;
var numberOfLines = inputMap.Count;
for (int i = 0; i < numberOfLines - 1; i = i + moveY)
{
currentX += moveX;
currentY += moveY;
var mapLine = inputMap[currentY];
if (currentX >= mapLine.Length)
{
currentX -= mapLine.Length;
}
if (mapLine[currentX] == '#')
{
numberOfHits++;
}
}
return numberOfHits;
}
Finally, as ever, another test case to use the file as input, and find out the actual number:
public void FileInputTest()
{
var filename = @"C:\_path_\Day1\Day3\input.txt";
var inputMap = File.ReadAllLines(filename).ToList();
var numberOfTreesHit = new Day3PartA().GetNumberOfHits(inputMap, 3, 1);
Assert.AreEqual(242, numberOfTreesHit);
}
Day 3 Part B
And then I got lucky with the next step. I was able to alter the signature method to take in the varying right (X) and down (Y) parameters and remove my previously declared constants. The only I change I needed to handle was the for loop index incrementing by the value for down (Y).
In order to test though, I used the DataRow method on Test Suites in MSTest, so the next test looked like:
[DataTestMethod]
[DataRow(1, 1, 2)]
[DataRow(3, 1, 7)]
[DataRow(5, 1, 3)]
[DataRow(7, 1, 4)]
[DataRow(1, 2, 2)]
public void ExampleMultipleRuns(int moveX, int moveY, int expectedHits)
{
// Example Input
var inputMap = new List<string>()
{
"..##.......",
"#...#...#..",
".#....#..#.",
"..#.#...#.#",
".#...##..#.",
"..#.##.....",
".#.#.#....#",
".#........#",
"#.##...#...",
"#...##....#",
".#..#...#.#"
};
var numberOfTreesHit = new Day3PartA().GetNumberOfHits(inputMap, moveX, moveY);
Assert.AreEqual(expectedHits, numberOfTreesHit);
}
And the finally was able to do the same test, but using the file for input:
[DataTestMethod]
[DataRow(1, 1, 82)]
[DataRow(3, 1, 242)]
[DataRow(5, 1, 71)]
[DataRow(7, 1, 67)]
[DataRow(1, 2, 24)]
public void FileInputMultipleRuns(int moveX, int moveY, int expectedHits)
{
var filename = @"C:\_path_\Day3\input.txt";
var inputMap = File.ReadAllLines(filename).ToList();
var numberOfTreesHit = new Day3PartA().GetNumberOfHits(inputMap, moveX, moveY);
Assert.AreEqual(expectedHits, numberOfTreesHit);
}