Hi,

Just a reminder that Advent of code has started for 2019.
If you don’t know it, it is a coding advent calendar which has a small programming task every day until christmas.

They usually start simple, but get really hard towards the end. (at least for me :) )

Its still fun to do, and the tasks are usually well and funny written.

3 Likes

It is, it’s great! And it’s a very good way to learn a language or keep learning it.

I personally do it every year because I like these relaxed challenges, and also to see if Crystal can handle all these problems. Many Enumerable methods were added to the std after doing some of these problems, and I think some improvements or bug fixes too.

We can also post our Crystal solutions here to compare and learn from each other.

Here’s my Day 1 solution:

``````puts File
.each_line
.map(&.to_i)
.map { |mass| fuel(mass) }
.sum

# This is for part 2: for part 1 it's just `mass // 3 - 2`
def fuel(mass)
fuel = mass // 3 - 2
fuel += fuel(fuel) if fuel > 0
fuel
end
``````
3 Likes

Did not know about the `//` operator! I used `Float64#trunc`, but that one is cleaner.

1 Like

Awesome, I will also be doing these to learn more about Crystal! (Probably just some of the first ones, because they can get really difficult towards the end.)

Sharing the solutions is a very good idea, I’m curious how much better others can make these

This is what I came up for the second half of day one:

``````def calc_fuel(mass)
(mass // 3) - 2
end

def fuel_for(mass)
fuel_needed = calc_fuel(mass)
if fuel_needed < 1
return mass
else
return mass + fuel_for(fuel_needed)
end
end

masses = [] of Int64
input.each_line do |line|
masses << line.to_i64
end

modules_fuel = masses.reduce(0) do |memo, m|
memo + calc_fuel(m)
end
puts "fuel for modules : " + modules_fuel.to_s

total_fuel = masses.reduce(0) do |memo, m|
memo + fuel_for(calc_fuel(m))
end
puts "fuel for modules with fuel for fuel: " + total_fuel.to_s
``````

You should avoid propagating `File.open.each_line` and `map.sum`. People probably look at your code as a reference for good practice ;)

2 Likes

It’s a good observation, but in a script like this leaking a file descriptor is no problem at all. That’s why it’s good to be able to have the possibility to do this in these scenarios. Also, I still think we should GC file descriptors once we run out of them.

But yeah, I guess I’ll change it to `read`

You can still use `File.open &.` instead of `File.open.`

1 Like

Totally agree. But it depends on the context and that’s not obvious without explanation.

Somehow my result for part 2 of day 1 is not correct. I ran @asterite’s algorithm from above and it returns the same value as mine.

Can someone confirm it returns the correct value for their input?

``````INPUT = File.read_lines(File.join(__DIR__, "../input/day1.txt")).map(&.to_i)

def fuel(mass)
fuel = mass // 3 - 2
if fuel > 0
fuel += fuel(fuel)
end
fuel
end

total_mass = INPUT.sum do |mass|
fuel(mass)
end

p total_mass
``````

Since the first part’s answer is correct, I assume it’s not an issue with the input data.

For my input the result is wrong.
I get

``````modules with fuel : 4946546
``````

and yours outputs 4946421

My solution was

``````require "./aoc2019"

module Day1
include Day
extend self

def to_list(input)
input.chomp.split(/[,\n]/).map(&.chomp).map(&.to_i)
end

def part1(input)
to_list(input).sum { |x| x // 3 - 2 }
end

def part2(input)
to_list(input).sum do |x|
sum = 0
loop do
x = x // 3 - 2
break if x <= 0
sum += x
end
sum
end
end
end
``````

You solution returns fuel = -1 for mass = 3, and should return 0.

1 Like

Great catch! I totally missed that. Thanks.
It seems, @asterite your’s, too =)

1 Like

Day 3 took me way too long.
I at first didn’t find the “&” function for Arrays. I was looking for something like exclusive union. Should just have looked at the simple things

I would be interested in other solutions, because my plot function does not seem very elegant.

``````input = File.read("input3").split().map(&.split(","))

record Point, x : Int32 = 0, y : Int32 = 0

def distance(a : Point, b : Point)
return ( a.x - b.x ).abs + ( a.y - b.y ).abs
end

class Plot
property points = [] of Point
property data = [] of String

def initialize(@data)
@points << Point.new
plot
end

def steps_to_first(point)
return @points.index(point)
end

def plot
current = Point.new
@data.each do |move|
matches = move.match(/([UDRL])(\d*)/)
if matches
whole, dir, steps = matches
steps = steps.to_i
case dir
when "U"
(current.y + 1).step(to: current.y + steps) do |y|
@points << Point.new( current.x, y )
end
current = Point.new(current.x, current.y + steps)
when "D"
(current.y - 1).step(to: current.y - steps, by: -1) do |y|
@points << Point.new( current.x, y )
end
current = Point.new(current.x, current.y - steps)
when "R"
(current.x + 1).step(to: current.x + steps) do |x|
@points << Point.new( x, current.y )
end
current = Point.new(current.x + steps, current.y)
when "L"
(current.x - 1).step(to: current.x - steps, by: -1) do |x|
@points << Point.new( x, current.y )
end
current = Point.new(current.x - steps, current.y)
end
end
end
end

end

# exampe inputs 1 and 2
input1a = "R75,D30,R83,U83,L12,D49,R71,U7,L72".split(",")
input1b = "U62,R66,U55,R34,D71,R55,D58,R83".split(",")
input2a = "R98,U47,R26,D63,R33,U87,L62,D20,R33,U53,R51".split(",")
input2b = "U98,R91,D20,R16,D67,R40,U7,R15,U6,R7".split(",")

a = Plot.new(input[0])
b = Plot.new(input[1])

result = a.points & b.points
result.delete Point.new #delete coordinate origin

distances = result.map do |point|
distance(Point.new, point).abs
end

puts "shortest distance: #{distances.sort.first}"

record StepsToPoint, steps : Int32 = 0, point : Point = Point.new

fastest = result.map do |point|
steps = a.points.index(point) || 0
steps += b.points.index(point) || 0
StepsToPoint.new( steps, point || Point.new )
end

fastest.sort_by!(&.steps)

puts "Fastest combined steps: #{ fastest.first.steps }"
``````
1 Like

Here’s mine for part 2.

I struggled a bit about finding a sweet spot between repetition and abstracting things away. For example, I had a macro at some point to define the methods in the `Point` struct as one-liners. Also tried ways to move the `steps.times` loops to one single place.

However, when comparing the listings, I was not convinced and the one below seemed the most easy to understand to me. Vertical alignment makes it easy to parse also.

``````struct Point
def initialize(@x : Int32, @y : Int32)
end

def left
@x -= 1; self
end

def right
@x += 1; self
end

def up
@y += 1; self
end

def down
@y -= 1; self
end
end

class Wire
getter :points

def initialize(path)
@points = [Point.new(0, 0)]
follow(path)
end

def follow(path)
path.split(",").each do |move|
steps = move[1..].to_i
case move[0]
when 'L' then steps.times { @points << points.last.left }
when 'R' then steps.times { @points << points.last.right }
when 'U' then steps.times { @points << points.last.up }
when 'D' then steps.times { @points << points.last.down }
end
end
end
end

def min_delay(path1, path2)
wire1 = Wire.new(path1)
wire2 = Wire.new(path2)

(wire1.points[1..] & wire2.points[1..]).map do |point|
wire1.points.index(point).not_nil! + wire2.points.index(point).not_nil!
end.min
end

p 30 == min_delay("R8,U5,L5,D3", "U7,R6,D4,L4")
p 610 == min_delay("R75,D30,R83,U83,L12,D49,R71,U7,L72", "U62,R66,U55,R34,D71,R55,D58,R83")
p 410 == min_delay("R98,U47,R26,D63,R33,U87,L62,D20,R33,U53,R51", "U98,R91,D20,R16,D67,R40,U7,R15,U6,R7")

path1 = "R1000,D940,L143,D182,L877,D709,L253,U248,L301,U434,R841,U715,R701,U92,R284,U115,R223,U702,R969,U184,L992,U47,L183,U474,L437,D769,L71,U96,R14,U503,R144,U432,R948,U96,L118,D696,R684,U539,L47,D851,L943,U606,L109,D884,R157,U946,R75,U702,L414,U347,R98,D517,L963,D177,R467,D142,L845,U427,R357,D528,L836,D222,L328,U504,R237,U99,L192,D147,L544,D466,R765,U845,L267,D217,L138,U182,R226,U466,R785,U989,R55,D822,L101,U292,R78,U962,R918,U218,L619,D324,L467,U885,L658,U890,L764,D747,R369,D930,L264,D916,L696,U698,R143,U537,L922,U131,R141,D97,L76,D883,R75,D657,R859,U503,R399,U33,L510,D318,L455,U128,R146,D645,L147,D651,L388,D338,L998,U321,L982,U150,R123,U834,R913,D200,L455,D479,L38,U860,L471,U945,L946,D365,L377,U816,R988,D597,R181,D253,R744,U472,L345,U495,L187,D443,R924,D536,R847,U430,L145,D827,L152,D831,L886,D597,R699,D751,R638,D580,L488,D566,L717,D220,L965,D587,L638,D880,L475,D165,L899,U388,R326,D568,R940,U550,R788,D76,L189,D641,R629,D383,L272,D840,L441,D709,L424,U158,L831,D576,R96,D401,R425,U525,L378,D907,L645,U609,L336,D232,L259,D280,L523,U938,R190,D9,L284,U941,L254,D657,R572,U443,L850,U508,L742,D661,L977,U910,L190,U626,R140,U762,L673,U741,R317,D518,R111,U28,R598,D403,R465,D684,R79,U725,L556,U302,L367,U306,R632,D550,R89,D292,R561,D84,L923,D109,L865,D880,L387,D24,R99,U934,L41,U29,L225,D12,L818,U696,R652,U327,L69,D773,L618,U803,L433,D467,R840,D281,R161,D400,R266,D67,L205,D94,R551,U332,R938,D759,L437,D515,L480,U774,L373,U478,R963,D863,L735,U138,L580,U72,L770,U968,L594"
path2 = "L990,D248,L833,U137,L556,U943,R599,U481,R963,U812,L825,U421,R998,D847,R377,D19,R588,D657,R197,D354,L548,U849,R30,D209,L745,U594,L168,U5,L357,D135,R94,D686,R965,U838,R192,U428,L861,U354,R653,U543,L633,D508,R655,U575,R709,D53,L801,D709,L92,U289,L466,D875,R75,D448,R576,D972,L77,U4,L267,D727,L3,D687,R743,D830,L803,D537,L180,U644,L204,U407,R866,U886,R560,D848,R507,U470,R38,D652,R806,D283,L836,D629,R347,D679,R609,D224,L131,D616,L687,U181,R539,D829,L598,D55,L806,U208,R886,U794,L268,D365,L145,U690,R50,D698,L140,D512,L551,U845,R351,U724,R405,D245,L324,U181,L824,U351,R223,D360,L687,D640,L653,U158,R786,D962,R931,D151,R939,D34,R610,U684,L694,D283,R402,D253,R388,D195,R732,U809,R246,D571,L820,U742,L507,U967,L886,D693,L273,U558,L914,D122,R146,U788,R83,U149,R241,U616,R326,U40,L192,D845,L577,U803,L668,D443,R705,D793,R443,D883,L715,U757,R767,D360,L289,D756,R696,D236,L525,U872,L332,U203,L152,D234,R559,U191,R340,U926,L746,D128,R867,D562,L100,U445,L489,D814,R921,D286,L378,D956,L36,D998,R158,D611,L493,U542,R932,U957,R55,D608,R790,D388,R414,U670,R845,D394,L572,D612,R842,U792,R959,U7,L285,U769,L410,D940,L319,D182,R42,D774,R758,D457,R10,U82,L861,D901,L310,D217,R644,U305,R92,U339,R252,U460,R609,D486,R553,D798,R809,U552,L183,D238,R138,D147,L343,D597,L670,U237,L878,U872,R789,U268,L97,D313,R22,U343,R907,D646,L36,D516,L808,U622,L927,D982,L810,D149,R390,U101,L565,U488,L588,U426,L386,U305,R503,U227,R969,U201,L698,D850,R800,D961,R387,U632,R543,D541,R750,D174,R543,D237,R487,D932,R220"

p min_delay(path1, path2)
``````

@fxn Your solution is much nicer!

I think I like this. I’ll try to keep posting mine and hope to see better solutions from others.

Like day4, I like my part 1, but for part 2 I didn’t find a solution that produced a correct result
for cases like 566677.
I didn’t have much time and too tired now to pick it up again.
If anyone has a good solution for part 2, I’d be interested.

``````range_start,range_end = "356261-846303".split("-").map(&.to_i)

def walk_num(num, &block)
str = num.to_s
result = [] of Bool
0.step(to: str.size - 2) do |i|
result << yield(str[i],str[i+1])
end
return result
end

def check_twin_digits(num)
result = walk_num(num) do |x,y|
x == y
end
result.includes? true
end

def check_increasing_digits(num)
result = walk_num(num) do |x,y|
x <= y
end
return !( result.includes? false )
end

result = [] of Int32
range_start.step(to: range_end) do |num|
if check_increasing_digits(num)
if check_twin_digits(num)
result << num
end
end
end

puts "part 1 solution: " + result.size.to_s
``````
1 Like

yeah, double digits at last position are special case. But my solutions are mostly imperative, so I didn’t care much:

``````  def check2(number)
digits = number.to_s.chars.map(&.ord)
match = 0
was_double = false
digits.each_cons(2, reuse: true) do |(first, second)|
return false if second < first
if first == second
match += 1
else
was_double = true if match == 1
match = 0
end
end
was_double || match == 1
end

def part2(input)
v1, v2 = input.chomp.split('-').map(&.to_i)
(v1..v2).count { |x| check2(x) }
end
``````

Similar solution here. Trailing double needed an inelegant for my taste last check:

``````def valid_password?(password : String)
non_trailing_double = false
streak = 0

if c == d
streak += 1
elsif c > d
return false
else
non_trailing_double = true if streak == 1
streak = 0
end
end

non_trailing_double || streak == 1
end

``````

I started writing such code to check for “groups of 2 but no more than 2” until I realized I could use `chunk_while`. This is my solution for Day 4 part 2:

``````different_passwords =
(138241..674034).count do |number|
digits = number.to_s.chars.map(&.to_i)

# Two adjacent digits are the same,
# are not part of a larger group of matching digits
digits.chunk_while { |x, y| x == y }.any? &.size.==(2) &&
# Going from left to right, the digits never decrease
digits.each_cons(2).all? { |(x, y)| x <= y }
end