Advent of Code 2022: Day 11

Again, nothing really new from an Icon language perspective.

Some convenient features though are that:

From a solution perspective, this is the first problem needing some special attention on efficiency. As all the "worry" divisors are primes, we can divide the worry by their product to keep the number to a reasonable size.

Final Program

procedure main(inputs)
  local filename

  if *inputs = 1 then {
    filename := inputs[1]

    # Part 1 solution
    write("Part 1: ", find_worry_levels(filename, 20, "by 3"))
    # Part 2 solution 
    write("Part 2: ", find_worry_levels(filename, 10000))
  } else {
    write("Provide a filename of data")
  }
end

record monkey_defn(index, num_inspections, items, operator, operand, divisible, on_true, on_false)

procedure find_worry_levels(filename, num_rounds, divide_by_three)
  local monkey, monkey_data, most_active

  monkey_data := read_data(filename)
  every 1 to num_rounds do
    do_round(monkey_data, divide_by_three)

  most_active := [0, 0]
  every monkey := !monkey_data do {
    if monkey.num_inspections > most_active[1] then
      most_active := [monkey.num_inspections, most_active[1]]
    else if monkey.num_inspections > most_active[2] then
      most_active := [most_active[1], monkey.num_inspections]
  }

  return most_active[1] * most_active[2]
end

procedure read_data(filename)
  local file, line
  local current_monkey, monkey_data

  file := open(filename) | stop("Cannot open ", filename)

  monkey_data := list()
  current_monkey := &null

  every line := !file do {
    line ? {
      tab(many(" \t")) # skip spaces
      if ="Monkey " then {
        current_monkey := monkey_defn(tab(upto(":")), 0, list(), &null, &null, &null, &null, &null)
      } else if ="Starting items: " then {
        while tab(upto(&digits)) do {
          put (current_monkey.items, tab(many(&digits)))
        }
      } else if ="Operation: " then {
        ="new = old "
        current_monkey.operator := move(1)
        move(1)
        current_monkey.operand := tab(0)
      } else if ="Test: " then {
        ="divisible by "
        current_monkey.divisible := integer(tab(0))
      } else if ="If true: " then {
        ="throw to monkey "
        current_monkey.on_true := integer(tab(0))
      } else if ="If false: " then {
        ="throw to monkey "
        current_monkey.on_false := integer(tab(0))
      } else {
        if \current_monkey then {
          put(monkey_data, current_monkey)
        }
      }
    }
  }

  close(file)

  return monkey_data
end

# Given a list of monkey definitions, in index order,
# do one round through every monkey and item
procedure do_round(monkey_data, divide_by_three)
  local item, monkey, new_worry, simplifier

  every monkey := !monkey_data do {
    while *monkey.items > 0 do {
      item := pop(monkey.items)
      monkey.num_inspections +:= 1
      new_worry := compute_worry(monkey.operator, monkey.operand, item)
      if \divide_by_three then
        new_worry /:= 3
      else {
        # all divisors are primes, so we can modulo by their product
        simplifier := 1
        every simplifier *:= (!monkey_data).divisible
        new_worry %:= simplifier
      }
      if new_worry % monkey.divisible = 0 then {
        put(monkey_data[monkey.on_true+1].items, new_worry)
      } else {
        put(monkey_data[monkey.on_false+1].items, new_worry)
      }
    }
  }
end

# Computes new worry value
procedure compute_worry(operator, operand, old_value)
  if operand == "old" then {
    return operator(old_value, old_value)
  } else {
    return operator(old_value, integer(operand))
  }
end

Page from Peter's Scrapbook, output from a VimWiki on 2024-01-29.