ADVENT OF CODE 2020–CSHARP – DAY 6

Day 6 Part A

Once you decode the problem, it essentially comes down to the number of unique answered questions within a group. Easiest solution (to be of my knowledge here) is to a use a HashSet, which will only allow unique values in.

Following the pattern to an earlier puzzle in the week, essentially a loop to store the answers and then add to the counter when a blank line is found. Also, note to handle the final line which probably isn’t ended with a blank line. Again, might be a nicer way to do this with LINQ.

So onto the Tests (always start with tests! – whether solving problems in spare time, or on paid time):

     [TestMethod]
     public void ExampleInput()
     {
         var inputList = new List<string>()
         {
             "abc", "", "a", "b", "c", "", "ab", "ac", "", 
              "a", "a", "a", "a", "", "b"
         };

         int sumOfCounts = new Day6PartA().GetSumOfAnswers(inputList);
         Assert.AreEqual(11, sumOfCounts);
     }

     [TestMethod]
     public void FileInput()
     {
         var filename = @"C:\_path_\Day6\input.txt";
         var inputList = File.ReadAllLines(filename).ToList();
         int sumOfCounts = new Day6PartA().GetSumOfAnswers(inputList);
         Assert.AreEqual(6443, sumOfCounts);
     }

The implementation then follows as this:

     public int GetSumOfAnswers(List<string> input)
     {
         int sum = 0;
         HashSet<char> charactersFound = new HashSet<char>();

         foreach (var line in input)
         {
             if (line.Trim().Length == 0)
             {
                 // End of processing, so process here:
                 sum += charactersFound.Count;
                 charactersFound = new HashSet<char>();
             }
             else
             {
                 // Need to process this line, 
                 // splitting into characters and then adding to the set                 
                 // Set should handle not adding duplicates automatically.                 
                 // Could probably do this with LINQ.
                 for (int i = 0; i < line.Length; i++)
                 {
                     charactersFound.Add(line[i]);
                 }
             }
         }

         // Don't forgot the final line!
         sum += charactersFound.Count;
         return sum;
     }

Day 6 Part B

So Part B is all about finding the consistent entries from the entire group, which makes it a little more interesting. With a few other commitments I quickly churned the following solution out, but it felt a bit over worked and I thought there might be a nicer way.

     public int GetAnswersGivenByEveryoneInGroup(List<string> input)
     {
         int sum = 0;
         List<string> tempCollection = new List<string>();

         foreach (var line in input)
         {
             if (line.Trim().Length == 0)
             {
                 if (tempCollection.Count == 1)
                 {
                     sum += tempCollection[0].Length;
                 }
                 else
                 {
                     Dictionary<char, int> countOfChars = new Dictionary<char, int>();
                     foreach (var tempLine in tempCollection)
                     {
                         for (int i = 0; i < tempLine.Length; i++)
                         {
                             var currentValue = countOfChars.GetValueOrDefault(tempLine[i], 0);
                             countOfChars[tempLine[i]] = ++currentValue;
                         }
                     }

                     foreach (var entry in countOfChars)
                     {
                         if (entry.Value == tempCollection.Count)
                         {
                             sum++;
                         }
                     }
                 }
                 tempCollection = new List<string>();
             }
             else
             {
                 tempCollection.Add(line);
             }
         }

         if (tempCollection.Count == 1)
         {
             sum += tempCollection[0].Length;
         }
         else
         {
             Dictionary<char, int> countOfChars = new Dictionary<char, int>();

             foreach (var tempLine in tempCollection)
             {
                 for (int i = 0; i < tempLine.Length; i++)
                 {
                     var currentValue = countOfChars.GetValueOrDefault(tempLine[i], 0);
                     countOfChars[tempLine[i]] = ++currentValue;
                 }
             }

             foreach (var entry in countOfChars)
             {
                 if (entry.Value == tempCollection.Count)
                 {
                     sum++;
                 }
             }
         }
         return sum;
     }

So it works, but there are loops within loops within loops, and its quite length (ok, I could refactor out to helper methods by still).

After a little break I came back to it, and came up with the following solution which only adds consistently appearing characters from each pass, and I think reads a bit better:

     public int GetAnswersGivenByEveryoneInGroupRefactor(List<string> input)
     {
         int sum = 0;
         string buildingList = string.Empty;
         var isFirstPass = true;

         foreach (var line in input)
         {
             if (line.Trim().Length == 0)
             {
                 sum += buildingList.Length;
                 buildingList = string.Empty;
                 isFirstPass = true;
             }
             else
             {
                 if (isFirstPass)
                 {
                     buildingList = line;
                     isFirstPass = false;
                 }
                 else
                 {
                     var tempNewList = new StringBuilder();

                     for (int i = 0; i < line.Length; i++)
                     {
                         if (buildingList.Contains(line[i]))
                         {
                             tempNewList.Append(line[i]); 
                         }
                     }

                     buildingList = tempNewList.ToString();
                 }
             }
         }

         sum += buildingList.Length;
         return sum;
     }

But I think this can still be improved on, using something like HashSet or Intersect. Indeed, I carried on for a bit longer and refactored to use LINQ Intersect, which now makes the code read as:

     public int GetAnswersGivenByEveryoneInGroupRefactor(List<string> input)
     {
         int sum = 0;
         string buildingList = string.Empty;
         var isFirstPass = true;

         foreach (var line in input)
         {
             if (line.Trim().Length == 0)
             {
                 sum += buildingList.Length;
                 buildingList = string.Empty;
                 isFirstPass = true;
             }
             else
             {
                 if (isFirstPass)
                 {
                     buildingList = line;
                     isFirstPass = false;
                 }
                 else
                 {
                     var buildingListArray = buildingList.ToCharArray();
                     var lineArray = line.ToCharArray();
                     var intersect = buildingListArray.Intersect(lineArray).ToArray();
                     buildingList = String.Join("", intersect);
                 }
             }
         }
         sum += buildingList.Length;
         return sum;
     }

I did look at if it would be possible drop the ‘isFirstPass’ boolean, but removing it stopped things from passing. On reflection I suspect its because there is a chance the ‘buildingList’ parameters empties through processing.

ADVENT OF CODE 2020–CSHARP – DAY 5

Day 5 Part A

So once overcoming the initial issue of understanding the problem, this is essentially removing either the top half of a range, or the bottom half, depending on the next value in the string.

I started by keeping things simple, and building confidence, so implemented 2 tests for determining Rows and Columns. This might have ended up being overkill for the final solution, and these methods wouldn’t need to be public for the longer term, but as a starting point it seemed sensible.

     [DataTestMethod]
     [DataRow("FBFBBFF", 44)]
     [DataRow("BFFFBBF", 70)]
     [DataRow("FFFBBBF", 14)]
     [DataRow("BBFFBBF", 102)]
     public void TestForRows(string input, int expectedRowId)
     {
         var rowId = new Day5PartA().GetRow(input);
         Assert.AreEqual(expectedRowId, rowId);
     }

     [DataTestMethod]
     [DataRow("RLR", 5)]
     [DataRow("RRR", 7)]
     [DataRow("RRR", 7)]
     [DataRow("RLL", 4)]
     public void TestForColumns(string input, int expectedColumnId)
     {
         var rowId = new Day5PartA().GetColumn(input);
         Assert.AreEqual(expectedColumnId, rowId);
     }

The 2 methods are identical, and could be refactored into a single method, but I decided to leave them as-is for now (a general tendency I see in S/W development is an over eagerness to implemented generic methods, which then quickly become convoluted with if…else.. and other not required complexity). I tend to stick to the principle of 3 or 5 – once its been implemented that many times, then refactor and only the common pieces.

     public int GetRow(string input)
     {
         int lowerBound = 0;
         int upperBound = 127;

         for (int i = 0; i < input.Length; i++)
         {
             var diff = (upperBound - lowerBound + 1) / 2;

             if (input[i] == 'F')
             {
                 upperBound -= diff;
             }
             else
             {
                 lowerBound += diff;
             }
         }

         if (input[6] == 'F')
         {
             return lowerBound;
         }
         return upperBound;
     }

     public int GetColumn(string input)
     {
         int lowerBound = 0;
         int upperBound = 7;

         for (int i = 0; i < input.Length; i++)
         {
             var diff = (upperBound - lowerBound + 1) / 2;

             if (input[i] == 'L')
             {
                 upperBound -= diff;
             }
             else
             {
                 lowerBound += diff;
             }
         }

         if (input[2] == 'L')
         {
             return lowerBound;
         }

         return upperBound;
     }

With in place, the GetSeatId method is a simple call of to these 2 methods and then doing the calculation:

     public int GetSeatId(string input)
     {
         var row = GetRow(input.Substring(0, 7));
         var column = GetColumn(input.Substring(7));

         return (row * 8) + column;
     }

Day 5 Part B

The hardest part of this question was working out what the question actually was. Finally I figured out it was essentially saying the range of Seat IDs is sequential, and there is a Seat ID missing – find it. NB. The range does not start at zero, so you need to handle that.

There are a few ways to implement this, but personally I find the following implementation using LINQ simplest to read (which is the important aspect of code, IMHO).

     public int MissingSeatId(List<int> seatInput)
     {
         var lowestSeatId = seatInput.Min();
         var highestSeatId = seatInput.Max();
         var missingSeatIds = Enumerable.Range(lowestSeatId, highestSeatId).Except(seatInput);

         return missingSeatIds.ElementAt(0);
     }

ADVENT OF CODE 2020–CSHARP – DAY 4

So onto Day 4, and a little bit quicker today, just because it’s a Friday.

Day 4 Part A

I’m sure there must be a nicer way to do this, but went for quick and dirty and implemented something with this high level thinking:

  • Process each line and store parameters into an object. Object relatively simple as a Dictionary, but only allow addition of ‘Valid’ keys – and cid is not a ‘valid’ key in the current implementation.
  • Need to handle the splitting of parameters on each line with a space initially (but being wary of lines with just a single entry, i.e. no space). And then split those parameters on the ‘:’.
  • On a blank line being reached, check validity of the object just being processed.
  • Also need to handle the last entry in the file, which probably won’t have a blank line to declare the end of its processing.
  • Finally, by only allowing ‘valid’ values into the Dictionary, a simple check of Length == 7 should suffice for whether the overall passport is valid.

As such the ‘supporting’ object looks like this:

public class PassportInfo {
     private Dictionary<string, string> PassportDictionary;
     private readonly List<string> ValidKeys = new List<string>()
     {
         "byr",
         "iyr",
         "eyr",
         "hgt",
         "hcl",
         "ecl",
         "pid"
     };

     public PassportInfo()
     {
         PassportDictionary = new Dictionary<string, string>();
     }

     public void UpdateInfo(string key, string value)
     {
         if (value.Length > 0 && ValidKeys.Contains(key))
         {
             PassportDictionary.Add(key, value);
         }
     }

     public bool IsValid()
     {
         if (PassportDictionary.Count == 7)
         {
             return true;
         }
         return false;
     }
}

And then the main method to parse the file looks like this:

     public int GetNumberOfValidPassports(List<string> passportFile)
     {
         var validPassportsCount = 0;
         PassportInfo passportInfo = new PassportInfo();
         foreach (var line in passportFile)
         {
             if (line.Trim().Length == 0)
             {
                 if (passportInfo.IsValid())
                 {
                     validPassportsCount++;
                 }
                 // Reset passportInfo.
                 passportInfo = new PassportInfo();
             }
             else
             {
                 string[] comps = new string[1];
                 comps[0] = line;
                 if (line.Contains(' '))
                 {
                     comps = line.Split(' ');
                 }

                 for (int i = 0; i < comps.Length; i++)
                 {
                     string[] keyValuePair = comps[i].Split(':');
                     passportInfo.UpdateInfo(keyValuePair[0], keyValuePair[1]);
                 }
             }
         }

         if (passportInfo.IsValid())
         {
             validPassportsCount++;
         }

         return validPassportsCount;
     }

I’ll skip over posting the supporting tests as they’re very similar to previous days.

Day 4 Part B

Truly horrible implementation this one, but its getting a little late. Got lucky by introducing the PassportInfo class in Part A, so it was really fleshing out the Adding to the Dictionary in the case of valid values being determined.

A couple of things I’d consider to improve:

  • Extracting each of the methods and Unit Testing to ensure they meet the requirements.
  • Look at C# Pattern Matching Syntax (in fact, decided to be less lazy and did use this implementation).
  • Other thing considered but not implemented was short-circuiting the processing of the passport on discovery of the 1st invalid piece of data.

But still, its got me past today’s challenge, and wraps up the working week.

     public void UpdateInfo(string key, string value)
     {
         if (ValidKeys.Contains(key))
         {
             switch (key)
             {
                 case "byr":
                     int tempBirthYear = Int16.Parse(value);
                     if (tempBirthYear >= 1920 && tempBirthYear <= 2002)
                     {
                         PassportDictionary.Add(key, value);
                     }
                     break;

                 case "iyr":
                     int tempIssueYear = Int16.Parse(value);
                     if (tempIssueYear >= 2010 && tempIssueYear <= 2020)
                     {
                         PassportDictionary.Add(key, value);
                     }
                     break;

                 case "eyr":
                     int tempExpiryYear = Int16.Parse(value);
                     if (tempExpiryYear >= 2020 && tempExpiryYear <= 2030)
                     {
                         PassportDictionary.Add(key, value);
                     }
                     break;

                 case "hgt":
                     if (value.ToLower().EndsWith("cm"))
                     {
                         var tempValue = value.Replace("cm", "");
                         int height = Int16.Parse(tempValue);
                         if (height >= 150 && height <= 193)
                         {
                             PassportDictionary.Add(key, value);
                         }
                     }
                     else if (value.ToLower().EndsWith("in"))
                     {
                         var tempValue = value.Replace("in", "");
                         int height = Int16.Parse(tempValue);
                         if (height >= 59 && height <= 76)
                         {
                             PassportDictionary.Add(key, value);
                         }
                     }
                     break;

                 case "hcl":
                     if (value.StartsWith("#") && value.Length == 7)
                     {
                         var tempValue = value.Substring(1);
                         if (tempValue.All(c => "0123456789abcdef".Contains(c)))
                         {
                             PassportDictionary.Add(key, value);
                         }
                     }
                     break;

                 case "ecl":
                     if (ValidEyeColourList.Contains(value))
                     {
                         PassportDictionary.Add(key, value);
                     }
                     break;

                 case "pid":
                     if (value.Length == 9 && Int32.TryParse(value, out _))
                     {
                         PassportDictionary.Add(key, value);
                     }
                     break;
             }
         }
     }

ADVENT OF CODE 2020–CSHARP – DAY 3

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);
     }

ADVENT OF CODE 2020–CSHARP – DAY 2

So Day 2 was a ‘password’ issue. Again it really boiled down to parsing a list of strings, separating the parts and checking the ‘validity of the password’.

Day 2 Part A

So again, starting with a test method to prove the example, the test code looks like this:

[TestMethod]
public void DetectedInvalidPasswords_List()
{
         List<string> inputList = new List<string>()
         {
             "1-3 a: abcde",
             "1-3 b: cdefg",
             "2-9 c: ccccccccc"
         };

         var invalidPasswordEntries = new Day2PartA().GetCountOfInvalidPasswords(inputList);            
         Assert.AreEqual(1, invalidPasswordEntries);
}

The processor for Invalid Password code was a simple splitting of the components, initially using <space> and that allowed me to get the constitute parts (I’d probably introduce some kind of object at this point to store the parts, but was lazy here).

Finally it was using linq to check how often the character appeared in the string, before then checking it was within the tolerances of the min and max values.

public int GetCountOfInvalidPasswords(List<string> inputList)
{
    var invalidCounter = 0;
     foreach (var entry in inputList)
     {
         var comps = entry.Split(' ');
         var occurances = comps[0].Split('-');

         int minOccurances = Int16.Parse(occurances[0]);
         int maxOccurances = Int16.Parse(occurances[1]);
         char requiredChar = comps[1][0];
         var password = comps[2];

         int charAppears = password.Count(c => c == requiredChar);

         if (!((charAppears >= minOccurances) && (charAppears <= maxOccurances)))
         {
             invalidCounter++;
         }
     }
     return invalidCounter;
}

Finally, introduced a test to process the file input as opposed to the list provided as an example:

[TestMethod]
public void DetectedInvalidPasswords_File()
{
    var filename = @"C:\_path_to_\AdventOfCode2020CS\Day1\Day2\input.txt";
    var lines = File.ReadAllLines(filename).ToList();

    var invalidPasswordEntries = new Day2PartA().GetCountOfInvalidPasswords(lines);

    Assert.AreEqual(500, invalidPasswordEntries);
}

Day 2 Part B

The next part changed the rule slightly, looking for characters in the ‘correct’ positions, helpfully highlighting that things were not zero-index.

So introducing a next test case for the example provided:

     [TestMethod]
     public void DetectedValidPasswords_List()
     {
         List<string> inputList = new List<string>()
         {
             "1-3 a: abcde",
             "1-3 b: cdefg",
             "2-9 c: ccccccccc"
         };

         var validPasswordEntries = new Day2PartB().GetCountOfValidPasswords(inputList);

         Assert.AreEqual(1, validPasswordEntries);
     }

The method to process the entries altered slightly – still using the same extractor, string splitting methods with minor renaming of variables. It was then use of the logical OR in C# to check that only one of the positions were set to the character provided, remembering to handle the non-zero index.

     public int GetCountOfValidPasswords(List<string> inputList)
     {
         var validCounter = 0;
         foreach (var entry in inputList)
         {
             var comps = entry.Split(' ');
             var occurances = comps[0].Split('-');

             int position1 = Int16.Parse(occurances[0]);
             int position2 = Int16.Parse(occurances[1]);
             char requiredChar = comps[1][0];
             var password = comps[2];

             if ((password[position1 - 1] == requiredChar) ^ (password[position2 - 1] == requiredChar))
             {
                 validCounter++;
             }
         }

         return validCounter;
     }

And then finally the test case to pass in the file:

     [TestMethod]
     public void DetectedValidPasswords_File()
     {
         var filename = @"C:\_path_to_\AdventOfCode2020CS\Day1\Day2\input.txt";
         var lines = File.ReadAllLines(filename).ToList();

         var validPasswordEntries = new Day2PartB().GetCountOfValidPasswords(lines);

         Assert.AreEqual(313, validPasswordEntries);
     }

Advent of code 2020–CSharp – Day 1

Almost as traditional as the FetchEveryone Advent Calendar, is the Advent of Code challenge each year. (#adventofcode)

This year though I’m going to try and sprinkle some additional learning in alongside it. (These things may not be new to you reader, but they’re likely to be things I’ve heard in passing but not tinkered with).

Spoiler Alert

Below I’ll start to (in draft form) outline some of my thought process whilst solving the problems, as well as the solution (if I manage to get there!). There are likely to be better, more efficient solutions – if you play and read along, and can see a better approach, please leave a comment.

So without further ado:

Day 1 Part A

So this problem is essentially finding two values in a list that sum up to 2020. And then multiplying them together.

The simple solution in my mind was to go for 2 lists, inside each other, but I was concerned about the performance of a nested loop. However, 1st step is to get something working.

So I created a .NET Core Console App, and then added a MSTest project to the solution.

I then focused on part 1 of the problem which was to locate the 2 numbers in the list (which is a little overkill for this challenge, but I always focus on breaking things down to their simplest components). As such test 1 looks like this:

[TestMethod]
public void Find2020SumParts_Local()
{
     List<int> inputList = new List<int>
     {
         1721, 979, 366, 299, 675, 1456
     };
     Day1PartA sut = new Day1PartA();
     var values = sut.GetNumbersSummingTo2020(inputList);
     Assert.AreEqual(2020, values.Item1 + values.Item2);
     Assert.AreEqual(299, values.Item1);
     Assert.AreEqual(1721, values.Item2);
} 

So implementing a nested for loop for the GetNumbersSummingTo2020() method looked like this:

public Tuple<int, int> GetNumbersSummingTo2020_Dirty(List<int> inputList)
{
     for (int i = 0; i < inputList.Count; i++)
     {
         for (int j = i + 1; j < inputList.Count; j++)
         {
             if (inputList[i] + inputList[j] == 2020)
             {
                 if (inputList[i] < inputList[j])
                 {
                     return Tuple.Create(inputList[i], inputList[j]);
                 }
                 return Tuple.Create(inputList[j], inputList[i]);
             }
         }
     }
     return Tuple.Create(0, 0);
}

A few minor things to point out here:

  • The use of C# Tuples which was introduced with C# 7 to return more than one parameter from a method.
  • Why +1 in the nested loop? Well, the solution asked for the 2 parameters that added up to 2020. If entry 1 had been 1010, then the nested loop would have returned the same 1st parameter, which I felt was incorrect behaviour.
  • Returning 0,0 at the end is a little poor – should really through an Exception or similar, but still.
  • The test for returning these values was a bit of a pain in hindsight – I had to introducing extra code to make sure the smaller value was retuned in Slot 1 of the Tuple.

And running this worked. I then introduced a second test method which read in the data from the file provided:

[TestMethod]
public void Find2020SumParts_FromFile()
{
     var filename = @"C:_path_to_file_\Day1\input.txt";
     var lines = File.ReadAllLines(filename);
     var inputList = lines.Select(int.Parse).ToList();
     Day1PartA sut = new Day1PartA();
     var values = sut.GetNumbersSummingTo2020(inputList);
     Assert.AreEqual(2020, values.Item1 + values.Item2);
     Assert.AreEqual(399, values.Item1);
     Assert.AreEqual(1621, values.Item2);
} 

And again this past and got me through Part A of Day 1.

However, I thought there must be a nicer way. A few thoughts included:

  • Sorting the list, adding the 1st number to the last number, and then decrementing down the list until the sum was below 2020, and then moving to the next number from the start.
  • Using just one for loop and removing the entry from 2020 and checking if the list contained the remaining value. (NB. This would break in the example I had catered for above, where a single value of 1010 could be returned – an additional check would need to be added).
  • There was probably a single liner with LINQ that might have solved the problem, but I didn’t investigate this.

Ultimately I implemented both, but not before experimenting with Benchmark.NET. I’ll refer to Steve Gordon’s excellent introductory post rather than duplicating material here.

So the sorted code is here:

 public Tuple<int, int> GetNumbersSummingTo2020_Sorted(List<int> inputList)
{
     // Sort list
     // do loop for front and back, and bail-out when sum is less than 2020
     inputList.Sort();
     for (int i = 0; i < inputList.Count; i++)
     {
         for (int j = inputList.Count - 1; j >= 0; j--)
         {
             var tempResult = inputList[i] + inputList[j];
             if (tempResult == 2020)
             {
                 return Tuple.Create(inputList[i], inputList[j]);
             }
             else if (tempResult < 2020)
             {
                 break;
             }
         }
     }
     return Tuple.Create(0, 0);
} 

And the single for loop example is here:

 public Tuple<int, int> GetNumbersSummingTo2020(List<int> inputList)
{
     inputList.Sort();
     for (int i = 0; i < inputList.Count; i++)
     {
         var remainder = 2020 - inputList[i];
         if (inputList.Contains(remainder))
         {
             if (remainder < inputList[i])
             {
                 return Tuple.Create(remainder, inputList[i]);
             }
             return Tuple.Create(inputList[i], remainder);
         }
     }
     return Tuple.Create(0, 0);
} 

Using Benchmark.NET, there was nothing really between the 3 implementations (using the file-based input), but the single for-loop example (with the unnecessary Sort() call) performed slightly better.

Day 1 Part B

Part B for Day 1 grew the problem slightly by asking for the 3 values, as opposed to just 2.

Nothing significantly different. I stuck with the remainder concept, and re-introduced the nested for loop.

Code looks like this:

public Tuple<int, int, int> GetNumbersSummingTo2020(List<int> inputList)
{
     for (int i = 0; i < inputList.Count; i++)
     {
         var remainder = 2020 - inputList[i];
         for (int j = i + 1; j < inputList.Count; j++)
         {
             if (inputList[j] < remainder)
             {
                 var finalRemainder = remainder - inputList[j];
                 if (inputList.Contains(finalRemainder))
                 {
                     return Tuple.Create(inputList[i], inputList[j], finalRemainder);
                 }
             }
         }
     }
     return Tuple.Create(0, 0, 0);
}

VISUAL STUDIO, DOCKER and immediate exit in Docker-compose file

This is a quick note for myself as much as anything else. In recent weeks I’ve started using a .NET Core Web App with Docker support option in Visual Studio during project creation. I’ve probably not read the documentation so fully accept I’m probably doing something wrong – now I’ve diagnosed what the issue is, I’ll see if the docs provide guidance as to what I’m doing wrong!

Whilst running within Visual Studio and Docker everything seems fine. But once I start to use the image within a docker-compose file the container starts and exits straight away. Initially I thought this was due to it struggling to get a connection to a RabbitMQ instance I was using. Eventually I removed all that code and still experienced the same behaviour.

I’ve finally determined that the application files were not being copied to the /app folder at some point during the DOCKERFILE execution. (Steps for how to determine this follow).

In order get round this from the command line, at the same level as the .csproj file I run the following command:

docker build -f Dockerfile ..

To determine this fault (and/or others for immediate exit of containers) its possible to run a container interactively and allow yourself a chance to look around the internals of the container and the file system:

docker run –it <image-name>

By navigating to the /app folder and running ‘ls’ I was able to see it was empty.

RABBITMQ, .NET CORE, DOCKER–Sending and receiving a message

This is blog post 3 of a short series on RabbitMQ, Docker and .NET Core. This post will focus on extending the previously created web application to connect and send a message to the RabbitMQ instance. We’ll then create a simple Console App that will read messages from the RabbitMQ instance.

Extend the .NET Web Application to Send A Message

1. Open the RabbitMQ Web Project previously created and we’ll create a new controller – namely RabbitMQController. To do this, right click on the ‘Controllers’ folder and select ‘Add Controller…’. From the dialog select API – Empty and give it a name of ‘RabbitMQController’.

2. When the controller has been created, we need to use Dependency Injection to provide the configuration so we can read the appropriate RabbitMQ address to connect to.

The code should look similar to the below (NB. type ctor[tab][tab] to automagically create the constructor signature for you).

private readonly IConfiguration Configuration;

public RabbitMQController(IConfiguration configuration)
{
   Configuration = configuration;
}
  1. Next we need to create a method which will:
    1. Create the connection to RabbitMQ
    2. Create the Queue to send to (to ensure it exists)
    3. Finally create a message and send. (NB. This particular code isn’t best practice, but should suffix to prove everything is working – we’ll comeback and refactor afterwards).

The code should look similar to the below:

        public void SendMessage()
        {
            var hostname = Configuration["rabbitMQHost"];
            var factory = new ConnectionFactory() { HostName = hostname };
            using var connection = factory.CreateConnection();
            using var channel = connection.CreateModel();

            channel.QueueDeclare(queue: "vle",
                                 durable: false,
                                 exclusive: false,
                                 autoDelete: false,
                                 arguments: null);

            string message = "Create module";
            var body = Encoding.UTF8.GetBytes(message);

            channel.BasicPublish(exchange: "",
                                 routingKey: "vle",
                                 basicProperties: null,
                                 body: body);
        }

4. Next we need to redeploy this within our docker-compose file, so we need to add an extra environment variable for the ‘rabbitMQHost’ value above.

  web:
    image: "rabbitmqweb:dev"
    environment:
      - rabbitconnstr=amqp://guest:guest@rabbitmq:5672
      - rabbitMQHost=rabbitmq
    ports:
      - "80:80"
      - "443:443"
    networks:
      - mynetwork

With this in place, we should be good to run:

docker-compose up -d

And we can navigate to http://localhost/api/RabbitMQ

Provided no errors are thrown, we should be able to go to the Management Console for RabbitMQ (localhost:15672) and log in with guest/guest. If we then select Queues, we should see our new queue (VLE) and it should have 1 message in it.

Create A Console App to Consume The Messages

Now open a new Visual Studio and Create a New C# Console Application.

newconsoleapp

Give the project a name of RabbitMQListener.

Next add the RabbitMQ.Client NuGet package.

Then in Main.cs add the following code:

            var factory = new ConnectionFactory() { HostName = "localhost" };
            using var connection = factory.CreateConnection();
            using var channel = connection.CreateModel();

            channel.QueueDeclare(queue: "vle",
                durable: false,
                exclusive: false,
                autoDelete: false,
                arguments: null);

            var consumer = new EventingBasicConsumer(channel);
            consumer.Received += (model, ea) =>
            {
                var body = ea.Body;
                var message = Encoding.UTF8.GetString(body);
                Console.WriteLine(" [x] Received {0}", message);
            };
            channel.BasicConsume(queue: "vle",
                                 autoAck: true,
                                 consumer: consumer);

            Console.WriteLine(" Press [enter] to exit.");
            Console.ReadLine();

Now run the Console Application. When it starts you should immediately see it output that it received the ‘Create Module’ message from earlier.

With the console app now running, also head to the Web App /api/RabbitMQ address and start reloading the page – you should see the Console App output the message being received.

received_messages

Tidying Up The Web Application Sending

Now we have everything working end-to-end, we can come back to the Sending API in the Web Application and instead of declaring the connection each time its called, we can re-use Dependency Injection to pass in the connection from the health check we already have configured.

The code in the RabbitMQ Controller constructor can change as follows (no need to pass the Configuration object anymore):

        private readonly IConnection RabbitMQConnection;

        public RabbitMQController(IConnection connection)
        {
            RabbitMQConnection = connection;
        }

Next the SendMessage() method opening can be simplified to the following:


        public void SendMessage()
        {
            using var channel = RabbitMQConnection.CreateModel();

            channel.QueueDeclare(queue: "vle",
                                 durable: false,
                                 exclusive: false,
                                 autoDelete: false,
                                 arguments: null);

            string message = "Create module";
            var body = Encoding.UTF8.GetBytes(message);

            channel.BasicPublish(exchange: "",
                                 routingKey: "vle",
                                 basicProperties: null,
                                 body: body);
        }

RabbitMQ and .NET Core Health Checks

This is blog post 2 of a short series on RabbitMQ, Docker and .NET Core. This post will focus on creating a .NET Core MVC application that has a dependency on the RabbitMQ installation – and we’ll use ASP.NET Core Health Checks to monitor the status of RabbitMQ.

1. Create the ASP.NET Core MVC Application

1.1 Start Visual Studio – create a new C# .NET Core project with Web Application (MVC). Also tick Add Docker Support and select Linux.

1.2 Start the web application to make sure everything was created as expected.

2. Implementing Health Checks

2.1 Stop the website and return to the Project. Right click on the Dependencies node in the solution and click ‘Manage NuGet packages…’. From there search and add 2 packages:

  • Microsoft.AspNetCore.Diagnostics.HealthChecks
  • AspNetCore.HealthChecks.Rabbitmq
healthchecknugets

2.2 In Startup.cs add the following 2 lines:

In ConfigureServices(…) add

services.AddHealthChecks();

In Configure(…) alter the UseEndpoints() to look as follows:

app.UseEndpoints(endpoints =>
 {
     endpoints.MapHealthChecks("/health");
     endpoints.MapControllerRoute(
         name: "default",
         pattern: "{controller=Home}/{action=Index}/{id?}");
 });

2.3 Again start the App, and then add /health to the website URL. You should receive a page saying ‘Healthy’.

healthyresponse

2.4 To add the RabbitMQ Health Check, stop the app and return to Startup.cs and change the AddHealthCheck() line previously added to be as follows:

var connStr = Configuration["rabbitconnstr"].ToString();
Log.Logger.Error("Conn Str: " + connStr);

var factory = new ConnectionFactory()
{
     Uri = new Uri(connStr),
     AutomaticRecoveryEnabled = true
};

try
{
    var connection = factory.CreateConnection();
    services
         .AddSingleton(connection)
         .AddHealthChecks()
         .AddRabbitMQ();
}
catch (Exception e)
{
    Log.Logger.Error("Exception raised: " + e.Message);
}

(PS. During some debugging I added some support for Serilog logging – you could remove the Log.Logger lines if you haven’t got logging enabled).

We’re using a Configuration setting to set the URL of the RabbitMQ AMQP connection – we need to alter this when running in a docker environment – so next step is to add a configuration value to our appSettings.json file.

2.5 Add the rabbitconnstr setting to the appSettings.json file:

"rabbitconnstr": "amqp://guest:guest@<your ip address>:5672"

(To find your IP Address on windows, run ipconfig from the CMD window).

2.6 With all this in place, again run the application, check everything works (provided the Docker Container for RabbitMQ is running from Blog Post 1). If not, you should expect an unhealthy report, or an unable to connect to the address specified above).

3. Migrate everything to a Docker Compose File

Next it’d be much better to bring our environment together in a single file to declare the environment. At this stage we have 2 components, namely:

  • The RabbitMQ Installation
  • Our Simple Web App

But this will soon grow (indeed to add a UI to display the health checks).

For this we’ll create a docker-compose file.

3.1 Important – I found that the Docker Image for the RabbitMQ Web App from Visual Studio was being created without the web app in place. Not sure if this was something unique to me during debugging, but to get round it I did 2 things:

3.1.a Remove the docker images on the machine for RabbitMQ Web App. To do this – run docker ps –a and stop and remove any web app containers (to do this, docker stop <container_id> and docker rm <container_id>).

3.1.b  Build the Web App from the command line rather than Visual Studio. To do this, open a Powershell Window in the same directory as the DOCKERFILE for the project. Then run the following command:

docker build -f Dockerfile ..

3.2 Next create a docker-compose.yml file. I’ve place this in a completely standalone directory (c:\docker\rabbitmq-example) and create it as follows:

version: '3'
services:
  web:
    image: "rabbitmqweb:dev"
    environment:
      - rabbitconnstr=amqp://guest:guest@rabbitmq:5672
    ports:
      - "80:80"
      - "443:443"
    networks:
      - mynetwork
  rabbitmq:
    image: "rabbitmq:management"
    container_name: rabbitmq
    ports:
      - "15672:15672"
      - "5672:5672"
    networks:
      - mynetwork
networks: 
  mynetwork: {}

NB. The big change here is the pointing of the RabbitMQ connection string to be rabbitmq:5672 as opposed to an IP Address. Because we’re now using docker-compose and specifying that these containers are running on the same network, they can now reference each other by name.

3.3 Get a Powershell window, navigate to your directory (mine, C:\Docker\Rabbit-Example) and run:

docker-compose up -d

3.4 Open a browser and navigate to http://localhost/ and check the web site appears. Also navigate to http://localhost/health and check the ‘Healthy’ status is returned.

3.5 Now test that if RabbitMQ stops, ‘Unhealthy’ is returned. To do this, run:

docker ps -a

to list all the containers. Then run docker stop <container_id> for the docker container that is rabbitmq:management.

stop-rabbitmq

Refresh the http://localhost/health URL and check ‘Unhealthy’ is returned.

To restart RabbitMQ, run:

docker start <container_id>

Refresh the http://localhost/health URL and check ‘Healthy’ is returned. (There may be a short delay as RabbitMQ starts).

4. Using the ASP.Net Core Health Checks UI to show results

We can now use a component to provide a GUI to show the health status of our services. Whats really nice is we can run this UI in another Docker container and we need to write minimal code to get this functionality.

4.1 Update the Web App project to customise the output of the health check for consumption by the UI component. Add the NuGet package ‘AspNetCore.HealthChecks.UI.Client’. Then update the Startup.cs file to match the following:

app.UseEndpoints(endpoints =>
 {
     endpoints.MapHealthChecks("/health", new HealthCheckOptions()
     {
         Predicate = _ => true,
         ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
     });
     endpoints.MapControllerRoute(
         name: "default",
         pattern: "{controller=Home}/{action=Index}/{id?}");
 });

(Note, I played it safe at this point, did a docker-compose down and then removed the previously rabbitmqweb:dev image (docker rmi <image_id> before then rebuilding it from the command line as per the earlier command).

4.2 Update the Docker Compose file to look as follows (the significant change is the addition of a new container which is the Health Checks UI):

version: '3'
services:
  web:
    image: "rabbitmqweb:dev"
    environment:
      - rabbitconnstr=amqp://guest:guest@rabbitmq:5672
    ports:
      - "80:80"
      - "443:443"
    networks:
      - mynetwork
  rabbitmq:
    image: "rabbitmq:management"
    container_name: rabbitmq
    ports:
      - "15672:15672"
      - "5672:5672"
    networks:
      - mynetwork
   healthui:
    image: "xabarilcoding/healthchecksui"
    environment: 
      - HealthChecksUI:HealthChecks:0:Name=rabbitmqweb
      - HealthChecksUI:HealthChecks:0:Uri=http://web:80/health
    ports:
      - "5000:80"
    networks:
      - mynetwork
networks: 
  mynetwork: {}

4.3 Now navigate to http://localhost:5000/healthchecks-ui/ and you should receive the following page:

healthchecks-ui

References / Links:

Microsoft Docs: https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/health-checks?view=aspnetcore-3.1

RabbitMQ Health Checks: https://github.com/Xabaril/AspNetCore.Diagnostics.HealthChecks/tree/master/src/HealthChecks.RabbitMQ

Visual Studio and Docker:
https://aka.ms/containerfastmode

RabbitMQ on Windows (with Docker)

Hoping to pull together a little series on .NET Core and RabbitMQ – hosting RabbitMQ in Docker. Plan will be to:

  • Install Docker (on Windows) and a RabbitMQ Container (this post)
  • Set-up Health Checks in .NET Core
  • Connect to RabbitMQ and Publish a Message and consume via another app
  • Connect to RabbitMQ and do a pub-sub pattern for multiple consumers

1. Install Docker Desktop

I’m using Windows 10, and as such want to install Docker on it so I can run a container with RabbitMQ to avoid having to install it locally on my machine. Note to install Docker for Desktop you’ll need Windows 10 Pro or Windows 10 Enterprise – this won’t work with Windows 10 Home.

The version of Docker Desktop at time of writing is 2.2.0.0.

1.1 Google for Docker Desktop for Windows. One of the top results will take you to Docker to download Docker Desktop (note, you’ll need to sign up for a Docker account).

1.2 Start the installer and accept the defaults – we might come back to change being able to run Windows-based containers later, but RabbitMQ tends to be available as a Linux container.

1.3 Once the installation completes, you’ll need to do a restart.

1.4 After restarting you should have a Docker Desktop icon on your desktop, and you’ll see Docker starting up during log in. You can follow its progress from the notification window in the program bar.

1.5 Start a Powershell command window and type in ‘docker info’ and check things appear to be ok.

2. Installing a RabbitMQ Docker Container

The RabbitMQ Docker Images are available from: https://hub.docker.com/_/rabbitmq

From the Powershell command line enter the command:

docker pull rabbitmq:management

(This version of the image auto installs the management plug-in for RabbitMQ).

Once the layers have all downloaded enter the command:

docker images

You should get output similar to below:

rabbitmq-image

Next run:

docker run –d --hostname my-rabbit --name some-rabbit –p 15672:15672 –p 5672:5672 rabbitmq:management

Once the container starts you can confirm the image is running with:

docker ps -a
dockerps

(Note the container ID will be different on your machine).

Next run:

docker logs <container_id>

(only enough characters to make it unique), e.g. docker logs fb

Finally to prove its running the management plug-in, launch a browser and head to localhost:15672 and you’ll get the log in page for RabbitMQ (with default username / password of guest/guest).

Other notes:

To stop a running container:

docker stop <container_id>

To remove a container:

docker rm <container_id>

To remove an image:

docker image rm <image_id>