@asterite that is very cool. So many cool methods in the standard library I have never seen.
My solution for day 5.
Once the description is carefully read, the logic is not big deal. However, it took me a bit to reach a notation that resulted in a case
statement I liked.
class TEST
@program : Array(Int32)
def initialize(listing : String, @input : Int32)
@program = listing.split(',').map(&.to_i)
@ip = 0 # instruction pointer
end
def run
loop do
case current_opcode
when 1
@program[read(3)] = value(1) + value(2)
@ip += 4
when 2
@program[read(3)] = value(1) * value(2)
@ip += 4
when 3
@program[read(1)] = @input
@ip += 2
when 4
puts @program[read(1)]
@ip += 2
when 5
@ip = value(1) != 0 ? value(2) : @ip + 3
when 6
@ip = value(1) == 0 ? value(2) : @ip + 3
when 7
@program[read(3)] = value(1) < value(2) ? 1 : 0
@ip += 4
when 8
@program[read(3)] = value(1) == value(2) ? 1 : 0
@ip += 4
when 99
break
end
end
end
private def current_instruction : Int32
read(0)
end
private def current_opcode : Int32
current_instruction % 100
end
private def current_modes : Int32
current_instruction // 100
end
private def mode(offset : Int32) : Int32
modes = current_modes
(offset - 1).times { modes //= 10 }
modes % 10
end
private def value(offset : Int32) : Int32
case mode(offset)
when 0 then @program[read(offset)]
when 1 then read(offset)
else raise "invalid mode"
end
end
private def read(offset : Int32) : Int32
@program[@ip + offset]
end
end
listing = "3,225,1,225,6,6,1100,1,238,225,104,0,1101,33,37,225,101,6,218,224,1001,224,-82,224,4,224,102,8,223,223,101,7,224,224,1,223,224,223,1102,87,62,225,1102,75,65,224,1001,224,-4875,224,4,224,1002,223,8,223,1001,224,5,224,1,224,223,223,1102,49,27,225,1101,6,9,225,2,69,118,224,101,-300,224,224,4,224,102,8,223,223,101,6,224,224,1,224,223,223,1101,76,37,224,1001,224,-113,224,4,224,1002,223,8,223,101,5,224,224,1,224,223,223,1101,47,50,225,102,43,165,224,1001,224,-473,224,4,224,102,8,223,223,1001,224,3,224,1,224,223,223,1002,39,86,224,101,-7482,224,224,4,224,102,8,223,223,1001,224,6,224,1,223,224,223,1102,11,82,225,1,213,65,224,1001,224,-102,224,4,224,1002,223,8,223,1001,224,6,224,1,224,223,223,1001,14,83,224,1001,224,-120,224,4,224,1002,223,8,223,101,1,224,224,1,223,224,223,1102,53,39,225,1101,65,76,225,4,223,99,0,0,0,677,0,0,0,0,0,0,0,0,0,0,0,1105,0,99999,1105,227,247,1105,1,99999,1005,227,99999,1005,0,256,1105,1,99999,1106,227,99999,1106,0,265,1105,1,99999,1006,0,99999,1006,227,274,1105,1,99999,1105,1,280,1105,1,99999,1,225,225,225,1101,294,0,0,105,1,0,1105,1,99999,1106,0,300,1105,1,99999,1,225,225,225,1101,314,0,0,106,0,0,1105,1,99999,1107,677,226,224,1002,223,2,223,1005,224,329,101,1,223,223,8,677,226,224,102,2,223,223,1006,224,344,1001,223,1,223,108,677,677,224,1002,223,2,223,1006,224,359,1001,223,1,223,1108,226,677,224,102,2,223,223,1006,224,374,1001,223,1,223,1008,677,226,224,102,2,223,223,1005,224,389,101,1,223,223,7,226,677,224,102,2,223,223,1005,224,404,1001,223,1,223,1007,677,677,224,1002,223,2,223,1006,224,419,101,1,223,223,107,677,226,224,102,2,223,223,1006,224,434,101,1,223,223,7,677,677,224,1002,223,2,223,1005,224,449,101,1,223,223,108,677,226,224,1002,223,2,223,1006,224,464,101,1,223,223,1008,226,226,224,1002,223,2,223,1006,224,479,101,1,223,223,107,677,677,224,1002,223,2,223,1006,224,494,1001,223,1,223,1108,677,226,224,102,2,223,223,1005,224,509,101,1,223,223,1007,226,677,224,102,2,223,223,1005,224,524,1001,223,1,223,1008,677,677,224,102,2,223,223,1005,224,539,1001,223,1,223,1107,677,677,224,1002,223,2,223,1006,224,554,1001,223,1,223,1007,226,226,224,1002,223,2,223,1005,224,569,1001,223,1,223,7,677,226,224,1002,223,2,223,1006,224,584,1001,223,1,223,108,226,226,224,102,2,223,223,1005,224,599,1001,223,1,223,8,677,677,224,102,2,223,223,1005,224,614,1001,223,1,223,1107,226,677,224,102,2,223,223,1005,224,629,1001,223,1,223,8,226,677,224,102,2,223,223,1006,224,644,1001,223,1,223,1108,226,226,224,1002,223,2,223,1006,224,659,101,1,223,223,107,226,226,224,1002,223,2,223,1006,224,674,1001,223,1,223,4,223,99,226"
TEST.new(listing, 5).run
Day 5 looked a lot harder than it realy was. But my solution is a lot less clean
input = File.read("input5")
data = input.split(",").map(&.to_i)
def process(data, input, program_pointer = 0)
begin
pp = program_pointer
output = [] of Int32
loop do
raw = sprintf("%04d", data[pp] )
opcode = raw[-2,2].to_i
if opcode == 99
return data[0]
end
begin
parm1 = raw[-3] == '0' ? data[data[pp+1]] : data[pp+1]
parm2 = raw[-4] == '0' ? data[data[pp+2]] : data[pp+2]
rescue e
puts e.message
parm1 = 0
parm2 = 0
end
#puts data[pp,18]
#puts "raw opcode: #{raw}, pp: #{pp}, parm1: #{parm1}, parm2: #{parm2}"
case opcode
when 1
data[data[ pp + 3 ]] = parm1 + parm2
pp+=4
when 2
data[data[ pp + 3 ]] = parm1 * parm2
pp+=4
when 3
data[data[pp + 1]] = input
pp+=2
when 4
if raw[-3] == '0'
puts "*************output: #{data[data[pp + 1]]}"
output << data[data[pp + 1]]
else
puts "*************output: #{data[pp + 1]}"
output << data[pp + 1]
end
pp+=2
when 5
if parm1 != 0
pp = parm2
else
pp += 3
end
when 6
if parm1 == 0
pp = parm2
else
pp += 3
end
when 7
if parm1 < parm2
data[data[ pp + 3 ]] = 1
else
data[data[ pp + 3 ]] = 0
end
pp += 4
when 8
if parm1 == parm2
data[data[ pp + 3 ]] = 1
else
data[data[ pp + 3 ]] = 0
end
pp += 4
else
raise "wrong opcode"
end
end
rescue e
puts pp
puts e.backtrace.join("\n")
puts "error #{e.message}"
end
end
process(data.dup, 1)
process(data, 5)
I’m a bit late to the party, but it’s a great opportunity to do some crystal again
Here is day 1:
INPUT = {{ read_file "./input" }}
def mass_to_fuel(mass)
mass // 3 - 2
end
def part1(modules_masses)
fuel_sum = modules_masses.sum { |mass| mass_to_fuel(mass) }
puts "Part1: #{fuel_sum}"
end
# -------------------------------
def mass_to_total_fuel(mass)
total = 0
while (fuel = mass_to_fuel(mass)) > 0
total += fuel
mass = fuel
end
total
end
def part2(modules_masses)
fuel_total_sum = modules_masses.sum { |mass| mass_to_total_fuel(mass) }
puts "Part2: #{fuel_total_sum}"
end
# -------------------------------
modules_masses = INPUT.split('\n', remove_empty: true).map &.to_i
part1(modules_masses)
part2(modules_masses)
And here is day2 (I may or may not have over-engineered this one…)
Note: this computer does not raise exception in case of invalid memory access, it was fun to experiment, @j8r would be proud
INPUT = {{ read_file "#{__DIR__}/input" }}
class Computer
abstract struct Error; end
record InvalidMemoryAccess < Error, idx : Int32
class Memory
def initialize(@raw : Array(Int32))
end
def [](idx)
return InvalidMemoryAccess.new(idx) unless check_idx(idx)
@raw[idx]
end
def []=(idx, value)
return InvalidMemoryAccess.new(idx) unless check_idx(idx)
@raw[idx] = value
end
delegate to_s, size, to: @raw
private def check_idx(idx)
0 <= idx < @raw.size
end
end
def self.from_program(prog_str)
raw_memory = prog_str.split(',').map &.to_i
new Memory.new raw_memory
end
property? debug = false
getter? running = false
getter memory : Memory
getter error : Error? = nil
def initialize(@memory)
@ip = 0
end
def run
@running = true
while @running
err = exec_next_instruction
if err.is_a? Error
@running = false
@error = err
end
end
@error.nil?
end
def exec_next_instruction
return false unless @running
case opcode = guard @memory[@ip]
when 1
from_addr1 = guard @memory[@ip + 1]
from_addr2 = guard @memory[@ip + 2]
to_addr = guard @memory[@ip + 3]
result = guard(@memory[from_addr1]) + guard(@memory[from_addr2])
__debug "[IP:#{@ip}] Opcode add : mem[#{to_addr}] <- mem[#{from_addr1}] + mem[#{from_addr2}]"
guard @memory[to_addr] = result
@ip += 4
when 2
from_addr1 = guard @memory[@ip + 1]
from_addr2 = guard @memory[@ip + 2]
to_addr = guard @memory[@ip + 3]
result = guard(@memory[from_addr1]) * guard(@memory[from_addr2])
__debug "[IP:#{@ip}] Opcode mul : mem[#{to_addr}] <- mem[#{from_addr1}] * mem[#{from_addr2}]"
guard @memory[to_addr] = result
@ip += 4
when 99
__debug "[IP:#{@ip}] Opcode quit!"
@running = false
else
puts "/!\\/!\\ WARNING: Unknown opcode #{opcode} at IP:#{@ip}, skipping"
@ip += 1
end
end
# Returns the result of the program
def result
@memory[0]
end
private def __debug(message)
return unless debug?
puts "DEBUG: #{message}"
end
# Returns the error from the current method if the result of *node* is an error.
private macro guard(node)
%value = ({{ node }})
return %value if %value.is_a?(Error)
%value
end
end
# ----------------------------
def part1_run_program(program, restore = false, debug = false)
puts "Program: #{program}" if debug
computer = Computer.from_program program
computer.debug = debug
if restore
puts "Restoring state before fire..."
computer.memory[1] = 12
computer.memory[2] = 2
end
unless computer.run
puts "!!! Program failed with error: #{computer.error}"
end
puts "Memory dump: #{computer.memory}" if debug
computer.result
end
puts "---- Given test program"
part1_run_program "1,1,1,4,99,5,6,0,99", debug: true
puts
puts "---- Test invalid memory access"
part1_run_program "1,1,1,999999,99"
puts
puts "---- Test unknown opcode"
part1_run_program "1,1,1,0,42,99", debug: true
puts
puts "---- Test without end"
part1_run_program "1,0,0,0"
puts
puts "---- Part1"
result = part1_run_program INPUT, restore: true, debug: false
# Result should be 2782414
puts "Part1 result: #{result}"
puts
puts "=" * 42
puts
# ----------------------------
def part2_try_noun_verb(program, noun, verb)
computer = Computer.from_program program
# computer.debug = true
computer.memory[1] = noun
computer.memory[2] = verb
unless computer.run
puts "!! Pair #{ {noun, verb} } failed with error: #{computer.error}"
end
computer.result
end
def part2
target_output = 19690720
noun = verb = 0
(0..99).each do |noun_test|
(0..99).each do |verb_test|
result = part2_try_noun_verb(INPUT, noun_test, verb_test)
if result == target_output
noun = noun_test
verb = verb_test
end
end
end
puts "Part2 result is: #{100 * noun + verb} (#{ {noun: noun, verb: verb} })"
# Result should be 9820
end
part2
No, no. Seems perfectly reasonable to me
this is my day 6 part 2. Much recursion. need a break.
input = File.read("input6")
arr = input.split().map(&.split(")")).transpose
data = Hash.zip(arr[1],arr[0])
class Planet
property children : Array( Planet )
property parent : Planet | Nil
property name : String
def initialize(@name : String, @parent : Planet | Nil, @children = [] of Planet)
end
def to_s()
name
end
def display
puts "parent: #{@parent.to_s} - name: #{@name} - children: #{ @children.map(&.to_s).join(" ")}"
end
def deep_display
display
@children.each do |p|
p.deep_display
end
end
def find(planet_name, accu = 0)
result = find_in_children(planet_name, accu)
if result[0]
return result
else
if temp = @parent
accu += 1
return temp.find(planet_name, accu)
else
[ nil, nil ]
end
end
end
def find_in_children(planet_name, accu = 0)
result = [nil, nil]
if @name == planet_name
result = [self, accu]
else
accu += 1
@children.each do |p|
temp = p.find_in_children(planet_name, accu)
if temp[0]
result = temp
end
end
end
return result
end
end
def find_bodys_around(planet,data)
ret = data.select do |k,v|
v == planet
end
ret.keys
end
def build_map(data, planet)
set = find_bodys_around(planet.to_s, data)
set.each do |p|
x = Planet.new(p, planet)
build_map(data, x)
planet.children << x
end
end
com = Planet.new("COM", nil)
build_map(data, com)
you = com.find_in_children("YOU")
you = you[0]
if you.is_a? Planet
result, accu = you.find("SAN")
if accu.is_a? Int32
puts "orbit transfers: #{accu - 2}"
end
end
Aaaand day3:
also available here: https://github.com/bew/adventofcode-2019
INPUT = {{ read_file "#{__DIR__}/input" }}
class Wire
def self.from_instructions(instructions_str)
instructions = instructions_str.split(',').map!(&.strip)
wire = new
instructions.each do |instr|
wire.do_instruction instr
end
wire
end
getter occupied_cells = Set({Int32, Int32}).new
@current_x = 0
@current_y = 0
OFFSETS_BY_DIRECTIONS = {
'U' => { 0, 1},
'D' => { 0, -1},
'R' => { 1, 0},
'L' => {-1, 0},
}
def do_instruction(instr)
direction_chr, move_count = instr[0], instr[1..].to_i
offset_x, offset_y = OFFSETS_BY_DIRECTIONS[direction_chr]
move_count.times do
@current_x, @current_y = @current_x + offset_x, @current_y + offset_y
occupied_cells << {@current_x, @current_y}
end
end
def distance_to_cell(x, y)
# Here we use a property of Crystal's Set, where the values are stored in
# order of insertion. And since a Set does not duplicate values, the first
# time we find a given position we can be sure it is the first time the wire
# enters this cell.
cells = occupied_cells.to_a
cells.delete({0, 0}) # pos {0, 0} does not count in the calculation
pos_idx = cells.index({x, y}).not_nil!
pos_idx + 1
end
def common_cells_with(wire : self)
common_cells = occupied_cells & wire.occupied_cells
common_cells.delete({0, 0}) # pos {0, 0} does not count
if common_cells.size == 0
puts "!!! No common cells between these wires"
return nil
end
common_cells
end
end
def part1(wire1, wire2)
common_cells = wire1.common_cells_with(wire2) || return nil
distances_to_common_cells = common_cells.map { |(x, y)| x.abs + y.abs }
distances_to_common_cells.sort.first
end
def part2(wire1, wire2)
common_cells = wire1.common_cells_with(wire2) || return nil
common_cells.map do |cell_pos|
wire1.distance_to_cell(*cell_pos) + wire2.distance_to_cell(*cell_pos)
end.min
end
input_lines = INPUT.lines
wire1 = Wire.from_instructions input_lines[0]
wire2 = Wire.from_instructions input_lines[1]
result = part1(wire1, wire2)
puts "Part1 result: #{result}" # Should be 386
result = part2(wire1, wire2)
puts "Part2 result: #{result}" # Should be 6484
Did some catching up, and day 8 was nice, but I feel like it has a lot of learning potential because my solution seems not very concise:
input = File.read("input8")
data = [] of Array(Array(Char))
cc = 0
1.step(to: 100) do |c|
layer = [] of Array(Char)
0.step(to: 5) do |i|
t2 = input[cc..(cc+24)]
cc += 25
layer << t2.chars
end
data << layer.dup
end
pd = data.map_with_index do |l|
{l.flatten.select('0').size,
l.flatten.select('1').size,
l.flatten.select('2').size}
end
r = pd.sort_by{|t| t[0]}.first
puts "lowest 0 count = #{ r[0] } ones times twos = #{r[1] * r[2]}"
cc = 0
27.times do
print '█'
end
puts
0.step(to:5) do |r|
print '█'
0.step(to: 24) do |c|
0.step(to: 100) do |l|
case data[l][r][c]
when '0'
print '█'
break
when '1'
print '_'
break
when '2'
end
end
end
puts '█'
end
27.times do
print '█'
end
puts
Share Day 5, with…yield statement is useful here.
PROGRAM = {{ read_file("#{__DIR__}/input_day5.txt").split(",").map(&.to_i) }}
struct Machine
getter memory : Array(Int32)
@ip : Pointer(Int32)
def initialize(@memory : Array(Int32))
@ip = typeof(@ip).null
end
def decode_0
yield @ip.value
end
def decode_1
decode_0 do |code|
yield (code % 100), (code // 100)
end
end
def decode_2
decode_1 do |code, param1|
yield code, (param1 % 10), (param1 // 10)
end
end
def input_inst_impl(op_code)
decode_0 do |code|
return false if code != op_code
@memory[@ip[1]] = yield
@ip += 2
end
true
end
def output_inst_impl(op_code)
decode_1 do |code, param|
return false if code != op_code
output = (param == 1) ? @ip[1] : @memory[@ip[1]]
yield output
@ip += 2
end
true
end
def arithmetic_inst_impl(op_code)
decode_2 do |code, param1, param2|
return false if code != op_code
src1 = (param1 == 1) ? @ip[1] : @memory[@ip[1]]
src2 = (param2 == 1) ? @ip[2] : @memory[@ip[2]]
@memory[@ip[3]] = yield src1, src2
@ip += 4
end
true
end
def branch_inst_impl(op_code)
decode_2 do |code, param1, param2|
return false if code != op_code
cond = (param1 == 1) ? @ip[1] : @memory[@ip[1]]
loc = (param2 == 1) ? @ip[2] : @memory[@ip[2]]
if yield cond
@ip = @memory.to_unsafe + loc
else
@ip += 3
end
end
true
end
def compare_inst_impl(op_code)
decode_2 do |code, param1, param2|
return false if code != op_code
src1 = (param1 == 1) ? @ip[1] : @memory[@ip[1]]
src2 = (param2 == 1) ? @ip[2] : @memory[@ip[2]]
if (yield src1, src2)
@memory[@ip[3]] = 1
else
@memory[@ip[3]] = 0
end
@ip += 4
end
true
end
def execute(exit_code)
@ip = @memory.to_unsafe
while @ip.value != exit_code
with self yield
end
end
macro with_inst(op_code, type, &block)
{% unless block.args.empty? %}
next if {{ type.id }}_inst_impl({{ op_code }}) do |{{ block.args.splat }}|
{{ block.body }}
end
{% else %}
next if {{ type.id }}_inst_impl({{ op_code }}) do
{{ block.body }}
end
{% end %}
end
end
def part1(input)
output = nil
machine = Machine.new {{ PROGRAM }}
machine.execute(exit_code: 99) do
# add instruction
with_inst(op_code: 1, type: :arithmetic) do |src1, src2|
src1 + src2
end
# multiply instruction
with_inst(op_code: 2, type: :arithmetic) do |src1, src2|
src1 * src2
end
# input instruction
with_inst(op_code: 3, type: :input) do
input
end
# output instruction
with_inst(op_code: 4, type: :output) do |value|
output = value
end
with itself yield
end
output.not_nil!
end
pp (part1(input: 1) { })
def part2(input)
part1(input) do
# jump if condition is non-zero
with_inst(op_code: 5, type: :branch) do |cond|
cond != 0
end
# jump if condition is zero
with_inst(op_code: 6, type: :branch) do |cond|
cond == 0
end
# less instruction
with_inst(op_code: 7, type: :compare) do |a, b|
a < b
end
# equal instruction
with_inst(op_code: 8, type: :compare) do |a, b|
a == b
end
with itself yield
end
end
pp (part2(input: 5) { })
here is my day1 code:
mass = ARGV[0]
def caculate_fuel_by_mass(mass : Number)
(-2 + (mass/3).to_i)
end
# part1
total_fuel = 0
File.each_line(mass) do |m|
total_fuel += caculate_fuel_by_mass(m.to_f)
end
puts "total_fuel is #{total_fuel} for part1"
# part2
total_fuel_real = 0
File.each_line(mass) do |m|
fuel = caculate_fuel_by_mass(m.to_f)
while fuel >0
total_fuel_real += fuel
fuel = caculate_fuel_by_mass(fuel)
end
end
puts "total_fuel_real is #{total_fuel_real} for part2"