DEV Community

Cover image for Advent of Code 2019 Solution Megathread - Day 5: Sunny with a Chance of Asteroids
Jon Bristow
Jon Bristow

Posted on • Updated on

Advent of Code 2019 Solution Megathread - Day 5: Sunny with a Chance of Asteroids

Oof! The opcode from Day 2 is back with a vengeance.

Day 5 - The Problem

As we feared, the IntCode interpreter is back. It looks the air conditioning unit is also controlled by the same kind of IntCode interpreter as we worked on for our fuel manager, but with more options.

Part 1 was a doozy of trying to figure out why things were loading/unloading from the wrong places.

Part 2 felt more like a victory lap... but this may be because I had so many problems parsing what Part 1 wanted me to do.

This was tough, but more from a comprehension standpoint compared to the actual implementation...

Edit: Early consensus is that the code is coming out “ugly”. So hopefully me noting this here will make people feel a little safer about sharing despite being self-conscious.

Ongoing Meta

Dev.to List of Leaderboards

If you were part of Ryan Palo's leaderboard last year, you're still a member of that!

If you want me to add your leaderboard code to this page, reply to one of these posts and/or send me a DM containing your code and any theming or notes you’d like me to add. (You can find your private leaderboard code on your "Private Leaderboard" page.)

I'll edit in any leaderboards that people want to post, along with any description for the kinds of people you want to have on it. (My leaderboard is being used as my office's leaderboard.) And if I get something wrong, please call me out or message me and I’ll fix it ASAP.

There's no limit to the number of leaderboards you can join, so there's no problem belonging to a "Beginner" and a language specific one if you want.

Neat Statistics

I'm planning on adding some statistics, but other than "what languages did we see yesterday" does anyone have any ideas?

Languages Seen On Day 04

  • JavaScript x 5
  • Python x 4
  • Kotlin x 2
  • Ruby x 2
  • Rust x 2
  • Clojure
  • COBOL
  • Elixir
  • Haskell
  • PHP
  • Prolog
  • Swift
  • Zig

Top comments (21)

Collapse
 
rpalo profile image
Ryan Palo

OK, I'm really, really happy with how my refactoring on Day 2 came out. Even the addition of the input modes fit into place in nice, neat, decoupled chunks. And then it was a matter of letting my interpreter know about all of the new opcodes.

Note: I was very much not expecting my Day 2 setup to hold up this well. I think I got exceptionally lucky at how today's puzzle was structured. I'm fully expecting to run into a future IntCode puzzle in the coming days that blows my architecture out of the water and makes me start over.

My actual Day 5 module is not very impressive:

/// Day 5: Sunny with a Chance of Asteroids
/// 
/// Test out the Thermal Environment Supervision Terminal
/// in the Intcode Interpreter

use std::fs;
use crate::intcode;

/// Parses the input.  Expects a single line of integers separated by
/// commas
fn parse_input() -> Vec<isize> {
    let text: String = fs::read_to_string("data/day5.txt").unwrap();
    let cleaned = text.trim();
    cleaned.split(",").map( |c| c.parse().unwrap()).collect()
}

pub fn run() {
    let ints = parse_input();
    let mut interpreter = intcode::IntcodeInterpreter::new(ints);
    interpreter.run();
}

Here's where all the work gets done. Also, side note, as a happy fallout of how Rust's stdin handles itself, I can either enter values or pipe them in one-per-line, if future input gets more complicated :)

use std::io;
use std::fs;

/// An Intcode Interpreter is a virtual machine that uses opcodes
/// to modify its internal memory
pub struct IntcodeInterpreter {
    memory: Vec<isize>,
    ip: usize,
}

impl IntcodeInterpreter {
    pub fn new(memory: Vec<isize>) -> Self {
        Self { memory, ip: 0}
    }

    /// Sets a memory address to a value
    pub fn set(&mut self, position: usize, value: isize) {
        self.memory[position] = value;
    }

    /// Reads from a memory address
    pub fn get(&self, position: usize) -> isize {
        self.memory[position]
    }

    /// Shows the memory for debugging
    pub fn print(&self) {
        println!("{:?}", self.memory);
    }

    /// Get the current instruction
    pub fn current_instruction(&self) -> isize {
        self.get(self.ip) % 100
    }

    /// Runs the program in memory until the stopcode (99) is reached
    /// 
    /// All new ops should have their own method.
    /// They take no rust args, but read in args as needed and
    /// shift the instruction pointer when they're done.
    /// Steps should be the number of args used + 1 for the opcode
    pub fn run(&mut self) {
        loop {
            match self.current_instruction() {
                1 => self.op1(),
                2 => self.op2(),
                3 => self.op3(),
                4 => self.op4(),
                5 => self.op5(),
                6 => self.op6(),
                7 => self.op7(),
                8 => self.op8(),
                99 => return,
                _ => panic!("Unrecognized opcode {}.", self.get(self.ip)),
            };
        }
    }

    /// Reads a number from STDIN
    fn read_stdin() -> isize {
        let mut buffer = String::new();
        io::stdin().read_line(&mut buffer).expect("STDIN read failed.");
        buffer.trim().parse::<isize>().unwrap()
    }

    /// Write a number to STDOUT
    fn write_stdout(number: isize) {
        println!("{}", number);
    }

    /// Process the parameter mode and provide the value given
    /// as a parameter
    fn arg(&self, offset: usize) -> isize {
        let new_index = (self.ip + offset) % self.memory.len();
        let mode = (self.memory[self.ip] / 10isize.pow(1 + offset as u32)) % 2;
        if mode == 1 {
            self.memory[new_index]
        } else if mode == 0 {
            self.get(self.memory[new_index] as usize)
        } else {
            panic!("Unknown parameter mode {}", mode);
        }
    }

    /// Returns the address to write output to
    fn output_address(&self, offset: usize) -> usize {
        let new_index = (self.ip + offset) % self.memory.len();
        self.memory[new_index] as usize
    }

    /// Steps the IP forward "count" steps, wrapping if needed
    fn step(&mut self, count: usize) {
        self.ip = (self.ip + count) % self.memory.len();
    }

    /// Add [1] + [2], store in [3]
    fn op1(&mut self) {
        let in1 = self.arg(1);
        let in2 = self.arg(2);
        let out = self.output_address(3);

        self.set(out, in1 + in2);

        self.step(4);
    }

    /// Mult [1] * [2], store in [3]
    fn op2(&mut self) {
        let in1 = self.arg(1);
        let in2 = self.arg(2);
        let out = self.output_address(3);

        self.set(out, in1 * in2);

        self.step(4);
    }

    /// Read one value from STDIN and store it in [1]
    fn op3(&mut self) {
        let out = self.output_address(1);

        self.set(out, Self::read_stdin());

        self.step(2);
    }

    /// Read [1] and send it to STDOUT
    fn op4(&mut self) {
        Self::write_stdout(self.arg(1));

        self.step(2);
    }

    /// If [1] != 0, set IP -> [2], else nothing
    fn op5(&mut self) {
        if self.arg(1) != 0 {
            self.ip = self.arg(2) as usize;
        } else {
            self.step(3);
        }
    }

    /// if [1] == 0, set IP -> [2], else nothing
    fn op6(&mut self) {
        if self.arg(1) == 0 {
            self.ip = self.arg(2) as usize;
        } else {
            self.step(3);
        }
    }

    /// if [1] < [2], set [3] to 1, else 0
    fn op7(&mut self) {
        let out = self.output_address(3);

        if self.arg(1) < self.arg(2) {
            self.set(out, 1);
        } else {
            self.set(out, 0);
        }

        self.step(4);
    }

    /// if [1] == [2], set [3] to 1, else 0
    fn op8(&mut self) {
        let out = self.output_address(3);

        if self.arg(1) == self.arg(2) {
            self.set(out, 1);
        } else {
            self.set(out, 0);
        }

        self.step(4);
    }
}
Collapse
 
fiddlerpianist profile image
fiddlerpianist • Edited

I really enjoyed this one, actually. This is so much better than this time last year, when I was doing all kinds of input parsing tricks and tripping over log files. Give me straight-up integers in a comma-separated list any day!

I decided to make my code really readable and branched out into creating 2 Python enums and a class. It made writing the code feel like a snap, as I no longer had to wrap my brain around all kinds of numbers and could hang my head on descriptive operations.

A couple of the lines are unnecessary (like writing the repl function as well as there's a DRY violation with parameter interpretation)...but I really like the ternary if/then/else operator in Python. I'm used to Java's print(x == 5 ? "yup" : "nope")... print("yup" if x is 5 else "nope") is far superior!

from enum import Enum

# Returns the number at the given position (0 being the rightmost)
def get_nth_digit(n, number):
    return number // 10**n % 10

class Operation(Enum):
    ADDITION = 1
    MULTIPLICATION = 2
    INPUT = 3
    OUTPUT = 4
    JUMP_IF_TRUE = 5
    JUMP_IF_FALSE = 6
    LESS_THAN = 7
    EQUALS = 8
    TERMINATION = 99

class Mode(Enum):
    POSITION = 0
    IMMEDIATE = 1

class Instruction:
    def __init__(self, opcode):
        # instruction: 1 is add, 2 is multiply, 3 is input, 4 is output, 99 is end
        self.operation = Operation(get_nth_digit(1, opcode) * 10 + get_nth_digit(0, opcode))
        # mode: 0 is indirect, 1 is immediate
        self.modes = list(map(Mode, [get_nth_digit(2, opcode), get_nth_digit(3, opcode), get_nth_digit(4, opcode)]))

    def __str__(self):
        return "{}, {}".format(repr(self.operation), self.modes)


def opcode_run(ops):
    i = 0
    while ops[i] != 99:
        instruction = Instruction(ops[i])
        if instruction.operation is Operation.ADDITION:
            first = ops[i+1] if instruction.modes[0] is Mode.IMMEDIATE else ops[ops[i+1]]
            second = ops[i+2] if instruction.modes[1] is Mode.IMMEDIATE else ops[ops[i+2]]
            result = first + second
            # the last mode should *always* be POSITION
            ops[ops[i+3]] = result
            i += 4
        elif instruction.operation is Operation.MULTIPLICATION:
            first = ops[i+1] if instruction.modes[0] is Mode.IMMEDIATE else ops[ops[i+1]]
            second = ops[i+2] if instruction.modes[1] is Mode.IMMEDIATE else ops[ops[i+2]]
            val = first * second
            # the last mode should *always* be POSITION
            ops[ops[i+3]] = val
            i += 4
        elif instruction.operation is Operation.INPUT:
            stuff = input("Please enter ID: --> ")
            ops[ops[i+1]] = int(stuff)
            i += 2
        elif instruction.operation is Operation.OUTPUT:
            print (ops[ops[i+1]])
            i += 2
        elif instruction.operation is Operation.JUMP_IF_TRUE:
            first = ops[i+1] if instruction.modes[0] is Mode.IMMEDIATE else ops[ops[i+1]]
            second = ops[i+2] if instruction.modes[1] is Mode.IMMEDIATE else ops[ops[i+2]]
            if first != 0:
                i = second
            else:
                i += 3
        elif instruction.operation is Operation.JUMP_IF_FALSE:
            first = ops[i+1] if instruction.modes[0] is Mode.IMMEDIATE else ops[ops[i+1]]
            second = ops[i+2] if instruction.modes[1] is Mode.IMMEDIATE else ops[ops[i+2]]
            if first == 0:
                i = second
            else:
                i += 3
        elif instruction.operation is Operation.LESS_THAN:
            first = ops[i+1] if instruction.modes[0] is Mode.IMMEDIATE else ops[ops[i+1]]
            second = ops[i+2] if instruction.modes[1] is Mode.IMMEDIATE else ops[ops[i+2]]
            ops[ops[i+3]] = 1 if first < second else 0
            i += 4
        elif instruction.operation is Operation.EQUALS:
            first = ops[i+1] if instruction.modes[0] is Mode.IMMEDIATE else ops[ops[i+1]]
            second = ops[i+2] if instruction.modes[1] is Mode.IMMEDIATE else ops[ops[i+2]]
            ops[ops[i+3]] = 1 if first == second else 0
            i += 4
    print ("HALT!")

# Initialize: open file, turn all op codes into integers
with open('day05.txt') as f:
    # split line into operation list
    opsAsStrings = f.read().split(",")
    # turn them all into integers
    ops = list(map(int, opsAsStrings))

# Part One: Enter 1 when prompted and enter number right before HALT!
# Part Two: Enter 5 when prompted and enter number right before HALT!
myOps = ops.copy()
opcode_run(myOps)
Collapse
 
savagepixie profile image
SavagePixie • Edited

Today's code is a lot uglier than yesterday's, but, oh well, it works. My JavaScript solution:

const opcodes = {
    1: {
            func: (intcode, posA, posB, posC, modA=0, modB=0) =>
                intcode[posC] = applyMode(intcode, posA, modA) + applyMode(intcode, posB, modB),
            next: 4,
        },
    2: {
            func: (intcode, posA, posB, posC, modA=0, modB=0) =>
                intcode[posC] = applyMode(intcode, posA, modA) * applyMode(intcode, posB, modB),
            next: 4,
        },
    3: {
            func: (intcode, pos, input) => intcode[pos] = input,
            next: 2,
        },
    4: {
            func: (intcode, pos, _, __, mod=0) => applyMode(intcode, pos, mod),
            next: 2,
        },
    5: {
            func: (intcode, posA, posB, _, modA=0, modB=0, position) =>
                opcodes[5].next = applyMode(intcode, posA, modA) != 0
                    ? applyMode(intcode, posB, modB) - position
                    : 3,
            next: 3
    },
    6: {
            func: (intcode, posA, posB, _, modA=0, modB=0, position) =>
                opcodes[6].next = applyMode(intcode, posA, modA) == 0
                    ? applyMode(intcode, posB, modB) - position
                    : 3,
            next: 3,
        },
    7: {
            func: (intcode, posA, posB, posC, modA=0, modB=0) =>
                intcode[posC] = applyMode(intcode, posA, modA) < applyMode(intcode, posB, modB)
                    ? 1
                    : 0,
            next: 4,
    },
    8: {
            func: (intcode, posA, posB, posC, modA=0, modB=0) =>
                intcode[posC] = applyMode(intcode, posA, modA) == applyMode(intcode, posB, modB)
                    ? 1
                    : 0,
            next: 4,
    },
}

const applyMode = (intcode, pos, mode) => mode == 0 ? intcode[pos] : pos

const computeOpcode = (intcode, input, position=0, diagnosticCode) => {
    if (intcode[position] == 99) return diagnosticCode
    const [ oc, _, m1, m2, m3 ] = intcode[position].toString().split('').reverse().map(x => +x)
    const [ a, b, dest ] = intcode.slice(position + 1)
    const bInput = oc == 3 ? input : b
    diagnosticCode = opcodes[oc].func(intcode, a, bInput, dest, m1, m2, position)
    if (oc == 4) console.log(diagnosticCode)
    return computeOpcode(intcode, input, position + opcodes[oc].next, diagnosticCode)
}

module.exports = input => {
    const data = input.split(',').map(x => +x)
    const dataOne = Object.assign([], data)
    const dataTwo = Object.assign([], data)
    const partOne = computeOpcode(dataOne, 1)
    console.log('Part Two')
    const partTwo = computeOpcode(dataTwo, 5)
    return({ partOne, partTwo })
}
Collapse
 
lindakatcodes profile image
Linda Thompson • Edited

So, I actually reasonably enjoyed this day! :) It did take a good bit of deciphering exactly what I needed to make it do....the wording is so similar in the description of things! But once I got it, I knew what to do, which I honestly really like. lol :)

I did refactor a bit of what I did for day 2, so this currently contains all of my opcode options. Fingers crossed it will be easier to add onto when the next day comes to update it! (Though knowing AoC, chances of that might be slim lol)

I've got a decent bit of descriptive comments in here, to help make things easier for myself in the future. :) So hopefully that helps some folks out, too! Also, I really enjoy switch statements, and I like that they've worked for multiple days so far this year. lol

// Memory - initial puzzle input, a list of integers


const input = [3,225,1,225,6,6,1100,99,226]; // shortened input for brevity

// copy of initial input, so we can reset properly
let inputCopy = [...input];

// opcode 1 - get values at position 1&2 right after code, add together, store in position 3
function opcode1 (a, b, c, p) {
  let valA = ptest(p[0], a);
  let valB = ptest(p[1], b);
  inputCopy[c] = valA + valB;
  console.log(`op1: ${valA} + ${valB} = ${valA + valB}`)
}

// opcode 2 - get values at position 1&2 right after code, multiply, store in position 3
function opcode2 (a, b, c, p) {
  let valA = ptest(p[0], a);
  let valB = ptest(p[1], b);
  inputCopy[c] = valA * valB;
  console.log(`op2: ${valA} * ${valB} = ${valA * valB}`)
}


// opcode 3 - takes an input and stores in position 1
function opcode3 (iv, s) {
  inputCopy[s] = iv;
  console.log(`op3: putting ${iv} into spot ${s}`)
}

// opcode 4 - outputs value at position 1
function opcode4 (s, p) {
  let val = ptest(p[0], s);
  console.log(`op4: outputting ${val}`)
  return val;
}

// opcode 5 - if position 1 != 0, changes i to position 2; otherwise, does nothing
function opcode5 (a, b, inp, p) {
  let valA = ptest(p[0], a);
  let valB = ptest(p[1], b);

  if (valA !== 0) {
    inp = valB;
  }
  console.log(`op5: inst. pointer is now ${inp}`);
  return inp;
}

// opcode 6 - if position 1 == 0, changes i to position 2; otherwise, does nothing
function opcode6 (a, b, inp, p) {
  let valA = ptest(p[0], a);
  let valB = ptest(p[1], b);

  if (valA === 0) {
    inp = valB;
  }
  console.log(`op6: inst. pointer is now ${inp}`);

  return inp;
}

// opcode 7 - if position 1 < position 2, position 3 is set to 1; otherwise, it's set to 0
function opcode7 (a, b, c, p) {
  let valA = ptest(p[0], a);
  let valB = ptest(p[1], b);

  if (valA < valB) {
    inputCopy[c] = 1;
  } else {
    inputCopy[c] = 0;
  }
  console.log(`op7: comparing if ${valA} is < ${valB}`);
}

// opcode 8 - if position 1 == position 2, position 3 is set to 1; otherwise, it's set to 0
function opcode8 (a, b, c, p) {
  let valA = ptest(p[0], a);
  let valB = ptest(p[1], b);

  if (valA == valB) {
    inputCopy[c] = 1;
  } else {
    inputCopy[c] = 0;
  }
  console.log(`op8: comparing if ${valA} equals ${valB}`);
}

// allows parameter modes - checks for 0 or 1, decides if returning actual number called or position of number in input
function ptest(param, checkval) {
  if (param == 0 || !param) {
    return inputCopy[checkval];
  } else if (param == 1) {
    return checkval;
  }
}

// opcode 99 - stop program

// run through memory input, following instructions until 99 is hit
function runProgram() {
  for (let i = 0; i < inputCopy.length; i++) {
    if (inputCopy[i] === 99) {
      break;
    }

    let instruct = inputCopy[i].toString();
    let opval = parseInt(instruct.slice(-2), 10);
    let params = instruct.slice(0, -2).split('').reverse();


    let ione = inputCopy[i+1];
    let itwo = inputCopy[i+2];
    let ithree = inputCopy[i+3];

    switch (opval) {
      case 01:
        opcode1(ione, itwo, ithree, params);
        i += 3;
        break;
      case 02:
        opcode2(ione, itwo, ithree, params);
        i += 3;
        break;
      case 03:
        opcode3(inputval, ione);
        i++;
        break;
      case 04:
        let res = opcode4(ione, params);
        console.log(res);
        i++;
        break;
      case 05:
        let checkt = opcode5(ione, itwo, i, params);
        if (i != checkt) {
          i = checkt - 1;
        } else {
          i += 2;
        }
        break;
      case 06:
        let checkf = opcode6(ione, itwo, i, params);
        if (i != checkf) {
          i = checkf - 1;
        } else {
          i += 2;
        }
        break;
      case 07:
        opcode7(ione, itwo, ithree, params);
        i += 3;
        break;
      case 08:
        opcode8(ione, itwo, ithree, params);
        i += 3;
        break;
    }
  }
}

// for part 1, inputval is 1; for part 2, it's 5
let inputval = 5;
runProgram();
Collapse
 
nordfjord profile image
Einar Norðfjörð

JavaScript solution

const { createInterface } = require('readline')

const rl = createInterface({
  input: process.stdin,
  output: process.stdout
})

const question = str =>
  new Promise(res => {
    rl.question(str, res)
  })

const INSTRUCTIONS = {
  ADD: 1,
  MULT: 2,
  INPUT: 3,
  OUTPUT: 4,
  JUMP_IF_TRUE: 5,
  JUMP_IF_FALSE: 6,
  LESS_THAN: 7,
  EQUALS: 8,
  HALT: 99
}

async function runProgram(instructions) {
  instructions = instructions.slice()

  loop: for (let i = 0; i < instructions.length; ++i) {
    const instruction = instructions[i]
    const parsed = String(instruction)
      .padStart(5, '0')
      .split('')
    const valueMode = (value, mode = '0') =>
      mode === '0' ? instructions[value] : value
    const opCode = Number(parsed.slice(3).join(''))
    const modes = parsed.slice(0, 3)
    switch (opCode) {
      case INSTRUCTIONS.ADD: {
        const x = valueMode(instructions[++i], modes[2])
        const y = valueMode(instructions[++i], modes[1])
        instructions[instructions[++i]] = x + y
        break
      }
      case INSTRUCTIONS.MULT: {
        const x = valueMode(instructions[++i], modes[2])
        const y = valueMode(instructions[++i], modes[1])
        instructions[instructions[++i]] = x * y
        break
      }
      case INSTRUCTIONS.INPUT: {
        instructions[instructions[++i]] = Number(await question('input: '))
        break
      }
      case INSTRUCTIONS.OUTPUT: {
        console.log(valueMode(instructions[++i], modes[2]))
        break
      }
      case INSTRUCTIONS.JUMP_IF_TRUE: {
        const compare = valueMode(instructions[++i], modes[2])
        const jumpTo = valueMode(instructions[++i], modes[1]) - 1
        if (compare != 0) {
          i = jumpTo
        }
        break
      }
      case INSTRUCTIONS.JUMP_IF_FALSE: {
        const compare = valueMode(instructions[++i], modes[2])
        const jumpTo = valueMode(instructions[++i], modes[1]) - 1
        if (compare == 0) {
          i = jumpTo
        }
        break
      }
      case INSTRUCTIONS.LESS_THAN: {
        const x = valueMode(instructions[++i], modes[2])
        const y = valueMode(instructions[++i], modes[1])
        instructions[instructions[++i]] = x < y ? 1 : 0
        break
      }
      case INSTRUCTIONS.EQUALS: {
        const x = valueMode(instructions[++i], modes[2])
        const y = valueMode(instructions[++i], modes[1])
        instructions[instructions[++i]] = x === y ? 1 : 0
        break
      }
      case INSTRUCTIONS.HALT:
        break loop
    }
  }
  return instructions[0]
}

module.exports = runProgram

if (process.argv[1] === __filename) {
  const input = require('fs')
    .readFileSync('./puzzle-input.txt')
    .toString()

  const instructions = input.split(',').map(Number)
  runProgram(instructions).then(() => process.exit(0))
}
Collapse
 
maxart2501 profile image
Massimo Artizzu

Jayyy-zus today was a long challenge 😫
I don't like it much when AoC does that, and when it back-reference problems from past days.

Anyway, here's my solution in JavaScript - just for part 2, as part 1 is basically embedded, you just have to change the input value to 1.

Not particularly proud of this, as the challenge itself was long but a bit bland. It's just parse, get instruction, execute, rinse and repeat. The only interesting part is the use of a generator function I guess...

const parameterCount = { 1: 3, 2: 3, 3: 1, 4: 1, 5: 2, 6: 2, 7: 3, 8: 3, 99: 0 };
const codes = input.split(',').map(Number);

let instructionPointer = 0;
function* getInstructions() {
  while (instructionPointer < codes.length) {
    const instruction = codes[instructionPointer];
    const opcode = instruction % 100;
    const modes = Array.from({ length: 3 }, (_, index) => Math.floor(instruction / 10 ** (index + 2) % 10));
    const paramCount = parameterCount[opcode];
    const params = codes.slice(instructionPointer + 1, instructionPointer + paramCount + 1);
    instructionPointer += paramCount + 1;
    yield { opcode, modes, params };
  }
}

function getValue(index, mode) {
  return mode === 0 ? codes[index] : index;
}

function execute({ opcode, modes, params }) {
  switch (opcode) {
    case 1:
      codes[params[2]] = getValue(params[0], modes[0]) + getValue(params[1], modes[1]);
      break;
    case 2:
      codes[params[2]] = getValue(params[0], modes[0]) * getValue(params[1], modes[1]);
      break;
    case 3:
      codes[params[0]] = 5;
      break;
    case 4:
      return getValue(params[0], modes[0]);
    case 5:
      if (getValue(params[0], modes[0])) {
        instructionPointer = getValue(params[1], modes[1]);
      }
      break;
    case 6:
      if (getValue(params[0], modes[0]) === 0) {
        instructionPointer = getValue(params[1], modes[1]);
      }
      break;
    case 7:
      codes[params[2]] = +(getValue(params[0], modes[0]) < getValue(params[1], modes[1]));
      break;
    case 8:
      codes[params[2]] = +(getValue(params[0], modes[0]) === getValue(params[1], modes[1]));
      break;
  }
}

let lastOutput;
for (const instruction of getInstructions()) {
  if (instruction.opcode === 99) {
    break;
  }
  const output = execute(instruction);
  if (typeof output === 'number') {
    lastOutput = output;
  }
}
console.log(lastOutput);

As usual, I'm publishing my solutions on GitHub.

Collapse
 
ballpointcarrot profile image
Christopher Kruse

I built my original solution to this on top of my Day 2 solution, but was really unhappy about how that solution had turned out. So, I spent time over the weekend and into this morning building out a more clean solution around the Intcode Computer (knowing that it's going to likely be used more later).

The implementation is below (and also accessible here), along with the new execution calls for Day 2:

;; incode_computer.clj
(ns aoc2019.intcode-computer)

(def operations
  {1 {:params 3, :action (fn [p1 p2 _] {:output (+ p1 p2)})}
   2 {:params 3, :action (fn [p1 p2 _] {:output (* p1 p2)})}
   3 {:params 1, :action (fn [_]
                           {:output (Integer/parseInt (read-line))})}
   4 {:params 1, :action (fn [p1] (println p1) {})}
   5 {:params 2, :action (fn [p1 p2] (if (not= 0 p1) {:jump p2} {}))}
   6 {:params 2, :action (fn [p1 p2] (if (= 0 p1) {:jump p2} {}))}
   7 {:params 3, :action (fn [p1 p2 _] (if (< p1 p2) {:output 1} {:output 0}))}
   8 {:params 3, :action (fn [p1 p2 _] (if (= p1 p2) {:output 1} {:output 0}))}
   99 {:params 0}})

(defn fetch
  "Retrieves the opcode, and the number of arguments for that opcode"
  [machine]
  (let [opcode (get (:instructions machine) (:program-counter machine))
        action (mod opcode 100)]
    {:opcode opcode
     :action action
     :params (:params (get operations action))}))

(defn opcode-to-parameters
  [opcode]
  (let [modes (->> opcode
                   (format "%05d")
                   (into [])
                   (take 3)
                   (reverse))]
    (map #(condp = %
            \0 {:mode :direct}
            \1 {:mode :immediate}) modes)))

(defn retrieve-args
  "Retrieves the parameters of the instruction from the code list, and
  returns a map of their positions, values, and mode"
  [machine fetched-instruction]
  (let [in-params (take (:params fetched-instruction)
                        (drop (inc (:program-counter machine)) (:instructions machine)))
        op-params (opcode-to-parameters (:opcode fetched-instruction))
        joiner (fn [in-param op-param]
                 {:mode (:mode op-param)
                  :param-pos in-param
                  :param-val (get (:instructions machine) in-param)})]
    (assoc fetched-instruction :params
           (map joiner in-params op-params))))

(defn execute
  "Executes the instruction provided, and returns any results of that
  execution."
  [instruction]
  (let [params (map #(condp = (:mode %)
                       :direct (:param-val %)
                       :immediate (:param-pos %)) (:params instruction))
        action (:action (get operations (:action instruction)))]
    (if action
      (assoc instruction :result (apply action params))
      (assoc instruction :result :stop))))

(defn apply-changes
  "Applies any changes based on the result of the instruction. Returns a
  potentially modified instruction list."
  [machine instruction-with-results]
  (let [result (:result instruction-with-results)
        inc-program-counter (fn []
                              (+ 1 (:program-counter machine)
                                 (count (:params instruction-with-results))))]
    (if (:jump result)
      (assoc machine :program-counter (:jump result))
      (if (= result :stop)
        (assoc machine :stop true)
        (if (:output result)
          (let [adjusted-instructions (assoc (:instructions machine)
                                             (:param-pos (last (:params instruction-with-results)))
                                             (:output result))]
            (assoc machine :instructions adjusted-instructions :program-counter (inc-program-counter)))
          (assoc machine :program-counter (inc-program-counter)))))))

;; day2.clj
(ns aoc2019.day2
  (:require [clojure.string :as st]
            [aoc2019.intcode-computer :as ic]))

(defn parse-input
  "Converts input into atom holding instruction integers."
  [input]
  (map #(Integer/parseInt %) (st/split (st/trim-newline input) #",")))

(defn new-calculate
  [input]
  (let [instructions (vec (parse-input input))]
    (loop [machine {:instructions instructions
                    :program-counter 0
                    :stop false}]
      (if (:stop machine)
        (first (:instructions machine))
        (recur (->> (ic/fetch machine)
                    (ic/retrieve-args machine)
                    (ic/execute)
                    (ic/apply-changes machine)))))))

(defn preprocess-input
  ([input]
   (preprocess-input input 12 2))
  ([input noun verb]
   (let [parsed-input (vec (parse-input input))]
     (st/join "," (-> parsed-input
                      (assoc 1 noun)
                      (assoc 2 verb))))))

(defn p2019-02-part1
  [input]
  (new-calculate (preprocess-input input)))

(defn p2019-02-part2
  "Using the calculator from part 1, determine the proper inputs for our expected value."
  [input]
  (let [expected-result 19690720
        noun (atom 0)
        verb (atom 0)]
    (while (not= expected-result (new-calculate (preprocess-input input @noun @verb)))
      (swap! verb inc)
      (if (= @verb 100)
        (do
          (swap! noun inc)
          (reset! verb 0))))
    (+ @verb (* 100 @noun))))

Collapse
 
jbristow profile image
Jon Bristow

Initial kotlin solution (I'm going to clean this up a LOT, but in the interest of sharing what I learn, I promise not to pave this post over.)

import arrow.core.None
import arrow.core.Option
import arrow.core.Some
import arrow.core.some
import java.nio.file.Files
import java.nio.file.Paths

object Day05 {
    sealed class Mode {
        object Immediate : Mode()
        object Position : Mode()
    }

    fun Char.toMode(): Mode {
        return when (this) {
            '0' -> Mode.Position
            '1' -> Mode.Immediate
            else -> throw Error("Bad mode $this")
        }
    }

    fun parseInstruction(instruction: String): Instruction {
        return Day05.Instruction(
            opcode = instruction.takeLast(2).toInt(),
            paramModeInput = instruction.take(
                kotlin.math.max(
                    0,
                    instruction.count() - 2
                )
            ).map { it.toMode() }.reversed()
        )
    }

    class Instruction(val opcode: Int, paramModeInput: List<Mode>) {
        val paramModes: Array<Mode> = arrayOf(Mode.Position, Mode.Position, Mode.Position, Mode.Position)

        init {
            paramModeInput.forEachIndexed { i, it ->
                paramModes[i] = it
            }
        }

        override fun toString(): String {
            return "Instruction[$opcode, ${paramModes.contentToString()}]"
        }
    }

    fun handle(pointer: Int, input: Int, code: Array<String>): Option<Int> {
        val instr = parseInstruction(code[pointer])
        val inputs = code.drop(pointer + 1).take(3)
        val params = inputs
            .zip(instr.paramModes).map { (value, mode) ->
                when (mode) {
                    Mode.Immediate -> value.toInt()
                    Mode.Position -> {
                        code.getOrNull(value.toInt())?.toInt() ?: -1000000
                    }
                }
            }
        return when (instr.opcode) {
            1 -> {
                code[inputs[2].toInt()] = (params[0] + params[1]).toString()
                (pointer + 4).some()
            }
            2 -> {
                code[inputs[2].toInt()] = (params[0] * params[1]).toString()
                (pointer + 4).some()
            }
            3 -> {
                code[inputs[0].toInt()] = input.toString()
                (pointer + 2).some()
            }
            4 -> {
                println("output:${params[0]}")
                (pointer + 2).some()
            }
            5 -> {
                when (params[0]) {
                    0 -> pointer + 3
                    else -> params[1]
                }.some()
            }
            6 -> {
                when (params[0]) {
                    0 -> params[1]
                    else -> pointer + 3
                }.some()
            }
            7 -> {
                code[inputs[2].toInt()] = when {
                    params[0] < params[1] -> "1"
                    else -> "0"
                }
                (pointer + 4).some()
            }
            8 -> {
                code[inputs[2].toInt()] = when {
                    params[0] == params[1] -> "1"
                    else -> "0"
                }
                (pointer + 4).some()
            }
            99 -> Option.empty<Int>()
            else -> throw Error("Unknown opcode: ${instr.opcode}")
        }
    }

    tailrec fun step(
        code: Array<String>,
        input: Int,
        instructionPointer: Option<Int> = 0.some()
    ) {
        return when (instructionPointer) {
            is None -> Unit
            is Some<Int> -> {
                val nextInstruction = handle(instructionPointer.t, input, code)
                step(code, input, nextInstruction)
            }
        }
    }

    const val FILENAME = "src/main/resources/day05.txt"
}

fun main() {
    // Part 01
    Day05.step(
        code = Files.readString(Paths.get(Day05.FILENAME)).trim().split(",").toTypedArray(),
        input = 1
    )

    // Part 02
    Day05.step(
        code = Files.readString(Paths.get(Day05.FILENAME)).trim().split(",").toTypedArray(),
        input = 5
    )
}

Collapse
 
jbristow profile image
Jon Bristow • Edited

Ok, as promised, here's the code I wanted to write, but I was halfway done before I saw it, and I wanted the answer so bad I didn't give myself the time to rewrite.

EDIT 2: Oops, all monads.

import arrow.core.Either
import arrow.core.Option
import arrow.core.flatMap
import arrow.core.getOrElse
import arrow.core.left
import arrow.core.right
import arrow.core.some
import java.nio.file.Files
import java.nio.file.Paths

sealed class Mode {
    object Immediate : Mode()
    object Position : Mode()
}

sealed class Instruction {
    abstract val opcodeFormat: String
    abstract fun execute(pointer: Int, code: Array<Int>, params: List<Int>): Either<Option<String>, Int>

    open fun findInputs(code: Array<Int>, pointer: Int) =
        code.drop(pointer + 1)
            .take(3)
            .zip(opcodeFormat.format(code[pointer] / 100).map {
                when (it) {
                    '0' -> Mode.Position
                    '1' -> Mode.Immediate
                    else -> throw Error("Bad mode $it")
                }
            }.reversed())
            .map { (it, mode) ->
                when (mode) {
                    Mode.Position -> code[it]
                    Mode.Immediate -> it
                }
            }

    sealed class ThreeParameterInstruction : Instruction() {
        override val opcodeFormat = "1%02d"

        class Add : ThreeParameterInstruction() {
            override fun execute(pointer: Int, code: Array<Int>, params: List<Int>): Either<Option<String>, Int> {
                code[params[2]] = params[0] + params[1]
                return (pointer + 4).right()
            }
        }

        class Multiply : ThreeParameterInstruction() {
            override fun execute(pointer: Int, code: Array<Int>, params: List<Int>): Either<Option<String>, Int> {
                code[params[2]] = params[0] * params[1]
                return (pointer + 4).right()
            }
        }

        class LessThan : ThreeParameterInstruction() {
            override fun execute(pointer: Int, code: Array<Int>, params: List<Int>): Either<Option<String>, Int> {
                code[params[2]] = when {
                    params[0] < params[1] -> 1
                    else -> 0
                }
                return (pointer + 4).right()
            }
        }

        class Equal : ThreeParameterInstruction() {
            override fun execute(pointer: Int, code: Array<Int>, params: List<Int>): Either<Option<String>, Int> {
                code[params[2]] = when {
                    params[0] == params[1] -> 1
                    else -> 0
                }
                return (pointer + 4).right()
            }
        }
    }

    sealed class TwoParameterInstruction : Instruction() {
        override val opcodeFormat = "%02d"

        class JumpIfTrue : TwoParameterInstruction() {
            override fun execute(pointer: Int, code: Array<Int>, params: List<Int>) =
                when (params[0]) {
                    0 -> pointer + 3
                    else -> params[1]
                }.right()
        }

        class JumpIfFalse : TwoParameterInstruction() {
            override fun execute(pointer: Int, code: Array<Int>, params: List<Int>) =
                when (params[0]) {
                    0 -> params[1]
                    else -> pointer + 3
                }.right()
        }
    }

    class SetFromInput(private val input: Int) : Instruction() {
        override val opcodeFormat = "1"
        override fun execute(pointer: Int, code: Array<Int>, params: List<Int>): Either<Option<String>, Int> {
            code[params[0]] = input
            return (pointer + 2).right()
        }
    }

    class Output : Instruction() {
        override val opcodeFormat = "0"
        override fun execute(pointer: Int, code: Array<Int>, params: List<Int>): Either<Option<String>, Int> {
            println("output: ${params[0]}")
            return (pointer + 2).right()
        }
    }

    object End : Instruction() {
        override val opcodeFormat: String
            get() = throw Error("No opcode format for End instructions.")

        override fun findInputs(code: Array<Int>, pointer: Int) = emptyList<Int>()
        override fun execute(pointer: Int, code: Array<Int>, params: List<Int>): Either<Option<String>, Int> =
            Option.empty<String>().left()
    }
}

object Day05 {

    private fun parseInstruction(instruction: String, input: Int) =
        when (instruction.takeLast(2).toInt()) {
            1 -> Instruction.ThreeParameterInstruction.Add().right()
            2 -> Instruction.ThreeParameterInstruction.Multiply().right()
            3 -> Instruction.SetFromInput(input).right()
            4 -> Instruction.Output().right()
            5 -> Instruction.TwoParameterInstruction.JumpIfTrue().right()
            6 -> Instruction.TwoParameterInstruction.JumpIfFalse().right()
            7 -> Instruction.ThreeParameterInstruction.LessThan().right()
            8 -> Instruction.ThreeParameterInstruction.Equal().right()
            99 -> Instruction.End.right()
            else -> "Problem parsing instruction $instruction".some().left()
        }

    private fun handleCodePoint(pointer: Int, input: Int, code: Array<Int>) =
        parseInstruction(code[pointer].toString(), input).flatMap { instr ->
            instr.execute(pointer, code, instr.findInputs(code, pointer))
        }

    tailrec fun step(
        code: Array<Int>,
        input: Int,
        instructionPointer: Either<Option<String>, Int> = Either.right(0)
    ): String = when (instructionPointer) {
        is Either.Left<Option<String>> -> instructionPointer.a.getOrElse { "Program terminated successfully." }
        is Either.Right<Int> -> {
            val nextInstruction = handleCodePoint(instructionPointer.b, input, code)
            step(code, input, nextInstruction)
        }
    }

    const val FILENAME = "src/main/resources/day05.txt"
}

fun main() {
    val problemInput = Files.readAllLines(Paths.get(Day05.FILENAME))
        .first()
        .split(",")
        .map { it.toInt() }

    // Part 01
    println("Part 01")
    Day05.step(code = problemInput.toTypedArray(), input = 1)

    // Part 02
    println("\nPart 02")
    Day05.step(code = problemInput.toTypedArray(), input = 5)

    println("\nDay 02")
    val day02Code = Files.readAllLines(Paths.get("src/main/resources/day02.txt")).first()
        .split(",")
        .map { it.toInt() }.toTypedArray()
    Day05.step(code = day02Code, input = 5)
    println(day02Code.take(10))
}
Collapse
 
ballpointcarrot profile image
Christopher Kruse

Definitely can say this implementation has a lot of class. 🤣

Thread Thread
 
jbristow profile image
Jon Bristow

Yeah, I wish there was a more elegant way of doing discriminated unions in Kotlin. I'm used to the lightweight kinds/types of Haskell. Arrow-KT lets you do basic stuff pretty nicely, but it gets ugly the more you try to be full functional style only.

I'm also disappointed in the type erasure.

Collapse
 
smh30 profile image
Stephanie Hope

I didn't enjoy this, it ate my entire evening, and I'm sad that it looks like I'm going to have to come back to it on future days. I've tried to clean it up so it's less of a nightmare to return to, but...

$input = file_get_contents("input5.txt");
$memory = explode(',', $input);
run_intcode();

function run_intcode()
{
    global $memory;

    $pointer = 0;
    $opcode_full =  "0000". $memory[$pointer];
    $opcode = substr($opcode_full, strlen($opcode_full) - 2);

    while ($opcode != 99) {

        $param1_mode = $opcode_full[-3];
        $param2_mode = $opcode_full[-4];

        $p1 = get_value($pointer+1, $param1_mode);
        $p2 = get_value($pointer+2, $param2_mode);

        switch ($opcode) {
            case 01:
                write_value($pointer+3,  $p1 + $p2);
                $pointer += 4;
                break;
            case 02:
                write_value($pointer+3,  $p1 * $p2);
                $pointer += 4;
                break;
            case 03:
                echo "enter the system ID number: ";
                $input_param = readline("enter the system to test: ");
                echo "\n";
                write_value($pointer+1, $input_param);
                $pointer += 2;
                break;
            case 04:
                echo 'output= ' . $p1 . "\n";
                $pointer += 2;
                break;
            case 05:
                if ($p1!=0){
                    $pointer = $p2;
                } else $pointer +=3;
                break;
            case 06:
                if($p1 ==0){
                    $pointer = $p2;
                } else $pointer += 3;
                break;
            case 07:
                write_value($pointer+3, $p1<$p2?1:0);
                $pointer +=4;
                break;
            case '08':
                write_value($pointer+3, $p1==$p2?1:0);
                $pointer += 4;
                break;
            default:
                echo "nope\n";
                break;
        }
        $opcode_full = "0000" . $memory[$pointer];
        $opcode = substr($opcode_full, strlen($opcode_full) - 2);
    }
}

function get_value($position, $mode){
    global $memory;
    $value = $mode ? $memory[$position] : $memory[$memory[$position]];
    return $value;
}

function write_value($position, $value){
    global $memory;
    $memory[$memory[$position]] = $value;
}
Collapse
 
katafrakt profile image
Paweł Świątkowski • Edited

I didn't reuse my code from day 2, instead I wrote a new one in Racket. I feel that the choice of the language was right, but I made a lot of mistakes and the task itself did not help with any example inputs. Finally I made it. It's not as bad as I feared, but could be better:

#lang racket

(require racket/match)

(define input (file->string "input"))
(define (codes input) 
    (map (lambda (elem) (string->number elem)) (string-split input ",")))

(define (parse-compound-opcode matches)
    (let* ([opcode (string->number (list-ref matches 2))]
        [modes-int (string->number (list-ref matches 1))]
        [modes (list (modulo modes-int 10) (modulo (quotient modes-int 10) 10) (modulo (quotient modes-int 100) 100))])
        (list opcode modes)))

(define (get-intructions index codes)
    (let* ([opcode (list-ref codes index)]
        [matches (regexp-match #px"(\\d+)(\\d{2})" (number->string opcode))])
        (if matches (parse-compound-opcode matches) (list opcode '(0 0 0)))))


(define (get-value codes position mode)
    (if (zero? mode) (list-ref codes (list-ref codes position)) (list-ref codes position)))

; operations
(define (do-the-math op codes index modes)
    (let* ([arg1 (get-value codes (+ 1 index) (list-ref modes 0))]
        [arg2 (get-value codes (+ 2 index) (list-ref modes 1))]
        [output-pos (get-value codes (+ 3 index) 1)]
        [result (op arg1 arg2)])
        (list-set codes output-pos result)))

(define (accept-input codes index modes)
    (let ([position (get-value codes (+ 1 index) 1)])
        (list-set codes position user-input)))

(define (append-output codes index modes outputs)
    (define value (get-value codes (+ 1 index) (list-ref modes 0)))
    (append outputs (list value)))

(define (jump-if bool codes index modes)
    (let ([value (get-value codes (+ 1 index) (list-ref modes 0))])
        (if (equal? bool (not (= 0 value))) (get-value codes (+ 2 index) (list-ref modes 1)) (+ index 3))))

(define (compare op codes index modes)
    (let* ([arg1 (get-value codes (+ 1 index) (list-ref modes 0))]
        [arg2 (get-value codes (+ 2 index) (list-ref modes 1))]
        [target (get-value codes (+ 3 index) 1)])
        (if (op arg1 arg2) (list-set codes target 1) (list-set codes target 0))))

(define (run-instruction opcode modes index codes outputs)
    (match opcode
        [1 (list (do-the-math + codes index modes) outputs 4)]
        [2 (list (do-the-math * codes index modes) outputs 4)]
        [3 (list (accept-input codes index modes) outputs 2)]
        [4 (list codes (append-output codes index modes outputs) 2)]
        [5 (list codes outputs (- (jump-if true codes index modes) index))]
        [6 (list codes outputs (- (jump-if false codes index modes) index))]
        [7 (list (compare < codes index modes) outputs 4)]
        [8 (list (compare = codes index modes) outputs 4)]))

(define (run-prog index codes outputs)
    (define instruction (get-intructions index codes))
    (define opcode (first instruction))
    (if (not (= opcode 99))
        (let* ([result (run-instruction opcode (second instruction) index codes outputs)]
            [new-codes (list-ref result 0)]
            [new-outputs (list-ref result 1)]
            [offset (list-ref result 2)])
            (run-prog (+ index offset) new-codes new-outputs)) 
        (printf "~s\n" outputs)))

(define user-input 1)
(run-prog 0 (codes input) '())

(set! user-input 5)
(run-prog 0 (codes input) '())

EDIT: Ugh, what is with this code highlighting? :/