rdmueller
Ralf D. Müller
Github: rdmueller,
Twitter: RalfDMueller
Blog : https://docs-as-co.de
rdmueller |
This year, I wil luse Advent of Code to learn ruby. The reason is simple. I don’t know ruby yet and Asciidoctor, the converter for AsciiDoc, is written in ruby. So this knowledge should help me to make better use of Asciidoctor.
What I’ve found to get started with ruby are the ruby koans. Programming koans are simply put just a bunch of unit tests which you have to fix to get them to pass. This way, you also learn the language they are written in. You also learn how to write tests in the given language which gives you a good start because you can do test driven development right from the start!
A second goal for this year it to use documentation driven development/design (DDD/D) 🤣. With this approach, I first write my thoughts down as documentation and then start to code.
While I write the docs, I will already decided about classes and methods I need. I will already add them to the docs as references to the code.
Next I will turn my thoughts into test driven development (TDD). As soon as I know what I want to code, I can already write some acceptance criteria and then some tests. These tests can also be referenced from the docs.
Now let’s start with the simple hello world
:
#!/usr/bin/env ruby
puts "Hello World"
Acceptance criteria is that the code returns the text "Hello World" when executed. Let’s write the tests as bash script :-)
#!/bin/bash
# run tests with
# ./test.bash &> results.txt
OUTPUT=$(./solution.rb)
if [ "$OUTPUT" == "Hello World" ]
then
echo "success"
else
echo "fail"
echo "$OUTPUT"
fi
success
This solution is written in Python. It is the soölution for Day1 taken from AoC-2018-
So, the solution seems to be simple - one line of code in Groovy :-)
Read a file line by line and add all numbers.
It seems that Python comes with great file ahndling methods, but I have to tell python in wich mode I open the file - read, write etc.
I also have to cast the line read to in via int()
.
Here is my solution for the first star:
startFrequency = 0
currentFrequency = startFrequency
file = open("input.txt", "r")
for line in file:
frequencyChange = int(line)
currentFrequency += frequencyChange
print("Solution Star One: ", currentFrequency)
file.close()
Now for the second part.
I started the live stream ión twitch by lizthegry. She just finished the stream - it seems I am slow :-) - 15 minutes into advent of code…
Now, for the second part I need a list to collect all the frequencies I’ve already encountered.
It already starts: I had to re-read the specs twice. The second time I also had to go carefully through one of the example to understand that you might have to read the input several times. That reminds me of last year where I’ve coded the solution as function and did test driven development. Maybe I start this tomorrow….
However, I collected my second star and already learned a lot about Python!
startFrequency = 0
currentFrequency = startFrequency
alreadySeen = [currentFrequency]
found = False
for times in range(1000):
file = open("input.txt", "r")
for line in file:
frequencyChange = int(line)
currentFrequency += frequencyChange
if currentFrequency in alreadySeen:
print("Solution Star two: ", currentFrequency)
found = True
break
alreadySeen.append(currentFrequency)
if found:
break
file.close()
Since I still don’t know too much about Ruby, I will have to google most of the solution.
The original puzzle can be found at https://adventofcode.com/2019/day/1 .
First, let’s set up the right equation as method.
It seems that my knowledge about Groovy helps me a lot with Ruby.
A method will return the result of the last statement, so no explicit return
necessary.
And the Integer DIV also rounds by default.
# calculate the fuel needs for one module
def rocket_equation(mass)
mass/3-2
end
Let’s write some tests for this. I found a good introductionn to unit test with ruby. That helped to get the tests qickly up and running.
def test_acceptance_criteria1
assert rocket_equation( 12) == 2
assert rocket_equation( 14) == 2
assert rocket_equation( 1969) == 654
assert rocket_equation(100756) == 33583
end
Now I need a method which adds the required fuel up. I already now about ruby arrays from the koans (day00), but I still have to figure out how to iterate over an array.
# sum up the requirements for a list of modules
def sum_up(mass_list)
sum = 0
mass_list.each { |mass|
sum += rocket_equation(mass)
}
return sum
end
And again some tests
def test_acceptance_criteria2
assert sum_up( [12, 14, 1969, 100756] ) == 2 + 2 + 654 + 33583
end
Now I need to read in the file with my input for day01.
def read_input()
input = File.open('input.txt').read
masses = []
input.each_line { |line|
masses << Integer(line)
}
return masses
end
This gives me the solution
3226488
Here is also the result of the tests:
3226488
Loaded suite ./tests/day1s1
Started
..
Finished in 0.000542482 seconds.
-------------------------------------------------------------------------------
2 tests, 5 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
-------------------------------------------------------------------------------
3686.76 tests/s, 9216.90 assertions/s
For star 2, we have to change the rocket equation:
# calculate the fuel needs for one module
def rocket_equation(mass)
neededFuel = 0
additionalFuel = mass/3-2
while (additionalFuel>=0) do
neededFuel += additionalFuel
additionalFuel = additionalFuel/3-2
end
return neededFuel
end
As you can see, I decided to use an iterative and not a recursive approach.
def test_acceptance_criteria1
assert_equal 2, rocket_equation( 14)
assert_equal 966, rocket_equation( 1969)
assert_equal 50346, rocket_equation(100756)
end
Works quite nice. The result is
4836845
and the result of the additional tests is:
4836845
Loaded suite ./tests/day1s2
Started
.
Finished in 0.000514866 seconds.
-------------------------------------------------------------------------------
1 tests, 3 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
-------------------------------------------------------------------------------
1942.25 tests/s, 5826.76 assertions/s
The original puzzle can be found at https://adventofcode.com/2019/day/2
Today, my quality criteria is just to do it quick. I only have time to spend while I commute, so apox 15 minutes in each direction. Let’s go!
I need a little interpreter for the code isntructions. First time, I didn’t get the reference right, but then I noticed that I had to use the instruction of the instruction (ìnt[int[x]]) and everything worked out.
def run(noun, verb)
inst = File.read("./input.txt").split(",").map(&:to_i)
inst[1]=noun
inst[2]=verb
pointer = 0
while inst[pointer]!=99 do
if inst[pointer]==1 then
inst[inst[pointer+3]]=inst[inst[pointer+1]]+inst[inst[pointer+2]]
end
if inst[pointer]==2 then
inst[inst[pointer+3]]=inst[inst[pointer+1]]*inst[inst[pointer+2]]
end
if inst[pointer]!=1 && inst[pointer]!=2 then
puts "error"
puts pointer
puts isntructions
puts "/error"
end
pointer += 4
end
return inst[0]
end
puts "star 1:"
puts run(12,2)
The code for the first star started out as a simple script. For the second star, I wrapped it as method and iterated brute force over it until it matched the result.
puts "star 2:"
(0..99).each { |noun|
(0..99).each { |verb|
if (run(noun,verb)==19690720) then
puts 100*noun+verb
end
}
}
I start to recognize that I use a special coding style when I code in a new language. Let’s call it the smallest common subset style (scs-style). I hate to look up language features because it is time consuming. So instead to use the best language constructs, I use the features which are most likely to work, because they are common to all languages.
As an example, I started to use if-then-else
instead of a switch statement.
That is horrible and doesn’t teach me anything.
So I wonder if it makes sense to first code in a language which I know well (Groovy) and then translate it to Ruby?
This sounds like an easy puzzle today. Let’s take the brute force approach for star one.
Let’s iterate over all possible passwords and count how many of them are valid. The processing power should be enough.
#!/usr/bin/env ruby
def checkPass(pass)
valid = false
# It is a six-digit number
# => yes, all are six-digit
# The value is within the range given in your puzzle input.
# yes, I iterate over the range
# Two adjacent digits are the same (like 22 in 122345).
pass_s = pass.to_s
if ( (pass_s[0]==pass_s[1]) ||
(pass_s[1]==pass_s[2]) ||
(pass_s[2]==pass_s[3]) ||
(pass_s[3]==pass_s[4]) ||
(pass_s[4]==pass_s[5])) then
# Going from left to right, the digits never decrease
if ( (pass_s[0]<=pass_s[1]) &&
(pass_s[1]<=pass_s[2]) &&
(pass_s[2]<=pass_s[3]) &&
(pass_s[3]<=pass_s[4]) &&
(pass_s[4]<=pass_s[5])) then
valid = true
end
end
return valid
end
validPass = 0
(130254..678275).each { |pass|
if (checkPass(pass)==true) then
validPass += 1
end
}
puts checkPass(111111)==true
puts checkPass(223450)==false
puts checkPass(123789)==false
puts validPass
I think I can stay with the brute force approach, but I have to update my checks.
#!/usr/bin/env ruby
def checkPass(pass)
valid = false
# It is a six-digit number
# => yes, all are six-digit
# The value is within the range given in your puzzle input.
# yes, I iterate over the range
# Two adjacent digits are the same (like 22 in 122345).
pass_s = pass.to_s
if ( (pass_s[0]==pass_s[1]) ||
(pass_s[1]==pass_s[2]) ||
(pass_s[2]==pass_s[3]) ||
(pass_s[3]==pass_s[4]) ||
(pass_s[4]==pass_s[5])) then
# Going from left to right, the digits never decrease
if ( (pass_s[0]<=pass_s[1]) &&
(pass_s[1]<=pass_s[2]) &&
(pass_s[2]<=pass_s[3]) &&
(pass_s[3]<=pass_s[4]) &&
(pass_s[4]<=pass_s[5])) then
# the two adjacent matching digits are not part of a larger group of matching digits
# let's count how many times each digit is in the pass
# and it is valid if one digit is two times in the pass
count = [0,0,0,0,0,0,0,0,0,0]
(0..9).each { |digit|
(0..5).each { |pos|
if (pass_s[pos]==digit.to_s) then
count[digit] += 1
end
}
}
# now check that one count is equal to 2
(0..9).each { |digit|
if (count[digit]==2) then
valid = true
end
}
end
end
return valid
end
validPass = 0
(130254..678275).each { |pass|
if (checkPass(pass)==true) then
validPass += 1
end
}
puts checkPass(111111)==true
puts checkPass(223450)==false
puts checkPass(123789)==false
puts validPass