I will do this year’s Advent of Code challeges in Pharo Smalltalk. Let’s see how far I can go with it.
Day 1 - Part 1
Here is the sample input for the first part of the puzzle:
1abc2
pqr3stu8vwx
a1b2c3d4e5f
treb7uchet
Our task here is to extract the first digit and the last digit to form a single two digit number. For the four sample lines, these numbers will be 12, 38, 15, and 77. The answer to the puzzle is the sum of all these numbers, 142 in this case.
well, Its time to start our pharo 11 vm.
Let’s get Petit Parser into our image from Github. Enter the following in the playground and “Do it” :
Metacello new
baseline: 'PetitParser2';
repository: 'github://kursjan/petitparser2';
load.
This will install the Petit parser 2. I spent some time playing with the documentaion to learn the basics. The first step is trying to parse all digits:
| input parser parseResult |
input := '1abc2
pqr3stu8vwx
a1b2c3d4e5f
treb7uchet'.
parser := #digit asParser.
parseResult := parser matchesIn: input.
which recognises all digits in the give input:
Update our parser
to also recognise new line:
parser := #digit asParser / #space asParser.
There is probably a better and faster way of doing this with Petit Parser itself, but let’s stop using it and try the collections API to do the rest.
First, Separate digits by line.
parseResult splitOn: Character cr.
and we have a collection of digits per line of input.
I made a mistake here of not assigning the result of splitOn
to the variable
and spent few minutes wondering about it. It is very easy to step through and
fix the mistake.
To make a single two digit number, first concatenate the first and last
characters in each line (pharo uses ,
to join strings) and then cast
it as a number:
asNumbers := parseResult collect:
[ :each | ((each first asString), (each last asString)) asNumber ].
inject: into:
reduces/folds asNumbers
to the sum of all elements.
asNumbers inject: 0 into: [ :sum :each | sum + each ].
Looks good. Now download the input.txt
into the same directory where the
pharo image is for simple lookup, because that is our current working directory
in pharo. And replace the sample input with actual input.
input := 'input.txt' asFileReference readStreamDo:
[ :stream | stream upToEnd ].
this required few additional tweaks, like the line ending of the file was
Charactel lf
, and there was an extra empty collection at the end of file. But
since we can always see the result, it was pretty easy to fix.
This concludes part 1.
Day 1 - Part 2
The sample input for second part is :
two1nine
eightwothree
abcone2threexyz
xtwone3four
4nineeightseven2
zoneight234
7pqrstsixteen
The end goal is still the same, but numbers in words like one
,two
,three
etc. are also counted as single digits. Accepted results for the input are 29,
83, 13, 24, 42, 14, and 76. Adding these together produces 281.
Trying to recognise words from one
to nine
:
wordDigit := 'one' asParser
/ 'two' asParser
/ 'three' asParser
/ 'four' asParser
/ 'five' asParser
/ 'six' asParser
/ 'seven' asParser
/ 'eight' asParser
/ 'nine' asParser .
wordDigit matchesIn: input.
This gives:
One interesting case is eightwothree
. should we treat it as eight
, wo
,
three
or eight
, two
, three
. Since the result is knows to be 83, lets go
with the first choice here. Instead of using matchesIn:
, we will use
matchesSkipIn:
, which doesn’t recognise partially overlapping words.
we can just combine this parser with the digit and space parsers from part 1:
parser := (wordDigit / #digit asParser) / #space asParser.
parser matchesSkipIn: input.
If we could replace the words with corresponding characters, the rest will be
the same as part 1. Petit parser makes it really easy. To replace two
with
$2
, we can just transform it as:
'two` asParser ==> [:str | $2]
The block takes two
as parameter. we ignore it simply return $2
(character literal). We shouldn’t try to convert it to a number just yet. Since
this works, do this to all words.
wordDigit := ('one' asParser ==> [ :str | $1 ])
/ ('two' asParser ==> [ :str | $2 ])
/ ('three' asParser ==> [ :str | $3 ])
/ ('four' asParser ==> [ :str | $4 ])
/ ('five' asParser ==> [ :str | $5 ])
/ ('six' asParser ==> [ :str | $6 ])
/ ('seven' asParser ==> [ :str | $7 ])
/ ('eight' asParser ==> [ :str | $8 ])
/ ('nine' asParser ==> [:str | $9]).
And rest of the problem is same as part 1.
281
is the expected answer. So trying this with the actual input.txt
:
The answer is rejected as incorrect. I spent going over everything and can’t
find anything wrong at all. Finally I turned to Reddit, and the top post at AOC
subreddit mentioned the partial case, that eightwothree
should be treated as
eight
, two
and three
. Felt an oversight, but on second thought, something
happening in real life all the time. All we have to do is use matchesIn:
instead of matchesSkipIn:
.
And the answer is accepted. We are done for the day. See you tomorrow.