Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
day04 "github.com/doniacld/adventofcode/puzzles/2020/04"
day05 "github.com/doniacld/adventofcode/puzzles/2020/05"
day06 "github.com/doniacld/adventofcode/puzzles/2020/06"
day08 "github.com/doniacld/adventofcode/puzzles/2020/08"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happened to day 7 ?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I forgot to assign you the code review...

"github.com/doniacld/adventofcode/puzzles/solver"
"log"
)
Expand Down Expand Up @@ -53,6 +54,8 @@ func main() {
s = day05.New("./puzzles/2020/05/input.txt")
case 6:
s = day06.New("./puzzles/2020/06/input.txt")
case 8:
s = day08.New("./puzzles/2020/08/input.txt")
}

out, err := s.Solve()
Expand Down
91 changes: 91 additions & 0 deletions puzzles/2020/08/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# Day 8: Handheld Halting

## Part One

Your flight to the major airline hub reaches cruising altitude without incident. While you consider checking the in-flight menu for one of those drinks that come with a little umbrella, you are interrupted by the kid sitting next to you.

Their handheld game console won't turn on! They ask if you can take a look.

You narrow the problem down to a strange infinite loop in the boot code (your puzzle input) of the device. You should be able to fix it, but first you need to be able to run the code in isolation.

The boot code is represented as a text file with one instruction per line of text. Each instruction consists of an Operation (acc, jmp, or nop) and an argument (a signed number like +4 or -20).

acc increases or decreases a single global value called the accumulator by the value given in the argument. For example, acc +7 would increase the accumulator by 7. The accumulator starts at 0. After an acc instruction, the instruction immediately below it is executed next.
jmp jumps to a new instruction relative to itself. The next instruction to execute is found using the argument as an offset from the jmp instruction; for example, jmp +2 would skip the next instruction, jmp +1 would continue to the instruction immediately below it, and jmp -20 would cause the instruction 20 lines above to be executed next.
nop stands for No OPeration - it does nothing. The instruction immediately below it is executed next.

For example, consider the following program:

nop +0
acc +1
jmp +4
acc +3
jmp -3
acc -99
acc +1
jmp -4
acc +6

These instructions are visited in this order:

nop +0 | 1
acc +1 | 2, 8(!)
jmp +4 | 3
acc +3 | 6
jmp -3 | 7
acc -99 |
acc +1 | 4
jmp -4 | 5
acc +6 |

First, the nop +0 does nothing. Then, the accumulator is increased from 0 to 1 (acc +1) and jmp +4 sets the next instruction to the other acc +1 near the bottom. After it increases the accumulator from 1 to 2, jmp -4 executes, setting the next instruction to the only acc +3. It sets the accumulator to 5, and jmp -3 causes the program to continue back at the first acc +1.

This is an infinite loop: with this sequence of jumps, the program will run forever. The moment the program tries to run any instruction a second time, you know it will never terminate.

Immediately before the program would run an instruction a second time, the value in the accumulator is 5.

Run your copy of the boot code. Immediately before any instruction is executed a second time, what value is in the accumulator?

Your puzzle answer was 2014.

The first half of this puzzle is complete! It provides one gold star: *

## Part Two

After some careful analysis, you believe that exactly one instruction is corrupted.

Somewhere in the program, either a jmp is supposed to be a nop, or a nop is supposed to be a jmp. (No acc instructions were harmed in the corruption of this boot code.)

The program is supposed to terminate by attempting to execute an instruction immediately after the last instruction in the file. By changing exactly one jmp or nop, you can repair the boot code and make it terminate correctly.

For example, consider the same program from above:

nop +0
acc +1
jmp +4
acc +3
jmp -3
acc -99
acc +1
jmp -4
acc +6

If you change the first instruction from nop +0 to jmp +0, it would create a single-instruction infinite loop, never leaving that instruction. If you change almost any of the jmp instructions, the program will still eventually find another jmp instruction and loop forever.

However, if you change the second-to-last instruction (from jmp -4 to nop -4), the program terminates! The instructions are visited in this order:

nop +0 | 1
acc +1 | 2
jmp +4 | 3
acc +3 |
jmp -3 |
acc -99 |
acc +1 | 4
nop -4 | 5
acc +6 | 6

After the last instruction (acc +6), the program terminates by attempting to run the instruction below the last instruction in the file. With this change, after the program terminates, the accumulator contains the value 8 (acc +1, acc +1, acc +6).

Fix the program so that it terminates normally by changing exactly one jmp (to nop) or nop (to jmp). What is the value of the accumulator after the program terminates?

Your puzzle answer was 2251.
37 changes: 37 additions & 0 deletions puzzles/2020/08/day08.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package day08

import (
"fmt"

"github.com/doniacld/adventofcode/internal/filereader"
"github.com/doniacld/adventofcode/puzzles/2020/08/game"
"github.com/doniacld/adventofcode/puzzles/solver"
)

func New(fileName string) solver.Solver {
return day08{fileName: fileName}
}

type day08 struct {
fileName string
}

func (d day08) Solve() (string, error) {

ops := game.NewOperations()
err := filereader.ReadAndExtract(d.fileName, func(line string) error {
err := ops.ParseLine(line)
return err
})
if err != nil {
return "", err
}
// store the initial ops before any modification by the Part one
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// store the initial ops before any modification by the Part one
// store the initial ops before any modification by Part one

tmp := ops.ResetOps()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe name it part2 rather than tmp


_, accCounter := ops.ComputeCorruptedAcc()
accCounterFixed := tmp.ComputeFixedAcc()
out := fmt.Sprintf("acc value: %d, after fix acc value: %d", accCounter, accCounterFixed)

return out, nil
}
59 changes: 59 additions & 0 deletions puzzles/2020/08/game/corrupted.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package game

// ComputeCorruptedAcc computes the accumulator in the corrupted game
// ends when an Operation is seen twice
func (ops Operations) ComputeCorruptedAcc() (int, int) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

name output fields when they have the same type

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done!

idx, accCounter := 0, 0
for true {
o := ops[idx]
// op already seen, game over
Copy link
Collaborator

@floppyzedolfin floppyzedolfin Jul 14, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather have this as the exit criterium of the infinite loop

for ; ! o.seen ; o = ops[idx] {

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thx !!

if o.seen {
return idx, accCounter
}

// let's see which Operation to apply
switch o.name {
case nopOp:
idx = ops.nop(idx)
break
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh you silly Java girl... You don't need this in go

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💯 thx

case accOp:
idx, accCounter = ops.acc(idx, accCounter)
break
case jmpOp:
idx = ops.jump(idx)
break
}

}
return idx, accCounter
}

// nop for no Operation does nothing, go to the next instruction
func (ops Operations) nop(idx int) int {
ops[idx].seen = true
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there's a dangerous side effect that you're applying here that's based on how the language copies items.

Use

func (ops *Operations) nop(idx int) int {

to be more explicit about this func. Same goes for all the funcs that affect the contents of ops.

return (idx + 1) % len(ops)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you ever check that idx is > 0 ?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's kind of done in fixed.go:81

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@floppyzedolfin Should I merge the duplicated operations ?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you can avoid having duplicated code, you should do what it takes

}

// acc for accumulator, increase the current value of the acc counter
// and go to the next instruction
func (ops Operations) acc(idx int, acc int) (int, int) {
ops[idx].seen = true
return (idx + 1) % len(ops), ops[idx].value + acc
}

// jump add the offset to the current index and go to this instruction
func (ops Operations) jump(idx int) int {
ops[idx].seen = true
if ops[idx].value < 0 {
return idx - (abs(ops[idx].value) % len(ops))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if value is negative, you are computing the absolute of it, which is always - value and substracting it, which means you are adding it. I therefore conslude that this line is exactly identical to line 50 and the entire if should be removed (with fire).

}
return (idx + ops[idx].value) % len(ops)
}

// abs returns the absolute value of x.
func abs(x int) int {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check that it is still useful.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not anymore, thank you !

if x < 0 {
return -x
}
return x
}
32 changes: 32 additions & 0 deletions puzzles/2020/08/game/corrupted_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package game

import (
"testing"

"github.com/doniacld/adventofcode/internal/filereader"
"github.com/stretchr/testify/assert"
)

func TestOperations_ComputeOperations(t *testing.T) {
tt := []struct {
description string
input string
output int
}{
{"nominal case", "./resources/input-test.txt", 5},
}

for _, tc := range tt {
t.Run(tc.description, func(t *testing.T) {
var ops Operations
err := filereader.ReadAndExtract(tc.input, func(line string) error {
err := ops.ParseLine(line)
return err
})
assert.NoError(t, err)
idx, acc := ops.ComputeCorruptedAcc()
assert.Equal(t, tc.output, acc)
assert.Equal(t, 1, idx)
})
}
}
114 changes: 114 additions & 0 deletions puzzles/2020/08/game/fixed.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package game

// ComputeFixedAcc returns the accumulator value
// game ends at the last instruction
// a jmp is replaced by a nop or the other way around
func (ops Operations) ComputeFixedAcc() int {
for i := 0; i < len(ops)-1; i++ {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or for _, op := range ops {


o := ops[i]
tmpOps := ops.ResetOps()

// switch on the nop or jmp to check if the replace is successful
switch o.name {
case nopOp:
if o.value == 0 || o.value == 1 {
} else {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. These two lines should be replaced by one if.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep !

tmpOps.replace(i, jmpOp)
break
}
case jmpOp:
if o.value == 1 {
} else {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep !

tmpOps.replace(i, nopOp)
break
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

huehuehue

}
}

// if the game ends at the last instruction, return accCounter
v, accCounter := tmpOps.computeToTheLastOp()
if v {
return accCounter
}
}
// should not happened
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// should not happened
// should not happen

return 0
}

func (ops Operations) ResetOps() Operations {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reset or duplicate ?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

duplicate !

// store the tmp ops for modification
tmpOps := make(Operations, len(ops))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's not temporary if you are returning it. The use of newOps or something would be clearer.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done!

copy(tmpOps, ops)
return tmpOps
}

// replace the current instruction by the new wanted one
func (ops Operations) replace(idx int, newOp string) {
ops[idx].name = newOp
}

// computeToTheLastOp calculates the accumulator counter depending on operations
// exit false if the the operation is already seen or the index is not anymore in the range
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// exit false if the the operation is already seen or the index is not anymore in the range
// returns false if the operation is already seen or the index is not in the range anymore

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

func (ops Operations) computeToTheLastOp() (bool, int) {
i, accCounter := 0, 0
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

currentIndex ?


for true {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

infinite loops deserve a comment on why we should expect them not to run forever

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed it !

o := ops[i]
idx := 0
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nextIndex ?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done!


switch o.name {
case nopOp:
idx = ops.nops(i)
break
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mahaha...

case accOp:
idx, accCounter = ops.accs(i, accCounter)
break
case jmpOp:
idx = ops.jumps(i)
break
}

// positive case
if idx == len(ops)-1 {
// compute the last value
if ops[idx].name == accOp {
_, accCounter = ops.accs(idx, accCounter)
}
return true, accCounter
}

// Operation already seen, this is not normal
if ops[idx].seen {
return false, accCounter
}

//idx not in the range anymore
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
//idx not in the range anymore
// idx not in the range anymore

if idx < 0 || idx > len(ops)-1 {
return false, accCounter
}

// let's continue, Operation is seen and save the index
ops[idx].seen = true
i = idx
}
return true, accCounter
}

// nop for no Operation does nothing, go to the next instruction
func (ops Operations) nops(idx int) int {
return idx + 1
}

// acc for accumulator, increase the current value of the acc counter
// and go to the next instruction
func (ops Operations) accs(idx int, acc int) (int, int) {
if idx == len(ops)-1 {
return idx, ops[idx].value + acc
}
return idx + 1, ops[idx].value + acc
}

// jump add the offset to the current index and go to this instruction
func (ops Operations) jumps(idx int) int {
return idx + ops[idx].value
}
31 changes: 31 additions & 0 deletions puzzles/2020/08/game/fixed_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package game

import (
"testing"

"github.com/doniacld/adventofcode/internal/filereader"
"github.com/stretchr/testify/assert"
)

func TestOperations_ComputeCorruptedAcc(t *testing.T) {
tt := []struct {
description string
input string
output int
}{
{"nominal case", "./resources/input-test.txt", 8},
}

for _, tc := range tt {
t.Run(tc.description, func(t *testing.T) {
ops := NewOperations()
err := filereader.ReadAndExtract(tc.input, func(line string) error {
err := ops.ParseLine(line)
return err
})
assert.NoError(t, err)
acc := ops.ComputeFixedAcc()
assert.Equal(t, tc.output, acc)
})
}
}
Loading