Skip to contents
using DelimitedFiles
using Combinatorics
using Formatting

Day 1: Report Repair

Part 1

# Read in data
path = joinpath("..", "inst", "2020", "day1.txt")
## "../inst/2020/day1.txt"
dat = vec(readdlm(path));

# Find the two entries that sum to 2020
numbers = [x for x in combinations(dat, 2) if sum(x) == 2020];

# Convert 1-element Vector{Vector{Float64}} to 2-element Vector{Float64}
numbers = reshape(numbers)[1];

# What do you get if you multiply them together?
prod(Int, numbers)
## 802011

1.727 sec.

Notes

  • Array comprehension has the following syntax:
[x^2 for x = 1:3]
## 3-element Vector{Int64}:
##  1
##  4
##  9

Part 2

# What is the product of the three entries that sum to 2020?
numbers = [x for x in combinations(dat, 3) if sum(x) == 2020];
numbers = reshape(numbers)[1];
res = prod(Int, numbers) 
## 248607374

0.307 sec.

Notes

  • Conditional comprehension has the following syntax:
[x^2 for x = 1:3 if x > 1]
## 2-element Vector{Int64}:
##  4
##  9

Day 2: Password Philosophy

Part 1

# Read in data
path = joinpath("..", "inst", "2020", "day2.txt");
dat = readlines(path);

function sled_password_check(dat)

    results = 0
    
    for i in dat
        m = match(r"(\d+)-(\d+)\s+(.):\s+(.+)", i)  # grep
        minimum = parse(Int64, m.captures[1])       # convert to Int64
        maximum = parse(Int64, m.captures[2])
        letter = m.captures[3]
        password = m.captures[4]
        n = count(letter, password)
        results += (n >= minimum && n <= maximum) ? 1 : 0
    end
    
    results

end;

# How many passwords are valid according to their policies?
sled_password_check(dat)
## 640

0.441 sec.

Notes

  • The ternary operator, ?: is related to if-elseif-else syntax and is written as condition ? value_if_true : value_if_false.

  • Julia complains of scoping issues when calling results += 1inside aforloop (ifresultswas defined outside of it). Rather than usingglobal results += 1`, putting everything into a function will achieve the expected behaviour.

Part 2

function toboggan_password_check(dat)
    
    results = 0

    for i in dat
        m = match(r"(\d+)-(\d+)\s+(.):\s+(.+)", i)  # grep
        pos1 = parse(Int64, m.captures[1])          # convert to Int64
        pos2 = parse(Int64, m.captures[2])
        letter = only(m.captures[3])                # convert to ASCII/Unicode
        password = m.captures[4]

        # Exactly one of these positions must contain the given letter
        test = (password[pos1] == letter) + (password[pos2] == letter) == 1
        results += test ? 1 : 0
    end

    results
    
end;

# How many passwords are valid according to the new interpretation of the policies?
toboggan_password_check(dat)
## 472

0.158 sec.

Day 3: Toboggan Trajectory

Part 1

# Read in data
path = joinpath("..", "inst", "2020", "day3.txt");
dat = readlines(path);

function trees(x, y, map)

    xpos = ypos = 1
    results = 0
    xlim = length(map[1])
    ylim = length(map)
    
    while ypos != ylim
        xpos += x
        xpos = (xpos > xlim) ? xpos - xlim : xpos
        ypos += y
        results += map[ypos][xpos] == only("#") ? 1 : 0
    end

    results
    
end;

# Starting at the top-left corner of your map and following a slope of right 3 and 
# down 1, how many trees would you encounter?
trees(3, 1, dat)
## 237

0.262 sec.

Part 2

# Determine the number of trees you would encounter if, for each of the following 
# slopes, you start at the top-left corner and traverse the map all the way to the 
# bottom
iterate = [[1,1], [3,1], [5,1], [7,1], [1,2]];
results = [trees(iterate[x][1], iterate[x][2], dat) for x in eachindex(iterate)];

# What do you get if you multiply together the number of trees encountered on each of  
# the listed slopes?
prod(results)
## 2106818610

0.313 sec.

Day 4: Passport Processing

Part 1

# Read in data
path = joinpath("..", "inst", "2020", "day4.txt");
dat = readlines(path);

function parsepassports(dat) 

    record = []
    d = Dict{String, String}()
    
    for i in eachindex(dat)

        if isempty(dat[i])
            push!(record, d)
            d = Dict{String, String}()
        else
            for j in split(dat[i])
                key, value = split(j, ":")
                d[key] = value
            end
            i == length(dat) ? push!(record, d) : nothing
        end

    end

    record

end;

function countpassports(passports)

    counter = 0
    fields = ["byr", "iyr", "eyr", "hgt", "hcl", "ecl", "pid"]
    
    for i in eachindex(passports)
        invalid = sum([!haskey(passports[i], key) for key in fields]) > 0
        counter += invalid ? 0 : 1
    end
    
    counter

end;

# How many passports are valid?
passports = parsepassports(dat);
countpassports(passports)
## 182

0.491 sec.

Part 2

function checkfields(passports)

    fields = ["byr", "iyr", "eyr", "hgt", "hcl", "ecl", "pid"]
    colours = ["amb", "blu", "brn", "gry", "grn", "hzl", "oth"]
    counter = 0
    
    for i in passports

        invalid = sum([!haskey(i, key) for key in fields]) > 0

        if (!invalid)
            byr = 1920 <= parse(Int, i["byr"]) <= 2002
            iyr = 2010 <= parse(Int, i["iyr"]) <= 2020
            eyr = 2020 <= parse(Int, i["eyr"]) <= 2030
    
            if endswith(i["hgt"], "cm")
                tmp = parse(Int, replace(i["hgt"], "cm" => ""))
                hgt = 150 <= tmp <= 193
            elseif endswith(i["hgt"], "in")
                tmp = parse(Int, replace(i["hgt"], "in" => ""))
                hgt = 59 <= tmp <= 76
            else 
                hgt = false
            end
    
            hcl = match(r"^#[0-9a-f]{6}", i["hcl"]) != nothing
            ecl = issubset([i["ecl"]], colours)
            pid = match(r"^[0-9]{9}$", i["pid"]) != nothing
            
            # Is passport valid?
            test = byr & iyr & eyr & hgt & hcl & ecl & pid
            counter += test ? 1 : 0
        end
        
    end

    counter

end;

# How many passports are valid?
checkfields(passports)
## 109

0.314 sec.

Day 5: Binary Boarding

Part 1

# Read in data
path = joinpath("..", "inst", "2020", "day5.txt");
dat = readlines(path);

function findseat(dat)
   
    output = []

    for i in eachindex(dat)
        
        row_min, row_max = 0, 127
        col_min, col_max = 0, 7

        # Get row
        row_directions = dat[i][1:7]
        for j in row_directions
            if j == only("F")
                row_max = floor(Int, row_max - (row_max - row_min) / 2)
            elseif j == only("B")
                row_min = ceil(Int, row_min + (row_max - row_min) / 2)
            end
        end

        # Get column
        seat = dat[i][8:10]
        for j in seat
            if j == only("L")
                col_max = floor(Int, col_max - (col_max - col_min) / 2)
            elseif j == only("R")
                col_min = ceil(Int, col_min + (col_max - col_min) / 2)
            end
        end

    # Calculate seat ID
    seat_id = (row_min * 8) + col_max
    push!(output, seat_id)
    end

    output
end;

# Try test data
test = readlines(joinpath("..", "inst", "2020", "day5-test.txt"));
@assert all(findseat(test) .== [357, 567, 119, 820])

# What is the highest seat ID on a boarding pass?
seat_ids = findseat(dat);
maximum(seat_ids)
## 906

0.678 sec.

Part 2

\in and \notin, then TAB:

# What is the ID of your seat?

seats = sort(seat_ids);

myseat = function(seats)
    for i in 1:maximum(seats)
        if i ∉ seats && i-1 ∈ seats && i+1 ∈ seats
            return(i)
        end
    end
end;

myseat(seats)
## 519

0.29 sec.

Day 6: Custom Customs

Part 1

# Read in data
path = joinpath("..", "inst", "2020", "day6-test.txt");
raw = read(path, String);

groups = split(raw, "\n\n");          # split into groups
## 5-element Vector{SubString{String}}:
##  "abc"
##  "a\nb\nc"
##  "ab\nac"
##  "a\na\na\na"
##  "b\n"
dat = replace.(groups, "\n" => "");   # remove line breaks
## 5-element Vector{String}:
##  "abc"
##  "abc"
##  "abac"
##  "aaaa"
##  "b"

# For each group, count the number of questions to which anyone answered "yes"
length.(unique.(dat));

# What is the sum of those counts?
sum(length.(unique.(dat)))
## 11

1.219 sec.

Day 7: Handy Haversacks

Part 1

Day 8: Handheld Halting

Part 1

# Read in data ------------------------------------------------------------

path = joinpath("..", "inst", "2020", "day8.txt");
dat = read(path, String);

# Define functions --------------------------------------------------------

function tidyday8(dat)
    dat = split(dat, "\n")                      # split by new line
    dat = filter(!isempty, dat)                 # remove empty last element
    dat = split.(dat, " ")                      # split by space

    # Tidy up data
    inst = [x[1] for x in dat]                  # extract instructions
    val = [x[2] for x in dat]                   # extract values

    vals = map(val) do str
    mat = match.(r"[\+|\-](\d*)", str)          # regex to extract number
    cap = mat.captures                          # extract capture group
    num = parse(Int64, cap[1])                  # convert string to number
    contains.(str, "-") ? num * -1 : num        # multiply by -1 if negative
    end

    inst, vals
end;

function boot(inst, vals)
    accumulator = 0;                            # initialise accumulator
    i = 1;                                      # initialise place in instruction list
    log = [];

    while i ∉ log                               # stop before an instruction is run again
        push!(log, i)                           # record i in log

        if inst[i] == "acc"
            accumulator += vals[i]              # add value to accumulator
            i += 1                              # next instruction
        elseif inst[i] == "jmp"
            i += vals[i]                        # jump to a new instruction
        elseif inst[i] == "nop"
            i += 1                              # next instruction
        end
    end
    return(accumulator)
end;

# Run boot code ----------------------------------------------------------

inst, vals = tidyday8(dat);

# Immediately before any instruction is executed a second time, what value is in the 
# accumulator?
boot(inst, vals)
## 1749

0.648 sec.

Part 2

# Run boot code ----------------------------------------------------------

function boot2(inst, vals)

    stop_condition = length(inst) + 1

    for i in 1:length(inst)

        new_inst = copy(inst)
    
        if inst[i] == "acc"
            continue
        elseif inst[i] == "jmp"
            new_inst[i] = "nop"
        elseif inst[i] == "nop"
            new_inst[i] = "jmp"
        end
        
        accumulator = 0                         # initialise accumulator
        j = 1                                   # initialise place in instruction list
        log = [] 
        
        while j ∉ log                           # stop before an instruction is run again
            if j == stop_condition              # found correct termination point!
                return(accumulator)  
            end
            push!(log, j)                       # record i in log
    
            if new_inst[j] == "acc"
                accumulator += vals[j]          # add value to accumulator
                j += 1                          # next instruction
            elseif new_inst[j] == "jmp"
                j += vals[j]                    # jump to a new instruction
            elseif new_inst[j] == "nop"
                j += 1                          # next instruction
            end
        end
    end
end;

boot2(inst, vals)
## 515

0.317 sec.

Day 9: Encoding Error

Part 1

# Read in data ------------------------------------------------------------

path = joinpath("..", "inst", "2020", "day9.txt");
dat = readlines(path);
                      
# Define functions --------------------------------------------------------

function day11(dat)
    # convert string to numeric
    parse.(Int64, dat)
end;

function checksums(dat)
    preamble = 25

    # generate pairwise combination index
    cmb = combinations(1:preamble, 2) |> collect    
    # find sum of each pairwise combination
    add = map(x -> dat[x[1]] + dat[x[2]], cmb)      
    
    for i in eachindex(dat)
        # don't check preamble
        if i ∈ 1:preamble                           
            continue
        end
    
        # remove elements outwith the preamble 
        tmp = i - preamble - 1                         
        remove = findall(x -> x[1] == tmp || x[2] == tmp, cmb)
        deleteat!(cmb, remove)
        deleteat!(add, remove)

        # generate next set of indices
        tmp = (tmp + 1):(i - 1)                     
        ind = map(x -> [i, x], tmp)                 
        append!(cmb, ind)
    
        # generate next set of additions     
        new = map(x -> dat[x[1]] + dat[x[2]], ind)            
        append!(add, new)

        # return invalid number
        if dat[i] ∉ add                             
            return(dat[i])
        end

    end
end;

# Find the first number in the list (after the preamble) which is not the sum of 
# two of the 25 numbers before it

# What is the first number that does not have this property?

dat = day11(dat) 
## 1000-element Vector{Int64}:
##                3
##               17
##               32
##                2
##               19
##               45
##               22
##               18
##               38
##                8
##                ⋮
##   92412361470618
##   93964358367387
##   94334470603341
##  123814366611218
##  110087627226740
##   98650943515733
##  104829897365462
##  104882565934705
##  108297043768811
invalid = checksums(dat)
## 257342611

1.119 sec.

Part 2

# Find a contiguous set of at least two numbers in your list which sum to the 
# invalid number from step 1

function findweakness(dat, invalid)
    for i in eachindex(dat)

        total = dat[i]
        j = i
    
        while(total < invalid)
            j += 1
            total += dat[j]
        end
    
        if total == invalid
            # Find encryption weakness
            vals = dat[i:j]                         
            weakness = minimum(vals) + maximum(vals)
            return(weakness)
        end
    
    end
end;

findweakness(dat, invalid)
## 35602097

0.231 sec.

Day 10: Adapter Array

Part 1

path = joinpath("..", "inst", "2020", "day10.txt");

# Find a chain that uses all of your adapters to connect the charging outlet to your 
# device's built-in adapter and count the joltage differences between the charging 
# outlet, the adapters, and your device

function day10(path)
    dat = readlines(path)                           # read data
    dat = parse.(Int64, dat)                        # convert string to integer
end;

function part1(dat)
    tmp = copy(dat) |> sort                         # sort values
    whole = vcat(0, tmp, tmp[end] + 3)              # prepend with 0, append with +3
    ones = count(==(1), diff(sort(whole)))          # count number of ones
    threes = count(==(3), diff(sort(whole)))        # count number of threes
    ones * threes
end;

# What is the number of 1-jolt differences multiplied by the number of 3-jolt 
# differences?
day10(path) |> part1
## 2450

0.753 sec.