Featured image of post Advent of Code 2020

Advent of Code 2020

This year I took part in “Advent of Code” - a challenge with the series of puzzles to solve using any programming language. I tried two years ago but resigned after the first day. This year was different, as we set the internal leaderboards, and I had a motivation to test my skills. My initial idea was to use only the PowerShell, but after some talks, I thought “maybe it’s a good moment to start learning go lang”?

The answer was: no.

I started solving puzzles with PowerShell and learned go along. But as puzzles began to be more difficult, I focused only on PowerShell. go has to wait a bit.

I collected 28 stars out of 50 possible. Which I think is not that bad result. (You see 29 on the picture in the header because I started searching how others solved the problems and implemented first overdue task).

But - to the point. Before AoC I thought I know and understand PowerShell pretty well. During the AoC, I had to revisit it. Some tasks I usually do without thinking started to cause troubles when solving the puzzles. Like: hashtables didn’t want to cooperate with numbers, or: read the file; the first part is X, the second is Y. I was too much used to my default set of techniques, and it was sometimes hard to think outside the box.

This post summarises what I learned (or reminded) during the AoC, sometimes with links for the broader explanation.

1. Reading files

Not much new stuff. As usual - use Get-Content, but become more familiar with -Raw parameter to read all data as one piece of text instead a string[] array. The -Raw parameter allows easy splitting data in the file that has to be separated via empty line. Like:

line1;val1;val2
line2;val1;val2

part2:val1,val2
part2:val3:val4

part3|abc
part3|def

To separate the above code into three separate elements, use “-split “`r’n’r’n” “. Remember to use double quotes.

$customDeclarations = Get-Content "$PSScriptRoot\CustomDeclarations.txt" -Raw
$cd = $customDeclarations -split "`r`n`r`n"

2. Named regex

When using -match we get $Matches array with the numbered matches. Say we have these lines (taken from my Day02 puzzle input):

3-7 r: mxvlzcjrsqst
1-3 c: ccpc
6-12 f: mqcccdhxfbrhfpf

The task was to check if the letter appears between X and Y number of times in the password. Looking at the first line:

  • 3-7 min/max appearances
  • r letter
  • mxvlzcjrsqst password

We can read data using regex like (\d+)-(\d+) ([a-z]): ([a-z]+), but we have to remember the indices of the groups, like $Matches[3] means the third group (the letter a-z).

$passwords = Get-Content .\Passwords.txt -First 3
$passwords | ForEach-Object {
    $_ -match '(\d+)-(\d+) ([a-z]): ([a-z]+)' | Out-Null
    $Matches
}

<#
Name                           Value
----                           -----
4                              mxvlzcjrsqst
3                              r
2                              7
1                              3
0                              3-7 r: mxvlzcjrsqst
4                              ccpc
3                              c
2                              3
1                              1
0                              1-3 c: ccpc
4                              mqcccdhxfbrhfpf
3                              f
2                              12
1                              6
0                              6-12 f: mqcccdhxfbrhfpf
#>

Instead, we can use named references in regexes using ?<name> construction before the pattern, like:

(?<minLength>\d+)-(?<maxLength>\d+) (?<letter>[a-z]): (?<password>[a-z]+)

$passwords = Get-Content .\Passwords.txt -First 3
$passwords | ForEach-Object {
    $_ -match '(?<minLength>\d+)-(?<maxLength>\d+) (?<letter>[a-z]): (?<password>[a-z]+)' | Out-Null
    $Matches
}

<#
Name                           Value
----                           -----
minLength                      3
maxLength                      7
letter                         r
password                       mxvlzcjrsqst
0                              3-7 r: mxvlzcjrsqst
minLength                      1
maxLength                      3
letter                         c
password                       ccpc
0                              1-3 c: ccpc
minLength                      6
maxLength                      12
letter                         f
password                       mqcccdhxfbrhfpf
0                              6-12 f: mqcccdhxfbrhfpf
#>

The Out-Null prevents the -match result to appear on the screen (True or False)

3. Join lines

How to join a few lines in one? Use -replace. Again - remember about double quotes.

$lines = '
hgt:176cm
iyr:2013
hcl:#fffffd ecl:amb
byr:2000
eyr:2034
cid:89 pid:934693255
'

$lines -replace "`n", ';'
# or: $lines -replace "`r`n", ';'

# hgt:176cm;iyr:2013;hcl:#fffffd ecl:amb;byr:2000;eyr:2034;cid:89 pid:934693255

4. Sort array of strings as numbers

AoC had almost all the puzzle inputs in the separate files. So I created the input files for each day and read it using Get-Content. Some files contained a series of numbers, and when we read data from a file, we get all as a string. So when I wanted to sort the array I read from the file, I got unexpected results.

# simulating array read from file
$a = [string[]]@(2, 3, 1, 11, 15, 21)
$a | Sort-Object

<#
1
11
15
2
21
3
#>

PowerShell is not aware that those strings are numbers, so it orders them as strings. To sort as the number take a look in the documentation and use ScriptBlock as the -Parameter:

# simulating array read from file
$a = [string[]]@(2, 3, 1, 11, 15, 21)

$a | Sort-Object  { [int]$_ }

<#
1
2
3
11
15
21
#>

The Stack Overflow answer has a bit more about it and led me to the documentation.

5. Expanding arrays

It’s not a PowerShell trick or feature. Day 11 had a calculation of seats, and one of the tricks was getting info around the corners and edges. Like on a chequerboard - you have 64 squares. Each of them - excluding the edges - have 8 adjacent fields. The corner has three, and the edge has five. To check all the fields, you have to consider edges and corners as a different case. And it adds an overhead to the code.

It’s easier to add a “border” to the lattice (again: think chequerboard), and analyse the original data. Like this:

# original, 10 x 10
L.LL.LL.LL
LLLLLLL.LL
L.L.L..L..
LLLL.LL.LL
L.LL.LL.LL
L.LLLLL.LL
..L.L.....
LLLLLLLLLL
L.LLLLLL.L
L.LLLLL.LL

# with border (using dots), 12 x 12
............
.L.LL.LL.LL.
.LLLLLLL.LL.
.L.L.L..L...
.LLLL.LL.LL.
.L.LL.LL.LL.
.L.LLLLL.LL.
...L.L......
.LLLLLLLLLL.
.L.LLLLLL.L.
.L.LLLLL.LL.
............

Now my code will look clearer as I don’t use additional ifs or switches.

To add the border, I used this code:

# $seats0 is the original lattice

# add top and bottom border, the same length as the original row
$seats = @('.' * $columns) + $seats0 + @('.' * $columns)

# add left and right border to each row (including boundaries)
for($i = 0; $i -le $rows + 1; $i ++)
{
    $seats[$i] = '.' + $seats[$i] + '.'
}

6. Hashtables and keys

Always be aware of the datatypes. A standard case with numbers:

$n = @(65, 66, 67, 68, 69)
$h = @{}
$n | ForEach-Object {$h[$_] = [char]$_}
$h

<#
Name                           Value
----                           -----
69                             E
68                             D
67                             C
66                             B
65                             A
#>

$h[69]
# E
$h.69
# E
$h.'69'
#(nothing)

But with the array of numbers as strings:

$n1 = @('65', '66', '67', '68', '69')
$h1 = @{}
$n1 | ForEach-Object {$h1[$_] = [char][int]$_}

<#
Name                           Value
----                           -----
67                             C
66                             B
65                             A
68                             D
69                             E
#>

$h1[67]
# (nothing)
$h1.67
# (nothing)
$h1.'67'
# C
$h1['67']
# C

Looks the same, but I had to use a string key, not a numeric, because of a different type.

7. Pre-fill an array with values

Sometimes I wanted to have an array with prefilled values. Like 10 values of 0:

$a = @(0, 0, 0, 0, 0, 0, 0, 0, 0, 0)

What if I know only the number of elements in an array (as a variable)? The fastest version:

$n = 57
$a = ,0 * $n

Also worth reading - an article on SimpleTalk.

8. Convert a number to a binary string

Use [Convert]::ToString($number, 2). Works with other bases too.

[Convert]::ToString(15, 2)
# 1111

[Convert]::ToString(15, 8)
# 17

[Convert]::ToString(15, 16)
# f

9. Pad numbers with zeroes

I want to prefix my number(s) with leading zeroes (like: 000000015), and keep the lengths consistent - all prefixed numbers should have a length of 10. I used it to visualise a bitmask but works for every number.

  1. Use formatting: '{0:d10}' -f $number; important: it has to be a number:

    $a = '15'
    '{0:d10}' -f $a
    # 15
    
    '{0:d10}' -f [int]$a
    # 0000000015
    
  2. Use $number.PadLeft(10, '0'); this time $number has to be a string:

    '15'.PadLeft(10, '0')
    # 0000000015
    
    $b = 15
    $b.PadLeft(10, '0')
    # InvalidOperation: Method invocation failed because [System.Int32] does not contain a method named 'PadLeft'.
    
    15.PadLeft(10, '0')
    # 15.PadLeft: The term '15.PadLeft' is not recognized as a name of a cmdlet, function, script file, or executable program.
    # Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
    
    (15).PadLeft(10, '0')
    #InvalidOperation: Method invocation failed because [System.Int32] does not contain a method named 'PadLeft'.
    
  3. Use ToString('0000000000'); works only with numbers:

    15.ToString('0000000000')
    # 15.ToString: The term '15.ToString' is not recognized as a name of a cmdlet, function, script file, or executable program.
    # Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
    
    (15).ToString('0000000000')
    # 0000000015
    
    '15'.ToString('0000000000')
    # MethodException: Cannot find an overload for "ToString" and the argument count: "1".
    

The list may expand in the future, as I plan to finish Advent of Code 2020, hopefully before AoC 2021.

(c) Bartosz Ratajczyk
Built with Hugo
Theme Stack designed by Jimmy