Advent of code 2019

@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 :smiley:

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)
1 Like

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 :smiley:

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
1 Like

No, no. Seems perfectly reasonable to me :slight_smile:

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"