├── 2015 ├── day01.pl ├── day02.lua ├── day03.hs ├── day04.sh ├── day05.scala ├── day06.c └── inputs │ ├── input01.txt │ ├── input02.txt │ ├── input03.txt │ ├── input05.txt │ └── input06.txt ├── 2016 ├── day01.rb ├── day02.rb ├── day03.rb ├── day04.rb ├── day05.rb ├── day06.rb ├── day07.rb ├── day08.rb ├── day09.rb ├── day10.rb ├── day11.rb ├── day12.rb ├── day13.rb ├── day14.rb ├── day15.rb ├── day16.rb ├── day17.rb ├── day18.rb ├── day19.rb ├── day20.rb ├── day21.rb ├── day22.rb ├── day23.rb ├── day24.rb ├── day25.rb └── inputs │ ├── input03.txt │ ├── input04.txt │ ├── input06.txt │ ├── input07.txt │ ├── input08.txt │ ├── input09.txt │ ├── input10.txt │ ├── input20.txt │ ├── input21.txt │ ├── input22.txt │ ├── input23.txt │ ├── input24.txt │ └── input25.txt ├── 2017 ├── day01.exs ├── day02.exs ├── day03.exs ├── day04.exs ├── day05.exs ├── day06.exs ├── day07.exs ├── day08.exs ├── day09.exs ├── day10.exs ├── day11.exs ├── day12.exs ├── day13.exs ├── day14.exs ├── day15.exs ├── day16.exs ├── day17.exs ├── day18.exs ├── day19.exs ├── day20.exs ├── day21.exs ├── day22.exs ├── day23.exs ├── day24.exs ├── day25.exs └── inputs │ ├── input04.txt │ ├── input05.txt │ ├── input07.txt │ ├── input08.txt │ ├── input09.txt │ ├── input11.txt │ ├── input12.txt │ ├── input13.txt │ ├── input16.txt │ ├── input18.txt │ ├── input19.txt │ ├── input20.txt │ ├── input21.txt │ ├── input22.txt │ ├── input23.txt │ ├── input23_optimised.txt │ └── input24.txt ├── 2018 ├── day01.exs ├── day02.exs ├── day03.exs ├── day04.exs ├── day05.exs ├── day06.exs ├── day07.exs ├── day08.exs ├── day09.exs ├── day10.exs ├── day11.exs ├── day12.exs ├── day13.exs ├── day14.exs ├── day15.exs ├── day16.exs ├── day17.exs ├── day18.exs ├── day19.exs ├── day20.exs ├── day21.exs ├── day22.exs ├── day23.exs ├── day24.exs ├── day25.exs └── inputs │ ├── input01.txt │ ├── input02.txt │ ├── input03.txt │ ├── input04.txt │ ├── input05.txt │ ├── input06.txt │ ├── input07.txt │ ├── input08.txt │ ├── input10.txt │ ├── input12.txt │ ├── input13.txt │ ├── input15.txt │ ├── input16-1.txt │ ├── input16-2.txt │ ├── input17.txt │ ├── input18.txt │ ├── input19.txt │ ├── input20.txt │ ├── input21-modified.txt │ ├── input21.txt │ ├── input23.txt │ ├── input24.txt │ └── input25.txt ├── 2019 ├── README.md ├── day01.awk ├── day02.pl ├── day03.hs ├── day04.lisp ├── day05.kt ├── day06.swift ├── day07.lua ├── day08.pl ├── day09.go ├── day10.c ├── day11.rs ├── day12.ml ├── day13.jl ├── day14.erl ├── day15.dart ├── day16 │ ├── day16.dylan │ ├── day16.lid │ └── library.dylan ├── day17.scala ├── day18.py ├── day19.cr ├── day20.nim ├── day21.rb ├── day22.R ├── day23.exs ├── day24.cpp ├── day25.js └── inputs │ ├── input01.txt │ ├── input02.txt │ ├── input03.txt │ ├── input05.txt │ ├── input06.txt │ ├── input07.txt │ ├── input08.txt │ ├── input09.txt │ ├── input10.txt │ ├── input11.txt │ ├── input13.txt │ ├── input14.txt │ ├── input15.txt │ ├── input16.txt │ ├── input17.txt │ ├── input18-1.txt │ ├── input18-2.txt │ ├── input19.txt │ ├── input20.txt │ ├── input21.txt │ ├── input22.txt │ ├── input23.txt │ ├── input24.txt │ └── input25.txt ├── 2020 ├── day01.exs ├── day02.exs ├── day03.exs ├── day04.exs ├── day05.exs ├── day06.exs ├── day07.exs ├── day08.exs ├── day09.exs ├── day10.exs ├── day11.exs ├── day12.exs ├── day13.exs ├── day14.exs ├── day15.exs ├── day16.exs ├── day17.exs ├── day18.exs ├── day19.exs ├── day20.exs ├── day21.exs ├── day22.exs ├── day23.exs ├── day24.exs ├── day25.exs └── inputs │ ├── input01.txt │ ├── input02.txt │ ├── input03.txt │ ├── input04.txt │ ├── input05.txt │ ├── input06.txt │ ├── input07.txt │ ├── input08.txt │ ├── input09.txt │ ├── input10.txt │ ├── input11.txt │ ├── input12.txt │ ├── input13.txt │ ├── input14.txt │ ├── input16.txt │ ├── input17.txt │ ├── input18.txt │ ├── input19.txt │ ├── input20.txt │ ├── input21.txt │ ├── input22.txt │ └── input24.txt ├── 2021 ├── Day19.hx ├── README.md ├── day01.sh ├── day02.f08 ├── day03.wls ├── day04.m ├── day05.d ├── day06.pas ├── day07.clj ├── day08.moon ├── day09.ooc ├── day10.adb ├── day11.arr ├── day12.rkt ├── day13.m ├── day14.v ├── day15.max ├── day16.coffee ├── day17.pike ├── day18.abs ├── day20.wren ├── day21.odin ├── day22.pony ├── day23.du ├── day24.groovy ├── day25.gravity └── inputs │ ├── input01.txt │ ├── input02.txt │ ├── input03.txt │ ├── input04.txt │ ├── input05.txt │ ├── input06.txt │ ├── input07.txt │ ├── input08.txt │ ├── input09.txt │ ├── input10.txt │ ├── input11.txt │ ├── input12.txt │ ├── input13.txt │ ├── input14.txt │ ├── input15.txt │ ├── input16.txt │ ├── input17.txt │ ├── input18.txt │ ├── input19.txt │ ├── input20.txt │ ├── input22.txt │ ├── input23.txt │ ├── input24.txt │ └── input25.txt ├── 2023 ├── day01.go ├── day02.go └── inputs │ ├── input01.txt │ └── input02.txt ├── 2024 ├── day01.exs ├── day02.exs ├── day03.exs ├── day04.exs ├── day05.exs ├── day06.exs ├── day07.exs ├── day08.exs ├── day09.exs ├── day10.exs ├── day11.exs ├── day12.exs ├── day13.exs ├── day14.exs ├── day15.exs ├── day16.exs ├── day17.exs ├── day18.exs ├── day19.exs ├── day20.exs ├── day21.exs ├── day22.exs ├── day23.exs ├── day24.exs └── day25.exs └── README.md /2015/day01.pl: -------------------------------------------------------------------------------- 1 | % --- Day 1: Not Quite Lisp --- 2 | 3 | floor(N) :- 4 | read_instructions(Text), 5 | string_chars(Text, Instructions), 6 | count_floors(Instructions, N). 7 | 8 | read_instructions(Text) :- 9 | open('inputs/input01.txt', read, Str), 10 | peek_string(Str, 8000, Text), 11 | close(Str). 12 | 13 | count_floors([], 0). 14 | count_floors(['('|Instructions], N) :- 15 | count_floors(Instructions, M), 16 | N is M + 1. 17 | count_floors([')'|Instructions], N) :- 18 | count_floors(Instructions, M), 19 | N is M - 1. 20 | 21 | 22 | % ?- floor(N). 23 | % N = 232 . 24 | 25 | % --- Part Two --- 26 | 27 | basement_index(Index) :- 28 | read_instructions(Text), 29 | string_chars(Text, Instructions), 30 | until_basement(Instructions, 0, Index, 0). 31 | 32 | until_basement(_, Index, Index, -1). 33 | until_basement(['('|Instructions], Index, BasementIndex, N) :- 34 | M is N + 1, 35 | Next is Index + 1, 36 | until_basement(Instructions, Next, BasementIndex, M). 37 | until_basement([')'|Instructions], Index, BasementIndex, N) :- 38 | M is N - 1, 39 | Next is Index + 1, 40 | until_basement(Instructions, Next, BasementIndex, M). 41 | 42 | % ?- basement_index(Index). 43 | % Index = 1783 . 44 | -------------------------------------------------------------------------------- /2015/day02.lua: -------------------------------------------------------------------------------- 1 | -- --- Day 2: I Was Told There Would Be No Math --- 2 | 3 | function readpresents(path) 4 | local presents = {} 5 | for line in io.lines(path) do 6 | presents[#presents + 1] = parsedimensions(line) 7 | end 8 | return presents 9 | end 10 | 11 | function parsedimensions(line) 12 | -- Each line is of the form: 19x18x22 13 | local vl, vw, vh = line:match("(%d+)x(%d+)x(%d+)") 14 | return {l=tonumber(vl), w=tonumber(vw), h=tonumber(vh)} 15 | end 16 | 17 | function paper(box) 18 | -- Surface area of the box, 2*l*w + 2*w*h + 2*h*l, plus area of the smallest side 19 | local areas = {box.l*box.w, box.w*box.h, box.h*box.l} 20 | return 2*areas[1] + 2*areas[2] + 2*areas[3] + math.min(unpack(areas)) 21 | end 22 | 23 | -- How many total square feet of wrapping paper should they order? 24 | local presents = readpresents("inputs/input02.txt") 25 | local total = 0 26 | for _, present in ipairs(presents) do 27 | total = total + paper(present) 28 | end 29 | 30 | print("Paper: " .. total) 31 | 32 | -- --- Part Two --- 33 | function ribbon(box) 34 | local perimeters = {box.l+box.w, box.w+box.h, box.h+box.l} 35 | return 2*math.min(unpack(perimeters)) + box.l*box.w*box.h 36 | end 37 | 38 | -- How many total feet of ribbon should they order? 39 | total = 0 40 | for _, present in ipairs(presents) do 41 | total = total + ribbon(present) 42 | end 43 | 44 | print("Ribbon: " .. total) 45 | -------------------------------------------------------------------------------- /2015/day03.hs: -------------------------------------------------------------------------------- 1 | -- --- Day 3: Perfectly Spherical Houses in a Vacuum --- 2 | 3 | import Data.List 4 | 5 | instructions :: String -> IO String 6 | instructions path = do 7 | contents <- readFile path 8 | return contents 9 | 10 | houses :: String -> [(Int, Int)] -> [(Int, Int)] 11 | houses "" list = list 12 | houses (h:hs) list = 13 | let current = head list 14 | in houses hs ([(move h current)] ++ list) 15 | 16 | everyOther :: String -> String 17 | everyOther "" = "" 18 | everyOther (h:hs) = h : everyOther (drop 1 hs) 19 | 20 | roboHouses :: String -> [(Int, Int)] -> [(Int, Int)] 21 | roboHouses (h:hs) list = 22 | let santaHouses = houses (everyOther (h:hs)) list 23 | roboSantaHouses = houses (everyOther hs) list 24 | in santaHouses ++ roboSantaHouses 25 | 26 | move :: Char -> (Int, Int) -> (Int, Int) 27 | move '<' (x, y) = (x - 1, y) 28 | move '^' (x, y) = (x, y - 1) 29 | move '>' (x, y) = (x + 1, y) 30 | move 'v' (x, y) = (x, y + 1) 31 | 32 | --How many houses receive at least one present? 33 | countUniq :: [(Int, Int)] -> Int 34 | countUniq houses = length (nub houses) 35 | 36 | main :: IO () 37 | main = do 38 | s <- instructions "inputs/input03.txt" 39 | print $ countUniq (houses s [(0, 0)]) 40 | print $ countUniq (roboHouses s [(0, 0)]) 41 | -------------------------------------------------------------------------------- /2015/day04.sh: -------------------------------------------------------------------------------- 1 | # --- Day 4: The Ideal Stocking Stuffer --- 2 | 3 | # Your puzzle input is bgvyzdsv. 4 | KEY=bgvyzdsv 5 | COUNTER=1 6 | HASH=`md5 -qs "$KEY$COUNTER"` 7 | while [[ "$HASH" != 00000* ]]; do 8 | let COUNTER=COUNTER+1 9 | HASH=`md5 -qs "$KEY$COUNTER"` 10 | done 11 | 12 | echo The lowest positive integer for 5 zeroes is $COUNTER 13 | 14 | while [[ "$HASH" != 000000* ]]; do 15 | let COUNTER=COUNTER+1 16 | HASH=`md5 -qs "$KEY$COUNTER"` 17 | done 18 | 19 | echo The lowest positive integer for 6 zeroes is $COUNTER 20 | -------------------------------------------------------------------------------- /2015/day05.scala: -------------------------------------------------------------------------------- 1 | /* --- Day 5: Doesn't He Have Intern-Elves For This? --- */ 2 | 3 | import scala.io.Source 4 | 5 | def readLines( filename:String ) : Iterator[String] = { 6 | return Source.fromFile(filename).getLines() 7 | } 8 | 9 | /* A nice string is one with all of the following properties: 10 | * It contains at least three vowels (aeiou only), like aei, xazegov, or aeiouaeiouaeiou. 11 | * It contains at least one letter that appears twice in a row, like xx, abcdde (dd), or aabbccdd (aa, bb, cc, or dd). 12 | * It does not contain the strings ab, cd, pq, or xy, even if they are part of one of the other requirements. 13 | */ 14 | def isNice( s:String ) : Boolean = { 15 | var vowels = """([aeiou].*){3}""".r 16 | var forbidden = """ab|cd|pq|xy""".r 17 | 18 | return vowels.findFirstIn(s) != None && forbidden.findFirstIn(s) == None && hasDoubleLetter(s) 19 | } 20 | 21 | def hasDoubleLetter( s:String ) : Boolean = { 22 | s.zipWithIndex.foreach{ case (char, index) => 23 | if ( index < s.length() - 1 && char == s(index + 1) ) 24 | return true 25 | } 26 | 27 | return false 28 | } 29 | 30 | println(readLines("inputs/input05.txt").count{ isNice }) 31 | 32 | // --- Part Two --- 33 | 34 | /* Now, a nice string is one with all of the following properties: 35 | * It contains a pair of any two letters that appears at least twice in the string without overlapping, 36 | * like xyxy (xy) or aabcdefgaa (aa), but not like aaa (aa, but it overlaps). 37 | * It contains at least one letter which repeats with exactly one letter between them, like xyx, abcdefeghi (efe), or even aaa. 38 | */ 39 | def isNiceWithNewRules( s:String ) : Boolean = { 40 | return hasRepeatedPair(s) && hasDoubleLetterWithOneInBetween(s) 41 | } 42 | 43 | def hasRepeatedPair( s:String ) : Boolean = { 44 | s.zipWithIndex.foreach{ case (char, index) => 45 | if ( index < s.length() - 2 && s.substring(index + 2).indexOf(s"$char${s(index + 1)}") >= 0) 46 | return true 47 | } 48 | 49 | return false 50 | } 51 | 52 | def hasDoubleLetterWithOneInBetween( s:String ) : Boolean = { 53 | s.zipWithIndex.foreach{ case (char, index) => 54 | if ( index < s.length() - 2 && char == s(index + 2) ) 55 | return true 56 | } 57 | 58 | return false 59 | } 60 | 61 | println(readLines("inputs/input05.txt").count{ isNiceWithNewRules }) 62 | -------------------------------------------------------------------------------- /2016/day01.rb: -------------------------------------------------------------------------------- 1 | grid = %w(R2 L3 R2 R4 L2 L1 R2 R4 R1 L4 L5 R5 R5 R2 R2 R1 L2 L3 L2 L1 R3 L5 R187 R1 R4 L1 R5 L3 L4 R50 L4 R2 R70 L3 L2 R4 R3 R194 L3 L4 L4 L3 L4 R4 R5 L1 L5 L4 2 | R1 L2 R4 L5 L3 R4 L5 L5 R5 R3 R5 L2 L4 R4 L1 R3 R1 L1 L2 R2 R2 L3 R3 R2 R5 R2 R5 L3 R2 L5 R1 R2 R2 L4 L5 L1 L4 R4 R3 R1 R2 L1 L2 R4 R5 L2 R3 L4 L5 L5 3 | L4 R4 L2 R1 R1 L2 L3 L2 R2 L4 R3 R2 L1 L3 L2 L4 L4 R2 L3 L3 R2 L4 L3 R4 R3 L2 L1 L4 R4 R2 L4 L4 L5 L1 R2 L5 L2 L3 R2 L2) 4 | 5 | # --- Part One --- 6 | 7 | def next_operation(operation, direction) 8 | case operation 9 | when [0, 1] # North 10 | direction == 'R' ? [1, 0] : [-1, 0] 11 | when [0, -1] # South 12 | direction == 'R' ? [-1, 0] : [1, 0] 13 | when [-1, 0] # West 14 | direction == 'R' ? [0, 1] : [0, -1] 15 | when [1, 0] # East 16 | direction == 'R' ? [0, -1] : [0, 1] 17 | end 18 | end 19 | 20 | def blocks_away_part_1(grid) 21 | position = [0, 0] 22 | operation = [0, 1] # Facing north 23 | 24 | grid.each do |move| 25 | direction = move[0] 26 | steps = move[1..-1].to_i 27 | operation = next_operation(operation, direction) 28 | position = [position[0] + operation[0]*steps, position[1] + operation[1]*steps] 29 | end 30 | 31 | position.map(&:abs).reduce(&:+) 32 | end 33 | 34 | 35 | puts blocks_away_part_1(grid) 36 | 37 | # --- Part Two --- 38 | 39 | def blocks_away_part_2(grid) 40 | position = [0, 0] 41 | operation = [0, 1] # Facing north 42 | positions = [] 43 | 44 | grid.each do |move| 45 | direction = move[0] 46 | steps = move[1..-1].to_i 47 | operation = next_operation(operation, direction) 48 | steps.times do 49 | position = [position[0] + operation[0], position[1] + operation[1]] 50 | return position.map(&:abs).reduce(&:+) if positions.include?(position) 51 | positions << position 52 | end 53 | end 54 | 55 | position.map(&:abs).reduce(&:+) 56 | end 57 | 58 | puts blocks_away_part_2(grid) -------------------------------------------------------------------------------- /2016/day03.rb: -------------------------------------------------------------------------------- 1 | # --- Day 3: Squares With Three Sides --- 2 | 3 | # --- Part One --- 4 | 5 | def possible?(triangle) 6 | (triangle[0] + triangle[1]) > triangle[2] && 7 | (triangle[1] + triangle[2]) > triangle[0] && 8 | (triangle[0] + triangle[2]) > triangle[1] 9 | end 10 | 11 | def possible_triangles(triangles) 12 | triangles.count { |triangle| possible?(triangle.map(&:to_i)) } 13 | end 14 | 15 | triangles = File.readlines('inputs/input03.txt').map(&:strip).map(&:split) 16 | 17 | puts possible_triangles(triangles) 18 | 19 | # --- Part Two --- 20 | lines = File.readlines('inputs/input03.txt').map(&:strip).map(&:split) 21 | transposed = [[], [], []] 22 | triangles = [] 23 | lines.each do |line| 24 | transposed[0] << line[0] 25 | transposed[1] << line[1] 26 | transposed[2] << line[2] 27 | end 28 | 29 | transposed.each do |column| 30 | while column.count > 0 31 | triangles << column.shift(3) 32 | end 33 | end 34 | 35 | puts possible_triangles(triangles) 36 | -------------------------------------------------------------------------------- /2016/day04.rb: -------------------------------------------------------------------------------- 1 | # --- Day 4: Security Through Obscurity --- 2 | 3 | # --- Part One --- 4 | rooms = File.readlines('4.in').map(&:strip) 5 | 6 | def room(line) 7 | return nil unless line =~ /((\w|-)+)-(\d+)\[(\w+)\]\z/ 8 | [$1, $3.to_i, $4] 9 | end 10 | 11 | def checksum(encrypted_name) 12 | letters = encrypted_name.split('-').join.chars 13 | counts = Hash.new(0).tap { |h| letters.each { |letter| h[letter] += 1 } } 14 | counts = counts.sort { |a, b| (a[1] == b[1]) ? a[0] <=> b[0] : -1*(a[1] <=> b[1]) } 15 | counts.take(5).map(&:first).join 16 | end 17 | 18 | sector_sums = 0 19 | rooms.each do |room_line| 20 | encrypted_name, sector_id, checksum = room(room_line) 21 | sector_sums += sector_id if checksum(encrypted_name) == checksum 22 | end 23 | 24 | puts sector_sums 25 | 26 | # --- Part Two --- 27 | 28 | def decrypt_room_name(encrypted_name, sector_id) 29 | letters = encrypted_name.chars 30 | decrypted_name = [] 31 | letters.each do |letter| 32 | if letter == '-' 33 | decrypted_name << ' ' 34 | else 35 | decrypted_name << ((letter.ord - 97 + sector_id) % 26 + 97).chr 36 | end 37 | end 38 | decrypted_name.join 39 | end 40 | 41 | names = [] 42 | rooms.each do |room_line| 43 | encrypted_name, sector_id, checksum = room(room_line) 44 | names << [decrypt_room_name(encrypted_name, sector_id), sector_id] if checksum(encrypted_name) == checksum 45 | end 46 | 47 | puts names.detect { |name, _| name[/north/i] } 48 | -------------------------------------------------------------------------------- /2016/day05.rb: -------------------------------------------------------------------------------- 1 | # --- Day 5: How About a Nice Game of Chess? --- 2 | require 'digest/md5' 3 | 4 | # --- Part One --- 5 | 6 | input = 'ugkcyxxp' 7 | index = 0 8 | password = '' 9 | 10 | # Result obtained 11 | password = 'd4cd2ee1' 12 | 13 | while password.size < 8 do 14 | key = Digest::MD5.hexdigest(input + index.to_s) 15 | index += 1 16 | next unless key.start_with? '00000' 17 | password += key[5] 18 | puts password 19 | end 20 | 21 | puts password 22 | 23 | # --- Part Two --- 24 | index = 0 25 | password = [nil]*8 26 | while password.compact.size < 8 do 27 | key = Digest::MD5.hexdigest(input + index.to_s) 28 | index += 1 29 | next unless key.start_with? '00000' 30 | position = key[5] 31 | next if position < '0' || position > '7' 32 | password[position.to_i] ||= key[6] 33 | puts password.join 34 | end 35 | 36 | puts password.join -------------------------------------------------------------------------------- /2016/day06.rb: -------------------------------------------------------------------------------- 1 | # --- Day 6: Signals and Noise --- 2 | 3 | # --- Part One --- 4 | lines = File.readlines('inputs/input06.txt').map(&:strip) 5 | 6 | columns = [] 7 | 8 | lines.first.size.times do |i| 9 | columns << lines.map { |l| l[i] } 10 | end 11 | 12 | message = '' 13 | columns.each do |column| 14 | counts = Hash.new(0).tap { |h| column.each { |letter| h[letter] += 1 } } 15 | counts = counts.sort { |a, b| (a[1] == b[1]) ? a[0] <=> b[0] : -1*(a[1] <=> b[1]) } 16 | message << counts.first.first 17 | end 18 | 19 | puts message 20 | 21 | # --- Part Two --- 22 | 23 | message = '' 24 | columns.each do |column| 25 | counts = Hash.new(0).tap { |h| column.each { |letter| h[letter] += 1 } } 26 | counts = counts.sort { |a, b| (a[1] == b[1]) ? a[0] <=> b[0] : (a[1] <=> b[1]) } 27 | message << counts.first.first 28 | end 29 | 30 | puts message 31 | 32 | 33 | -------------------------------------------------------------------------------- /2016/day07.rb: -------------------------------------------------------------------------------- 1 | # --- Day 7: Internet Protocol Version 7 --- 2 | 3 | # --- Part One --- 4 | 5 | def abba?(chars) 6 | chars.count.times do |i| 7 | break if i + 3 > chars.count - 1 8 | 9 | return true if chars[i] != chars[i+1] && chars[i] == chars[i+3] && chars[i+1] == chars[i+2] 10 | end 11 | 12 | false 13 | end 14 | 15 | def ins_and_outs(ip) 16 | groups = ip.scan(/\w*\[\w+\]\w*/) 17 | ins = [] 18 | outs = [] 19 | groups.each do |group| 20 | if group =~ /(\w*)\[(\w+)\](\w*)/ 21 | ins << $2 22 | outs << $1 unless $1.empty? 23 | outs << $3 unless $3.empty? 24 | end 25 | end 26 | 27 | [ins, outs] 28 | end 29 | 30 | def tls?(ip) 31 | ins, outs = ins_and_outs(ip) 32 | outs.any? { |s| abba?(s.chars) } && !ins.any? { |s| abba?(s.chars) } 33 | end 34 | 35 | lines = File.readlines('inputs/input07.txt').map(&:strip) 36 | 37 | puts lines.count { |line| tls?(line) } 38 | 39 | # --- Part Two --- 40 | 41 | def abas(chars) 42 | abas = [] 43 | chars.count.times do |i| 44 | break if i + 2 > chars.count - 1 45 | abas << chars[i..i+2].join if chars[i] != chars[i+1] && chars[i] == chars[i+2] 46 | end 47 | abas 48 | end 49 | 50 | def bab(aba) 51 | bab = [aba[1], aba[0], aba[1]].join 52 | end 53 | 54 | def ssl?(ip) 55 | ins, outs = ins_and_outs(ip) 56 | 57 | all_abas = outs.map { |s| abas(s.chars) }.flatten 58 | all_babs = all_abas.map { |aba| bab(aba) } 59 | ins.any? do |out| 60 | all_babs.any? { |bab| out.include? (bab) } 61 | end 62 | end 63 | 64 | puts lines.count { |line| ssl?(line) } 65 | -------------------------------------------------------------------------------- /2016/day08.rb: -------------------------------------------------------------------------------- 1 | # --- Day 8: Two-Factor Authentication --- 2 | 3 | # --- Part One --- 4 | 5 | COLS = 50 6 | ROWS = 6 7 | 8 | # COLS = 7 9 | # ROWS = 3 10 | 11 | def process(instruction, screen) 12 | if instruction =~ /rect (\d+)x(\d+)/ 13 | a = $1.to_i 14 | b = $2.to_i 15 | b.times do |i| 16 | a.times do |j| 17 | screen[i][j] = '#' 18 | end 19 | end 20 | elsif instruction =~ /rotate row y=(\d+) by (\d+)/ 21 | a = $1.to_i 22 | b = $2.to_i 23 | rotated = screen[a].dup 24 | screen[a].each_with_index do |p, i| 25 | rotated[(i+b)% COLS] = p 26 | end 27 | screen[a] = rotated 28 | elsif instruction =~ /rotate column x=(\d+) by (\d+)/ 29 | a = $1.to_i 30 | b = $2.to_i 31 | 32 | rotated = screen.map { |r| r[a] } 33 | ROWS.times do |i| 34 | p = screen[i][a] 35 | rotated[(i+b)%ROWS] = p 36 | end 37 | 38 | rotated.each_with_index do |p, i| 39 | screen[i][a] = p 40 | end 41 | end 42 | 43 | screen 44 | end 45 | 46 | screen = [] 47 | ROWS.times do 48 | screen << ['.']*COLS 49 | end 50 | 51 | instructions = File.readlines('inputs/input08.txt').map(&:strip) 52 | 53 | # instructions = ['rect 3x2', 'rotate column x=1 by 1', 'rotate row y=0 by 4', 'rotate column x=1 by 1'] 54 | 55 | instructions.each do |instruction| 56 | screen = process(instruction, screen) 57 | end 58 | 59 | puts screen.map(&:join).join("\n") 60 | puts screen.flatten.count('#') 61 | 62 | # --- Part Two --- 63 | 64 | puts screen.map(&:join).join("\n") 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /2016/day09.rb: -------------------------------------------------------------------------------- 1 | # --- Day 9: Explosives in Cyberspace --- 2 | 3 | # --- Part One --- 4 | 5 | compressed = File.read('inputs/input09.txt').gsub(/\n|\s/, '') 6 | 7 | def marker(marker) 8 | return [0, 1] unless marker && marker =~ /(\d+)x(\d+)/ 9 | [$1, $2].map(&:to_i) 10 | end 11 | 12 | def process_marker(data, current_marker) 13 | x, y = current_marker 14 | result = if x >= data.size 15 | data.size * y 16 | else 17 | x * y + data.size - x 18 | end 19 | result 20 | end 21 | 22 | def decompressed_length(compressed, rec = false) 23 | count = 0 24 | data = '' 25 | marker = nil 26 | current_marker = [0, 1] 27 | section = 0 28 | 29 | compressed.chars.each do |c| 30 | if c == '(' && marker.nil? && section <= 0 31 | # Start new marker 32 | count += (rec ? process_marker_rec(data, current_marker) : process_marker(data, current_marker)) 33 | section = 0 34 | marker = '' 35 | elsif c == ')' && !marker.nil? 36 | # Finish storing a marker and start storing data 37 | current_marker = marker(marker) 38 | section = current_marker[0] 39 | data = '' 40 | marker = nil 41 | elsif c != '(' && c != ')' && !marker.nil? 42 | # Continue processing a new marker 43 | marker << c 44 | else 45 | data << c 46 | section -= 1 47 | end 48 | end 49 | 50 | # Add remaining data 51 | count + (rec ? process_marker_rec(data, current_marker) : process_marker(data, current_marker)) 52 | end 53 | 54 | puts decompressed_length(compressed) 55 | 56 | # --- Part Two --- 57 | 58 | def process_marker_rec(data, current_marker) 59 | x, y = current_marker 60 | 61 | # Check if we need to expand an extra marker 62 | # within the current marker section 63 | marker_section = data[0...x] 64 | size = if marker_section =~ /\(\d+x\d+\)/ 65 | decompressed_length(marker_section, true) 66 | else 67 | marker_section.size 68 | end 69 | 70 | result = if x >= data.size 71 | size * y 72 | else 73 | size * y + data.size - x 74 | end 75 | result 76 | end 77 | 78 | puts decompressed_length(compressed, true) 79 | -------------------------------------------------------------------------------- /2016/day10.rb: -------------------------------------------------------------------------------- 1 | # --- Day 10: Balance Bots --- 2 | 3 | # --- Part One --- 4 | 5 | def init_game(init) 6 | bots = {} 7 | init.each do |i| 8 | next unless i =~ /value (\d+) goes to bot (\d+)/ 9 | value = $1.to_i 10 | bot = "bot #{$2}" 11 | bots[bot] ||= [] 12 | bots[bot] << value 13 | end 14 | 15 | bots 16 | end 17 | 18 | def assign_instructions(instructions) 19 | assigned = {} 20 | 21 | instructions.each do |instruction| 22 | next unless instruction =~ /(bot \d+) gives low to (bot \d+|output \d+) and high to (bot \d+|output \d+)/ 23 | bot = $1 24 | low = $2 25 | high = $3 26 | assigned[bot] = [low, high] 27 | end 28 | 29 | assigned 30 | end 31 | 32 | def play_round(bots, outputs, instructions) 33 | # Look for bots with 2 chips, and process them 34 | playing = bots.select { |b, values| values.count == 2} 35 | playing.each do |bot, values| 36 | values.sort! 37 | next unless instructions[bot] 38 | low, high = instructions[bot] 39 | if low.start_with? 'bot' 40 | bots[low] ||= [] 41 | bots[low] << values[0] 42 | else 43 | outputs[low] ||= [] 44 | outputs[low] << values[0] 45 | end 46 | 47 | if high.start_with? 'bot' 48 | bots[high] ||= [] 49 | bots[high] << values[1] 50 | else 51 | outputs[high] ||= [] 52 | outputs[high] << values[1] 53 | end 54 | 55 | bots[bot] = [] 56 | end 57 | end 58 | 59 | def go!(bots, instructions, magic_pair = [17, 61]) 60 | # Check if some bot have the magic pair, and if it has, return 61 | winner = bots.detect { |b, values| values.sort == magic_pair } 62 | return winner.first if winner 63 | 64 | play_round(bots, {}, instructions) 65 | 66 | go!(bots, instructions, magic_pair) 67 | end 68 | 69 | instructions = File.readlines('inputs/input10.txt').map(&:strip) 70 | 71 | init = instructions.select { |ins| ins.start_with? 'value' } 72 | bots = init_game(init) 73 | instructions = assign_instructions(instructions - init) 74 | puts go!(bots, instructions) 75 | 76 | # --- Part Two --- 77 | 78 | def output!(bots, outputs, instructions) 79 | while bots.detect { |_, values| values.count == 2 } 80 | play_round(bots, outputs, instructions) 81 | end 82 | end 83 | 84 | outputs = {} 85 | output!(bots, outputs, instructions) 86 | 87 | puts %w(0 1 2).inject(1) { |acc, o| acc * outputs["output #{o}"].first.to_i } 88 | -------------------------------------------------------------------------------- /2016/day12.rb: -------------------------------------------------------------------------------- 1 | # --- Day 12: Leonardo's Monorail --- 2 | 3 | # --- Part One --- 4 | 5 | code = %(cpy 1 a 6 | cpy 1 b 7 | cpy 26 d 8 | jnz c 2 9 | jnz 1 5 10 | cpy 7 c 11 | inc d 12 | dec c 13 | jnz c -2 14 | cpy a c 15 | inc a 16 | dec b 17 | jnz b -2 18 | cpy c b 19 | dec d 20 | jnz d -6 21 | cpy 14 c 22 | cpy 14 d 23 | inc a 24 | dec d 25 | jnz d -2 26 | dec c 27 | jnz c -5).split("\n").map(&:strip) 28 | 29 | def process_instruction(instruction, pointer, registers) 30 | case instruction 31 | when /cpy (\d+) ([a-d])/ 32 | registers[$2.to_sym] = $1.to_i 33 | pointer = pointer + 1 34 | when /cpy ([a-d]) ([a-d])/ 35 | registers[$2.to_sym] = registers[$1.to_sym] 36 | pointer = pointer + 1 37 | when /(inc|dec) ([a-d])/ 38 | change = $1 == 'inc' ? 1 : -1 39 | registers[$2.to_sym] += change 40 | pointer = pointer + 1 41 | when /jnz ([a-d]) (-?\d+)/ 42 | pointer = registers[$1.to_sym] != 0 ? pointer + $2.to_i : pointer + 1 43 | when /jnz (\d+) (-?\d+)/ 44 | pointer = $1.to_i != 0 ? pointer + $2.to_i : pointer + 1 45 | end 46 | 47 | pointer 48 | end 49 | 50 | def execute(code, registers) 51 | i = 0 52 | while i < code.count 53 | i = process_instruction(code[i], i, registers) 54 | end 55 | 56 | registers[:a] 57 | end 58 | 59 | puts execute(code, { a: 0, b: 0, c: 0, d: 0}) 60 | 61 | 62 | # --- Part Two --- 63 | 64 | puts execute(code, { a: 0, b: 0, c: 1, d: 0}) 65 | -------------------------------------------------------------------------------- /2016/day13.rb: -------------------------------------------------------------------------------- 1 | # --- Day 13: A Maze of Twisty Little Cubicles --- 2 | 3 | # --- Part One --- 4 | 5 | def print_board(board) 6 | puts board.map { |l| l.join }.join("\n") 7 | end 8 | 9 | def draw_board(favourite_number, size) 10 | board = [] 11 | size.times do 12 | board << ['.']*size 13 | end 14 | 15 | size.times do |y| 16 | size.times do |x| 17 | exp = x*x + 3*x + 2*x*y + y + y*y + favourite_number 18 | ones = exp.to_s(2).chars.count('1') 19 | board[y][x] = '#' unless ones % 2 == 0 20 | end 21 | end 22 | 23 | board 24 | end 25 | 26 | def children(x, y, board) 27 | children = [] 28 | 29 | children << [x+1, y] if x + 1 < board.count && board[y][x+1] == '.' 30 | children << [x-1, y] if x - 1 >= 0 && board[y][x-1] == '.' 31 | children << [x, y+1] if y + 1 < board.count && board[y+1][x] == '.' 32 | children << [x, y-1] if y - 1 >= 0 && board[y-1][x] == '.' 33 | 34 | children 35 | end 36 | 37 | def bfs(board) 38 | x, y = 1, 1 39 | board[y][x] = 0 # visited 40 | queue = [[x, y]] 41 | distance = 0 42 | while queue.size > 0 do 43 | v = queue.shift 44 | # puts "v: #{v}" 45 | # puts "queue: #{queue}" 46 | x, y = v 47 | distance = board[y][x] 48 | children(x, y, board).each do |u| 49 | ux, uy = u 50 | board[uy][ux] = distance + 1 # visited 51 | queue << [ux, uy] 52 | end 53 | end 54 | end 55 | 56 | favourite_number = 1358 57 | target = [31, 39] 58 | current = [1, 1] 59 | 60 | # favourite_number = 10 61 | # target = [7, 4] 62 | # current = [1, 1] 63 | 64 | board = draw_board(favourite_number, 50) 65 | bfs(board) 66 | puts board[target[1]][target[0]] 67 | 68 | # --- Part Two --- 69 | 70 | puts board.flatten.count { |d| d != '.' && d != '#' && d <= 50 } 71 | print_board(board) 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /2016/day14.rb: -------------------------------------------------------------------------------- 1 | # --- Day 14: One-Time Pad --- 2 | 3 | # --- Part One --- 4 | require 'digest/md5' 5 | 6 | def next_1000(salt, index, stretches = 1) 7 | i = index 8 | r = [] 9 | 1000.times do 10 | h = salt + i.to_s 11 | stretches.times do 12 | h = Digest::MD5.hexdigest(h) 13 | end 14 | r << h 15 | i += 1 16 | end 17 | r 18 | end 19 | 20 | def find_triplet(key) 21 | triplet = [] 22 | key.chars.each do |c| 23 | if triplet.last == c 24 | triplet << c 25 | break if triplet.size == 3 26 | else 27 | triplet = [c] 28 | end 29 | end 30 | triplet.size == 3 ? triplet.join : nil 31 | end 32 | 33 | def key?(triplet, batch) 34 | batch[0...1000].any? { |key| key.include? triplet } 35 | end 36 | 37 | salt = 'zpqevtbw' 38 | keys = [] 39 | index = 0 40 | batch = next_1000(salt, index) 41 | batch |= next_1000(salt, index + 1000) 42 | 43 | while keys.size < 64 do 44 | key = batch.shift 45 | triplet = find_triplet(key) 46 | keys << [index, key] if triplet && key?(triplet + triplet[0..1], batch) 47 | index += 1 48 | batch |= next_1000(salt, index + 1000) if index % 1000 == 0 49 | end 50 | 51 | puts keys.map { |k| k.join(' ') }.join("\n") 52 | 53 | # --- Part Two --- 54 | 55 | salt = 'zpqevtbw' 56 | # salt = 'abc' 57 | keys = [] 58 | index = 0 59 | batch = next_1000(salt, index, 2017) 60 | batch |= next_1000(salt, index + 1000, 2017) 61 | 62 | while keys.size < 64 do 63 | key = batch.shift 64 | triplet = find_triplet(key) 65 | keys << [index, key] if triplet && key?(triplet + triplet[0..1], batch) 66 | index += 1 67 | batch |= next_1000(salt, index + 1000, 2017) if index % 1000 == 0 68 | end 69 | 70 | puts keys.map { |k| k.join(' ') }.join("\n") 71 | -------------------------------------------------------------------------------- /2016/day15.rb: -------------------------------------------------------------------------------- 1 | # --- Day 15: Timing is Everything --- 2 | 3 | # --- Part One --- 4 | 5 | instructions = ["Disc #1 has 17 positions; at time=0, it is at position 15.", 6 | "Disc #2 has 3 positions; at time=0, it is at position 2.", 7 | "Disc #3 has 19 positions; at time=0, it is at position 4.", 8 | "Disc #4 has 13 positions; at time=0, it is at position 2.", 9 | "Disc #5 has 7 positions; at time=0, it is at position 2.", 10 | "Disc #6 has 5 positions; at time=0, it is at position 0."] 11 | 12 | def valid?(multipliers, offsets, i) 13 | multipliers.each_with_index do |m, j| 14 | if (offsets[j] + i) % m != 0 15 | return false 16 | end 17 | end 18 | 19 | true 20 | end 21 | 22 | def time(instructions) 23 | multipliers = [] 24 | offsets = [] 25 | i = 1 26 | instructions.each do |instruction| 27 | instruction =~ /has (\d+) positions; at time=0, it is at position (\d+)./ 28 | multipliers << $1.to_i 29 | offsets << ($2.to_i + i) % $1.to_i 30 | i += 1 31 | end 32 | 33 | factor = multipliers.max 34 | i = factor - offsets[multipliers.index(factor)] 35 | 36 | while !valid?(multipliers, offsets, i) 37 | i += factor 38 | end 39 | 40 | i 41 | end 42 | 43 | puts time(instructions) 44 | 45 | # --- Part Two --- 46 | 47 | instructions << "Disc #7 has 11 positions; at time=0, it is at position 0." 48 | puts time(instructions) -------------------------------------------------------------------------------- /2016/day16.rb: -------------------------------------------------------------------------------- 1 | # --- Day 16: Dragon Checksum --- 2 | 3 | # --- Part One --- 4 | 5 | def neg(b) 6 | b.chars.collect { |c| c == '1' ? '0' : '1' }.join 7 | end 8 | 9 | def fill_disk(init, size) 10 | a = init 11 | while a.size < size 12 | b = a 13 | b = b.reverse 14 | b = neg(b) 15 | a = a + '0' + b 16 | end 17 | a 18 | end 19 | 20 | def checksum(data) 21 | data = data.chars 22 | checksum = [] 23 | while true 24 | data.each_slice(2) do |g| 25 | if g[0] == g[1] 26 | checksum << '1' 27 | else 28 | checksum << '0' 29 | end 30 | end 31 | 32 | return checksum.join if checksum.size.odd? 33 | 34 | data = checksum.dup 35 | checksum = [] 36 | end 37 | 38 | checksum.join 39 | end 40 | 41 | input = '11110010111001001' 42 | length = 272 43 | 44 | data = fill_disk(input, length) 45 | puts checksum(data[0...length]) 46 | 47 | # --- Part Two --- 48 | 49 | length = 35651584 50 | 51 | data = fill_disk(input, length) 52 | puts checksum(data[0...length]) 53 | -------------------------------------------------------------------------------- /2016/day17.rb: -------------------------------------------------------------------------------- 1 | # --- Day 17: Two Steps Forward --- 2 | 3 | require 'digest/md5' 4 | 5 | # --- Part One --- 6 | 7 | def next_steps(string) 8 | h = Digest::MD5.hexdigest(string).chars[0...4] 9 | # up, down, left, and right 10 | h.map { |c| ('b'..'f').include?(c) } 11 | end 12 | 13 | def children(i, j, current_path) 14 | children = [] 15 | steps = next_steps(current_path) 16 | # up 17 | children << [i-1, j, 'U'] if i-1 >= 0 && steps[0] 18 | # down 19 | children << [i+1, j, 'D'] if i+1 < 4 && steps[1] 20 | # left 21 | children << [i, j-1, 'L'] if j-1 >= 0 && steps[2] 22 | # right 23 | children << [i, j+1, 'R'] if j+1 < 4 && steps[3] 24 | 25 | children 26 | end 27 | 28 | def shortest_path(i, j, current_path) 29 | return current_path if i == 3 && j == 3 30 | 31 | children = children(i, j, current_path) 32 | return nil unless children.any? 33 | 34 | path = nil 35 | children.each do |child| 36 | child_path = shortest_path(child[0], child[1], current_path + child[2]) 37 | path = child_path if child_path && (path.nil? || path.size > child_path.size) 38 | end 39 | 40 | path 41 | end 42 | 43 | passcode = 'qzthpkfp' 44 | puts shortest_path(0, 0, passcode).gsub(passcode, '') 45 | 46 | # --- Part Two --- 47 | 48 | def longest_path(i, j, current_path) 49 | return current_path if i == 3 && j == 3 50 | 51 | children = children(i, j, current_path) 52 | return nil unless children.any? 53 | 54 | path = nil 55 | children.each do |child| 56 | child_path = longest_path(child[0], child[1], current_path + child[2]) 57 | path = child_path if child_path && (path.nil? || path.size < child_path.size) 58 | end 59 | 60 | path 61 | end 62 | 63 | puts longest_path(0, 0, passcode).gsub(passcode, '').size -------------------------------------------------------------------------------- /2016/day18.rb: -------------------------------------------------------------------------------- 1 | # --- Day 18: Like a Rogue --- 2 | 3 | # --- Part One --- 4 | 5 | def next_row(row) 6 | tiles = row.chars 7 | next_row = [] 8 | # A new tile is a trap only in one of the following situations: 9 | # - Its left and center tiles are traps, but its right tile is not. 10 | # - Its center and right tiles are traps, but its left tile is not. 11 | # - Only its left tile is a trap. 12 | # - Only its right tile is a trap. 13 | tiles.count.times do |i| 14 | if i > 0 && tiles[i-1] == '^' && tiles[i] == '^' && (i == tiles.count - 1 || tiles[i+1] == '.') 15 | next_row << '^' 16 | elsif i < tiles.count - 1 && tiles[i+1] == '^' && tiles[i] == '^' && (i == 0 || tiles[i-1] == '.') 17 | next_row << '^' 18 | elsif i > 0 && tiles[i-1] == '^' && tiles[i] == '.' && (i == tiles.count - 1 || tiles[i+1] == '.') 19 | next_row << '^' 20 | elsif i < tiles.count - 1 && tiles[i+1] == '^' && tiles[i] == '.' && (i == 0 || tiles[i-1] == '.') 21 | next_row << '^' 22 | else 23 | next_row << '.' 24 | end 25 | end 26 | 27 | next_row.join 28 | end 29 | 30 | def complete_room(start, size) 31 | room = [] 32 | size.times do 33 | room << start 34 | start = next_row(start) 35 | end 36 | 37 | room 38 | end 39 | 40 | start = '.^^..^...^..^^.^^^.^^^.^^^^^^.^.^^^^.^^.^^^^^^.^...^......^...^^^..^^^.....^^^^^^^^^....^^...^^^^..^' 41 | 42 | room = complete_room(start, 40) 43 | puts room.join.chars.count('.') 44 | 45 | # --- Part Two --- 46 | 47 | room = complete_room(start, 400000) 48 | puts room.join.chars.count('.') 49 | 50 | 51 | -------------------------------------------------------------------------------- /2016/day19.rb: -------------------------------------------------------------------------------- 1 | # --- Day 17: Two Steps Forward --- 2 | 3 | # --- Part One --- 4 | elves = 3012210 5 | gifts = [true]*elves 6 | i = 0 7 | 8 | while gifts.count(true) > 1 9 | gifts.index(true).upto(elves - 1) do |i| 10 | next unless gifts[i] 11 | j = (i + 1) % elves 12 | while !gifts[j] do 13 | j = (j + 1) % elves 14 | end 15 | gifts[j] = false 16 | end 17 | end 18 | 19 | puts gifts.index(true) + 1 20 | 21 | # --- Part Two --- 22 | 23 | elves = (1..3012210).to_a 24 | 25 | i = 0 26 | while elves.count > 1 27 | c = (i + elves.count/2) % elves.count 28 | elves.delete_at(c) 29 | i -= 1 if c < i 30 | i = (i + 1) % elves.count 31 | end 32 | 33 | puts elves.first 34 | -------------------------------------------------------------------------------- /2016/day20.rb: -------------------------------------------------------------------------------- 1 | # --- Day 20: Firewall Rules --- 2 | 3 | # --- Part One --- 4 | MAX = 4294967295 5 | # MAX = 9 6 | intervals = File.readlines('inputs/input20.txt').map(&:strip).map { |block| block.split('-').map(&:to_i) } 7 | # intervals = [[5,8], [0,2], [4,7]] 8 | intervals.sort_by! { |i| i.first } 9 | 10 | ip = 0 11 | intervals.each do |interval| 12 | ip = interval.last + 1 if interval.first <= ip && ip <= interval.last 13 | end 14 | 15 | puts ip 16 | 17 | # --- Part Two --- 18 | 19 | ip = 0 20 | allowed = 0 21 | intervals.each do |interval| 22 | allowed += interval.first - ip if interval.first >= ip 23 | ip = interval.last + 1 if ip <= interval.last 24 | end 25 | 26 | allowed += MAX - ip + 1 27 | 28 | puts allowed 29 | -------------------------------------------------------------------------------- /2016/day22.rb: -------------------------------------------------------------------------------- 1 | # --- Day 22: Grid Computing --- 2 | 3 | # --- Part One --- 4 | 5 | class Node 6 | attr_accessor :size, :used, :avail, :percent, :x, :y 7 | 8 | def inspect 9 | "#{name} #{size}T #{used}T #{avail}T #{percent}%" 10 | end 11 | 12 | def to_s 13 | self.inspect 14 | end 15 | 16 | def ==(node) 17 | self.name == node.name 18 | end 19 | 20 | def name 21 | "node-x#{x}-y#{y}" 22 | end 23 | end 24 | 25 | def viable_pairs(nodes) 26 | pairs = [] 27 | 28 | nodes.each do |node_a| 29 | nodes.each do |node_b| 30 | # Nodes A and B are not the same node. 31 | # Node A is not empty (its Used is not zero). 32 | # The data on node A (its Used) would fit on node B (its Avail). 33 | next unless node_a != node_b && node_a.used > 0 && node_a.used <= node_b.avail 34 | pairs << [node_a, node_b] 35 | end 36 | end 37 | 38 | pairs 39 | end 40 | 41 | 42 | df = File.readlines('inputs/input22.txt').map(&:strip) 43 | nodes = [] 44 | 45 | df.each do |df_line| 46 | # Filesystem Size Used Avail Use% 47 | # /dev/grid/node-x0-y0 92T 70T 22T 76% 48 | next unless df_line =~ /\/dev\/grid\/node-x(\d+)-y(\d+)\s+(\d+)T\s+(\d+)T\s+(\d+)T\s+(\d+)%/ 49 | node = Node.new 50 | node.x = $1.to_i 51 | node.y = $2.to_i 52 | node.size = $3.to_i 53 | node.used = $4.to_i 54 | node.avail = $5.to_i 55 | node.percent = $6.to_i 56 | nodes << node 57 | end 58 | 59 | pairs = viable_pairs(nodes) 60 | puts pairs.count 61 | 62 | # --- Part Two --- 63 | 64 | # We are looking for a path from G (node with y=0 and the highest x, that is, the node 65 | # in the top-right corner) to the init node (x0-y0). 66 | g = Node.new 67 | g.x = nodes.map(&:x).max 68 | g.y = 0 69 | s = Node.new 70 | s.x = s.y = 0 71 | 72 | g_pairs = pairs.select { |p| p.first == g || p.last == g } 73 | s_pairs = pairs.select { |p| p.first == s || p.last == s } 74 | middle = [s_pairs + g_pairs].flatten.uniq 75 | middle.delete(g) 76 | middle.delete(s) 77 | middle = middle.first 78 | 79 | obstacles = nodes.select { |n| !pairs.flatten.include? n } 80 | obs_x = obstacles.map(&:x).min 81 | # Going up to y0: going up to obstacle, going around obstacle 82 | steps = middle.y + (middle.x - obs_x + 1)*2 83 | # Going right up to g.x 84 | steps += g.x - middle.x 85 | # 5 steps on moving G to the left 86 | steps += 5*(g.x - 1) 87 | 88 | puts steps 89 | -------------------------------------------------------------------------------- /2016/day24.rb: -------------------------------------------------------------------------------- 1 | # --- Day 24: Air Duct Spelunking --- 2 | 3 | # --- Part One --- 4 | 5 | def open(value) 6 | value == '.' 7 | end 8 | 9 | def children(x, y, board) 10 | children = [] 11 | 12 | children << [x+1, y] if x + 1 < board.first.count && open(board[y][x+1]) 13 | children << [x-1, y] if x - 1 >= 0 && open(board[y][x-1]) 14 | children << [x, y+1] if y + 1 < board.count && open(board[y+1][x]) 15 | children << [x, y-1] if y - 1 >= 0 && open(board[y-1][x]) 16 | 17 | children 18 | end 19 | 20 | def bfs(board, start) 21 | x, y = start 22 | board[y][x] = 0 # visited 23 | queue = [[x, y]] 24 | distance = 0 25 | while queue.size > 0 do 26 | v = queue.shift 27 | x, y = v 28 | distance = board[y][x] 29 | children(x, y, board).each do |u| 30 | ux, uy = u 31 | board[uy][ux] = distance + 1 # visited 32 | queue << [ux, uy] 33 | end 34 | end 35 | end 36 | 37 | def index(number, board) 38 | y = board.index { |l| l.include? number } 39 | x = board[y].index(number) 40 | [x, y] 41 | end 42 | 43 | def dup(board) 44 | dupped = [] 45 | board.each do |row| 46 | dup_row = [] 47 | row.each do |c| 48 | dup_row << c 49 | end 50 | dupped << dup_row 51 | end 52 | dupped 53 | end 54 | 55 | def len(order, distances) 56 | len = 0 57 | current = '0' 58 | order.each do |c| 59 | len += distances[[current, c].sort] 60 | current = c 61 | end 62 | len 63 | end 64 | 65 | board = File.readlines('inputs/input24.txt').map(&:strip).map(&:chars) 66 | 67 | numbers = board.flatten.select { |d| d =~ /\d+/ }.sort 68 | indexes = {} 69 | # Replace all numbers with '.' and store the indexes 70 | numbers.each do |n| 71 | x, y = index(n, board) 72 | board[y][x] = '.' 73 | indexes[n] = [x, y] 74 | end 75 | 76 | distances = {} 77 | 0.upto(numbers.count - 2) do |i| 78 | a = numbers[i] 79 | aux_board = dup(board) 80 | bfs(aux_board, indexes[a]) 81 | (i+1).upto(numbers.count - 1) do |j| 82 | b = numbers[j] 83 | target = indexes[b] 84 | distances[[a, b]] = aux_board[target[1]][target[0]] 85 | end 86 | end 87 | 88 | min_steps = board.count * board.first.count 89 | min_order = nil 90 | (numbers - ["0"]).permutation do |order| 91 | candidate = len(order, distances) 92 | if min_steps > candidate 93 | min_steps = candidate 94 | min_order = order 95 | end 96 | end 97 | 98 | puts min_steps 99 | puts min_order.join(' ') 100 | 101 | # --- Part Two --- 102 | 103 | min_steps = board.count * board.first.count 104 | min_order = nil 105 | (numbers - ["0"]).permutation do |order| 106 | # Always go back to 0 107 | order << '0' 108 | candidate = len(order, distances) 109 | if min_steps > candidate 110 | min_steps = candidate 111 | min_order = order 112 | end 113 | end 114 | 115 | puts min_steps 116 | puts min_order.join(' ') 117 | 118 | -------------------------------------------------------------------------------- /2016/day25.rb: -------------------------------------------------------------------------------- 1 | # --- Day 25: Clock Signal --- 2 | 3 | # --- Part One --- 4 | require 'byebug' 5 | def optimise(code) 6 | # Convert loops in multiplications 7 | # cpy something 8 | # inc 9 | # dec/inc 10 | # jnz -2 11 | # dec/inc 12 | # jnz -5 13 | # This is add * 14 | 15 | to_optimise = code.select { |inst| inst =~ /jnz [a-d] -5/ } 16 | to_optimise.each do |jump| 17 | i = code.index(jump) 18 | jump =~ /jnz ([a-d]) -5/ 19 | reg3 = $1.to_sym 20 | next unless i - 5 >= 0 21 | next unless code[i-1] =~ /(dec|inc) #{reg3}/ && code[i-2] =~ /jnz ([a-d]) -2/ 22 | reg2 = $1.to_sym 23 | next unless code[i-3] =~ /(dec|inc) #{reg2}/ && code[i-4] =~ /inc ([a-d])/ 24 | reg1 = $1.to_sym 25 | next unless code[i-5] =~ /cpy [^\s]+ #{reg2}/ 26 | code = code[0..i-5] + ["multadd #{reg1} #{reg2} #{reg3}"] + ['skip']*4 + code[i+1..-1] 27 | end 28 | 29 | code 30 | end 31 | 32 | def value(operand, registers) 33 | 'abcd'.include?(operand) ? registers[operand.to_sym] : operand.to_i 34 | end 35 | 36 | def process_instruction(instruction, pointer, registers, output) 37 | # debugger if pointer >= 20 38 | case instruction 39 | when /cpy ([a-d]|(-?\d+)) ([a-d])/ 40 | registers[$3.to_sym] = value($1, registers) 41 | pointer = pointer + 1 42 | when /(inc|dec) ([a-d])/ 43 | change = $1 == 'inc' ? 1 : -1 44 | registers[$2.to_sym] += change 45 | pointer = pointer + 1 46 | when /jnz ([a-d]|-?\d+) ([a-d]|-?\d+)/ 47 | check = value($1, registers) 48 | steps = value($2, registers) 49 | pointer = check != 0 ? pointer + steps : pointer + 1 50 | when /multadd ([a-d]) ([a-d]) ([a-d])/ 51 | registers[$1.to_sym] += registers[$2.to_sym]*registers[$3.to_sym] 52 | registers[$2.to_sym] = 0 53 | registers[$3.to_sym] = 0 54 | pointer = pointer + 1 55 | when /out ([a-d])/ 56 | value = registers[$1.to_sym] 57 | puts "Output: #{value}" 58 | if output.last.nil? && value == 0 || !output.last.nil? && value != output.last.last 59 | return :success if output.count > 10000 60 | output << [registers, value] 61 | else 62 | return :error 63 | end 64 | pointer = pointer + 1 65 | else 66 | # Invalid instruction: skip 67 | pointer = pointer + 1 68 | end 69 | 70 | pointer 71 | end 72 | 73 | def execute(code, registers) 74 | i = 0 75 | output = [] 76 | while i < code.count 77 | i = process_instruction(code[i], i, registers, output) 78 | return i if i == :error || i == :success 79 | end 80 | end 81 | 82 | code = File.readlines('inputs/input25.txt').map(&:strip) 83 | code = optimise(code) 84 | 85 | result = :error 86 | a = 1 87 | while result != :success 88 | result = execute(code, { a: a, b: 0, c: 0, d: 0 }) 89 | puts "Got #{result} for a=#{a}" 90 | a += 1 91 | end 92 | -------------------------------------------------------------------------------- /2016/inputs/input23.txt: -------------------------------------------------------------------------------- 1 | cpy a b 2 | dec b 3 | cpy a d 4 | cpy 0 a 5 | cpy b c 6 | inc a 7 | dec c 8 | jnz c -2 9 | dec d 10 | jnz d -5 11 | dec b 12 | cpy b c 13 | cpy c d 14 | dec d 15 | inc c 16 | jnz d -2 17 | tgl c 18 | cpy -16 c 19 | jnz 1 c 20 | cpy 87 c 21 | jnz 80 d 22 | inc a 23 | inc d 24 | jnz d -2 25 | inc c 26 | jnz c -5 -------------------------------------------------------------------------------- /2016/inputs/input25.txt: -------------------------------------------------------------------------------- 1 | cpy a d 2 | cpy 9 c 3 | cpy 282 b 4 | inc d 5 | dec b 6 | jnz b -2 7 | dec c 8 | jnz c -5 9 | cpy d a 10 | jnz 0 0 11 | cpy a b 12 | cpy 0 a 13 | cpy 2 c 14 | jnz b 2 15 | jnz 1 6 16 | dec b 17 | dec c 18 | jnz c -4 19 | inc a 20 | jnz 1 -7 21 | cpy 2 b 22 | jnz c 2 23 | jnz 1 4 24 | dec b 25 | dec c 26 | jnz 1 -4 27 | jnz 0 0 28 | out b 29 | jnz a -19 30 | jnz 1 -21 31 | -------------------------------------------------------------------------------- /2017/day01.exs: -------------------------------------------------------------------------------- 1 | # --- Day 1: Inverse Captcha --- 2 | 3 | input = "6592822488931338589815525425236818285229555616392928433262436847386544514648645288129834834862363847542262953164877694234514375164927616649264122487182321437459646851966649732474925353281699895326824852555747127547527163197544539468632369858413232684269835288817735678173986264554586412678364433327621627496939956645283712453265255261565511586373551439198276373843771249563722914847255524452675842558622845416218195374459386785618255129831539984559644185369543662821311686162137672168266152494656448824719791398797359326412235723234585539515385352426579831251943911197862994974133738196775618715739412713224837531544346114877971977411275354168752719858889347588136787894798476123335894514342411742111135337286449968879251481449757294167363867119927811513529711239534914119292833111624483472466781475951494348516125474142532923858941279569675445694654355314925386833175795464912974865287564866767924677333599828829875283753669783176288899797691713766199641716546284841387455733132519649365113182432238477673375234793394595435816924453585513973119548841577126141962776649294322189695375451743747581241922657947182232454611837512564776273929815169367899818698892234618847815155578736875295629917247977658723868641411493551796998791839776335793682643551875947346347344695869874564432566956882395424267187552799458352121248147371938943799995158617871393289534789214852747976587432857675156884837634687257363975437535621197887877326295229195663235129213398178282549432599455965759999159247295857366485345759516622427833518837458236123723353817444545271644684925297477149298484753858863551357266259935298184325926848958828192317538375317946457985874965434486829387647425222952585293626473351211161684297351932771462665621764392833122236577353669215833721772482863775629244619639234636853267934895783891823877845198326665728659328729472456175285229681244974389248235457688922179237895954959228638193933854787917647154837695422429184757725387589969781672596568421191236374563718951738499591454571728641951699981615249635314789251239677393251756396" 4 | 5 | list = Enum.map(String.graphemes(input), &String.to_integer/1) 6 | size = length(list) 7 | 8 | is_valid = fn({x, i}) -> x == Enum.at(list, (rem i+1, size)) end 9 | 10 | Enum.filter(Enum.with_index(list), fn(t) -> is_valid.(t) end) 11 | |> Enum.map(fn(t) -> elem(t,0) end) 12 | |> Enum.reduce(fn(x, acc) -> x + acc end) 13 | |> IO.puts 14 | 15 | # --- Part Two --- 16 | half = div(size, 2) 17 | is_valid = fn({x, i}) -> x == Enum.at(list, rem(i+half, size)) end 18 | 19 | Enum.filter(Enum.with_index(list), fn(t) -> is_valid.(t) end) 20 | |> Enum.map(fn(t) -> elem(t,0) end) 21 | |> Enum.reduce(fn(x, acc) -> x + acc end) 22 | |> IO.puts 23 | -------------------------------------------------------------------------------- /2017/day02.exs: -------------------------------------------------------------------------------- 1 | # --- Day 2: Corruption Checksum --- 2 | 3 | spreadsheet = "104 240 147 246 123 175 372 71 116 230 260 118 202 270 277 292 4 | 740 755 135 205 429 822 844 90 828 115 440 805 526 91 519 373 5 | 1630 991 1471 1294 52 1566 50 1508 1367 1489 55 547 342 512 323 51 6 | 1356 178 1705 119 1609 1409 245 292 1434 694 405 1692 247 193 1482 1407 7 | 2235 3321 3647 212 1402 3711 3641 1287 2725 692 1235 3100 123 144 104 101 8 | 1306 1224 1238 186 751 734 1204 1275 366 149 1114 166 1118 239 153 943 9 | 132 1547 1564 512 2643 2376 2324 2159 1658 107 1604 145 2407 131 2073 1878 10 | 1845 91 1662 108 92 1706 1815 1797 1728 1150 1576 83 97 547 1267 261 11 | 78 558 419 435 565 107 638 173 93 580 338 52 633 256 377 73 12 | 1143 3516 4205 3523 148 401 3996 3588 300 1117 2915 1649 135 134 182 267 13 | 156 2760 1816 2442 2985 990 2598 1273 167 821 138 141 2761 2399 1330 1276 14 | 3746 3979 2989 161 4554 156 3359 173 3319 192 3707 264 762 2672 4423 2924 15 | 3098 4309 4971 5439 131 171 5544 595 154 571 4399 4294 160 6201 4329 5244 16 | 728 249 1728 305 2407 239 691 2241 2545 1543 55 2303 1020 753 193 1638 17 | 260 352 190 877 118 77 1065 1105 1085 1032 71 87 851 56 1161 667 18 | 1763 464 182 1932 1209 640 545 931 1979 197 1774 174 2074 1800 939 161" 19 | 20 | input = spreadsheet |> String.split("\n") 21 | |> Enum.map(fn(x) -> String.split(x, ~r{\s}, trim: true) end) 22 | |> Enum.map(fn(x) -> Enum.map(x, &String.to_integer/1) end) 23 | 24 | 25 | # --- Part One --- 26 | input |> Enum.map(fn(x) -> Enum.max(x) - Enum.min(x) end) 27 | |> Enum.reduce(&(&1 + &2)) 28 | |> IO.puts 29 | 30 | 31 | # --- Part Two --- 32 | pairs = fn(list) -> Enum.map(list, fn(x) -> {x, Enum.find(list -- [x], fn(y) -> rem(x, y) == 0 end)} end) end 33 | 34 | input |> Enum.map(fn(x) -> Enum.find(pairs.(x), &(!is_nil(elem(&1,1)))) end) 35 | |> Enum.map(fn({x, y}) -> div(x, y) end) 36 | |> Enum.reduce(fn(x, acc) -> x + acc end) 37 | |> IO.puts 38 | 39 | 40 | -------------------------------------------------------------------------------- /2017/day03.exs: -------------------------------------------------------------------------------- 1 | # --- Day 3: Spiral Memory --- 2 | 3 | # 17 16 15 14 13 4 | # 18 5 4 3 12 5 | # 19 6 1 2 11 6 | # 20 7 8 9 10 7 | # 21 22 23---> ... 8 | 9 | defmodule Memory do 10 | 11 | defp size(location) do 12 | closest(location) |> make_odd 13 | end 14 | 15 | defp base(size) do 16 | div(size, 2) 17 | end 18 | 19 | defp offset(size, location) do 20 | start = (size - 2)*(size - 2) + 1 21 | finish = size*size - 1 22 | perimeter = finish - start + 2 23 | segment_size = div(perimeter, 4) 24 | n_segment = div((location - start), segment_size) 25 | middle_segment = n_segment * segment_size + start + div(segment_size, 2) 26 | end 27 | 28 | defp closest(location) do 29 | :math.sqrt(location) |> :math.ceil |> round 30 | end 31 | 32 | defp make_odd(n) when rem(n, 2) == 0, do: n + 1 33 | defp make_odd(n), do: n 34 | 35 | # iex(7)> Memory.size(location) 36 | # 527 37 | # First segment: 38 | # 275626 to 276152 39 | # Second segment: 40 | # 276152 to 276678 41 | # Third segment: 42 | # 276678 to 277204 43 | # Fourth segment: 44 | # 277204 to 277730 45 | # middle point = 277467 46 | # Distance = 211 47 | # Total = 211 + base = 211 + 263 + 1 = 475 48 | 49 | # --- Part Two --- 50 | # 147 142 133 122 59 51 | # 304 5 4 2 57 52 | # 330 10 1 1 54 53 | # 351 11 23 25 26 54 | # 362 747 806---> ... 55 | end 56 | 57 | # Part two 58 | # https://oeis.org/A141481 59 | # https://oeis.org/A141481/b141481.txt -------------------------------------------------------------------------------- /2017/day04.exs: -------------------------------------------------------------------------------- 1 | # --- Day 4: High-Entropy Passphrases --- 2 | 3 | # To ensure security, a valid passphrase must contain no duplicate words. 4 | # aa bb cc dd ee is valid. 5 | # aa bb cc dd aa is not valid - the word aa appears more than once. 6 | # aa bb cc dd aaa is valid - aa and aaa count as different words. 7 | 8 | defmodule Passphrase do 9 | def how_many?(file, function) do 10 | File.read!(file) 11 | |> String.split(~r{\n}, trim: true) 12 | |> Enum.count(function) 13 | end 14 | 15 | def valid_simple?(passphrase) do 16 | list = words(passphrase) 17 | total_words(list) == total_unique_words(list) 18 | end 19 | 20 | def valid_anagrams?(passphrase) do 21 | list = words(passphrase) |> Enum.map(&sort_string/1) 22 | total_words(list) == total_unique_words(list) 23 | end 24 | 25 | defp words(passphrase), do: String.split(passphrase, ~r{\s}, trim: true) 26 | defp total_words(passphrase), do: Enum.count(passphrase) 27 | defp total_unique_words(passphrase), do: Enum.uniq(passphrase) |> Enum.count 28 | defp sort_string(string), do: String.graphemes(string) |> Enum.sort |> Enum.join 29 | end 30 | 31 | Passphrase.how_many?("./inputs/input04.txt", &Passphrase.valid_simple?/1) |> IO.puts 32 | 33 | # --- Part Two --- 34 | 35 | Passphrase.how_many?("./inputs/input04.txt", &Passphrase.valid_anagrams?/1) |> IO.puts 36 | -------------------------------------------------------------------------------- /2017/day05.exs: -------------------------------------------------------------------------------- 1 | # --- Day 5: A Maze of Twisty Trampolines, All Alike --- 2 | 3 | defmodule Jumps do 4 | def number_of_steps(filename, next_function) do 5 | steps({offsets(filename), 0}, 0, next_function) 6 | end 7 | 8 | def next_part_1(offsets, i) do 9 | offset = Enum.at(offsets, i) 10 | {List.replace_at(offsets, i, offset + 1), i + offset} 11 | end 12 | 13 | def next_part_2(offsets, i) do 14 | offset = Enum.at(offsets, i) 15 | mod = if(offset >= 3, do: offset - 1, else: offset + 1) 16 | {List.replace_at(offsets, i, mod), i + offset} 17 | end 18 | 19 | defp steps({_, i}, n, _) when i < 0, do: n 20 | defp steps({offsets, i}, n, _) when i >= length(offsets), do: n 21 | defp steps({offsets, i}, n, next_function) do 22 | steps(next_function.(offsets, i), n+1, next_function) 23 | end 24 | 25 | defp offsets(file) do 26 | File.read!(file) 27 | |> String.split(~r{\n}, trim: true) 28 | |> Enum.map(&String.to_integer/1) 29 | end 30 | end 31 | 32 | Jumps.number_of_steps("./inputs/input05.txt", &Jumps.next_part_1/2) |> IO.puts 33 | 34 | # --- Part Two --- 35 | 36 | Jumps.number_of_steps("./inputs/input05.txt", &Jumps.next_part_2/2) |> IO.puts 37 | -------------------------------------------------------------------------------- /2017/day06.exs: -------------------------------------------------------------------------------- 1 | # --- Day 6: Memory Reallocation --- 2 | 3 | defmodule Memory do 4 | def reallocation_steps(banks) do 5 | steps(%{blocks(banks) => 0}, blocks(banks), 0) 6 | end 7 | 8 | defp steps(visited, current, n) do 9 | next = reallocate(current) 10 | if Map.has_key?(visited, next) do 11 | {n + 1, n - visited[next] + 1} 12 | else 13 | steps(Map.put(visited, next, n + 1), next, n + 1) 14 | end 15 | 16 | end 17 | 18 | defp reallocate(blocks) do 19 | max = Enum.max(blocks) 20 | start = Enum.find_index(blocks, fn(x) -> x == max end) 21 | reallocate(List.replace_at(blocks, start, 0), rem(start + 1, length(blocks)), max) 22 | end 23 | 24 | defp reallocate(blocks, _, nblocks) when nblocks == 0, do: blocks 25 | defp reallocate(blocks, i, nblocks) do 26 | value = Enum.at(blocks, i) 27 | reallocate(List.replace_at(blocks, i, value + 1), rem(i + 1, length(blocks)), nblocks - 1) 28 | end 29 | 30 | defp blocks(banks) do 31 | String.split(banks, ~r{\s}, trim: true) 32 | |> Enum.map(&String.to_integer/1) 33 | end 34 | end 35 | 36 | banks = "11 11 13 7 0 15 5 5 4 4 1 1 7 1 15 11" 37 | Memory.reallocation_steps(banks) |> IO.inspect 38 | 39 | 40 | -------------------------------------------------------------------------------- /2017/day08.exs: -------------------------------------------------------------------------------- 1 | # --- Day 8: I Heard You Like Registers --- 2 | 3 | # b inc 5 if a > 1 4 | # a inc 1 if b < 5 5 | # c dec -10 if a >= 1 6 | # c inc -20 if c == 10 7 | 8 | defmodule Register do 9 | 10 | def execute_program(filename) do 11 | execute_program(%{}, lines(filename)) |> Map.values |> Enum.max 12 | end 13 | 14 | def execute_program(registers, []), do: registers 15 | def execute_program(registers, [line|lines]) do 16 | execute_instruction(registers, line) |> execute_program(lines) 17 | end 18 | 19 | def execute_program_biggest_held(filename) do 20 | execute_program_biggest_held(%{}, lines(filename)) |> elem(0) 21 | end 22 | 23 | def execute_program_biggest_held(registers, []), do: {Map.values(registers) |> Enum.max, registers} 24 | def execute_program_biggest_held(registers, [line|lines]) do 25 | registers = execute_instruction(registers, line) 26 | max = Map.values(registers) |> Enum.max 27 | {final_max, final_registers} = execute_program_biggest_held(registers, lines) 28 | {Enum.max([max, final_max]), final_registers} 29 | end 30 | 31 | defp execute_instruction(registers, line) do 32 | [operation, condition] = String.split(line, " if ", trim: true) 33 | case evaluate_condition(registers, condition) do 34 | {true, registers} -> execute_operation(registers, operation) 35 | {false, registers} -> registers 36 | end 37 | end 38 | 39 | defp evaluate_condition(registers, condition) do 40 | [_, register, operator, value] = Regex.run(~r{(\w+)\s(<|>|!=|==|>=|<=)\s(-?\d+)}, condition) 41 | {true_or_false?(registers[register], operator, value), Map.put_new(registers, register, 0)} 42 | end 43 | 44 | defp true_or_false?(nil, operator, value), do: true_or_false?(0, operator, value) 45 | defp true_or_false?(register_value, operator, value), do: eval("#{register_value} #{operator} #{value}") 46 | 47 | defp execute_operation(registers, operation) do 48 | [_, register, operator, value] = Regex.run(~r{(\w+)\s(inc|dec)\s(-?\d+)}, operation) 49 | Map.put(registers, register, perform_operation(registers[register], equivalent_operator(operator), value)) 50 | end 51 | 52 | defp perform_operation(nil, operator, value), do: perform_operation(0, operator, value) 53 | defp perform_operation(register_value, operator, value), do: eval("#{register_value} #{operator} #{value}") 54 | 55 | defp equivalent_operator("inc"), do: "+" 56 | defp equivalent_operator("dec"), do: "-" 57 | 58 | defp eval(code), do: Code.eval_string(code) |> elem(0) 59 | 60 | defp lines(file) do 61 | File.read!(file) 62 | |> String.split(~r{\n}, trim: true) 63 | end 64 | end 65 | 66 | Register.execute_program("./inputs/input08.txt") |> IO.puts 67 | 68 | # --- Part Two --- 69 | 70 | Register.execute_program_biggest_held("./inputs/input08.txt") |> IO.puts -------------------------------------------------------------------------------- /2017/day11.exs: -------------------------------------------------------------------------------- 1 | # --- Day 11: Hex Ed --- 2 | 3 | # \ n / 4 | # nw +--+ ne 5 | # / \ 6 | # -+ +- 7 | # \ / 8 | # sw +--+ se 9 | # / s \ 10 | 11 | defmodule HexGrid do 12 | 13 | def steps(file) do 14 | path(file) 15 | |> go({0,0}) 16 | |> distance({0,0}) 17 | end 18 | 19 | def furthest(file) do 20 | path(file) 21 | |> go({0,0}, {0,0}, 0) 22 | end 23 | 24 | def distance({x, y}, {p, q}) do 25 | max(abs(x-p), abs(y-q)) |> round 26 | end 27 | 28 | def go([], current), do: current 29 | def go([move|path], current), do: go(path, next(current, move)) 30 | 31 | def go([], _, _, max), do: max 32 | def go([move|path], current, start, max) do 33 | distance = distance(current, start) 34 | if distance > max do 35 | go(path, next(current, move), start, distance) 36 | else 37 | go(path, next(current, move), start, max) 38 | end 39 | end 40 | 41 | def next({x, y}, move) do 42 | # n: (+0, +1), s: (+0, -1) 43 | # ne: (+1, +0.5), nw: (-1, +0.5) 44 | # se: (+1, -0.5), sw: (-1, -0.5) 45 | case move do 46 | "n" -> {x, y+1} 47 | "s" -> {x, y-1} 48 | "ne" -> {x+1, y+0.5} 49 | "nw" -> {x-1, y+0.5} 50 | "se" -> {x+1, y-0.5} 51 | "sw" -> {x-1, y-0.5} 52 | end 53 | end 54 | 55 | defp path(file) do 56 | File.read!(file) 57 | |> String.split(",", trim: true) 58 | |> Enum.map(&String.trim/1) 59 | end 60 | end 61 | 62 | HexGrid.steps("./inputs/input11.txt") |> IO.puts 63 | 64 | # --- Part Two --- 65 | 66 | HexGrid.furthest("./inputs/input11.txt") |> IO.puts 67 | -------------------------------------------------------------------------------- /2017/day12.exs: -------------------------------------------------------------------------------- 1 | # --- Day 12: Digital Plumber --- 2 | 3 | # 0 <-> 2 4 | # 1 <-> 1 5 | # 2 <-> 0, 3, 4 6 | # 3 <-> 2, 4 7 | # 4 <-> 2, 3, 6 8 | # 5 <-> 6 9 | # 6 <-> 4, 5 10 | 11 | # In this example, the following programs are in the group that contains program ID 0: 12 | 13 | # Program 0 by definition. 14 | # Program 2, directly connected to program 0. 15 | # Program 3 via program 2. 16 | # Program 4 via program 2. 17 | # Program 5 via programs 6, then 4, then 2. 18 | # Program 6 via programs 4, then 2. 19 | 20 | defmodule Pipes do 21 | 22 | def group_0_size(file) do 23 | lines(file) 24 | |> build_graph() 25 | |> bfs("0") 26 | |> Enum.count 27 | end 28 | 29 | def groups(file) do 30 | lines(file) 31 | |> build_graph() 32 | |> all_bfs() 33 | end 34 | 35 | defp all_bfs(graph), do: all_bfs(graph, Map.keys(graph), []) 36 | 37 | defp all_bfs(_, [], groups), do: groups 38 | defp all_bfs(graph, [next|nodes], groups) do 39 | group = bfs(graph, next) 40 | all_bfs(graph, list_difference(nodes, group), groups ++ [group]) 41 | end 42 | 43 | defp bfs(graph, root), do: bfs(graph, [root], []) 44 | 45 | defp bfs(_, [], nodes), do: nodes 46 | defp bfs(graph, [next|stack], nodes) do 47 | bfs(graph, stack ++ list_difference(graph[next], nodes), Enum.uniq(nodes ++ [next])) 48 | end 49 | 50 | defp list_difference(list1, list2) do 51 | MapSet.difference(MapSet.new(list1), MapSet.new(list2)) 52 | |> MapSet.to_list 53 | end 54 | 55 | defp build_graph(lines) do 56 | build_graph(%{}, lines) 57 | end 58 | 59 | defp build_graph(graph, []), do: graph 60 | defp build_graph(graph, [line|lines]) do 61 | extract_edges(line) 62 | |> add_edges(graph) 63 | |> build_graph(lines) 64 | end 65 | 66 | defp add_edges({_, []}, graph), do: graph 67 | defp add_edges({v1, [v2|nodes]}, graph) do 68 | new_graph = add_edges({v1, nodes}, graph) 69 | |> Map.put_new(v1, MapSet.new) 70 | |> Map.put_new(v2, MapSet.new) 71 | 72 | Map.put(new_graph, v1, MapSet.union(new_graph[v1], MapSet.new([v2]))) 73 | |> Map.put(v2, MapSet.union(new_graph[v2], MapSet.new([v1]))) 74 | end 75 | 76 | defp extract_edges(line) do 77 | [node|nodes] = String.split(line, " <-> ", trim: true) 78 | {node, String.split(hd(nodes), ", ", trim: true)} 79 | end 80 | 81 | defp lines(file) do 82 | File.read!(file) 83 | |> String.split(~r{\n}, trim: true) 84 | end 85 | end 86 | 87 | 88 | Pipes.group_0_size("./inputs/input12.txt") |> IO.puts 89 | 90 | # --- Part Two --- 91 | 92 | Pipes.groups("./inputs/input12.txt") |> Enum.count |> IO.puts 93 | 94 | 95 | -------------------------------------------------------------------------------- /2017/day13.exs: -------------------------------------------------------------------------------- 1 | # --- Day 13: Packet Scanners --- 2 | 3 | # 0: 3 4 | # 1: 2 5 | # 4: 4 6 | # 6: 4 7 | 8 | # 0 1 2 3 4 5 6 9 | # [ ] [ ] ... ... [ ] ... [ ] 10 | # [ ] [ ] [ ] [ ] 11 | # [ ] [ ] [ ] 12 | # [ ] [ ] 13 | 14 | # Picosecond 0: 15 | # 0 1 2 3 4 5 6 16 | # [S] [S] ... ... [S] ... [S] 17 | # [ ] [ ] [ ] [ ] 18 | # [ ] [ ] [ ] 19 | # [ ] [ ] 20 | 21 | # Picosecond 1: 22 | # 0 1 2 3 4 5 6 23 | # [ ] [ ] ... ... [ ] ... [ ] 24 | # [S] [S] [S] [S] 25 | # [ ] [ ] [ ] 26 | # [ ] [ ] 27 | 28 | # Picosecond 2: 29 | # 0 1 2 3 4 5 6 30 | # [ ] [S] ... ... [ ] ... [ ] 31 | # [ ] [ ] [ ] [ ] 32 | # [S] [S] [S] 33 | # [ ] [ ] 34 | 35 | # Picosecond 3: 36 | # 0 1 2 3 4 5 6 37 | # [ ] [ ] ... ... [ ] ... [ ] 38 | # [S] [S] [ ] [ ] 39 | # [ ] [ ] [ ] 40 | # [S] [S] 41 | 42 | # The severity of getting caught on a layer is equal to its depth multiplied by its range. 43 | # (Ignore layers in which you do not get caught.) 44 | # The severity of the whole trip is the sum of these values. 45 | # In the example above, the trip severity is 0*3 + 6*4 = 24. 46 | 47 | defmodule Firewall do 48 | 49 | def severity_of_trip(file) do 50 | lines(file) 51 | |> build_firewall() 52 | |> severity() 53 | end 54 | 55 | def delay(file) do 56 | lines(file) 57 | |> build_firewall() 58 | |> calculate_delay(1, nil) 59 | end 60 | 61 | defp calculate_delay(_, delay, :found), do: delay 62 | defp calculate_delay(firewall, delay, nil) do 63 | if Enum.any?(firewall, fn(layer) -> position_at_depth(layer, delay) == 0 end) do 64 | calculate_delay(firewall, delay + 1, nil) 65 | else 66 | calculate_delay(firewall, delay, :found) 67 | end 68 | end 69 | 70 | defp severity({depth, range}), do: depth * range 71 | defp severity(firewall) do 72 | Enum.filter(firewall, fn(layer) -> position_at_depth(layer) == 0 end) 73 | |> Enum.reduce(0, fn(layer, acc) -> severity(layer) + acc end) 74 | end 75 | 76 | defp position_at_depth({depth, range}, delay \\ 0), do: rem(depth + delay, 2*(range - 1)) 77 | 78 | defp build_firewall(lines) do 79 | Enum.map(lines, &layer/1) 80 | end 81 | 82 | defp layer(line) do 83 | String.split(line, ~r{:\s+}, trim: true) 84 | |> Enum.map(&String.to_integer/1) 85 | |> List.to_tuple 86 | end 87 | 88 | defp lines(file) do 89 | File.read!(file) 90 | |> String.split(~r{\n}, trim: true) 91 | end 92 | end 93 | 94 | 95 | Firewall.severity_of_trip("./inputs/input13.txt") |> IO.puts 96 | 97 | # --- Part Two --- 98 | 99 | # Not very efficient... ^_^U 100 | Firewall.delay("./inputs/input13.txt") 101 | 102 | -------------------------------------------------------------------------------- /2017/day15.exs: -------------------------------------------------------------------------------- 1 | # --- Day 15: Dueling Generators --- 2 | 3 | # A judge waits for each of them to generate its next value, compares the lowest 16 bits of both values, 4 | # and keeps track of the number of times those parts of the values match. 5 | 6 | # The generators both work on the same principle. 7 | # To create its next value, a generator will take the previous value it produced, multiply it by a factor 8 | # (generator A uses 16807; generator B uses 48271), and then keep the remainder of dividing that resulting 9 | # product by 2147483647. That final remainder is the value it produces next. 10 | 11 | # The judge would like to consider 40 million pairs 12 | 13 | use Bitwise, only_operators: true 14 | 15 | defmodule Generators do 16 | 17 | def judge(seeds, count, next), do: judge(seeds, count, 0, next) 18 | 19 | def judge(_, 0, matches, _), do: matches 20 | 21 | def judge(values, count, matches, next) do 22 | if match?(values) do 23 | judge(next.(values), count - 1, matches + 1, next) 24 | else 25 | judge(next.(values), count - 1, matches, next) 26 | end 27 | end 28 | 29 | # generator A uses 16807; generator B uses 48271 30 | # keep the remainder of dividing that resulting product by 2147483647 31 | defp next({a, b}), do: {value(a, 16807), value(b, 48271)} 32 | 33 | # Generator A looks for values that are multiples of 4. 34 | # Generator B looks for values that are multiples of 8. 35 | defp next_picky({a, b}), do: {next_mul(a, 16807, 4), next_mul(b, 48271, 8)} 36 | 37 | defp next_mul(n, multiplier, modulo), do: next_mul(n, multiplier, modulo, value(n, multiplier)) 38 | defp next_mul(n, multiplier, modulo, result) do 39 | if rem(result, modulo) == 0 do 40 | result 41 | else 42 | next_mul(n, multiplier, modulo, value(result, multiplier)) 43 | end 44 | end 45 | 46 | defp value(n, multiplier), do: rem(n * multiplier, 2147483647) 47 | 48 | defp match?({a, b}), do: lowest_16(a) == lowest_16(b) 49 | 50 | defp lowest_16(n), do: n &&& 0xFFFF 51 | end 52 | 53 | # Generator A starts with 116 54 | # Generator B starts with 299 55 | Generators.judge({116, 299}, 40_000_000, &Generators.next/1) |> IO.puts 56 | 57 | # --- Part Two --- 58 | Generators.judge({116, 299}, 5_000_000, &Generators.next_picky/1) |> IO.puts 59 | 60 | -------------------------------------------------------------------------------- /2017/inputs/input13.txt: -------------------------------------------------------------------------------- 1 | 0: 4 2 | 1: 2 3 | 2: 3 4 | 4: 5 5 | 6: 6 6 | 8: 4 7 | 10: 8 8 | 12: 6 9 | 14: 6 10 | 16: 8 11 | 18: 8 12 | 20: 6 13 | 22: 8 14 | 24: 9 15 | 26: 8 16 | 28: 8 17 | 30: 12 18 | 32: 12 19 | 34: 10 20 | 36: 12 21 | 38: 12 22 | 40: 10 23 | 42: 12 24 | 44: 12 25 | 46: 12 26 | 48: 12 27 | 50: 12 28 | 52: 14 29 | 54: 14 30 | 56: 12 31 | 58: 14 32 | 60: 14 33 | 62: 14 34 | 64: 17 35 | 66: 14 36 | 70: 14 37 | 72: 14 38 | 74: 14 39 | 76: 14 40 | 78: 18 41 | 82: 14 42 | 88: 18 43 | 90: 14 44 | -------------------------------------------------------------------------------- /2017/inputs/input18.txt: -------------------------------------------------------------------------------- 1 | set i 31 2 | set a 1 3 | mul p 17 4 | jgz p p 5 | mul a 2 6 | add i -1 7 | jgz i -2 8 | add a -1 9 | set i 127 10 | set p 735 11 | mul p 8505 12 | mod p a 13 | mul p 129749 14 | add p 12345 15 | mod p a 16 | set b p 17 | mod b 10000 18 | snd b 19 | add i -1 20 | jgz i -9 21 | jgz a 3 22 | rcv b 23 | jgz b -1 24 | set f 0 25 | set i 126 26 | rcv a 27 | rcv b 28 | set p a 29 | mul p -1 30 | add p b 31 | jgz p 4 32 | snd a 33 | set a b 34 | jgz 1 3 35 | snd b 36 | set f 1 37 | add i -1 38 | jgz i -11 39 | snd a 40 | jgz f -16 41 | jgz a -19 42 | -------------------------------------------------------------------------------- /2017/inputs/input22.txt: -------------------------------------------------------------------------------- 1 | ###.#######...#####.#..## 2 | .####...###.##...#..#.... 3 | .#.#...####.###..##..##.# 4 | ########.#.#...##.#.##.#. 5 | ..#.#...##..#.#.##..####. 6 | ..#.#.....#....#####..#.. 7 | #.#..##...#....#.##...### 8 | .#.##########...#......#. 9 | .#...#..##...#...###.#... 10 | ......#.###.#..#...#.#### 11 | .#.###.##...###.###.###.# 12 | .##..##...#.#.#####.#...# 13 | #...#..###....#.##....... 14 | ####.....######.#.##..#.. 15 | ..#...#..##.####.#####.## 16 | #...#.#.#.#.#...##..##.#. 17 | #####.#...#.#.#.#.##.#### 18 | ....###...#.##.#.##.####. 19 | .#....###.#####...#.....# 20 | #.....#....#####.#..#.... 21 | .#####.#....#..##.#.#.### 22 | ####.#..#..##..#.#..#.### 23 | .##.##.#.#.#.#.#..####.#. 24 | #####..##.#.#..#..#...#.. 25 | #.#..#.###...##....###.## 26 | -------------------------------------------------------------------------------- /2017/inputs/input23.txt: -------------------------------------------------------------------------------- 1 | set b 93 2 | set c b 3 | jnz a 2 4 | jnz 1 5 5 | mul b 100 6 | sub b -100000 7 | set c b 8 | sub c -17000 9 | set f 1 10 | set d 2 11 | set e 2 12 | set g d 13 | mul g e 14 | sub g b 15 | jnz g 2 16 | set f 0 17 | sub e -1 18 | set g e 19 | sub g b 20 | jnz g -8 21 | sub d -1 22 | set g d 23 | sub g b 24 | jnz g -13 25 | jnz f 2 26 | sub h -1 27 | set g b 28 | sub g c 29 | jnz g 2 30 | jnz 1 3 31 | sub b -17 32 | jnz 1 -23 33 | -------------------------------------------------------------------------------- /2017/inputs/input23_optimised.txt: -------------------------------------------------------------------------------- 1 | set b 93 2 | set c b 3 | mul b 100 4 | sub b -100000 5 | set c b 6 | sub c -17000 7 | jp b 2 8 | sub h -1 9 | set g b 10 | sub g c 11 | jnz g 2 12 | jnz 1 3 13 | sub b -17 14 | jnz 1 -7 15 | -------------------------------------------------------------------------------- /2017/inputs/input24.txt: -------------------------------------------------------------------------------- 1 | 14/42 2 | 2/3 3 | 6/44 4 | 4/10 5 | 23/49 6 | 35/39 7 | 46/46 8 | 5/29 9 | 13/20 10 | 33/9 11 | 24/50 12 | 0/30 13 | 9/10 14 | 41/44 15 | 35/50 16 | 44/50 17 | 5/11 18 | 21/24 19 | 7/39 20 | 46/31 21 | 38/38 22 | 22/26 23 | 8/9 24 | 16/4 25 | 23/39 26 | 26/5 27 | 40/40 28 | 29/29 29 | 5/20 30 | 3/32 31 | 42/11 32 | 16/14 33 | 27/49 34 | 36/20 35 | 18/39 36 | 49/41 37 | 16/6 38 | 24/46 39 | 44/48 40 | 36/4 41 | 6/6 42 | 13/6 43 | 42/12 44 | 29/41 45 | 39/39 46 | 9/3 47 | 30/2 48 | 25/20 49 | 15/6 50 | 15/23 51 | 28/40 52 | 8/7 53 | 26/23 54 | 48/10 55 | 28/28 56 | 2/13 57 | 48/14 58 | -------------------------------------------------------------------------------- /2018/day01.exs: -------------------------------------------------------------------------------- 1 | # --- Day 1: Chronal Calibration --- 2 | 3 | # +1, +1, +1 results in 3 4 | # +1, +1, -2 results in 0 5 | # -1, -2, -3 results in -6 6 | defmodule Calibrator do 7 | def final_frequency(file) do 8 | file 9 | |> lines() 10 | |> changes() 11 | |> Enum.reduce(&+/2) 12 | end 13 | 14 | def frequency_reached_twice(file) do 15 | file 16 | |> lines() 17 | |> changes() 18 | |> frequency_reached_twice(0, [0]) 19 | end 20 | 21 | def frequency_reached_twice(changes, index, seen) when index >= length(changes), do: frequency_reached_twice(changes, 0, seen) 22 | 23 | def frequency_reached_twice(changes, index, seen = [last | rest]) do 24 | if last in rest do 25 | last 26 | else 27 | frequency_reached_twice(changes, index + 1, [last + Enum.at(changes, index) | seen]) 28 | end 29 | end 30 | 31 | defp changes(lines) do 32 | lines |> 33 | Enum.map(&String.to_integer/1) 34 | end 35 | 36 | defp lines(file) do 37 | File.read!(file) 38 | |> String.split(~r{\n}, trim: true) 39 | end 40 | end 41 | 42 | Calibrator.final_frequency("./inputs/input01.txt") |> IO.puts 43 | 44 | # --- Part Two --- 45 | 46 | Calibrator.frequency_reached_twice("./inputs/input01.txt") |> IO.puts -------------------------------------------------------------------------------- /2018/day02.exs: -------------------------------------------------------------------------------- 1 | # --- Day 2: Inventory Management System --- 2 | 3 | # - abcdef contains no letters that appear exactly two or three times. 4 | # - bababc contains two a and three b, so it counts for both. 5 | # - abbcde contains two b, but no letter appears exactly three times. 6 | # - abcccd contains three c, but no letter appears exactly two times. 7 | # - aabcdd contains two a and two d, but it only counts once. 8 | # - abcdee contains two e. 9 | # - ababab contains three a and three b, but it only counts once. 10 | # Of these box IDs, four of them contain a letter which appears exactly twice, and three of them contain 11 | # a letter which appears exactly three times. Multiplying these together produces a checksum of 4 * 3 = 12. 12 | defmodule Inventory do 13 | def checksum({two, three}), do: two * three 14 | def checksum(file) do 15 | file 16 | |> lines() 17 | |> ids() 18 | |> two_and_three() 19 | |> checksum() 20 | end 21 | 22 | def fabrics_common_letters(file) do 23 | file 24 | |> lines() 25 | |> find_boxes() 26 | |> common_letters() 27 | end 28 | 29 | defp common_letters({id1, id2}) do 30 | String.myers_difference(id1, id2) 31 | |> Keyword.get_values(:eq) 32 | |> Enum.join() 33 | end 34 | 35 | defp two_and_three(lines), do: two_and_three(lines, {0, 0}) 36 | defp two_and_three([], result), do: result 37 | defp two_and_three([id | list], {two, three}), do: two_and_three(list, analyse(id, {two, three})) 38 | 39 | defp analyse(id, {two, three}) do 40 | {x, y} = id 41 | |> frequencies() 42 | |> analyse() 43 | 44 | {two + x, three + y} 45 | end 46 | defp analyse(list), do: { Enum.count(list, fn x -> x == 2 end), Enum.count(list, fn x -> x == 3 end) } 47 | 48 | defp frequencies(id) do 49 | id 50 | |> Enum.reduce(%{}, fn x, acc -> Map.update(acc, x, 1, &(&1 + 1)) end) 51 | |> Map.values() 52 | |> Enum.uniq() 53 | end 54 | 55 | defp find_boxes(lines) do 56 | lines 57 | |> Enum.find_value(fn x -> find_box(x, lines) end) 58 | end 59 | 60 | defp find_box(id, ids) do 61 | box = ids 62 | |> Enum.find(fn x -> valid_boxes?(id, x) end) 63 | 64 | if box, do: { id, box }, else: nil 65 | end 66 | 67 | defp valid_boxes?(id1, id2) do 68 | count = common_letters({id1, id2}) 69 | |> String.length() 70 | 71 | count == String.length(id1) - 1 && count == String.length(id2) - 1 72 | end 73 | 74 | defp ids(lines), do: Enum.map(lines, &String.codepoints/1) 75 | 76 | defp lines(file) do 77 | File.read!(file) 78 | |> String.split(~r{\n}, trim: true) 79 | end 80 | end 81 | 82 | Inventory.checksum("./inputs/input02.txt") |> IO.puts 83 | 84 | # --- Part Two --- 85 | 86 | Inventory.fabrics_common_letters("./inputs/input02.txt") |> IO.puts 87 | -------------------------------------------------------------------------------- /2018/day05.exs: -------------------------------------------------------------------------------- 1 | # --- Day 5: Alchemical Reduction --- 2 | 3 | # dabAcCaCBAcCcaDA The first 'cC' is removed. 4 | # dabAaCBAcCcaDA This creates 'Aa', which is removed. 5 | # dabCBAcCcaDA Either 'cC' or 'Cc' are removed (the result is the same). 6 | # dabCBAcaDA No further actions can be taken. 7 | # After all possible reactions, the resulting polymer contains 10 units. 8 | defmodule Polymer do 9 | def reaction(polymer) do 10 | polymer 11 | |> units() 12 | |> apply_reactions([]) 13 | end 14 | 15 | def read(file) do 16 | File.read!(file) 17 | |> String.trim() 18 | end 19 | 20 | def reaction_with_removal(polymer) do 21 | polymer 22 | |> units() 23 | |> apply_all_reactions() 24 | |> Enum.min_by(&Enum.count/1) 25 | end 26 | 27 | defp apply_all_reactions(units) do 28 | symbols(units) 29 | |> Enum.map(fn symbol -> remove(units, symbol) end) 30 | |> Enum.map(fn simplified_units -> apply_reactions(simplified_units, []) end) 31 | end 32 | 33 | defp symbols(units), do: Enum.map(units, &String.downcase/1) |> Enum.uniq() 34 | 35 | defp remove(units, symbol), do: Enum.filter(units, fn u -> String.downcase(u) != symbol end) 36 | 37 | defp apply_reactions(units, reacted), do: apply_reactions(units, reacted, length(units)) 38 | 39 | defp apply_reactions([], reacted, l) when length(reacted) == l, do: reacted 40 | defp apply_reactions([], reacted, _) do 41 | apply_reactions(reacted, [], length(reacted)) 42 | end 43 | defp apply_reactions([u], reacted, l), do: apply_reactions([], reacted ++ [u], l) 44 | defp apply_reactions([u1, u2 | units], reacted, l) do 45 | {result, remainder, delete_at} = react(u1, u2, List.last(reacted), l) 46 | apply_reactions(remainder ++ units, List.delete_at(reacted, delete_at) ++ result, l) 47 | end 48 | 49 | defp react(u1, u2, nil, l), do: react(u1, u2, [], l) 50 | defp react(u1, u2, v, l) when is_list(v) do 51 | if react?(u1, u2) do 52 | {[], v, -1} 53 | else 54 | {[u1], [u2], l+1} 55 | end 56 | end 57 | defp react(u1, u2, v, l), do: react(u1, u2, [v], l) 58 | 59 | defp react?(u1, u2), do: u1 != u2 && (String.downcase(u1) == u2 || u1 == String.downcase(u2)) 60 | 61 | defp units(line), do: String.graphemes(line) 62 | end 63 | 64 | Polymer.read("./inputs/input05.txt") |> Polymer.reaction() |> Enum.count() |> IO.puts() 65 | 66 | # --- Part Two --- 67 | Polymer.read("./inputs/input05.txt") |> Polymer.reaction_with_removal() |> Enum.count() |> IO.puts() 68 | -------------------------------------------------------------------------------- /2018/day25.exs: -------------------------------------------------------------------------------- 1 | # --- Day 25: Four-Dimensional Adventure --- 2 | 3 | defmodule Constellations do 4 | def read_and_parse_points(file) do 5 | read(file) 6 | |> Enum.map(&parse/1) 7 | end 8 | 9 | def find_constellations(points), do: find_constellations(points, []) 10 | def find_constellations([], constellations), do: merge(constellations) 11 | def find_constellations([point | points], constellations) do 12 | index = Enum.find_index(constellations, fn c -> in_constellation?(point, c) end) 13 | if index do 14 | constellation = Enum.at(constellations, index) 15 | find_constellations(points, List.replace_at(constellations, index, [point | constellation])) 16 | else 17 | find_constellations(points, [[point] | constellations]) 18 | end 19 | end 20 | 21 | def merge(constellations), do: merge(constellations, constellations, []) 22 | def merge(initial, [], merged) when length(initial) == length(merged), do: merged 23 | def merge(_initial, [], merged), do: merge(merged) 24 | def merge(initial, [constellation | constellations], merged) do 25 | mergeable = Enum.filter(constellations, fn c -> mergeable?(constellation, c) end) 26 | merge(initial, constellations -- mergeable, [Enum.reduce([constellation | mergeable], [], fn c, acc -> acc ++ c end) | merged]) 27 | end 28 | 29 | def mergeable?(c1, c2), do: Enum.any?(c1, fn point -> in_constellation?(point, c2) end) 30 | 31 | defp in_constellation?(point, constellation), do: Enum.any?(constellation, fn q -> distance(point, q) <= 3 end) 32 | 33 | defp parse(line), do: String.split(line, ",", trim: true) |> Enum.map(&String.to_integer/1) 34 | 35 | defp distance(p1, p2), do: Enum.zip(p1, p2) |> Enum.map(fn {x, y} -> abs(x - y) end) |> Enum.sum() 36 | 37 | defp read(file) do 38 | File.read!(file) 39 | |> String.split(~r{\n}, trim: true) 40 | end 41 | end 42 | 43 | Constellations.read_and_parse_points("./inputs/input25.txt") 44 | |> Constellations.find_constellations() 45 | |> Enum.count() 46 | |> IO.puts() 47 | 48 | # --- Part Two --- 49 | 50 | -------------------------------------------------------------------------------- /2018/inputs/input06.txt: -------------------------------------------------------------------------------- 1 | 81, 46 2 | 330, 289 3 | 171, 261 4 | 248, 97 5 | 142, 265 6 | 139, 293 7 | 309, 208 8 | 315, 92 9 | 72, 206 10 | 59, 288 11 | 95, 314 12 | 126, 215 13 | 240, 177 14 | 78, 64 15 | 162, 168 16 | 75, 81 17 | 271, 258 18 | 317, 223 19 | 210, 43 20 | 47, 150 21 | 352, 116 22 | 316, 256 23 | 269, 47 24 | 227, 343 25 | 125, 290 26 | 245, 310 27 | 355, 301 28 | 251, 282 29 | 353, 107 30 | 254, 298 31 | 212, 128 32 | 60, 168 33 | 318, 254 34 | 310, 303 35 | 176, 345 36 | 110, 109 37 | 217, 338 38 | 344, 330 39 | 231, 349 40 | 259, 208 41 | 201, 57 42 | 200, 327 43 | 354, 111 44 | 166, 214 45 | 232, 85 46 | 96, 316 47 | 151, 288 48 | 217, 339 49 | 62, 221 50 | 307, 68 51 | -------------------------------------------------------------------------------- /2018/inputs/input12.txt: -------------------------------------------------------------------------------- 1 | .#.#. => . 2 | ...#. => # 3 | ..##. => . 4 | ....# => . 5 | ##.#. => # 6 | .##.# => # 7 | .#### => # 8 | #.#.# => # 9 | #..#. => # 10 | ##..# => . 11 | ##### => . 12 | ...## => . 13 | .#... => . 14 | ###.. => # 15 | #..## => . 16 | #...# => . 17 | .#..# => # 18 | .#.## => . 19 | #.#.. => # 20 | ..... => . 21 | ####. => . 22 | ##.## => # 23 | ..### => # 24 | #.... => . 25 | ###.# => . 26 | .##.. => # 27 | #.### => # 28 | ..#.# => . 29 | .###. => # 30 | ##... => # 31 | #.##. => # 32 | ..#.. => # 33 | -------------------------------------------------------------------------------- /2018/inputs/input15.txt: -------------------------------------------------------------------------------- 1 | ################################ 2 | #########################.G.#### 3 | #########################....### 4 | ##################.G.........### 5 | ##################.##.......#### 6 | #################...#.........## 7 | ################..............## 8 | ######..########...G...#.#....## 9 | #####....######.G.GG..G..##.#### 10 | #######.#####G............#.#### 11 | #####.........G..G......#...#### 12 | #####..G......G..........G....## 13 | ######GG......#####........E.### 14 | #######......#######..........## 15 | ######...G.G#########........### 16 | ######......#########.....E..### 17 | #####.......#########........### 18 | #####....G..#########........### 19 | ######.##.#.#########......##### 20 | #######......#######.......##### 21 | #######.......#####....E...##### 22 | ##.G..#.##............##.....### 23 | #.....#........###..#.#.....#### 24 | #.........E.E...#####.#.#....### 25 | ######......#.....###...#.#.E### 26 | #####........##...###..####..### 27 | ####...G#.##....E####E.####...## 28 | ####.#########....###E.####....# 29 | ###...#######.....###E.####....# 30 | ####..#######.##.##########...## 31 | ####..######################.### 32 | ################################ 33 | -------------------------------------------------------------------------------- /2018/inputs/input18.txt: -------------------------------------------------------------------------------- 1 | .|#.#|..#...|..##||...|#..##..#..|#|....#.#|.|.... 2 | .||....#..#...|#....#.||....||...||...|..#|..||..| 3 | ......|.|.#.#.#..|.....#.###.....#........|.||..#| 4 | ..|.....||...#||#.#|#.....|##.|.|....|#....#|..#.# 5 | |...#.|..#|#.#....|.#.#.|.#...#..#|#.....##|#..#.| 6 | #....|#|......#.|||..#..#..||...#.#...|||##|..|#.. 7 | .#||.|....|.......#|##...|.#.....#.##...|.|.#...#| 8 | ....#|.|.|...##.......|#.....|..#......#...#|.#..# 9 | ...#.|....#.|.#...|||......##..|#|||#..#...|.|#.#. 10 | .#..|...|..#.|##.#.#......#...||.||.#...|.#.#.#|.. 11 | |...#.|||...#|.#||.......|.##.....|..||...####...| 12 | .##..|..##|...##.#...#...#.#|.|###...#............ 13 | |.....||...#.......|.#..|#.....|.|#.|..||.##.|#|.. 14 | .#..##|.#|.|..|.#..#.|.#..#|......##...#.#.......# 15 | ...#.##.|..|.#.....#....#..#.............|.|##||.. 16 | ||.##.||.|.|..|..#.|.|.##|.|...|.#.|#.......#.|... 17 | ..|#.##..#|.#|#.#...........|.|........#...#|...#| 18 | ....|.#|..|.#|#|...|.|.#..#.....#|##|||##.#....#.. 19 | ...###.#.....#.||......#|#..##...|#....#....|#|.#. 20 | .##.|.##|.#.||....#|....|.#.|#.|....##..#.##|..... 21 | |...|...#|....#....#...#|#...|..#.#.|.|.....|.#|.. 22 | .|.|.#.#.#|.#.|#....|.|###..#......|...|...#.|#... 23 | ..|...||.|..##|...|..|#|...|......#.||.#...#..|#.| 24 | ........|..#||..|....|.....|.|#..#....|#..|.#..... 25 | #.|.|#....#...|..|....|.#.....||.....|..|........# 26 | ...||||....|.#.|....#..#....|###....|...#...##.... 27 | |||........|.#|.|......||#.|.....|.||#|.##....#|.. 28 | .....|#|#..||#...|##.|..||....####.|#.|..#....|.#. 29 | .||..#||....#.....#.#|.|....|.##|..|.#..|##....##. 30 | .|#.#|#|#|.....||..|.|.|.#......#..|.#..#..|.#||#. 31 | |.|#.......|..#|#|....|.#.#.#.|...|.......##.|||#| 32 | ..|.....#...||.|....|##|...#..#.#.....|##|##.##... 33 | .|.|..##.#|..|.|#.......#....#||.|...||#...|...... 34 | |.|##.#....|#..|....#..#..|##.|.##..#......#|##|.. 35 | ..#....#.|#...#.#...|.....|.||.#.#|.#.|###..|..#.# 36 | ..|.##...........|..###.||.|.##.|....|.|.#|#.#.|#| 37 | ..|....|.|#|...#|#...|.#......#.#||...|.#|...#.|#. 38 | ..#.......|.||.....||.|....|#||..........#...|#... 39 | .|..#....|#|||#..##||..#|.......|..|###..|.#...|.| 40 | |..|.#|.#...#....|.....#.....#....#...|..|.|.#.|.# 41 | ....###.#....|.#..#...#...###.|.|.....#|...#.....| 42 | ..#....##.....##..|.#.||#.|.#|#||..|...#|..|.#.... 43 | |#..#.#|||#.|#..#........#......||...#.|..#|....#| 44 | ......#|...#.|...#...|.|...|#|#......#|.##.#|.|.#| 45 | #||.#......#.##......#..||.##|.|.||..|#....#..#... 46 | #.#...#.|.#|#||#.#......#....|##|.........##.#|... 47 | .....###...#||....|####..#|||...#..#|.|....#|..#.. 48 | ......|#..#.#.#..|.#|#||..||.|...#....##...|...... 49 | ...#...|..#..##.||.#.#.....|.###.....##|#||..#..#| 50 | .#..#||.#....||....|##..|||...|.||...#..##.#....#. 51 | -------------------------------------------------------------------------------- /2018/inputs/input19.txt: -------------------------------------------------------------------------------- 1 | addi 1 16 1 2 | seti 1 4 2 3 | seti 1 0 3 4 | mulr 2 3 4 5 | eqrr 4 5 4 6 | addr 4 1 1 7 | addi 1 1 1 8 | addr 2 0 0 9 | addi 3 1 3 10 | gtrr 3 5 4 11 | addr 1 4 1 12 | seti 2 4 1 13 | addi 2 1 2 14 | gtrr 2 5 4 15 | addr 4 1 1 16 | seti 1 1 1 17 | mulr 1 1 1 18 | addi 5 2 5 19 | mulr 5 5 5 20 | mulr 1 5 5 21 | muli 5 11 5 22 | addi 4 2 4 23 | mulr 4 1 4 24 | addi 4 16 4 25 | addr 5 4 5 26 | addr 1 0 1 27 | seti 0 7 1 28 | setr 1 5 4 29 | mulr 4 1 4 30 | addr 1 4 4 31 | mulr 1 4 4 32 | muli 4 14 4 33 | mulr 4 1 4 34 | addr 5 4 5 35 | seti 0 9 0 36 | seti 0 4 1 37 | -------------------------------------------------------------------------------- /2018/inputs/input21-modified.txt: -------------------------------------------------------------------------------- 1 | seti 123 0 5 2 | bani 5 456 5 3 | eqri 5 72 5 4 | addr 5 3 3 5 | seti 0 0 3 6 | seti 0 3 5 7 | bori 5 65536 4 8 | seti 8858047 4 5 9 | bani 4 255 2 10 | addr 5 2 5 11 | bani 5 16777215 5 12 | muli 5 65899 5 13 | bani 5 16777215 5 14 | gtir 256 4 2 15 | addr 2 3 3 16 | addi 3 1 3 17 | seti 27 5 3 18 | seti 0 6 2 19 | addi 2 1 1 20 | muli 1 256 1 21 | gtrr 1 4 1 22 | addr 1 3 3 23 | addi 3 1 3 24 | seti 25 1 3 25 | addi 2 1 2 26 | seti 17 4 3 27 | setr 2 1 4 28 | seti 7 3 3 29 | eqrr 5 0 2 30 | -------------------------------------------------------------------------------- /2018/inputs/input21.txt: -------------------------------------------------------------------------------- 1 | seti 123 0 5 2 | bani 5 456 5 3 | eqri 5 72 5 4 | addr 5 3 3 5 | seti 0 0 3 6 | seti 0 3 5 7 | bori 5 65536 4 8 | seti 8858047 4 5 9 | bani 4 255 2 10 | addr 5 2 5 11 | bani 5 16777215 5 12 | muli 5 65899 5 13 | bani 5 16777215 5 14 | gtir 256 4 2 15 | addr 2 3 3 16 | addi 3 1 3 17 | seti 27 5 3 18 | seti 0 6 2 19 | addi 2 1 1 20 | muli 1 256 1 21 | gtrr 1 4 1 22 | addr 1 3 3 23 | addi 3 1 3 24 | seti 25 1 3 25 | addi 2 1 2 26 | seti 17 4 3 27 | setr 2 1 4 28 | seti 7 3 3 29 | eqrr 5 0 2 30 | addr 2 3 3 31 | seti 5 2 3 32 | -------------------------------------------------------------------------------- /2018/inputs/input24.txt: -------------------------------------------------------------------------------- 1 | Immune System: 2 | 1193 units each with 4200 hit points (immune to slashing, radiation, fire) with an attack that does 33 bludgeoning damage at initiative 2 3 | 151 units each with 9047 hit points (immune to slashing, cold; weak to fire) with an attack that does 525 slashing damage at initiative 1 4 | 218 units each with 4056 hit points (weak to radiation; immune to fire, slashing) with an attack that does 176 fire damage at initiative 9 5 | 5066 units each with 4687 hit points (weak to slashing, fire) with an attack that does 8 slashing damage at initiative 8 6 | 2023 units each with 5427 hit points (weak to slashing) with an attack that does 22 slashing damage at initiative 3 7 | 3427 units each with 5532 hit points (weak to slashing) with an attack that does 15 cold damage at initiative 13 8 | 1524 units each with 8584 hit points (immune to fire) with an attack that does 49 fire damage at initiative 7 9 | 82 units each with 2975 hit points (weak to cold, fire) with an attack that does 359 bludgeoning damage at initiative 5 10 | 5628 units each with 9925 hit points (weak to fire; immune to cold) with an attack that does 17 radiation damage at initiative 11 11 | 1410 units each with 9872 hit points (weak to cold; immune to fire) with an attack that does 60 slashing damage at initiative 10 12 | 13 | Infection: 14 | 5184 units each with 12832 hit points (weak to fire, cold) with an attack that does 4 fire damage at initiative 20 15 | 2267 units each with 13159 hit points (weak to fire; immune to bludgeoning) with an attack that does 10 fire damage at initiative 4 16 | 3927 units each with 50031 hit points (weak to slashing, cold; immune to fire, radiation) with an attack that does 23 cold damage at initiative 17 17 | 9435 units each with 23735 hit points (immune to cold) with an attack that does 4 cold damage at initiative 12 18 | 3263 units each with 26487 hit points (weak to fire) with an attack that does 11 fire damage at initiative 14 19 | 222 units each with 15916 hit points (weak to fire) with an attack that does 135 fire damage at initiative 18 20 | 972 units each with 45332 hit points (weak to bludgeoning, slashing) with an attack that does 86 cold damage at initiative 19 21 | 1456 units each with 39583 hit points (immune to radiation; weak to cold, fire) with an attack that does 53 bludgeoning damage at initiative 16 22 | 2813 units each with 28251 hit points with an attack that does 17 cold damage at initiative 15 23 | 1179 units each with 42431 hit points (immune to fire, slashing) with an attack that does 55 fire damage at initiative 6 24 | -------------------------------------------------------------------------------- /2019/README.md: -------------------------------------------------------------------------------- 1 | ### Advent of Code 2019 2 | 3 | Polyglot challenge! This year I tried to do every problem using a different language. A lot of fun and a great learning experience, 4 | albeit a quite maddening one, especially as I re-implemented the Intcode interpreter over and over. 5 | 6 | - Day 1 in [awk](https://en.wikipedia.org/wiki/AWK) 7 | - Day 2 in [Prolog](https://www.swi-prolog.org/) 8 | - Day 3 in [Haskell](https://www.haskell.org/) 9 | - Day 4 in [Common Lisp](https://common-lisp.net/) 10 | - Day 5 in [Kotlin](https://kotlinlang.org/) 11 | - Day 6 in [Swift](https://www.swift.org/) 12 | - Day 7 in [Lua](https://www.lua.org/) 13 | - Day 8 in [Perl](https://www.perl.org/) 14 | - Day 9 in [Go](https://go.dev/) 15 | - Day 10 in [C](https://en.wikipedia.org/wiki/C_(programming_language)) 16 | - Day 11 in [Rust](https://www.rust-lang.org/) 17 | - Day 12 in [OCaml](https://ocaml.org/) 18 | - Day 13 in [Julia](https://julialang.org/) 19 | - Day 14 in [Erlang](https://www.erlang.org/) 20 | - Day 15 in [Dart](https://dart.dev/) 21 | - Day 16 in [Dylan](https://opendylan.org/) 22 | - Day 17 in [Scala](https://www.scala-lang.org/) 23 | - Day 18 in [Python](https://www.python.org/) 24 | - Day 19 in [Crystal](https://crystal-lang.org/) 25 | - Day 20 in [Nim](https://nim-lang.org/) 26 | - Day 21 in [Ruby](https://www.ruby-lang.org/en/) 27 | - Day 22 in [R](https://www.r-project.org/) 28 | - Day 23 in [Elixir](https://elixir-lang.org/) 29 | - Day 24 in [C++](https://isocpp.org/) 30 | - Day 25 in [JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript) 31 | -------------------------------------------------------------------------------- /2019/day01.awk: -------------------------------------------------------------------------------- 1 | #!/bin/awk 2 | # --- Day 1: The Tyranny of the Rocket Equation --- 3 | # --- Part Two --- 4 | 5 | BEGIN { 6 | total = 0; 7 | total_plus_fuel = 0; 8 | } 9 | { 10 | fuel = int($0/3) - 2 11 | total = total + fuel 12 | 13 | while (fuel > 0) { 14 | total_plus_fuel = total_plus_fuel + fuel 15 | fuel = int(fuel/3) - 2 16 | } 17 | } 18 | END { 19 | printf("Part One: %d\n", total) 20 | printf("Part Two: %d\n", total_plus_fuel) 21 | } 22 | 23 | # awk -f day01.awk inputs/input01.txt 24 | # Part One: 3372756 25 | # Part Two: 5056279 26 | -------------------------------------------------------------------------------- /2019/day02.pl: -------------------------------------------------------------------------------- 1 | % --- Day 2: 1202 Program Alarm --- 2 | 3 | run(R) :- 4 | read_program(Program), 5 | replace_at(Program, 1, 12, Program2), % Replace position 1 with the value 12 6 | replace_at(Program2, 2, 2, Program3), % and replace position 2 with the value 2 7 | execute(Program3, Program3, 0, R). 8 | 9 | read_program(Intcodes) :- 10 | open('inputs/input02.txt', read, Stream), 11 | peek_string(Stream, 8000, Program), 12 | close(Stream), 13 | split_string(Program, ",", "\n", Stringcodes), 14 | maplist(number_codes, Intcodes, Stringcodes). 15 | 16 | % 99 : halt 17 | % 1 P1 P2 P3 : P3 <- P1 + P2 18 | % 2 P1 P2 P3 : P3 <- P1 * P2 19 | execute([99|_], [R|_], _, R). 20 | execute([Opcode, P1, P2, P3|_], Program, Ip, Result) :- 21 | apply_intcode(Opcode, P1, P2, Program, R), 22 | replace_at(Program, P3, R, Changed), 23 | Jp is Ip + 4, 24 | drop(Changed, Jp, Rest), 25 | execute(Rest, Changed, Jp, Result). 26 | 27 | apply_intcode(Opcode, P1, P2, Program, R) :- 28 | nth0(P1, Program, I1), 29 | nth0(P2, Program, I2), 30 | apply_intcode(Opcode, I1, I2, R). 31 | apply_intcode(1, I1, I2, R) :- R is I1 + I2. 32 | apply_intcode(2, I1, I2, R) :- R is I1 * I2. 33 | 34 | replace_at([_|T], 0, X, [X|T]). 35 | replace_at([H|T], I, X, [H|R]) :- 36 | I > 0, 37 | J is I - 1, 38 | replace_at(T, J, X, R). 39 | 40 | drop(L, 0, L). 41 | drop([_|T], I, R) :- 42 | J is I - 1, 43 | drop(T, J, R). 44 | 45 | % ?- run(R). 46 | % R = 2692315 47 | 48 | % --- Part Two --- 49 | 50 | % Find the input noun and verb that cause the program to 51 | % produce the output 19690720. What is 100 * noun + verb? 52 | find_input(R) :- 53 | noun_and_verb(N, V), 54 | R is 100 * N + V. 55 | 56 | noun_and_verb(N, V) :- 57 | read_program(Program), 58 | noun_and_verb(Program, 0, 0, 19690720, N, V). 59 | 60 | noun_and_verb(Program, N, V, R, N, V) :- run(Program, N, V, R), !. 61 | noun_and_verb(Program, N, V, R, N1, V1) :- 62 | V =< 99, 63 | V2 is V + 1, 64 | noun_and_verb(Program, N, V2, R, N1, V1). 65 | noun_and_verb(Program, N, 100, R, N1, V1) :- 66 | N2 is N + 1, 67 | noun_and_verb(Program, N2, 0, R, N1, V1). 68 | 69 | run(Program, N, V, R) :- 70 | replace_at(Program, 1, N, Program2), % Replace position 1 with the noun 71 | replace_at(Program2, 2, V, Program3), % and replace position 2 with the verb 72 | execute(Program3, Program3, 0, R). 73 | 74 | % ?- noun_and_verb(N, V). 75 | % N = 95, 76 | % V = 7 77 | -------------------------------------------------------------------------------- /2019/day03.hs: -------------------------------------------------------------------------------- 1 | -- --- Day 3: Crossed Wires --- 2 | 3 | import Data.List 4 | import Data.List.Split 5 | import Data.Maybe 6 | 7 | wires :: String -> [[String]] 8 | wires contents = map (splitOn ",") (lines contents) 9 | 10 | coords :: [String] -> [(Int, Int)] -> [(Int, Int)] 11 | coords [] list = list 12 | coords (h:hs) list = 13 | let current = head list 14 | c = head h 15 | n = read (tail h) :: Int 16 | in coords hs ((move c n current) ++ list) 17 | 18 | crosses :: [(Int, Int)] -> [(Int, Int)] -> [(Int, Int)] 19 | crosses list1 list2 = delete (0, 0) (intersect list1 list2) 20 | 21 | move :: Char -> Int -> (Int, Int) -> [(Int, Int)] 22 | move 'L' n (x, y) = reverse (map (\i -> (x - i, y)) [1..n]) 23 | move 'U' n (x, y) = reverse (map (\i -> (x, y - i)) [1..n]) 24 | move 'R' n (x, y) = reverse (map (\i -> (x + i, y)) [1..n]) 25 | move 'D' n (x, y) = reverse (map (\i -> (x, y + i)) [1..n]) 26 | 27 | distances :: [(Int, Int)] -> [Int] 28 | distances list = map distance list 29 | 30 | distance :: (Int, Int) -> Int 31 | distance (x, y) = (abs x) + (abs y) 32 | 33 | closestDistance :: [[String]] -> Int 34 | closestDistance [wire1, wire2] = 35 | let coords1 = coords wire1 [(0, 0)] 36 | coords2 = coords wire2 [(0, 0)] 37 | candidates = crosses coords1 coords2 38 | in foldr1 min (distances candidates) 39 | 40 | -- --- Part Two --- 41 | delay :: (Int, Int) -> [(Int, Int)] -> Int 42 | delay point coords = fromMaybe (-1) (elemIndex point coords) 43 | 44 | combinedDelays :: [(Int, Int)] -> [(Int, Int)] -> [(Int, Int)] -> [Int] 45 | combinedDelays list coords1 coords2 = map (\point -> (delay point coords1) + (delay point coords2)) list 46 | 47 | minCombinedDelay :: [[String]] -> Int 48 | minCombinedDelay [wire1, wire2] = 49 | let coords1 = coords wire1 [(0, 0)] 50 | coords2 = coords wire2 [(0, 0)] 51 | candidates = crosses coords1 coords2 52 | in foldr1 min (combinedDelays candidates (reverse coords1) (reverse coords2)) 53 | 54 | ---- 55 | main :: IO () 56 | main = do 57 | contents <- readFile "inputs/input03.txt" 58 | print $ (closestDistance (wires contents)) 59 | print $ (minCombinedDelay (wires contents)) 60 | 61 | --ghc day03.hs 62 | --./day03 63 | --1519 64 | --14358 65 | -------------------------------------------------------------------------------- /2019/day04.lisp: -------------------------------------------------------------------------------- 1 | ; --- Day 4: Secure Container --- 2 | 3 | (defun number-to-digits (number) 4 | (map 'list #'digit-char-p (princ-to-string number))) 5 | 6 | ; Your puzzle input is 158126-624574. 7 | (defparameter *range* 8 | (list (number-to-digits 158126) (number-to-digits 624574))) 9 | 10 | ; Two adjacent digits are the same (like 22 in 122345). 11 | ; Going from left to right, the digits never decrease, 12 | ; they only ever increase or stay the same (like 111123 or 135679). 13 | (defun has-two-adjacent-same-digits (password) 14 | (if (< (length password) 2) nil 15 | (or (= (first password) (second password)) 16 | (has-two-adjacent-same-digits (cdr password))))) 17 | 18 | (defun digits-never-decrease (password) 19 | (if (< (length password) 2) T 20 | (and (<= (first password) (second password)) 21 | (digits-never-decrease (cdr password))))) 22 | 23 | (defun password-candidates (start end) 24 | (loop for i from start below end collect i)) 25 | 26 | (defun valid-password (number criteria) 27 | (let ((password (number-to-digits number))) 28 | (and (funcall criteria password) 29 | (digits-never-decrease password)))) 30 | 31 | (defun count-passwords (start end criteria) 32 | (count-if (lambda(x) (valid-password x criteria)) (password-candidates start end))) 33 | 34 | ; > (count-passwords 158126 624574 #'has-two-adjacent-same-digits) 35 | ; 1665 36 | 37 | ; --- Part Two --- 38 | 39 | ; The two adjacent matching digits are not part of a larger group of matching digits. 40 | (defun has-two-adjacent-same-digits-not-in-group (password &optional prev current_count) 41 | (cond ((null current_count) (has-two-adjacent-same-digits-not-in-group (cdr password) (car password) 1)) 42 | ((and (not (null password)) (= prev (car password))) (has-two-adjacent-same-digits-not-in-group (cdr password) prev (+ current_count 1))) 43 | ((= current_count 2) T) 44 | ((null password) nil) 45 | (T (has-two-adjacent-same-digits-not-in-group (cdr password) (car password) 1)))) 46 | 47 | ; > (count-passwords 158126 624574 #'has-two-adjacent-same-digits-not-in-group) 48 | ; 1131 49 | -------------------------------------------------------------------------------- /2019/day08.pl: -------------------------------------------------------------------------------- 1 | # --- Day 8: Space Image Format --- 2 | 3 | use strict; 4 | use warnings; 5 | use 5.010; 6 | 7 | my $IMAGE_WIDTH = 25; 8 | my $IMAGE_HEIGHT = 6; 9 | my $LAYER_SIZE = $IMAGE_WIDTH * $IMAGE_HEIGHT; 10 | 11 | sub read_layers { 12 | my $digits = $_[0]; 13 | # Remove all non-digits 14 | $digits =~ s/\D//g; 15 | 16 | # Split into layers 17 | my @layers = unpack( "(A$LAYER_SIZE)*", $digits ); 18 | 19 | return @layers; 20 | } 21 | 22 | sub find_layer_with_fewest_zeros { 23 | my @layers = @_; 24 | my $min_zeros = $LAYER_SIZE; 25 | my $min_layer = ""; 26 | foreach my $layer (@layers) { 27 | my $count = () = $layer =~ /\Q0/g; 28 | if($count < $min_zeros) { 29 | $min_zeros = $count; 30 | $min_layer = $layer; 31 | } 32 | } 33 | 34 | return $min_layer; 35 | } 36 | 37 | sub corruption_check { 38 | my $layer = $_[0]; 39 | my $count_ones = () = $layer =~ /\Q1/g; 40 | my $count_twos = () = $layer =~ /\Q2/g; 41 | 42 | return $count_ones * $count_twos; 43 | } 44 | 45 | # 0 is black, 1 is white, and 2 is transparent. 46 | sub combine_layers { 47 | my @layers = @_; 48 | my $combined_layer = ''; 49 | 50 | foreach my $i ((0..$LAYER_SIZE)) { 51 | my $j = 0; 52 | my $pixel = ''; 53 | do { 54 | $pixel = substr( $layers[$j], $i , 1 ); 55 | $j++; 56 | } until ($pixel ne '2'); 57 | $combined_layer .= ( $pixel eq '1' ? '#' : '.'); 58 | } 59 | 60 | return $combined_layer; 61 | } 62 | 63 | sub print_layer { 64 | my @rows = unpack( "(A$IMAGE_WIDTH)*", $_[0] ); 65 | foreach my $row (@rows) { 66 | print "$row\n"; 67 | } 68 | } 69 | 70 | open my $input_handle, '<', 'inputs/input08.txt' or die "Can't open file $!"; 71 | read $input_handle, my $image, -s $input_handle; 72 | 73 | my @layers = read_layers( $image ); 74 | print corruption_check( find_layer_with_fewest_zeros( @layers ) ) . "\n"; 75 | # 1441 76 | 77 | # --- Part Two --- 78 | print_layer( combine_layers( @layers ) ); 79 | # ###..#..#.####.###..###.. 80 | # #..#.#..#....#.#..#.#..#. 81 | # #..#.#..#...#..###..#..#. 82 | # ###..#..#..#...#..#.###.. 83 | # #.#..#..#.#....#..#.#.... 84 | # #..#..##..####.###..#.... 85 | # RUZBP 86 | -------------------------------------------------------------------------------- /2019/day16/day16.lid: -------------------------------------------------------------------------------- 1 | Library: day16 2 | Files: library 3 | day16 4 | -------------------------------------------------------------------------------- /2019/day16/library.dylan: -------------------------------------------------------------------------------- 1 | Module: dylan-user 2 | 3 | define library day16 4 | use common-dylan; 5 | use io; 6 | end library day16; 7 | 8 | define module day16 9 | use common-dylan; 10 | use format-out; 11 | use streams; 12 | use standard-io; 13 | end module day16; 14 | -------------------------------------------------------------------------------- /2019/inputs/input01.txt: -------------------------------------------------------------------------------- 1 | 57351 2 | 149223 3 | 142410 4 | 129063 5 | 91757 6 | 52486 7 | 125555 8 | 124161 9 | 104558 10 | 110002 11 | 140284 12 | 131259 13 | 142148 14 | 69648 15 | 73179 16 | 89820 17 | 125606 18 | 70238 19 | 131217 20 | 99388 21 | 71989 22 | 126743 23 | 55136 24 | 128148 25 | 52974 26 | 131314 27 | 82350 28 | 126565 29 | 54418 30 | 105347 31 | 71981 32 | 146156 33 | 113626 34 | 117829 35 | 55419 36 | 91350 37 | 137748 38 | 113160 39 | 102462 40 | 100948 41 | 101731 42 | 131526 43 | 139132 44 | 51796 45 | 100849 46 | 122579 47 | 132301 48 | 51675 49 | 86607 50 | 140890 51 | 77532 52 | 81217 53 | 149549 54 | 113161 55 | 119361 56 | 109709 57 | 64495 58 | 103062 59 | 72313 60 | 140119 61 | 77352 62 | 91658 63 | 141341 64 | 91664 65 | 64771 66 | 88263 67 | 102357 68 | 149925 69 | 123608 70 | 88368 71 | 57809 72 | 65165 73 | 63937 74 | 78600 75 | 134725 76 | 58438 77 | 62763 78 | 131789 79 | 119646 80 | 65649 81 | 143975 82 | 142866 83 | 97922 84 | 64427 85 | 149451 86 | 84896 87 | 75863 88 | 53950 89 | 55625 90 | 146904 91 | 50460 92 | 99284 93 | 125904 94 | 85856 95 | 60281 96 | 79113 97 | 111661 98 | 145106 99 | 105568 100 | 147400 101 | -------------------------------------------------------------------------------- /2019/inputs/input02.txt: -------------------------------------------------------------------------------- 1 | 1,0,0,3,1,1,2,3,1,3,4,3,1,5,0,3,2,1,6,19,1,19,5,23,2,13,23,27,1,10,27,31,2,6,31,35,1,9,35,39,2,10,39,43,1,43,9,47,1,47,9,51,2,10,51,55,1,55,9,59,1,59,5,63,1,63,6,67,2,6,67,71,2,10,71,75,1,75,5,79,1,9,79,83,2,83,10,87,1,87,6,91,1,13,91,95,2,10,95,99,1,99,6,103,2,13,103,107,1,107,2,111,1,111,9,0,99,2,14,0,0 2 | -------------------------------------------------------------------------------- /2019/inputs/input03.txt: -------------------------------------------------------------------------------- 1 | R998,D934,L448,U443,R583,U398,R763,U98,R435,U984,L196,U410,L475,D163,R776,D796,R175,U640,R805,D857,R935,D768,L99,D75,R354,U551,L986,D592,R51,U648,L108,U8,R44,U298,L578,U710,R745,U60,L536,D62,R620,D454,L143,U407,R465,U606,L367,U107,L581,U900,R495,D12,R763,D244,R946,D424,R367,D696,L534,U452,R274,D942,L813,U336,L742,U134,R571,U703,R941,D532,L903,D833,L821,D577,L598,D83,R858,U798,L802,D852,R913,U309,L784,D235,L446,D571,R222,D714,R6,D379,R130,D313,R276,U632,L474,U11,L551,U257,R239,U218,R592,U901,L596,D367,L34,D397,R520,U547,L795,U192,R960,U77,L825,U954,R307,D399,R958,U239,R514,D863,L162,U266,R705,U731,R458,D514,R42,U314,R700,D651,L626,U555,R774,U773,R553,D107,L404,D100,R149,U845,L58,U674,R695,U255,R816,D884,R568,U618,R510,D566,L388,D947,L851,U127,L116,U143,L744,D361,L336,U903,L202,U683,R287,D174,L229,U371,L298,U839,L27,U462,R443,D39,R411,U788,L197,D160,L289,U840,L78,D262,R352,U83,R20,U109,R657,D225,R587,D968,R576,D791,R493,U805,R139,D699,R783,U140,L371,D170,L635,U257,R331,D311,R725,D970,R57,D986,L222,D760,L830,D960,L901,D367,R469,D560,L593,D940,L71,D384,R603,D689,R250,D859,L156,U499,L850,U166,R726,D210,L36,D584,R672,U47,L713,U985,R551,U22,L499,D575,R210,D829,L186,U340,R696,D939,L744,D46,L896,U467,L214,D71,R376,D379,L1,U870,R785,D779,L94,U723,L199,D185,R210,U937,R645,U25,R116,D821,R964,U959,R569,U496,R809,U112,R712,D315,L747,U754,L66,U614,L454,D945,R214,U965,L248,U702,L287,D863,R700,U768,R139,D242,R914,D818,R340,D60,L400,D924,R69,U73,L449,U393,L906 2 | L1005,D207,R487,U831,R81,U507,R701,D855,R978,U790,R856,U517,R693,D726,L857,D442,L13,U441,R184,D42,R27,D773,R797,D242,L689,D958,R981,D279,L635,D881,L907,U716,L90,U142,R618,D188,L725,U329,R717,D857,L583,U851,L140,U903,R363,U226,L413,U240,R772,U523,L860,U596,L861,D198,L44,U956,R862,U683,L542,U581,L346,U376,L568,D488,L254,D565,R480,D418,L567,U73,R397,U265,R632,U87,R803,D85,L100,D12,L989,U886,R279,U507,R274,U17,L36,U309,L189,D145,R50,U408,L451,D37,R930,D566,R96,U673,L302,U859,R814,U478,R218,U494,R177,D85,L376,U545,L106,U551,L469,U333,R685,U625,L933,U99,R817,D473,R412,D203,R912,U460,L527,D730,L193,U894,L256,D209,L868,D942,L8,U532,L270,U147,R919,U899,R256,U124,R204,D199,L170,D844,R974,U16,R722,U12,L470,D51,R821,U730,L498,U311,R587,D570,R981,D917,R440,D485,R179,U874,R26,D310,R302,U260,R446,D241,R694,D138,L400,D852,L194,U598,R73,U387,R660,D597,L803,D571,L956,D89,L394,U564,L287,U668,L9,D103,R152,D318,L215,U460,L870,U997,L595,D479,R262,U531,R609,U50,L165,U704,L826,D527,L901,D857,L914,U623,R432,D988,R562,D301,L277,U274,R39,D177,L827,U944,R64,U560,R801,D83,R388,U978,R387,U435,L759,U200,L760,U403,L218,D399,L178,U700,L75,U749,R85,U368,R538,U3,L172,D634,R518,D435,L542,U347,L745,U353,L178,D133,L475,U459,L522,U354,R184,U339,R845,D145,L44,U61,L603,U256,R534,U558,L998,D36,R42,U379,R813,D412,R878,D370,R629,U883,L490,D674,L863,U506,L961,D882,R436,D984,L229,D78,L779,D117,L674,U850,L494,D205,L988,D202,L368,U955,L662,U647,R774,D575,L753,D294,R131,U318,R873,U114,L30 3 | -------------------------------------------------------------------------------- /2019/inputs/input05.txt: -------------------------------------------------------------------------------- 1 | 3,225,1,225,6,6,1100,1,238,225,104,0,1101,72,36,225,1101,87,26,225,2,144,13,224,101,-1872,224,224,4,224,102,8,223,223,1001,224,2,224,1,223,224,223,1102,66,61,225,1102,25,49,224,101,-1225,224,224,4,224,1002,223,8,223,1001,224,5,224,1,223,224,223,1101,35,77,224,101,-112,224,224,4,224,102,8,223,223,1001,224,2,224,1,223,224,223,1002,195,30,224,1001,224,-2550,224,4,224,1002,223,8,223,1001,224,1,224,1,224,223,223,1102,30,44,225,1102,24,21,225,1,170,117,224,101,-46,224,224,4,224,1002,223,8,223,101,5,224,224,1,224,223,223,1102,63,26,225,102,74,114,224,1001,224,-3256,224,4,224,102,8,223,223,1001,224,3,224,1,224,223,223,1101,58,22,225,101,13,17,224,101,-100,224,224,4,224,1002,223,8,223,101,6,224,224,1,224,223,223,1101,85,18,225,1001,44,7,224,101,-68,224,224,4,224,102,8,223,223,1001,224,5,224,1,223,224,223,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,7,677,226,224,102,2,223,223,1005,224,329,101,1,223,223,8,677,226,224,1002,223,2,223,1005,224,344,1001,223,1,223,1107,677,677,224,102,2,223,223,1005,224,359,1001,223,1,223,1107,226,677,224,102,2,223,223,1005,224,374,101,1,223,223,7,226,677,224,102,2,223,223,1005,224,389,101,1,223,223,8,226,677,224,1002,223,2,223,1005,224,404,101,1,223,223,1008,226,677,224,1002,223,2,223,1005,224,419,1001,223,1,223,107,677,677,224,102,2,223,223,1005,224,434,101,1,223,223,1108,677,226,224,1002,223,2,223,1006,224,449,101,1,223,223,1108,677,677,224,102,2,223,223,1006,224,464,101,1,223,223,1007,677,226,224,102,2,223,223,1006,224,479,101,1,223,223,1008,226,226,224,102,2,223,223,1006,224,494,101,1,223,223,108,226,226,224,1002,223,2,223,1006,224,509,101,1,223,223,107,226,226,224,102,2,223,223,1006,224,524,101,1,223,223,1107,677,226,224,102,2,223,223,1005,224,539,1001,223,1,223,108,226,677,224,1002,223,2,223,1005,224,554,101,1,223,223,1007,226,226,224,102,2,223,223,1005,224,569,101,1,223,223,8,226,226,224,102,2,223,223,1006,224,584,101,1,223,223,1008,677,677,224,1002,223,2,223,1005,224,599,1001,223,1,223,107,226,677,224,1002,223,2,223,1005,224,614,1001,223,1,223,1108,226,677,224,102,2,223,223,1006,224,629,101,1,223,223,7,677,677,224,1002,223,2,223,1005,224,644,1001,223,1,223,108,677,677,224,102,2,223,223,1005,224,659,101,1,223,223,1007,677,677,224,102,2,223,223,1006,224,674,101,1,223,223,4,223,99,226 2 | -------------------------------------------------------------------------------- /2019/inputs/input07.txt: -------------------------------------------------------------------------------- 1 | 3,8,1001,8,10,8,105,1,0,0,21,46,55,72,85,110,191,272,353,434,99999,3,9,1002,9,5,9,1001,9,2,9,102,3,9,9,101,2,9,9,102,4,9,9,4,9,99,3,9,102,5,9,9,4,9,99,3,9,1002,9,2,9,101,2,9,9,1002,9,2,9,4,9,99,3,9,1002,9,4,9,101,3,9,9,4,9,99,3,9,1002,9,3,9,101,5,9,9,1002,9,3,9,101,3,9,9,1002,9,5,9,4,9,99,3,9,1001,9,2,9,4,9,3,9,101,2,9,9,4,9,3,9,101,2,9,9,4,9,3,9,1001,9,2,9,4,9,3,9,102,2,9,9,4,9,3,9,102,2,9,9,4,9,3,9,102,2,9,9,4,9,3,9,1002,9,2,9,4,9,3,9,102,2,9,9,4,9,3,9,1002,9,2,9,4,9,99,3,9,102,2,9,9,4,9,3,9,102,2,9,9,4,9,3,9,1001,9,1,9,4,9,3,9,1001,9,1,9,4,9,3,9,1002,9,2,9,4,9,3,9,1001,9,2,9,4,9,3,9,102,2,9,9,4,9,3,9,101,1,9,9,4,9,3,9,1001,9,1,9,4,9,3,9,101,2,9,9,4,9,99,3,9,1002,9,2,9,4,9,3,9,1001,9,2,9,4,9,3,9,101,2,9,9,4,9,3,9,1001,9,1,9,4,9,3,9,1002,9,2,9,4,9,3,9,1001,9,1,9,4,9,3,9,1001,9,1,9,4,9,3,9,1001,9,2,9,4,9,3,9,1001,9,1,9,4,9,3,9,101,2,9,9,4,9,99,3,9,1001,9,2,9,4,9,3,9,102,2,9,9,4,9,3,9,101,2,9,9,4,9,3,9,1001,9,2,9,4,9,3,9,1001,9,2,9,4,9,3,9,1002,9,2,9,4,9,3,9,102,2,9,9,4,9,3,9,1002,9,2,9,4,9,3,9,1002,9,2,9,4,9,3,9,1001,9,1,9,4,9,99,3,9,101,1,9,9,4,9,3,9,102,2,9,9,4,9,3,9,102,2,9,9,4,9,3,9,1001,9,2,9,4,9,3,9,1002,9,2,9,4,9,3,9,1002,9,2,9,4,9,3,9,102,2,9,9,4,9,3,9,102,2,9,9,4,9,3,9,1001,9,2,9,4,9,3,9,1001,9,2,9,4,9,99 2 | -------------------------------------------------------------------------------- /2019/inputs/input10.txt: -------------------------------------------------------------------------------- 1 | #....#.....#...#.#.....#.#..#....# 2 | #..#..##...#......#.....#..###.#.# 3 | #......#.#.#.....##....#.#.....#.. 4 | ..#.#...#.......#.##..#........... 5 | .##..#...##......##.#.#........... 6 | .....#.#..##...#..##.....#...#.##. 7 | ....#.##.##.#....###.#........#### 8 | ..#....#..####........##.........# 9 | ..#...#......#.#..#..#.#.##......# 10 | .............#.#....##.......#...# 11 | .#.#..##.#.#.#.#.......#.....#.... 12 | .....##.###..#.....#.#..###.....## 13 | .....#...#.#.#......#.#....##..... 14 | ##.#.....#...#....#...#..#....#.#. 15 | ..#.............###.#.##....#.#... 16 | ..##.#.........#.##.####.........# 17 | ##.#...###....#..#...###..##..#..# 18 | .........#.#.....#........#....... 19 | #.......#..#.#.#..##.....#.#.....# 20 | ..#....#....#.#.##......#..#.###.. 21 | ......##.##.##...#...##.#...###... 22 | .#.....#...#........#....#.###.... 23 | .#.#.#..#............#..........#. 24 | ..##.....#....#....##..#.#.......# 25 | ..##.....#.#...................... 26 | .#..#...#....#.#.....#.........#.. 27 | ........#.............#.#......... 28 | #...#.#......#.##....#...#.#.#...# 29 | .#.....#.#.....#.....#.#.##......# 30 | ..##....#.....#.....#....#.##..#.. 31 | #..###.#.#....#......#...#........ 32 | ..#......#..#....##...#.#.#...#..# 33 | .#.##.#.#.....#..#..#........##... 34 | ....#...##.##.##......#..#..##.... -------------------------------------------------------------------------------- /2019/inputs/input11.txt: -------------------------------------------------------------------------------- 1 | 3,8,1005,8,314,1106,0,11,0,0,0,104,1,104,0,3,8,1002,8,-1,10,1001,10,1,10,4,10,108,1,8,10,4,10,1002,8,1,28,2,2,16,10,1,1108,7,10,1006,0,10,1,5,14,10,3,8,102,-1,8,10,101,1,10,10,4,10,108,1,8,10,4,10,102,1,8,65,1006,0,59,2,109,1,10,1006,0,51,2,1003,12,10,3,8,102,-1,8,10,1001,10,1,10,4,10,108,1,8,10,4,10,1001,8,0,101,1006,0,34,1,1106,0,10,1,1101,17,10,3,8,102,-1,8,10,101,1,10,10,4,10,1008,8,0,10,4,10,1001,8,0,135,3,8,1002,8,-1,10,101,1,10,10,4,10,108,0,8,10,4,10,1001,8,0,156,3,8,1002,8,-1,10,101,1,10,10,4,10,108,0,8,10,4,10,1001,8,0,178,1,108,19,10,3,8,102,-1,8,10,101,1,10,10,4,10,108,0,8,10,4,10,1002,8,1,204,1,1006,17,10,3,8,102,-1,8,10,101,1,10,10,4,10,108,1,8,10,4,10,102,1,8,230,1006,0,67,1,103,11,10,1,1009,19,10,1,109,10,10,3,8,102,-1,8,10,101,1,10,10,4,10,1008,8,0,10,4,10,101,0,8,268,3,8,102,-1,8,10,101,1,10,10,4,10,1008,8,1,10,4,10,1002,8,1,290,2,108,13,10,101,1,9,9,1007,9,989,10,1005,10,15,99,109,636,104,0,104,1,21101,48210224024,0,1,21101,0,331,0,1105,1,435,21101,0,937264165644,1,21101,0,342,0,1105,1,435,3,10,104,0,104,1,3,10,104,0,104,0,3,10,104,0,104,1,3,10,104,0,104,1,3,10,104,0,104,0,3,10,104,0,104,1,21101,235354025051,0,1,21101,389,0,0,1105,1,435,21102,29166169280,1,1,21102,400,1,0,1105,1,435,3,10,104,0,104,0,3,10,104,0,104,0,21102,709475849060,1,1,21102,1,423,0,1106,0,435,21102,868498428684,1,1,21101,434,0,0,1105,1,435,99,109,2,21201,-1,0,1,21101,0,40,2,21102,1,466,3,21101,456,0,0,1105,1,499,109,-2,2105,1,0,0,1,0,0,1,109,2,3,10,204,-1,1001,461,462,477,4,0,1001,461,1,461,108,4,461,10,1006,10,493,1101,0,0,461,109,-2,2106,0,0,0,109,4,2102,1,-1,498,1207,-3,0,10,1006,10,516,21102,1,0,-3,21201,-3,0,1,21201,-2,0,2,21102,1,1,3,21102,535,1,0,1106,0,540,109,-4,2106,0,0,109,5,1207,-3,1,10,1006,10,563,2207,-4,-2,10,1006,10,563,21202,-4,1,-4,1106,0,631,21201,-4,0,1,21201,-3,-1,2,21202,-2,2,3,21101,582,0,0,1105,1,540,22102,1,1,-4,21102,1,1,-1,2207,-4,-2,10,1006,10,601,21101,0,0,-1,22202,-2,-1,-2,2107,0,-3,10,1006,10,623,22102,1,-1,1,21101,623,0,0,105,1,498,21202,-2,-1,-2,22201,-4,-2,-4,109,-5,2105,1,0 -------------------------------------------------------------------------------- /2019/inputs/input14.txt: -------------------------------------------------------------------------------- 1 | 2 WZMS, 3 NPNFD => 5 SLRGD 2 | 4 QTFCJ, 1 RFZF => 1 QFQPN 3 | 2 LCDPV => 6 DGPND 4 | 1 MVSHM, 3 XSDR, 1 RSJD => 6 GNKB 5 | 6 XJRML, 1 LCDPV => 7 HTSJ 6 | 3 LQBX => 3 GKNTG 7 | 2 NZMLP, 5 FTNZQ => 2 QSLTQ 8 | 8 WZMS, 4 XSDR, 2 NPNFD => 9 CJVT 9 | 16 HFHB, 1 TRVQG => 8 QTBQ 10 | 177 ORE => 7 DNWGS 11 | 10 ZJFM, 4 MVSHM => 8 LCDPV 12 | 1 LTVKM => 5 ZJFM 13 | 5 QFJS => 6 LTVKM 14 | 4 CZHM, 12 CJVT => 9 PGMS 15 | 104 ORE => 8 QCGM 16 | 1 JWLZ, 5 QTFCJ => 4 DHNL 17 | 20 VKRBJ => 3 FQCKM 18 | 1 FTNZQ, 1 QSLTQ => 4 HFHB 19 | 1 JLPVD => 2 JGJFQ 20 | 12 PTDL => 1 LVPK 21 | 31 JGJFQ, 5 PGMS, 38 PTDL, 1 PGCZ, 3 LVPK, 47 JGHWZ, 21 LVPJ, 27 LTVKM, 5 ZDQD, 5 LCDPV => 1 FUEL 22 | 6 WFJT, 2 VKRBJ => 8 NZMLP 23 | 21 HNJW, 3 NXTL, 8 WZMS, 5 SLRGD, 2 VZJHN, 6 QFQPN, 5 DHNL, 19 RNXQ => 2 PGCZ 24 | 1 QTBQ, 3 MVSHM => 1 XSDR 25 | 25 ZKZNB => 9 VZJHN 26 | 4 WHLT => 9 PHFKW 27 | 29 QPVNV => 9 JGHWZ 28 | 13 ZJFM => 2 RNXQ 29 | 1 DGPND, 12 PHFKW => 9 BXGXT 30 | 25 ZJFM => 6 WHLT 31 | 3 QPVNV => 9 BTLH 32 | 1 KXQG => 8 TRVQG 33 | 2 JWLZ => 8 JLPVD 34 | 2 GKNTG => 6 NXTL 35 | 28 VKRBJ => 2 DXWSH 36 | 126 ORE => 7 VKRBJ 37 | 11 WHLT => 8 QTFCJ 38 | 1 NZMLP, 1 DNWGS, 8 VKRBJ => 5 XJRML 39 | 16 XJRML => 6 SKHJL 40 | 3 QTFCJ, 6 ZTHWQ, 15 GKNTG, 1 NXRZL, 1 DGBRZ, 1 SKHJL, 1 VZJHN => 7 LVPJ 41 | 1 HFHB, 16 QTBQ, 7 XJRML => 3 NPNFD 42 | 2 TRVQG => 4 JWLZ 43 | 8 GKNTG, 1 NSVG, 23 RNXQ => 9 NXRZL 44 | 3 QTFCJ => 6 CZHM 45 | 2 NPNFD => 8 JQSTD 46 | 1 DXWSH, 1 DGPND => 4 DGBRZ 47 | 3 DXWSH, 24 QFJS, 8 FTNZQ => 8 KXQG 48 | 6 FXJQX, 14 ZKZNB, 3 QTFCJ => 2 ZTHWQ 49 | 31 NSVG, 1 NXRZL, 3 QPVNV, 2 RNXQ, 17 NXTL, 6 BTLH, 1 HNJW, 2 HTSJ => 1 ZDQD 50 | 5 RNXQ, 23 BXGXT, 5 JQSTD => 7 QPVNV 51 | 8 NPNFD => 7 WZMS 52 | 6 KXQG => 7 ZDZM 53 | 129 ORE => 9 WFJT 54 | 9 NZMLP, 5 FQCKM, 8 QFJS => 1 LQBX 55 | 170 ORE => 9 GDBNV 56 | 5 RSJD, 3 CZHM, 1 GNKB => 6 HNJW 57 | 14 HTSJ => 7 FXJQX 58 | 11 NPNFD, 1 LCDPV, 2 FXJQX => 6 RSJD 59 | 9 DGBRZ => 6 ZKZNB 60 | 7 GDBNV, 1 QCGM => 8 QFJS 61 | 2 QFQPN, 5 JWLZ => 4 NSVG 62 | 8 QFJS, 1 ZDZM, 4 QSLTQ => 7 MVSHM 63 | 1 LTVKM => 8 RFZF 64 | 4 DNWGS => 3 FTNZQ 65 | 6 VZJHN => 9 PTDL 66 | -------------------------------------------------------------------------------- /2019/inputs/input16.txt: -------------------------------------------------------------------------------- 1 | 59727310424796235189476878806940387435291429226818921130171187957262146115559932358924341808253400617220924411865224341744614706346865536561788244183609411225788501102400269978290670307147139438239865673058478091682748114942700860895620690690625512670966265975462089087644554004423208369517716075591723905075838513598360188150158989179151879406086757964381549720210763972463291801513250953430219653258827586382953297392567981587028568433943223260723561880121205475323894070000380258122357270847092900809245133752093782889315244091880516672127950518799757198383131025701009960944008679555864631340867924665650332161673274408001712152664733237178121872 2 | -------------------------------------------------------------------------------- /2019/inputs/input19.txt: -------------------------------------------------------------------------------- 1 | 109,424,203,1,21102,11,1,0,1105,1,282,21102,1,18,0,1106,0,259,2101,0,1,221,203,1,21102,1,31,0,1106,0,282,21102,38,1,0,1105,1,259,20101,0,23,2,22101,0,1,3,21101,1,0,1,21101,57,0,0,1105,1,303,2101,0,1,222,21001,221,0,3,21002,221,1,2,21101,0,259,1,21102,80,1,0,1106,0,225,21102,89,1,2,21102,91,1,0,1105,1,303,2101,0,1,223,20101,0,222,4,21101,0,259,3,21102,1,225,2,21102,225,1,1,21102,118,1,0,1106,0,225,20101,0,222,3,21101,136,0,2,21101,133,0,0,1106,0,303,21202,1,-1,1,22001,223,1,1,21101,148,0,0,1105,1,259,1202,1,1,223,20102,1,221,4,21001,222,0,3,21102,18,1,2,1001,132,-2,224,1002,224,2,224,1001,224,3,224,1002,132,-1,132,1,224,132,224,21001,224,1,1,21102,195,1,0,106,0,108,20207,1,223,2,20102,1,23,1,21101,-1,0,3,21101,214,0,0,1105,1,303,22101,1,1,1,204,1,99,0,0,0,0,109,5,1202,-4,1,249,21201,-3,0,1,22102,1,-2,2,21202,-1,1,3,21102,1,250,0,1105,1,225,21201,1,0,-4,109,-5,2105,1,0,109,3,22107,0,-2,-1,21202,-1,2,-1,21201,-1,-1,-1,22202,-1,-2,-2,109,-3,2105,1,0,109,3,21207,-2,0,-1,1206,-1,294,104,0,99,22102,1,-2,-2,109,-3,2105,1,0,109,5,22207,-3,-4,-1,1206,-1,346,22201,-4,-3,-4,21202,-3,-1,-1,22201,-4,-1,2,21202,2,-1,-1,22201,-4,-1,1,21201,-2,0,3,21102,343,1,0,1106,0,303,1105,1,415,22207,-2,-3,-1,1206,-1,387,22201,-3,-2,-3,21202,-2,-1,-1,22201,-3,-1,3,21202,3,-1,-1,22201,-3,-1,2,21202,-4,1,1,21102,384,1,0,1105,1,303,1106,0,415,21202,-4,-1,-4,22201,-4,-3,-4,22202,-3,-2,-2,22202,-2,-4,-4,22202,-3,-2,-3,21202,-4,-1,-2,22201,-3,-2,1,21202,1,1,-4,109,-5,2106,0,0 -------------------------------------------------------------------------------- /2019/inputs/input22.txt: -------------------------------------------------------------------------------- 1 | deal with increment 46 2 | cut 679 3 | deal into new stack 4 | deal with increment 50 5 | cut -9441 6 | deal into new stack 7 | cut 3142 8 | deal with increment 35 9 | cut -5753 10 | deal with increment 33 11 | cut -7554 12 | deal into new stack 13 | deal with increment 29 14 | deal into new stack 15 | cut 9611 16 | deal into new stack 17 | deal with increment 33 18 | cut 9851 19 | deal with increment 48 20 | cut 2187 21 | deal with increment 43 22 | cut 7371 23 | deal with increment 61 24 | cut -7807 25 | deal with increment 43 26 | cut 443 27 | deal into new stack 28 | cut 4405 29 | deal with increment 41 30 | cut 4281 31 | deal with increment 31 32 | cut 1211 33 | deal with increment 62 34 | cut 1970 35 | deal into new stack 36 | deal with increment 60 37 | cut -5057 38 | deal with increment 59 39 | cut -8537 40 | deal with increment 30 41 | deal into new stack 42 | deal with increment 13 43 | cut 9313 44 | deal with increment 17 45 | deal into new stack 46 | deal with increment 2 47 | cut 9199 48 | deal with increment 29 49 | cut 4299 50 | deal with increment 34 51 | cut -1599 52 | deal with increment 53 53 | cut 176 54 | deal into new stack 55 | deal with increment 16 56 | cut -1292 57 | deal with increment 49 58 | cut 6401 59 | deal with increment 32 60 | cut -7177 61 | deal with increment 70 62 | cut -486 63 | deal with increment 16 64 | deal into new stack 65 | cut -39 66 | deal with increment 19 67 | deal into new stack 68 | deal with increment 53 69 | cut 3475 70 | deal with increment 42 71 | cut -4515 72 | deal with increment 27 73 | cut -140 74 | deal with increment 60 75 | deal into new stack 76 | cut -3470 77 | deal with increment 42 78 | cut -3952 79 | deal with increment 20 80 | cut 2394 81 | deal with increment 72 82 | cut 8012 83 | deal with increment 54 84 | cut 3503 85 | deal with increment 60 86 | cut -5474 87 | deal with increment 30 88 | cut 9038 89 | deal with increment 41 90 | cut 5497 91 | deal with increment 27 92 | cut -9002 93 | deal into new stack 94 | deal with increment 30 95 | cut -8369 96 | deal with increment 15 97 | cut 610 98 | deal into new stack 99 | deal with increment 64 100 | cut 2470 101 | -------------------------------------------------------------------------------- /2019/inputs/input24.txt: -------------------------------------------------------------------------------- 1 | ###.. 2 | #...# 3 | .#.## 4 | ##.#. 5 | #.### -------------------------------------------------------------------------------- /2020/day01.exs: -------------------------------------------------------------------------------- 1 | # --- Day 1: Report Repair --- 2 | 3 | defmodule ExpenseReport do 4 | def two_entries_summing_2020(file) do 5 | file 6 | |> lines() 7 | |> find_two_entries(2020) 8 | end 9 | 10 | def three_entries_summing_2020(file) do 11 | file 12 | |> lines() 13 | |> find_three_entries(2020) 14 | end 15 | 16 | defp find_two_entries([entry | rest], sum) do 17 | if sum - entry in rest do 18 | [entry, sum - entry] 19 | else 20 | find_two_entries(rest, sum) 21 | end 22 | end 23 | defp find_two_entries([], _), do: nil 24 | 25 | defp find_three_entries([entry | rest], sum) do 26 | two_entries = find_two_entries(rest, sum - entry) 27 | if two_entries do 28 | [entry | two_entries] 29 | else 30 | find_three_entries(rest, sum) 31 | end 32 | end 33 | 34 | defp lines(file) do 35 | File.read!(file) 36 | |> String.split(~r{\n}, trim: true) 37 | |> Enum.map(&String.to_integer/1) 38 | end 39 | end 40 | 41 | ExpenseReport.two_entries_summing_2020("./inputs/input01.txt") |> Enum.reduce(&*/2) |> IO.puts 42 | 43 | # --- Part Two --- 44 | 45 | ExpenseReport.three_entries_summing_2020("./inputs/input01.txt") |> Enum.reduce(&*/2) |> IO.puts -------------------------------------------------------------------------------- /2020/day02.exs: -------------------------------------------------------------------------------- 1 | # --- Day 2: Password Philosophy --- 2 | 3 | defmodule Toboggan do 4 | def number_of_valid_passwords_with_first_interpretation(file) do 5 | file 6 | |> lines() 7 | |> Enum.map(&to_password_and_policy/1) 8 | |> Enum.count(&valid_with_first_interpretation?/1) 9 | end 10 | 11 | def number_of_valid_passwords_with_second_interpretation(file) do 12 | file 13 | |> lines() 14 | |> Enum.map(&to_password_and_policy/1) 15 | |> Enum.count(&valid_with_second_interpretation?/1) 16 | end 17 | 18 | 19 | defp valid_with_first_interpretation?({from, to, letter, password}) do 20 | occurrences = Enum.count(String.graphemes(password), fn x -> x == letter end) 21 | occurrences >= from && occurrences <= to 22 | end 23 | 24 | defp valid_with_second_interpretation?({first, second, letter, password}) do 25 | String.at(password, first - 1) == letter && String.at(password, second - 1) != letter || 26 | String.at(password, first - 1) != letter && String.at(password, second - 1) == letter 27 | end 28 | 29 | defp to_password_and_policy(line) do 30 | [_, from, to, letter, password] = Regex.run(~r{(\d+)-(\d+) ([a-z]): ([a-z]+)}, line) 31 | {String.to_integer(from), String.to_integer(to), letter, password} 32 | end 33 | 34 | defp lines(file) do 35 | File.read!(file) 36 | |> String.split(~r{\n}, trim: true) 37 | end 38 | end 39 | 40 | Toboggan.number_of_valid_passwords_with_first_interpretation("./inputs/input02.txt") |> IO.puts 41 | 42 | # --- Part Two --- 43 | 44 | Toboggan.number_of_valid_passwords_with_second_interpretation("./inputs/input02.txt") |> IO.puts 45 | -------------------------------------------------------------------------------- /2020/day03.exs: -------------------------------------------------------------------------------- 1 | # --- Day 3: Toboggan Trajectory --- 2 | 3 | defmodule Trajectory do 4 | def move_and_count_trees(map, slopes) when is_list(slopes) do 5 | Enum.map(slopes, fn slope -> move_and_count_trees(map, slope) end) 6 | |> Enum.reduce(&(&1 * &2)) 7 | end 8 | 9 | def move_and_count_trees(map, slope), do: move_and_count_trees(map, slope, {0, 0}, 0) 10 | def move_and_count_trees({trees, _}, _, {i, _}, count) when map_size(trees) == i, do: count 11 | def move_and_count_trees({trees, width}, slope, position, count) do 12 | move_and_count_trees({trees, width}, slope, move(slope, position, width), (if trees[position] == "#", do: count + 1, else: count)) 13 | end 14 | 15 | def read_and_parse_map(file) do 16 | lines = read(file) 17 | { parse_map(lines), width(lines) } 18 | end 19 | 20 | defp move({x, y}, {i, j}, width), do: {i + x, rem(j + y, width) } 21 | 22 | defp parse_map(lines), do: parse_map(lines, %{}, 0) 23 | defp parse_map([], trees, _i), do: trees 24 | defp parse_map([row | rows], trees, i) do 25 | updated_trees = parse_map(row, trees, i, 0) 26 | parse_map(rows, updated_trees, i + 1) 27 | end 28 | 29 | defp parse_map([], trees, _i, _j), do: trees 30 | defp parse_map(["." | row], trees, i, j), do: parse_map(row, trees, i, j + 1) 31 | defp parse_map(["#" | row], trees, i, j) do 32 | parse_map(row, Map.put(trees, {i, j}, "#"), i, j + 1) 33 | end 34 | 35 | defp width(lines) do 36 | lines 37 | |> List.first() 38 | |> Enum.count() 39 | end 40 | 41 | defp read(file) do 42 | File.read!(file) 43 | |> String.split(~r{\n}, trim: true) 44 | |> Enum.map(&String.graphemes/1) 45 | end 46 | end 47 | 48 | Trajectory.read_and_parse_map("inputs/input03.txt") |> Trajectory.move_and_count_trees({1, 3}) |> IO.puts 49 | 50 | # --- Part Two --- 51 | 52 | # Right 1, down 1. 53 | # Right 3, down 1. (This is the slope you already checked.) 54 | # Right 5, down 1. 55 | # Right 7, down 1. 56 | # Right 1, down 2. 57 | 58 | Trajectory.read_and_parse_map("inputs/input03.txt") |> Trajectory.move_and_count_trees([{1, 1}, {1, 3}, {1, 5}, {1, 7}, {2, 1}]) |> IO.puts 59 | -------------------------------------------------------------------------------- /2020/day04.exs: -------------------------------------------------------------------------------- 1 | # --- Day 4: Passport Processing --- 2 | 3 | defmodule Passports do 4 | @required_fields ["byr", "iyr", "eyr", "hgt", "hcl", "ecl", "pid"] 5 | 6 | def read_passports(file) do 7 | File.read!(file) 8 | |> String.split(~r{\n\n}, trim: true) 9 | |> Enum.map(fn x -> String.split(x, ~r{\s}, trim: true) end) 10 | |> Enum.map(fn x -> parse_passport(x) end) 11 | end 12 | 13 | def present?(passport) do 14 | Enum.all?(@required_fields, fn field -> Map.has_key?(passport, field) end) 15 | end 16 | 17 | def valid?(passport) do 18 | Enum.all?(@required_fields, fn field -> valid?(field, passport[field]) end) 19 | end 20 | 21 | defp valid?(_, nil), do: false 22 | # byr (Birth Year) - four digits; at least 1920 and at most 2002. 23 | defp valid?("byr", value), do: four_digits?(value) && in_range?(1920..2002, value) 24 | # iyr (Issue Year) - four digits; at least 2010 and at most 2020. 25 | defp valid?("iyr", value), do: four_digits?(value) && in_range?(2010..2020, value) 26 | # eyr (Expiration Year) - four digits; at least 2020 and at most 2030. 27 | defp valid?("eyr", value), do: four_digits?(value) && in_range?(2020..2030, value) 28 | # hgt (Height) - a number followed by either cm or in: 29 | # If cm, the number must be at least 150 and at most 193. 30 | # If in, the number must be at least 59 and at most 76. 31 | defp valid?("hgt", value) do 32 | matches = Regex.run(~r{(\d+)(in|cm)}, value) 33 | if matches do 34 | [_, number, unit] = matches 35 | case unit do 36 | "cm" -> in_range?(150..193, number) 37 | "in" -> in_range?(59..76, number) 38 | _ -> false 39 | end 40 | end 41 | end 42 | # hcl (Hair Color) - a # followed by exactly six characters 0-9 or a-f. 43 | defp valid?("hcl", value), do: String.match?(value, ~r/^#[0-9a-f]{6}$/) 44 | # ecl (Eye Color) - exactly one of: amb blu brn gry grn hzl oth. 45 | defp valid?("ecl", value), do: Enum.member?(["amb", "blu", "brn", "gry", "grn", "hzl", "oth"], value) 46 | # pid (Passport ID) - a nine-digit number, including leading zeroes. 47 | defp valid?("pid", value), do: String.match?(value, ~r/^\d{9}$/) 48 | 49 | defp four_digits?(value), do: String.length(value) == 4 50 | defp in_range?(range, value), do: Enum.member?(range, String.to_integer(value)) 51 | 52 | defp parse_passport(passport) do 53 | Enum.map(passport, &String.split(&1, ":")) 54 | |> Enum.map(&List.to_tuple/1) 55 | |> Enum.into(%{}) 56 | end 57 | end 58 | 59 | Passports.read_passports("inputs/input04.txt") |> Enum.count(&Passports.present?/1) |> IO.puts 60 | 61 | # --- Part Two --- 62 | Passports.read_passports("inputs/input04.txt") |> Enum.count(&Passports.valid?/1) |> IO.puts 63 | -------------------------------------------------------------------------------- /2020/day05.exs: -------------------------------------------------------------------------------- 1 | # --- Day 5: Binary Boarding --- 2 | 3 | defmodule BoardingPass do 4 | def missing(ids) do 5 | Enum.find(ids, fn x -> (x + 2 in ids) && !(x + 1 in ids) end) + 1 6 | end 7 | 8 | def all_ids() do 9 | Enum.map((0..127), &(8 * &1)) 10 | |> Enum.flat_map(fn x -> Enum.map((0..7), &(&1 + x)) end) 11 | end 12 | 13 | def ids(file), do: read_boarding_passes(file) |> Enum.map(&id/1) 14 | 15 | defp id(pass) do 16 | String.replace(pass, "F", "0") 17 | |> String.replace("B", "1") 18 | |> String.replace("L", "0") 19 | |> String.replace("R", "1") 20 | |> String.to_integer(2) 21 | end 22 | 23 | defp read_boarding_passes(file) do 24 | File.read!(file) 25 | |> String.split(~r{\n}, trim: true) 26 | end 27 | end 28 | 29 | BoardingPass.ids("inputs/input05.txt") |> Enum.max |> IO.puts 30 | 31 | # --- Part Two --- 32 | BoardingPass.ids("inputs/input05.txt") |> BoardingPass.missing |> IO.inspect 33 | -------------------------------------------------------------------------------- /2020/day06.exs: -------------------------------------------------------------------------------- 1 | # --- Day 6: Custom Customs --- 2 | 3 | defmodule Customs do 4 | def questions_anyone_answered_yes(file) do 5 | read_answers(file) 6 | |> Enum.map(&String.replace(&1, ~r{\s}, "")) 7 | |> strings_to_sets() 8 | |> combine_and_sum() 9 | end 10 | 11 | def questions_everyone_answered_yes(file) do 12 | read_answers(file) 13 | |> Enum.map(&intersections/1) 14 | |> combine_and_sum() 15 | end 16 | 17 | defp combine_and_sum(anwers) do 18 | Enum.map(anwers, &MapSet.size/1) 19 | |> Enum.reduce(&(&1 + &2)) 20 | end 21 | 22 | defp intersections(answers) do 23 | String.split(answers, ~r{\n}, trim: true) 24 | |> strings_to_sets() 25 | |> Enum.reduce(&(MapSet.intersection(&1, &2))) 26 | end 27 | 28 | defp strings_to_sets(strings) do 29 | Enum.map(strings, &String.graphemes/1) 30 | |> Enum.map(&MapSet.new/1) 31 | end 32 | 33 | defp read_answers(file) do 34 | File.read!(file) 35 | |> String.split(~r{\n\n}, trim: true) 36 | end 37 | end 38 | 39 | Customs.questions_anyone_answered_yes("inputs/input06.txt") |> IO.puts 40 | 41 | # --- Part Two --- 42 | 43 | Customs.questions_everyone_answered_yes("inputs/input06.txt") |> IO.puts 44 | -------------------------------------------------------------------------------- /2020/day07.exs: -------------------------------------------------------------------------------- 1 | # --- Day 7: Handy Haversacks --- 2 | 3 | defmodule Luggage do 4 | def required_bags(rules, colour), do: required_bags(Enum.into(rules, %{}), colour, 0) 5 | def required_bags(rules, colour, number) do 6 | rules[colour] 7 | |> Enum.map(fn {n, bag} -> n + n * required_bags(rules, bag, number) end) 8 | |> Enum.reduce(0, &(&1 + &2)) 9 | |> Kernel.+(number) 10 | end 11 | 12 | def suitable_containers(rules, colour) do 13 | build_graph(rules) 14 | |> reachable_nodes(colour) 15 | end 16 | 17 | def reachable_nodes(graph, colour), do: reachable_nodes(graph, colour, MapSet.new) 18 | def reachable_nodes(graph, colour, nodes) do 19 | if Map.has_key?(graph, colour) do 20 | children = MapSet.new(Enum.map(graph[colour], &elem(&1, 1))) 21 | Enum.map(children, fn child -> reachable_nodes(graph, child, MapSet.put(nodes, child)) end) 22 | |> Enum.reduce(&(MapSet.union(&1, &2))) 23 | else 24 | nodes 25 | end 26 | end 27 | 28 | def build_graph(rules), do: build_graph(rules, %{}) 29 | def build_graph([], graph), do: graph 30 | def build_graph([rule|rules], graph), do: build_graph(rules, insert_rule(rule, graph)) 31 | 32 | def insert_rule({_, []}, graph), do: graph 33 | def insert_rule({container, [{quantity, colour}|contents]}, graph) do 34 | parents = graph[colour] || [] 35 | insert_rule({container, contents}, graph) 36 | |> Map.put(colour, [{quantity, container} | parents]) 37 | end 38 | 39 | def read_rules(file) do 40 | File.read!(file) 41 | |> String.split(~r{\n}, trim: true) 42 | |> Enum.map(&parse_rule/1) 43 | end 44 | 45 | defp parse_rule(rule) do 46 | # light red bags contain 1 bright white bag, 2 muted yellow bags. 47 | # dotted orange bags contain 3 clear cyan bags, 5 shiny silver bags, 2 muted gold bags, 2 dim tomato bags. 48 | [_, container, contents] = Regex.run(~r{([\s\w]+) bags contain ([^.]+).}, rule) 49 | {container, parse_contents(contents)} 50 | end 51 | 52 | defp parse_contents("no other bags"), do: [] 53 | defp parse_contents(contents) do 54 | String.split(contents, ",", trim: true) 55 | |> Enum.map(&parse_content/1) 56 | end 57 | 58 | defp parse_content(content) do 59 | [_, quantity, colour] = Regex.run(~r{(\d+) ([\s\w]+) bags?}, content) 60 | {String.to_integer(quantity), colour} 61 | end 62 | end 63 | 64 | Luggage.read_rules("inputs/input07.txt") |> Luggage.suitable_containers("shiny gold") |> MapSet.size |> IO.puts 65 | 66 | # --- Part Two --- 67 | 68 | Luggage.read_rules("inputs/input07.txt") |> Luggage.required_bags("shiny gold") |> IO.puts 69 | -------------------------------------------------------------------------------- /2020/day08.exs: -------------------------------------------------------------------------------- 1 | # --- Day 8: Handheld Halting --- 2 | 3 | defmodule Handheld do 4 | def run_up_to_infinite_loop_or_halt(instructions), do: run_up_to_infinite_loop_or_halt({0, 0}, instructions, MapSet.new) 5 | def run_up_to_infinite_loop_or_halt({acc, ip}, instructions, ip_values) do 6 | cond do 7 | ip in ip_values -> {:loop, acc} # Infinite loop 8 | ip >= Enum.count(instructions) -> {:halt, acc} # Halt 9 | true -> execute(Enum.at(instructions, ip), acc, ip) |> run_up_to_infinite_loop_or_halt(instructions, MapSet.put(ip_values, ip)) 10 | end 11 | end 12 | 13 | def fix_program(instructions), do: fix_program(instructions, 0) 14 | def fix_program(instructions, index) do 15 | instruction = Enum.at(instructions, index) 16 | if fixable?(instruction) do 17 | {state, acc} = List.replace_at(instructions, index, fixed(instruction)) 18 | |> run_up_to_infinite_loop_or_halt() 19 | case state do 20 | :loop -> fix_program(instructions, index + 1) 21 | :halt -> {state, acc} 22 | end 23 | else 24 | fix_program(instructions, index + 1) 25 | end 26 | end 27 | 28 | defp fixable?({operation, argument}), do: operation == "jmp" || (operation == "nop" && argument != 0) 29 | 30 | defp fixed({"nop", argument}), do: {"jmp", argument} 31 | defp fixed({"jmp", argument}), do: {"nop", argument} 32 | 33 | # acc increases or decreases a single global value called the accumulator by the value given in the argument 34 | # jmp jumps to a new instruction relative to itself 35 | # nop stands for No OPeration - it does nothing. The instruction immediately below it is executed next. 36 | defp execute({"acc", argument}, acc, ip), do: {acc + argument, ip + 1} 37 | defp execute({"jmp", argument}, acc, ip), do: {acc, ip + argument} 38 | defp execute({"nop", _}, acc, ip), do: {acc, ip + 1} 39 | 40 | def read_instructions(file) do 41 | File.read!(file) 42 | |> String.split(~r{\n}, trim: true) 43 | |> Enum.map(&parse_instruction/1) 44 | end 45 | 46 | defp parse_instruction(instruction) do 47 | [operation, argument] = String.split(instruction, ~r{\s}, trim: true) 48 | {operation, String.to_integer(argument)} 49 | end 50 | end 51 | 52 | Handheld.read_instructions("inputs/input08.txt") |> Handheld.run_up_to_infinite_loop_or_halt() |> IO.inspect 53 | 54 | # --- Part Two --- 55 | 56 | Handheld.read_instructions("inputs/input08.txt") |> Handheld.fix_program() |> IO.inspect 57 | -------------------------------------------------------------------------------- /2020/day09.exs: -------------------------------------------------------------------------------- 1 | # --- Day 9: Encoding Error --- 2 | 3 | defmodule XMAS do 4 | def find_first_error({_, []}), do: nil 5 | def find_first_error({[p|preamble], [n|numbers]}) do 6 | if valid?([p|preamble], n) do 7 | find_first_error({preamble ++ [n], numbers}) 8 | else 9 | n 10 | end 11 | end 12 | 13 | def encryption_weakness(_, 0), do: 0 14 | def encryption_weakness({preamble, numbers}, first_error), do: encryption_weakness(preamble ++ numbers, first_error, [], 0) 15 | def encryption_weakness(_, first_error, range, first_error), do: Enum.min(range) + Enum.max(range) 16 | def encryption_weakness([n|numbers], first_error, range, sum) do 17 | if sum + n <= first_error do 18 | encryption_weakness(numbers, first_error, range ++ [n], sum + n) 19 | else 20 | [drop|rest] = range 21 | encryption_weakness([n|numbers], first_error, rest, sum - drop) 22 | end 23 | end 24 | 25 | def read_numbers(file, preamble_size) do 26 | File.read!(file) 27 | |> String.split(~r{\n}, trim: true) 28 | |> Enum.map(&String.to_integer/1) 29 | |> Enum.split(preamble_size) 30 | end 31 | 32 | defp valid?([], _), do: false 33 | defp valid?([p|preamble], number), do: (number - p in preamble) || valid?(preamble, number) 34 | end 35 | 36 | numbers = XMAS.read_numbers("inputs/input09.txt", 25) 37 | first_error = XMAS.find_first_error(numbers) |> IO.inspect 38 | 39 | # --- Part Two --- 40 | 41 | XMAS.encryption_weakness(numbers, first_error) |> IO.inspect 42 | -------------------------------------------------------------------------------- /2020/day10.exs: -------------------------------------------------------------------------------- 1 | # --- Day 10: Adapter Array --- 2 | 3 | defmodule JoltageAdapters do 4 | def arrangements(ratings) do 5 | compute_differences(ratings) 6 | |> count_one_sequences() 7 | |> compute_arrangements() 8 | end 9 | 10 | def difference_product(ratings) do 11 | differences = compute_differences(ratings) 12 | Enum.count(differences, &(&1 == 1)) * Enum.count(differences, &(&1 == 3)) 13 | end 14 | 15 | defp compute_differences(ratings), do: compute_differences([0] ++ Enum.sort(ratings) ++ [device_rating(ratings)], []) 16 | defp compute_differences([_], differences), do: differences 17 | defp compute_differences([r1, r2|ratings], differences), do: compute_differences([r2|ratings], differences ++ [r2 - r1]) 18 | 19 | defp count_one_sequences(differences), do: count_one_sequences(differences, [], 0) 20 | defp count_one_sequences([], sequences, current), do: sequences ++ [current] |> Enum.filter(&(&1 > 0)) 21 | defp count_one_sequences([1|differences], sequences, current), do: count_one_sequences(differences, sequences, current + 1) 22 | defp count_one_sequences([3|differences], sequences, current), do: count_one_sequences(differences, sequences ++ [current], 0) 23 | 24 | defp compute_arrangements(one_sequences), do: compute_arrangements(one_sequences, 1) 25 | defp compute_arrangements([], arrangements), do: arrangements 26 | defp compute_arrangements([ones|one_sequences], arrangements) do 27 | case ones do 28 | 1 -> compute_arrangements(one_sequences, arrangements) 29 | 2 -> compute_arrangements(one_sequences, arrangements * 2) 30 | 3 -> compute_arrangements(one_sequences, arrangements * 4) 31 | 4 -> compute_arrangements(one_sequences, arrangements * 7) 32 | end 33 | end 34 | 35 | defp device_rating(adapter_ratings), do: Enum.max(adapter_ratings) + 3 36 | 37 | def read_ratings(file) do 38 | File.read!(file) 39 | |> String.split(~r{\n}, trim: true) 40 | |> Enum.map(&String.to_integer/1) 41 | end 42 | end 43 | 44 | JoltageAdapters.read_ratings("inputs/input10.txt") |> JoltageAdapters.difference_product() |> IO.puts 45 | 46 | # --- Part Two --- 47 | 48 | JoltageAdapters.read_ratings("inputs/input10.txt") |> JoltageAdapters.arrangements() |> IO.inspect 49 | -------------------------------------------------------------------------------- /2020/day11.exs: -------------------------------------------------------------------------------- 1 | # --- Day 11: Seating System --- 2 | 3 | defmodule SeatingSystem do 4 | def occupied_seats_after_simulation(layout, criteria) do 5 | simulate(layout, criteria) 6 | |> turn_into_list() 7 | |> Enum.count(&(&1 == "#")) 8 | end 9 | 10 | def simulate(layout, criteria) do 11 | next_state = next_state(layout, criteria) 12 | if layout == next_state, do: next_state, else: simulate(next_state, criteria) 13 | end 14 | 15 | def next_state(layout, criteria) do 16 | Enum.map(0..Enum.count(layout) - 1, fn i -> {i, next_state(layout, criteria, i)} end) 17 | |> Enum.into(Map.new) 18 | end 19 | 20 | def next_state(layout, criteria, i) do 21 | Enum.map(0..Enum.count(layout[i]) - 1, fn j -> {j, next_state(layout, criteria, i, j)} end) 22 | |> Enum.into(Map.new) 23 | end 24 | 25 | def next_state(layout, criteria, i, j) do 26 | neighbours = apply(SeatingSystem, criteria, [layout, i, j]) 27 | case layout[i][j] do 28 | "L" -> if Enum.all?(neighbours, &(&1 != "#")), do: "#", else: "L" 29 | "#" -> if Enum.count(neighbours, &(&1 == "#")) >= threshold(criteria), do: "L", else: "#" 30 | "." -> "." 31 | end 32 | end 33 | 34 | defp threshold(:adjacents), do: 4 35 | defp threshold(:visible), do: 5 36 | 37 | def adjacents(layout, i, j) do 38 | (for x <- -1..1, y <- -1..1, do: {x, y}) 39 | |> Enum.reject(&(&1 == {0, 0})) 40 | |> Enum.map(fn {x, y} -> layout[i+x][j+y] end) 41 | |> Enum.reject(&is_nil/1) 42 | end 43 | 44 | def visible(layout, i, j) do 45 | (for x <- -1..1, y <- -1..1, do: {x, y}) 46 | |> Enum.reject(&(&1 == {0, 0})) 47 | |> Enum.map(fn {x, y} -> visible(layout, i, j, x, y) end) 48 | |> Enum.reject(&is_nil/1) 49 | end 50 | 51 | def visible(layout, i, j, x, y) do 52 | if layout[i+x][j+y] != ".", do: layout[i+x][j+y], else: visible(layout, i+x, j+y, x, y) 53 | end 54 | 55 | def read_layout(file) do 56 | File.read!(file) 57 | |> String.split(~r{\n}, trim: true) 58 | |> Enum.map(&String.graphemes/1) 59 | |> turn_into_map() 60 | end 61 | 62 | defp turn_into_map(layout) do 63 | if is_list(List.first(layout)) do 64 | Enum.map(layout, fn row -> turn_into_map(row) end) 65 | |> turn_into_map() 66 | else 67 | Enum.zip(0..Enum.count(layout) - 1, layout) 68 | |> Enum.into(Map.new) 69 | end 70 | end 71 | 72 | defp turn_into_list(layout), do: Map.values(layout) |> Enum.flat_map(&Map.values/1) 73 | end 74 | 75 | SeatingSystem.read_layout("inputs/input11.txt") |> SeatingSystem.occupied_seats_after_simulation(:adjacents) |> IO.inspect 76 | 77 | # --- Part Two --- 78 | 79 | SeatingSystem.read_layout("inputs/input11.txt") |> SeatingSystem.occupied_seats_after_simulation(:visible) |> IO.inspect 80 | 81 | -------------------------------------------------------------------------------- /2020/day13.exs: -------------------------------------------------------------------------------- 1 | # --- Day 13: Shuttle Search --- 2 | 3 | defmodule Shuttle do 4 | def closest_bus({timestamp, buses}) do 5 | Enum.reject(buses, &(&1 == "x")) 6 | |> Enum.map(fn bus -> waiting_time(timestamp, bus) end) 7 | |> Enum.min_by(&(elem(&1, 1))) 8 | |> multiply() 9 | end 10 | 11 | # We need to find t such that each bus bi in position i 12 | # t ≡ bi - i (mod bi). This means that: 13 | # t ≡ b0 (mod b0) 14 | # t ≡ b1 - 1 (mod b1) 15 | # ... 16 | # t ≡ bn - n (mod bn) 17 | # This has a solution if b1..bn are coprime, and the solution is unique modulo b0*b1*...*bn, 18 | # by the Chinese Remainder Theorem 19 | def winning_time(buses) do 20 | Enum.zip(buses, 0..Enum.count(buses)) 21 | |> Enum.reject(fn {bus, _} -> bus == "x" end) 22 | |> Enum.map(fn {bus, i} -> {bus, bus - i} end) 23 | |> apply_chinese_remainder_theorem() 24 | end 25 | 26 | def apply_chinese_remainder_theorem(congruences), do: apply_chinese_remainder_theorem(congruences, modulo(congruences), []) 27 | def apply_chinese_remainder_theorem([], n, results), do: results |> Enum.reduce(&(&1 + &2)) |> rem(n) 28 | def apply_chinese_remainder_theorem([{b, i}|congruences], n, results) do 29 | y = div(n, b) 30 | z = modular_inverse(y, b) 31 | apply_chinese_remainder_theorem(congruences, n, results ++ [i * y * z]) 32 | end 33 | 34 | def modulo(congruences), do: Enum.map(congruences, &(elem(&1, 0))) |> Enum.reduce(&(&1 * &2)) 35 | 36 | def modular_inverse(y, n), do: extended_euclid(y, n) |> elem(1) |> rem(n) |> normalize(n) 37 | 38 | def extended_euclid(0, b), do: {b, 0, 1} 39 | def extended_euclid(a, b) do 40 | {gcd, x1, y1} = extended_euclid(rem(b, a), a) 41 | {gcd, y1 - div(b, a) * x1, x1} 42 | end 43 | 44 | def normalize(m, n), do: rem(m + n, n) 45 | 46 | def read_notes(file) do 47 | File.read!(file) 48 | |> String.split(~r{\n}, trim: true) 49 | |> parse_notes() 50 | end 51 | 52 | defp multiply({x, y}), do: x * y 53 | 54 | defp waiting_time(timestamp, bus), do: {bus, bus - (timestamp - div(timestamp, bus) * bus)} 55 | 56 | defp parse_notes([timestamp, buses]) do 57 | { String.to_integer(timestamp), parse_buses(buses) } 58 | end 59 | 60 | defp parse_buses(buses) do 61 | String.split(buses, ~r{,}, trim: true) 62 | |> Enum.map(fn x -> if x == "x", do: x, else: String.to_integer(x) end) 63 | end 64 | end 65 | 66 | Shuttle.read_notes("inputs/input13.txt") |> Shuttle.closest_bus() |> IO.inspect 67 | 68 | # --- Part Two --- 69 | 70 | Shuttle.read_notes("inputs/input13.txt") |> elem(1) |> Shuttle.winning_time() |> IO.inspect 71 | -------------------------------------------------------------------------------- /2020/day15.exs: -------------------------------------------------------------------------------- 1 | # --- Day 15: Rambunctious Recitation --- 2 | 3 | defmodule MemoryGame do 4 | def play(start, until) do 5 | spoken = Enum.zip(start, 1..Enum.count(start)) |> Enum.into(%{}) 6 | play({spoken, List.last(start), %{}}, Enum.count(start) + 1, until) 7 | end 8 | 9 | def play({_, previous, _}, turn, until) when turn == until + 1, do: previous 10 | def play({spoken, previous, previously_spoken}, turn, until) do 11 | speak(spoken, previous, previously_spoken, turn) 12 | |> play(turn + 1, until) 13 | end 14 | 15 | # Each turn consists of considering the most recently spoken number: 16 | # - If that was the first time the number has been spoken, the current player says 0. 17 | # - Otherwise, the number had been spoken before; the current player announces how many 18 | # turns apart the number is from when it was previously spoken. 19 | defp speak(spoken, previous, previously_spoken, turn) do 20 | next = if previously_spoken[previous], do: turn - 1 - previously_spoken[previous], else: 0 21 | updated_previously_spoken = if spoken[next], do: Map.put(previously_spoken, next, spoken[next]), else: previously_spoken 22 | {Map.put(spoken, next, turn), next, updated_previously_spoken} 23 | end 24 | end 25 | 26 | MemoryGame.play([7,14,0,17,11,1,2], 2020) |> IO.puts 27 | 28 | # --- Part Two --- 29 | 30 | MemoryGame.play([7,14,0,17,11,1,2], 30000000) |> IO.puts 31 | -------------------------------------------------------------------------------- /2020/day18.exs: -------------------------------------------------------------------------------- 1 | # --- Day 18: Operation Order --- 2 | 3 | defmodule MathHomework do 4 | def solve_homework(operations) do 5 | Enum.map(operations, &eval_operation/1) 6 | |> Enum.sum 7 | end 8 | 9 | def translate(operations, mapping) do 10 | Enum.reduce(mapping, operations, fn ({from, to}, acc) -> translate(acc, from, to) end) 11 | end 12 | def translate(operations, from, to) do 13 | Enum.map(operations, fn operation -> String.replace(operation, from, to) end) 14 | end 15 | 16 | def read_homework(file) do 17 | File.read!(file) 18 | |> String.split(~r{\n}, trim: true) 19 | end 20 | 21 | # Take advantage of + and - precedence 22 | def a - b, do: a * b 23 | 24 | # Take advantage of / and - precedence 25 | def a / b, do: a + b 26 | 27 | defp eval_operation(operation) do 28 | Code.string_to_quoted!(operation) 29 | |> in_module(__MODULE__) 30 | |> Code.eval_quoted 31 | |> elem(0) 32 | end 33 | 34 | defp in_module(ast, mod) do 35 | quote do 36 | import unquote(mod) 37 | import Kernel, except: [-: 2, /: 2] 38 | unquote(ast) 39 | end 40 | end 41 | end 42 | 43 | MathHomework.read_homework("inputs/input18.txt") |> MathHomework.translate(%{"*" => "-"}) |> MathHomework.solve_homework |> IO.inspect 44 | 45 | # --- Part Two --- 46 | 47 | MathHomework.read_homework("inputs/input18.txt") |> MathHomework.translate(%{"*" => "-", "+" => "/"}) |> MathHomework.solve_homework |> IO.inspect 48 | -------------------------------------------------------------------------------- /2020/day22.exs: -------------------------------------------------------------------------------- 1 | # --- Day 22: Crab Combat --- 2 | 3 | defmodule Combat do 4 | def score({_, deck}), do: score(deck) 5 | def score(deck) do 6 | Enum.with_index(deck) 7 | |> Enum.map(fn {card, i} -> card * (Enum.count(deck) - i) end) 8 | |> Enum.sum 9 | end 10 | 11 | def play_regular({deck1, []}), do: {:player1, deck1} 12 | def play_regular({[], deck2}), do: {:player2, deck2} 13 | def play_regular(decks), do: play_regular_round(decks) |> play_regular 14 | 15 | def play_recursive({deck1, deck2}), do: play_recursive({deck1, deck2}, MapSet.new) 16 | def play_recursive({deck1, []}, _), do: {:player1, deck1} 17 | def play_recursive({[], deck2}, _), do: {:player2, deck2} 18 | def play_recursive({deck1, deck2}, configurations) do 19 | if MapSet.member?(configurations, identifier(deck1, deck2)) do 20 | {:player1, deck1} 21 | else 22 | play_recursive_round({deck1, deck2}) 23 | |> play_recursive(MapSet.put(configurations, identifier(deck1, deck2))) 24 | end 25 | end 26 | 27 | def play_recursive_round(decks = {[card1|cards1], [card2|cards2]}) do 28 | if Enum.count(cards1) >= card1 && Enum.count(cards2) >= card2 do 29 | {winner, _} = play_recursive({Enum.take(cards1, card1), Enum.take(cards2, card2)}) 30 | case winner do 31 | :player1 -> {cards1 ++ [card1, card2], cards2} 32 | :player2 -> {cards1, cards2 ++ [card2, card1]} 33 | end 34 | else 35 | play_regular_round(decks) 36 | end 37 | end 38 | 39 | def play_regular_round({[card1|cards1], [card2|cards2]}) when card1 > card2, do: {cards1 ++ [card1, card2], cards2} 40 | def play_regular_round({[card1|cards1], [card2|cards2]}), do: {cards1, cards2 ++ [card2, card1]} 41 | 42 | def identifier(deck1, deck2), do: Enum.join(deck1, ",") <> "|" <> Enum.join(deck2, ",") 43 | 44 | def read_decks(file) do 45 | File.read!(file) 46 | |> String.split(~r{\n\n}, trim: true) 47 | |> Enum.map(fn deck -> String.split(deck, ~r{\n}, trim: true) end) 48 | |> Enum.map(&parse_deck/1) 49 | |> List.to_tuple 50 | end 51 | 52 | defp parse_deck([_|cards]), do: Enum.map(cards, &String.to_integer/1) 53 | end 54 | 55 | Combat.read_decks("inputs/input22.txt") |> Combat.play_regular |> Combat.score |> IO.puts 56 | 57 | # --- Part Two --- 58 | 59 | Combat.read_decks("inputs/input22.txt") |> Combat.play_recursive |> Combat.score |> IO.inspect 60 | 61 | -------------------------------------------------------------------------------- /2020/day25.exs: -------------------------------------------------------------------------------- 1 | # --- Day 25: Combo Breaker --- 2 | 3 | defmodule ComboBreaker do 4 | 5 | # Transform a subject number. 6 | # To transform a subject number, start with the value 1. 7 | # Then, a number of times called the loop size, perform the following steps: 8 | # - Set the value to itself multiplied by the subject number. 9 | # - Set the value to the remainder after dividing the value by 20201227. 10 | def transform_subject_number(number, loop_size), do: transform_subject_number(1, number, loop_size) 11 | def transform_subject_number(value, _, 0), do: value 12 | def transform_subject_number(value, number, loop_size) do 13 | transform_subject_step(value, number) 14 | |> transform_subject_number(number, loop_size - 1) 15 | end 16 | 17 | def transform_subject_step(value, number), do: value * number |> rem(20201227) 18 | 19 | # The cryptographic handshake works like this: 20 | # - The card transforms the subject number of 7 according to the card's secret loop size. The result is called the card's public key. 21 | # - The door transforms the subject number of 7 according to the door's secret loop size. The result is called the door's public key. 22 | # - The card and door use the wireless RFID signal to transmit the two public keys (your puzzle input) to the other device. 23 | # Now, the card has the door's public key, and the door has the card's public key. 24 | # - Because you can eavesdrop on the signal, you have both public keys, but neither device's loop size. 25 | # - The card transforms the subject number of the door's public key according to the card's loop size. The result is the encryption key. 26 | # - The door transforms the subject number of the card's public key according to the door's loop size. The result is the same encryption key as the card calculated. 27 | def workout_loop_size(public_key), do: workout_loop_size(7, 1, public_key) 28 | def workout_loop_size(public_key, loop_size, public_key), do: loop_size 29 | def workout_loop_size(value, loop_size, public_key) do 30 | transform_subject_step(value, 7) 31 | |> workout_loop_size(loop_size + 1, public_key) 32 | end 33 | end 34 | 35 | # Input 36 | # 17773298 37 | # 15530095 38 | # 39 | card_public_key = 17773298 40 | door_public_key = 15530095 41 | card_loop_size = ComboBreaker.workout_loop_size(card_public_key) 42 | door_loop_size = ComboBreaker.workout_loop_size(door_public_key) 43 | 44 | ComboBreaker.transform_subject_number(door_public_key, card_loop_size) |> IO.puts 45 | ComboBreaker.transform_subject_number(card_public_key, door_loop_size) |> IO.puts 46 | -------------------------------------------------------------------------------- /2020/inputs/input01.txt: -------------------------------------------------------------------------------- 1 | 1993 2 | 1715 3 | 1997 4 | 1666 5 | 1676 6 | 1830 7 | 1203 8 | 1800 9 | 1125 10 | 1191 11 | 1902 12 | 1972 13 | 1471 14 | 1137 15 | 2003 16 | 1250 17 | 1548 18 | 1070 19 | 1152 20 | 2004 21 | 1127 22 | 1111 23 | 1898 24 | 1848 25 | 1934 26 | 1236 27 | 1704 28 | 1950 29 | 1387 30 | 1713 31 | 1214 32 | 1266 33 | 1114 34 | 1089 35 | 1677 36 | 1207 37 | 1341 38 | 1689 39 | 1772 40 | 1901 41 | 1932 42 | 1645 43 | 1285 44 | 1884 45 | 883 46 | 1291 47 | 1543 48 | 1455 49 | 1213 50 | 1088 51 | 1784 52 | 1506 53 | 1879 54 | 1811 55 | 1880 56 | 994 57 | 1021 58 | 1585 59 | 1662 60 | 1683 61 | 1071 62 | 1643 63 | 1754 64 | 1389 65 | 1124 66 | 1820 67 | 1168 68 | 1875 69 | 1017 70 | 1180 71 | 1375 72 | 1359 73 | 1311 74 | 1357 75 | 1501 76 | 1719 77 | 1584 78 | 1609 79 | 1977 80 | 1786 81 | 1232 82 | 1263 83 | 1748 84 | 1664 85 | 1693 86 | 1766 87 | 1598 88 | 1053 89 | 1277 90 | 1466 91 | 1877 92 | 1844 93 | 1829 94 | 1165 95 | 1606 96 | 1298 97 | 1963 98 | 1873 99 | 1911 100 | 1729 101 | 1418 102 | 1372 103 | 1777 104 | 1371 105 | 1588 106 | 1329 107 | 1029 108 | 1931 109 | 1115 110 | 1810 111 | 1595 112 | 1237 113 | 1282 114 | 1838 115 | 1642 116 | 1937 117 | 1343 118 | 1578 119 | 1425 120 | 1814 121 | 1690 122 | 1129 123 | 1321 124 | 1174 125 | 1863 126 | 1405 127 | 1066 128 | 1220 129 | 1780 130 | 1410 131 | 1156 132 | 1991 133 | 1568 134 | 1368 135 | 99 136 | 1750 137 | 1280 138 | 1400 139 | 1601 140 | 1804 141 | 1363 142 | 1613 143 | 1252 144 | 1434 145 | 1094 146 | 1867 147 | 1542 148 | 1093 149 | 1926 150 | 1251 151 | 1348 152 | 689 153 | 1441 154 | 1913 155 | 1969 156 | 1409 157 | 1201 158 | 1459 159 | 1110 160 | 1452 161 | 1051 162 | 1860 163 | 1346 164 | 1537 165 | 1060 166 | 1182 167 | 1386 168 | 1141 169 | 1184 170 | 1989 171 | 1852 172 | 1097 173 | 1135 174 | 1078 175 | 1587 176 | 1984 177 | 1970 178 | 1259 179 | 1281 180 | 1092 181 | 1294 182 | 1233 183 | 1186 184 | 1555 185 | 1755 186 | 1886 187 | 1030 188 | 1706 189 | 1313 190 | 1481 191 | 1998 192 | 1181 193 | 1244 194 | 1269 195 | 1684 196 | 1798 197 | 1023 198 | 1960 199 | 1050 200 | 1293 -------------------------------------------------------------------------------- /2020/inputs/input10.txt: -------------------------------------------------------------------------------- 1 | 115 2 | 134 3 | 121 4 | 184 5 | 78 6 | 84 7 | 77 8 | 159 9 | 133 10 | 90 11 | 71 12 | 185 13 | 152 14 | 165 15 | 39 16 | 64 17 | 85 18 | 50 19 | 20 20 | 75 21 | 2 22 | 120 23 | 137 24 | 164 25 | 101 26 | 56 27 | 153 28 | 63 29 | 70 30 | 10 31 | 72 32 | 37 33 | 86 34 | 27 35 | 166 36 | 186 37 | 154 38 | 131 39 | 1 40 | 122 41 | 95 42 | 14 43 | 119 44 | 3 45 | 99 46 | 172 47 | 111 48 | 142 49 | 26 50 | 82 51 | 8 52 | 31 53 | 53 54 | 28 55 | 139 56 | 110 57 | 138 58 | 175 59 | 108 60 | 145 61 | 58 62 | 76 63 | 7 64 | 23 65 | 83 66 | 49 67 | 132 68 | 57 69 | 40 70 | 48 71 | 102 72 | 11 73 | 105 74 | 146 75 | 149 76 | 66 77 | 38 78 | 155 79 | 109 80 | 128 81 | 181 82 | 43 83 | 44 84 | 94 85 | 4 86 | 169 87 | 89 88 | 96 89 | 60 90 | 69 91 | 9 92 | 163 93 | 116 94 | 45 95 | 59 96 | 15 97 | 178 98 | 34 99 | 114 100 | 17 101 | 16 102 | 79 103 | 91 104 | 100 105 | 162 106 | 125 107 | 156 108 | 65 -------------------------------------------------------------------------------- /2020/inputs/input13.txt: -------------------------------------------------------------------------------- 1 | 1000507 2 | 29,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,37,x,x,x,x,x,631,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,13,19,x,x,x,23,x,x,x,x,x,x,x,383,x,x,x,x,x,x,x,x,x,41,x,x,x,x,x,x,17 -------------------------------------------------------------------------------- /2020/inputs/input17.txt: -------------------------------------------------------------------------------- 1 | .##.##.. 2 | ..###.## 3 | .##....# 4 | ###..##. 5 | #.###.## 6 | .#.#..#. 7 | .......# 8 | .#..#..# -------------------------------------------------------------------------------- /2020/inputs/input22.txt: -------------------------------------------------------------------------------- 1 | Player 1: 2 | 18 3 | 50 4 | 9 5 | 4 6 | 25 7 | 37 8 | 39 9 | 40 10 | 29 11 | 6 12 | 41 13 | 28 14 | 3 15 | 11 16 | 31 17 | 8 18 | 1 19 | 38 20 | 33 21 | 30 22 | 42 23 | 15 24 | 26 25 | 36 26 | 43 27 | 28 | Player 2: 29 | 32 30 | 44 31 | 19 32 | 47 33 | 12 34 | 48 35 | 14 36 | 2 37 | 13 38 | 10 39 | 35 40 | 45 41 | 34 42 | 7 43 | 5 44 | 17 45 | 46 46 | 21 47 | 24 48 | 49 49 | 16 50 | 22 51 | 20 52 | 27 53 | 23 -------------------------------------------------------------------------------- /2021/README.md: -------------------------------------------------------------------------------- 1 | ### Advent of Code 2021 2 | 3 | _Hyper_ Polyglot challenge! This year I tried to do every problem using a different language, without repeating any of the languages I used in [my previous polyglot challenge in 2019](https://github.com/rosa/advent-of-code/tree/main/2019). Fun learning experience or pointless self-inflicted brain damage? The line that separates both is too thin. 4 | 5 | 6 | - Day 1 in [Bash](https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html) 7 | - Day 2 in [Fortran](https://fortran-lang.org/) 8 | - Day 3 in [Wolfram Language](https://www.wolfram.com/language/) 9 | - Day 4 in [Objective-C](https://en.wikipedia.org/wiki/Objective-C) 10 | - Day 5 in [D](https://dlang.org/) 11 | - Day 6 in [Pascal](https://en.wikipedia.org/wiki/Pascal_(programming_language)) 12 | - Day 7 in [Clojure](https://clojure.org/) 13 | - Day 8 in [MoonScript](https://moonscript.org/) 14 | - Day 9 in [ooc](https://ooc-lang.org/) 15 | - Day 10 in [Ada](https://en.wikipedia.org/wiki/Ada_(programming_language)) 16 | - Day 11 in [Pyret](https://www.pyret.org/) 17 | - Day 12 in [Racket](https://racket-lang.org/) 18 | - Day 13 in [Octave](https://www.gnu.org/software/octave/index) 19 | - Day 14 in [V](https://vlang.io/) 20 | - Day 15 in [Maxima](https://maxima.sourceforge.io/) 21 | - Day 16 in [CoffeeScript](https://coffeescript.org/) 22 | - Day 17 in [Pike](https://pike.lysator.liu.se/) 23 | - Day 18 in [ABS](https://www.abs-lang.org/) 24 | - Day 19 in [Haxe](https://haxe.org/) 25 | - Day 20 in [Wren](https://wren.io/) 26 | - Day 21 in [Odin](https://odin-lang.org/) 27 | - Day 22 in [Pony](https://www.ponylang.io/) 28 | - Day 23 in [Dictu](https://dictu-lang.com/) 29 | - Day 24 in [Groovy](https://groovy-lang.org/) 30 | - Day 25 in [Gravity](http://gravity-lang.org/) 31 | 32 | You can find the problem statements in [the Advent of Code site for 2021](https://adventofcode.com/2021). 33 | 34 | If you liked this, make sure to check [Alfredo's polyglot challenge](https://github.com/abeaumont/competitive-programming/blob/master/advent-of-code/2021/README.md) as well! 35 | -------------------------------------------------------------------------------- /2021/day01.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # --- Day 1: Sonar Sweep --- 3 | 4 | increases_count=0 5 | window_increases_count=0 6 | previous="" 7 | window=() 8 | 9 | while IFS= read -r line 10 | do 11 | if ! [[ -z "$previous" ]] && (($previous < $line)) 12 | then 13 | ((increases_count++)) 14 | fi 15 | previous=$line 16 | 17 | if ((${#window[@]} == 3)) 18 | then 19 | if (($line > ${window[0]})) 20 | then 21 | ((window_increases_count++)) 22 | fi 23 | 24 | window=( "${window[@]:1}" ) 25 | fi 26 | window+=( "$line" ) 27 | 28 | done < "$1" 29 | 30 | echo "$increases_count" 31 | echo "$window_increases_count" 32 | 33 | # --- Part Two --- 34 | # 199 A 35 | # 200 A B 36 | # 208 A B C 37 | # 210 B C D 38 | # 200 E C D 39 | # 207 E F D 40 | # 240 E F G 41 | # 269 F G H 42 | # 260 G H 43 | # 263 H 44 | 45 | # This can be simplified as: 46 | # 199 A1 47 | # 200 A2 B1 48 | # 208 A3 B2 C1 49 | # 210 B3 C2 D1 50 | # 200 E1 C3 D2 51 | # 207 E2 F1 D3 52 | # 240 E3 F2 G1 53 | # 269 F3 G2 H1 54 | # 260 G3 H2 55 | # 263 H3 56 | 57 | # A1 + A2 + A3 < B1 + B2 + B3 <=> A1 + A2 + A3 - B1 - B2 - B3 < 0 <=> A1 - B3 < 0 <=> B3 - A1 > 0 58 | # So, for each line, if (value at line in position + 3 - value at line > 0) 59 | 60 | # ./day01.sh inputs/input01.txt 61 | # 1475 62 | # 1516 63 | -------------------------------------------------------------------------------- /2021/day02.f08: -------------------------------------------------------------------------------- 1 | program day02AoC 2 | 3 | ! --- Day 2: Dive! --- 4 | implicit none 5 | 6 | integer :: value, horizontal, depth, aim 7 | character(len=32) :: filename 8 | character(len=8) :: instruction 9 | 10 | value = 0 11 | horizontal = 0 12 | depth = 0 13 | aim = 0 14 | 15 | call get_command_argument(1, filename) 16 | open (unit=42,file=filename) 17 | 18 | 18 read (42,*,end=30) instruction, value 19 | 20 | if (instruction == "forward") then 21 | horizontal = horizontal + value 22 | depth = depth + value*aim 23 | else if (instruction == "up") then 24 | aim = aim - value 25 | else if (instruction == "down") then 26 | aim = aim + value 27 | end if 28 | go to 18 29 | 30 | 30 close(42) 31 | print *, horizontal * aim 32 | print *, horizontal * depth 33 | 34 | end program day02AoC 35 | 36 | 37 | ! gfortran day02.f08 -o day02 38 | ! ./day02 inputs/input02.txt 39 | ! 1714680 40 | ! 1963088820 41 | -------------------------------------------------------------------------------- /2021/day03.wls: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env wolframscript 2 | 3 | (*--- Day 3: Binary Diagnostic ---*) 4 | 5 | (* Functions *) 6 | FlipBits[0] := 1 7 | FlipBits[n_] := 2^BitLength[n] + BitNot[n] /; n > 0 8 | MSBPosition[list_] := BitLength[Max[list]] - 1 9 | 10 | MostCommonBit[report_, p_] := Max[Commonest[BitGet[#, p] & /@ report]] 11 | LeastCommonBit[report_, p_] := FlipBits[MostCommonBit[report, p]] 12 | 13 | FilterByBitCriteria[list_, c_, p_] := Select[list, BitGet[#, p] == c[list, p] &] 14 | FindByBitCriteria[list_, c_, p_] := First[list] /; Length[list] == 1 15 | FindByBitCriteria[list_, c_, p_] := FindByBitCriteria[FilterByBitCriteria[list, c, p], c, p - 1] /; Length[list] > 1 16 | 17 | GammaRate[report_] := FromDigits[MostCommonBit[report, #] & /@ Reverse[Range[0, MSBPosition[report]]], 2] 18 | OxygenGeneratorRating[report_]:= FindByBitCriteria[report, MostCommonBit, MSBPosition[report]] 19 | CO2ScrubberRating[report_]:= FindByBitCriteria[report, LeastCommonBit, MSBPosition[report]] 20 | 21 | (* Invoking functions *) 22 | filename = $ScriptCommandLine[[2]] 23 | report = FromDigits[#,2] & /@ ReadList[filename, String] 24 | 25 | gamma = GammaRate[report] 26 | epsilon = FlipBits[gamma] 27 | Print[gamma*epsilon] 28 | 29 | oxygen = OxygenGeneratorRating[report] 30 | co2 = CO2ScrubberRating[report] 31 | Print[oxygen*co2] 32 | 33 | (* 34 | ./day03.wls "inputs/input03.txt" 35 | 3969000 36 | 4267809 37 | *) 38 | -------------------------------------------------------------------------------- /2021/day05.d: -------------------------------------------------------------------------------- 1 | // --- Day 5: Hydrothermal Venture --- 2 | 3 | import std.stdio, std.array, std.regex, std.algorithm, std.conv, std.range, std.container; 4 | 5 | struct Segment { 6 | int x1, y1, x2, y2; 7 | 8 | void init(int[] coords) { 9 | assert(coords.length == 4); 10 | x1 = coords[0]; 11 | y1 = coords[1]; 12 | x2 = coords[2]; 13 | y2 = coords[3]; 14 | } 15 | 16 | bool isVertical() { 17 | return x1 == x2; 18 | } 19 | 20 | bool isHorizontal() { 21 | return y1 == y2; 22 | } 23 | 24 | auto verticalRange() { 25 | int step = (y1 <= y2) ? 1 : -1; 26 | return iota(y1, y2 + step, step); 27 | } 28 | 29 | auto horizontalRange() { 30 | int step = (x1 <= x2) ? 1 : -1; 31 | return iota(x1, x2 + step, step); 32 | } 33 | } 34 | 35 | struct Floor { 36 | int[][] grid = new int[][](1000, 1000); 37 | bool diagonals = false; 38 | 39 | void addSegment(Segment s) { 40 | if (s.isHorizontal()) { 41 | foreach (i; s.horizontalRange()) { 42 | grid[s.y1][i]++; 43 | } 44 | } else if (s.isVertical()) { 45 | foreach (i; s.verticalRange()) { 46 | grid[i][s.x1]++; 47 | } 48 | } else if (diagonals) { 49 | foreach (i, j; zip(s.verticalRange(), s.horizontalRange())) { 50 | grid[i][j]++; 51 | } 52 | } 53 | } 54 | 55 | ulong overlaps() { 56 | return grid.map!(r => overlaps(r)).array.reduce!("a + b"); 57 | } 58 | 59 | ulong overlaps(int[] row) { 60 | return row.count!("a > 1"); 61 | } 62 | } 63 | 64 | void main(string[] args) { 65 | Floor floor1, floor2 = { diagonals:true }; 66 | 67 | foreach (line; args[1].File.byLine) { 68 | // Line of vents: 0,9 -> 5,9 69 | auto vents = line.matchFirst(`(\d+),(\d+) -> (\d+),(\d+)`); 70 | vents.popFront(); 71 | 72 | Segment s; 73 | s.init(vents.map!(s => s.to!int).array); 74 | 75 | floor1.addSegment(s); 76 | floor2.addSegment(s); 77 | } 78 | 79 | writeln(floor1.overlaps()); 80 | writeln(floor2.overlaps()); 81 | } 82 | 83 | // dmd day05.d 84 | // ./day05 inputs/input05.txt 85 | // 7085 86 | // 20271 87 | -------------------------------------------------------------------------------- /2021/day06.pas: -------------------------------------------------------------------------------- 1 | // --- Day 6: Lanternfish --- 2 | 3 | program Day06; {$MODE OBJFPC} {$COPERATORS ON} 4 | 5 | uses 6 | Sysutils, 7 | StrUtils, 8 | Types; 9 | 10 | const 11 | FILE_NAME = 'inputs/input06.txt'; 12 | 13 | type 14 | FishCounts = Array[0..8] of Int64; 15 | 16 | var 17 | fish: TIntegerDynArray; 18 | counts: FishCounts; 19 | i, f: Integer; 20 | 21 | 22 | function StartingFish(filename: String): TIntegerDynArray; 23 | var 24 | line: AnsiString; 25 | input: TextFile; 26 | stringFish: TStringDynArray; 27 | integerFish: TIntegerDynArray; 28 | i: Integer; 29 | 30 | begin 31 | Assign (input,filename); 32 | Reset (input); 33 | line := ''; 34 | while not Eof(input) and (line<>'\n') do 35 | Readln (input,line); 36 | Close (input); 37 | 38 | stringFish := SplitString (line,','); 39 | SetLength (integerFish,Length(stringFish)); 40 | 41 | for i := 0 to Length(stringFish) - 1 do 42 | integerFish[i] := StrToInt(stringFish[i]); 43 | 44 | StartingFish := integerFish; 45 | end; 46 | 47 | 48 | procedure SimulateDay(var current: FishCounts); 49 | var 50 | next: FishCounts; 51 | i: Integer; 52 | 53 | begin 54 | for i := 0 to Length(current) - 1 do 55 | next[i] := current[(i + 1) mod Length (current)]; 56 | next[6] := next[6] + next[8]; 57 | 58 | current := next; 59 | end; 60 | 61 | function Simulate(current: FishCounts; days: Integer): Int64; 62 | var 63 | i, sum: Int64; 64 | 65 | begin 66 | for i := 1 to days do 67 | SimulateDay (current); 68 | 69 | sum := 0; 70 | for i in current do 71 | sum := sum + i; 72 | 73 | Simulate := sum; 74 | end; 75 | 76 | 77 | begin 78 | for i := 0 to Length(counts) - 1 do 79 | counts[i] := 0; 80 | 81 | fish := StartingFish (FILE_NAME); 82 | for f in fish do 83 | counts[f] := counts[f] + 1; 84 | 85 | writeln (Simulate (counts, 80)); 86 | writeln (Simulate (counts, 256)); 87 | end. 88 | 89 | // fpc day06.pas 90 | // ./day06 91 | // 354564 92 | // 1609058859115 93 | -------------------------------------------------------------------------------- /2021/day07.clj: -------------------------------------------------------------------------------- 1 | ; --- Day 7: The Treachery of Whales --- 2 | 3 | (require '[clojure.string :as str]) 4 | 5 | (defn int-range [start end] 6 | (map int (range start end))) 7 | 8 | (defn find-min-fuel [fuel-fn crabs] 9 | (->> 10 | (int-range 0 (apply max crabs)) 11 | (map #(fuel-fn crabs %)) 12 | (apply min))) 13 | 14 | (defn distances [crabs position] 15 | (map #(Math/abs (- % position)) crabs)) 16 | 17 | (defn fuel-constant [crabs position] 18 | (->> 19 | (distances crabs position) 20 | (reduce +))) 21 | 22 | (defn fuel-linear [crabs position] 23 | (->> 24 | (distances crabs position) 25 | (map #(int-range 1 (+ % 1))) 26 | (map #(reduce + %)) 27 | (reduce +))) 28 | 29 | (map #(map int (range 1 (+ % 1))) distances) 30 | 31 | (defn crab-positions [string] 32 | (->> 33 | (clojure.string/split string #",") 34 | (map #(Integer/parseInt %)))) 35 | 36 | (def crabs 37 | (->> 38 | (slurp "inputs/input07.txt") 39 | str/trim 40 | crab-positions)) 41 | 42 | (->> crabs (find-min-fuel fuel-constant) println) 43 | (->> crabs (find-min-fuel fuel-linear) println) 44 | 45 | ; user=> (load-file "day07.clj") 46 | ; 356179 47 | ; 99788435 48 | ; nil 49 | -------------------------------------------------------------------------------- /2021/day12.rkt: -------------------------------------------------------------------------------- 1 | ; --- Day 12: Passage Pathing --- 2 | 3 | #lang racket 4 | 5 | (define (add-connection graph from to) 6 | (let ([caves (hash-ref graph from '())]) 7 | (if (member to caves) graph 8 | (add-connection (hash-set graph from (cons to caves)) to from)))) 9 | 10 | (define (build-caves-graph lines graph) 11 | (if (null? lines) graph 12 | (let ([connection (string-split (car lines) "-")]) 13 | (build-caves-graph (cdr lines) (add-connection graph (car connection) (car (cdr connection))))))) 14 | 15 | (define (uppercase? string) 16 | (string=? (string-upcase string) string)) 17 | 18 | (define (valid-with-no-small-cave-repeat? cave path) 19 | (or (uppercase? cave) (not (member cave path)))) 20 | 21 | (define (valid-with-one-small-cave-repeat? cave path) 22 | (or 23 | (valid-with-no-small-cave-repeat? cave path) 24 | (and (not (member cave '("start" "end"))) 25 | (not (check-duplicates (filter-not uppercase? path)))))) 26 | 27 | (define (next-caves graph path predicate) 28 | (let ([caves (hash-ref graph (car path) '())]) 29 | (filter (λ (cave) (predicate cave path)) caves))) 30 | 31 | (define (continue-paths graph path predicate) 32 | (map (λ (cave) (cons cave path)) (next-caves graph path predicate))) 33 | 34 | (define (all-paths graph in-progress completed predicate) 35 | (if (null? in-progress) completed 36 | (let ([path (car in-progress)]) 37 | (if (equal? (car path) "end") 38 | (all-paths graph (cdr in-progress) (cons (reverse path) completed) predicate) 39 | (all-paths graph (append (continue-paths graph path predicate) (cdr in-progress)) completed predicate))))) 40 | 41 | 42 | (define input 43 | (string-split 44 | (string-trim 45 | (port->string 46 | (open-input-file "inputs/input12.txt") 47 | #:close? #t)) 48 | "\n")) 49 | 50 | (define graph (build-caves-graph input (make-immutable-hash))) 51 | (display (length (all-paths graph '(("start")) '() valid-with-no-small-cave-repeat?))) 52 | (newline) 53 | (display (length (all-paths graph '(("start")) '() valid-with-one-small-cave-repeat?))) 54 | (newline) 55 | 56 | ; racket day12.rkt 57 | ; 5920 58 | ; 155477 59 | -------------------------------------------------------------------------------- /2021/day13.m: -------------------------------------------------------------------------------- 1 | % --- Day 13: Transparent Origami --- 2 | 3 | 1; 4 | 5 | function parsedinput = parseinput (path) 6 | fid = fopen (path); 7 | 8 | coords = []; 9 | folds = []; 10 | 11 | do 12 | line = fgetl (fid); 13 | 14 | if (line != -1 && length(line) > 0) 15 | tokens = regexp(line, 'fold along (y|x)=(\d+)', 'tokens'); 16 | if (isempty(tokens)) 17 | c = strsplit(line, ","); 18 | coords = [coords; [str2num(c{1,1}), str2num(c{1,2})]]; 19 | else 20 | folds = [folds; [tokens{1,1}]]; 21 | endif 22 | endif 23 | until (line == -1) 24 | 25 | fclose (fid); 26 | 27 | parsedinput = {coords, folds}; 28 | endfunction 29 | 30 | function padedup = padup (mat, tosize) 31 | padedup = [zeros(tosize(1) - size(mat)(1), tosize(2)); mat]; 32 | endfunction 33 | 34 | function folded = foldup (paper, y) 35 | top = paper(1:y,:); 36 | bottom = flipud(paper(y+2:end,:)); 37 | folded = top + padup(bottom, size(top)); 38 | endfunction 39 | 40 | function folded = foldleft (paper, x) 41 | % Transpose and fold up 42 | folded = foldup (paper', x); 43 | folded = folded'; 44 | endfunction 45 | 46 | function folded = fold (paper, type, axis) 47 | switch (type) 48 | case "x" 49 | folded = foldleft (paper, axis); 50 | case "y" 51 | folded = foldup (paper, axis); 52 | endswitch 53 | endfunction 54 | 55 | parsedinput = parseinput ("inputs/input13.txt"); 56 | coords = parsedinput{1,1}; 57 | folds = parsedinput{1,2}; 58 | 59 | paper = zeros (max (coords(:,2)) + 1, max (coords(:,1)) + 1); 60 | for i = 1:length(coords) 61 | paper(coords(i,2) + 1, coords(i,1) + 1) = 1; 62 | endfor 63 | 64 | % First fold: 65 | folded = fold (paper, folds{1,1}, str2num (folds{1,2})); 66 | display (nnz (folded)); 67 | 68 | % Then all remaining folds: 69 | for i = 2:length(folds) 70 | folded = fold (folded, folds{i,1}, str2num (folds{i,2})); 71 | end 72 | 73 | % This displays a black and white image with the code 74 | imshow(folded) 75 | 76 | % octave day13.m 77 | % 17 78 | % ECFHLHZF 79 | -------------------------------------------------------------------------------- /2021/day15.max: -------------------------------------------------------------------------------- 1 | /* --- Day 15: Chiton --- */ 2 | 3 | load(graphs)$ 4 | 5 | parse_input(name) := block( 6 | [lines: [ ], file: openr(name), line], 7 | while stringp(line: readline(file)) do lines: endcons(map(eval_string, charlist(line)) , lines), 8 | close(file), 9 | lines 10 | )$ 11 | 12 | build_graph(cave_map) := block( 13 | [n: length(cave_map), m: length(cave_map[1]), edges: []], 14 | for i:1 thru n do for j:1 thru m do block( 15 | [vertex: (i-1)*m + (j-1)], 16 | if i > 1 then edges: cons([[vertex, vertex - m], cave_map[i-1][j]], edges), 17 | if j > 1 then edges: cons([[vertex, vertex - 1], cave_map[i][j-1]], edges), 18 | if i < n then edges: cons([[vertex, vertex + m], cave_map[i+1][j]], edges), 19 | if j < m then edges: cons([[vertex, vertex + 1], cave_map[i][j+1]], edges) 20 | ), 21 | create_graph(n*m, edges, directed) 22 | )$ 23 | 24 | increase_risk(cave_map) := map(lambda([list], map(lambda([risk], if risk = 9 then 1 else risk + 1), list)), cave_map)$ 25 | 26 | expand_right(cave_map) := block ( 27 | [expansion: cave_map], 28 | for i:1 thru 4 do ( 29 | expansion: increase_risk(expansion), 30 | cave_map: map(lambda([l1, l2], append(l1, l2)), cave_map, expansion) 31 | ), 32 | cave_map 33 | )$ 34 | 35 | expand_downwards(cave_map) := block( 36 | [expansion: cave_map], 37 | for i:1 thru 4 do ( 38 | expansion: increase_risk(expansion), 39 | cave_map: append(cave_map, expansion) 40 | ), 41 | cave_map 42 | )$ 43 | 44 | expand(cave_map) := expand_downwards(expand_right(cave_map))$ 45 | 46 | /* Part one */ 47 | cave_map: parse_input("inputs/input15.txt")$ 48 | graph: build_graph(cave_map); 49 | path: shortest_weighted_path(0, graph_order(graph) - 1, graph); 50 | print(first(path))$ 51 | 52 | /* Part two */ 53 | expanded_cave_map: expand(cave_map)$ 54 | graph: build_graph(expanded_cave_map); 55 | path: shortest_weighted_path(0, graph_order(graph) - 1, graph); 56 | print(first(path))$ 57 | 58 | /* 59 | maxima --batch=day15.max 60 | 613 61 | 2899 62 | */ 63 | -------------------------------------------------------------------------------- /2021/day17.pike: -------------------------------------------------------------------------------- 1 | #! /usr/local/bin/pike 2 | 3 | // --- Day 17: Trick Shot --- 4 | 5 | mapping(string:array(int)) parse_area(string data) 6 | { 7 | array area = array_sscanf(data, "target area: x=%d..%d, y=%d..%d"); 8 | return ([ "x":({area[0], area[1]}), "y":({area[2], area[3]}) ]); 9 | } 10 | 11 | bool in_area(mapping(string:array(int)) area, int x, int y) 12 | { 13 | return area["x"][0] <= x && x <= area["x"][1] && area["y"][0] <= y && y <= area["y"][1]; 14 | } 15 | 16 | bool passed_area(mapping(string:array(int)) area, int x, int y) 17 | { 18 | return x > area["x"][1] || y < area["y"][0]; 19 | } 20 | 21 | int highest_point_reached(int velocity, int steps) 22 | { 23 | if (steps <= velocity) 24 | return (2 * velocity - steps + 1) * steps / 2; 25 | else 26 | return velocity * (velocity + 1) / 2; 27 | } 28 | 29 | int fire_probe(mapping(string:int) velocities, mapping(string:array(int)) area) 30 | { 31 | int x, y, steps = 0; 32 | int velocity = velocities["y"]; 33 | 34 | while (true) { 35 | x += velocities["x"]; 36 | y += velocities["y"]; 37 | steps++; 38 | 39 | if (velocities["x"] > 0) 40 | velocities["x"] = velocities["x"] - 1; 41 | velocities["y"] = velocities["y"] - 1; 42 | 43 | if (in_area(area, x, y)) 44 | return highest_point_reached(velocity, steps); 45 | 46 | if (passed_area(area, x, y)) 47 | return -1; 48 | } 49 | } 50 | 51 | int find_best_probe(mapping(string:array(int)) area) 52 | { 53 | int highest, candidate = 0; 54 | 55 | for (int vx = 1; vx <= area["x"][1]; vx++) 56 | for (int vy = area["y"][0]; vy <= area["x"][1]*2; vy++) 57 | { 58 | candidate = fire_probe((["x": vx, "y": vy]), area); 59 | if (candidate > highest) 60 | highest = candidate; 61 | } 62 | 63 | return highest; 64 | } 65 | 66 | int count_all_probes(mapping(string:array(int)) area) 67 | { 68 | int candidate, count = 0; 69 | 70 | for (int vx = 1; vx <= area["x"][1]; vx++) 71 | for (int vy = area["y"][0]; vy <= area["x"][1]*2; vy++) 72 | { 73 | candidate = fire_probe((["x": vx, "y": vy]), area); 74 | if (candidate >= 0) 75 | count++; 76 | } 77 | 78 | return count; 79 | } 80 | 81 | int main(int argc, array(string) argv) 82 | { 83 | Stdio.File file = Stdio.File(argv[1], "r"); 84 | string data = file.read(); 85 | file.close(); 86 | 87 | mapping(string:array(int)) area = parse_area(data); 88 | int highest = find_best_probe(area); 89 | write("%d\n", highest); 90 | 91 | int count = count_all_probes(area); 92 | write("%d\n", count); 93 | 94 | return 0; 95 | } 96 | 97 | // pike day17.pike inputs/input17.txt 98 | // 13203 99 | // 5644 100 | -------------------------------------------------------------------------------- /2021/day21.odin: -------------------------------------------------------------------------------- 1 | package day21 2 | 3 | import "core:fmt" 4 | 5 | play_deterministic :: proc(start: [2]i64, goal: i64) -> ([2]i64, i64) { 6 | current: [2]i64 = start - {1, 1} 7 | scores := [2]i64{0, 0} 8 | count, die: i64 = 0, 0 9 | 10 | for scores[0] < (goal - 1) && scores[1] < (goal - 1) { 11 | for _, i in current { 12 | current[i] += die * 3 + 6 13 | current[i] %= 10 14 | die += 3 15 | die %= 100 16 | count += 3 17 | 18 | scores[i] += current[i] + 1 19 | 20 | if scores[i] >= (goal - 1) { 21 | break 22 | } 23 | } 24 | } 25 | 26 | return scores, count 27 | } 28 | 29 | // Rolling the Dirac dice 3 times can only yield numbers from 30 | // 3 to 9 in total, and not all of them with the same probability 31 | // These are the times each number is yield: 32 | // 3 - 1 time (1 + 1 + 1) 33 | // 4 - 3 times (1 + 1 + 2, 1 + 2 + 1...) 34 | // 5 - 6 times (1 + 2 + 2, 1 + 3 + 1...) 35 | // 6 - 7 times (1 + 2 + 3, 2 + 2 + 2...) 36 | // 7 - 6 times (1 + 3 + 3, 2 + 2 + 3...) 37 | // 8 - 3 times (2 + 3 + 3, 3 + 2 + 3...) 38 | // 9 - 1 time (3 + 3 + 3) 39 | // For a total of 27 (3^2) results 40 | branches := map[i64]i64{3 = 1, 4 = 3, 5 = 6, 6 = 7, 7 = 6, 8 = 3, 9 = 1,} 41 | 42 | play_dirac :: proc(start: [2]i64, goal: i64) -> [2]i64 { 43 | current := start - {1, 1} 44 | return play_dirac_turn(current, goal, 0, {0, 0}, {0, 0}) 45 | } 46 | 47 | play_dirac_turn :: proc(current_position: [2]i64, goal: i64, turn: i64, scores: [2]i64, universes: [2]i64) -> [2]i64 { 48 | next_universes := universes 49 | next_turn := (turn + 1) % 2 50 | 51 | for die, multiplier in branches { 52 | next_position := current_position 53 | next_position[turn] += die 54 | next_position[turn] %= 10 55 | 56 | next_scores := scores 57 | next_scores[turn] += next_position[turn] + 1 58 | if next_scores[turn] >= goal { 59 | next_universes[turn] += multiplier 60 | } else { 61 | continue_playing := play_dirac_turn(next_position, goal, next_turn, next_scores, universes) 62 | next_universes += {multiplier, multiplier} * continue_playing 63 | } 64 | } 65 | 66 | return next_universes 67 | } 68 | 69 | main :: proc() { 70 | scores, count := play_deterministic({5, 9}, 1000) 71 | fmt.println(count * min(scores[0], scores[1])) 72 | 73 | universes := play_dirac({5, 9}, 21) 74 | fmt.println(max(universes[0], universes[1])) 75 | } 76 | 77 | // odin run day21.odin 78 | // 989352 79 | // 430229563871565 80 | -------------------------------------------------------------------------------- /2021/day25.gravity: -------------------------------------------------------------------------------- 1 | // --- Day 25: Sea Cucumber --- 2 | 3 | class Herd { 4 | private var _map; 5 | private var _stopped = true; 6 | 7 | func init(map) { 8 | _map = map; 9 | } 10 | 11 | func print() { 12 | for (var row in _map) { 13 | System.print(row.join("")); 14 | } 15 | System.print("\n"); 16 | } 17 | 18 | func step() { 19 | var movedEast = moveEastFacing(); 20 | var movedSouth = moveSouthFacing(); 21 | return movedEast || movedSouth; 22 | } 23 | 24 | func run() { 25 | var moved = true; 26 | var steps = 0; 27 | repeat { 28 | moved = step(); 29 | // print(); 30 | steps += 1; 31 | print(steps); 32 | } while(moved); 33 | 34 | return steps; 35 | } 36 | 37 | func moveEastFacing() { 38 | var moved = false; 39 | 40 | for (var row in _map) { 41 | var moves = [:]; 42 | for (var j in 0...row.count - 1) { 43 | if (row[j] == ">") { 44 | var k = (j + 1) % row.count; 45 | if (row[k] == ".") { 46 | moves[j] = k; 47 | moved = true; 48 | } 49 | } 50 | } 51 | 52 | for (var from in moves.keys()) { 53 | row[moves[from]] = row[from]; 54 | row[from] = "."; 55 | } 56 | } 57 | 58 | return moved; 59 | } 60 | 61 | func moveSouthFacing() { 62 | var moved = false; 63 | 64 | for (var j in 0..._map[0].count - 1) { 65 | var moves = [:]; 66 | for (var i in 0..._map.count - 1) { 67 | if (_map[i][j] == "v") { 68 | var k = (i + 1) % _map.count; 69 | if (_map[k][j] == ".") { 70 | moves[i] = k; 71 | moved = true; 72 | } 73 | } 74 | } 75 | 76 | for (var from in moves.keys()) { 77 | _map[moves[from]][j] = _map[from][j]; 78 | _map[from][j] = "."; 79 | } 80 | } 81 | 82 | return moved; 83 | } 84 | } 85 | 86 | func readInitialMap(filename) { 87 | return File.read(filename).split("\n").filter(func(s) { 88 | return s.length > 0; 89 | }).map(func(s) { 90 | return s.split(""); 91 | }); 92 | } 93 | 94 | func main() { 95 | var herd = Herd(readInitialMap("inputs/input25.txt")); 96 | var count = herd.run(); 97 | System.print(count); 98 | } 99 | 100 | // ./gravity/gravity day25.gravity 101 | // 474 102 | -------------------------------------------------------------------------------- /2021/inputs/input06.txt: -------------------------------------------------------------------------------- 1 | 3,5,1,2,5,4,1,5,1,2,5,5,1,3,1,5,1,3,2,1,5,1,1,1,2,3,1,3,1,2,1,1,5,1,5,4,5,5,3,3,1,5,1,1,5,5,1,3,5,5,3,2,2,4,1,5,3,4,2,5,4,1,2,2,5,1,1,2,4,4,1,3,1,3,1,1,2,2,1,1,5,1,1,4,4,5,5,1,2,1,4,1,1,4,4,3,4,2,2,3,3,2,1,3,3,2,1,1,1,2,1,4,2,2,1,5,5,3,4,5,5,2,5,2,2,5,3,3,1,2,4,2,1,5,1,1,2,3,5,5,1,1,5,5,1,4,5,3,5,2,3,2,4,3,1,4,2,5,1,3,2,1,1,3,4,2,1,1,1,1,2,1,4,3,1,3,1,2,4,1,2,4,3,2,3,5,5,3,3,1,2,3,4,5,2,4,5,1,1,1,4,5,3,5,3,5,1,1,5,1,5,3,1,2,3,4,1,1,4,1,2,4,1,5,4,1,5,4,2,1,5,2,1,3,5,5,4,5,5,1,1,4,1,2,3,5,3,3,1,1,1,4,3,1,1,4,1,5,3,5,1,4,2,5,1,1,4,4,4,2,5,1,2,5,2,1,3,1,5,1,2,1,1,5,2,4,2,1,3,5,5,4,1,1,1,5,5,2,1,1 2 | -------------------------------------------------------------------------------- /2021/inputs/input11.txt: -------------------------------------------------------------------------------- 1 | 4836484555 2 | 4663841772 3 | 3512484556 4 | 1481547572 5 | 7741183422 6 | 8683222882 7 | 4215244233 8 | 1544712171 9 | 5725855786 10 | 1717382281 11 | -------------------------------------------------------------------------------- /2021/inputs/input12.txt: -------------------------------------------------------------------------------- 1 | end-ry 2 | jf-jb 3 | jf-IO 4 | jb-hz 5 | jo-LM 6 | hw-end 7 | hw-LM 8 | hz-ry 9 | WI-start 10 | LM-start 11 | kd-jf 12 | xi-WI 13 | hw-jb 14 | hz-jf 15 | LM-jb 16 | jb-xi 17 | ry-jf 18 | WI-jb 19 | end-hz 20 | jo-start 21 | WI-jo 22 | xi-ry 23 | xi-LM 24 | xi-hw 25 | jo-xi 26 | WI-jf 27 | -------------------------------------------------------------------------------- /2021/inputs/input14.txt: -------------------------------------------------------------------------------- 1 | SVKVKCCBNHNSOSCCOPOC 2 | 3 | KK -> B 4 | CS -> P 5 | VV -> O 6 | KO -> S 7 | PO -> N 8 | PH -> K 9 | BV -> O 10 | VH -> V 11 | PF -> P 12 | HB -> B 13 | OB -> V 14 | FC -> F 15 | OS -> H 16 | NB -> P 17 | SH -> S 18 | KV -> K 19 | SO -> C 20 | NP -> B 21 | NV -> F 22 | CP -> O 23 | KS -> N 24 | FP -> B 25 | VN -> V 26 | NC -> S 27 | FH -> N 28 | CB -> V 29 | PV -> B 30 | NH -> B 31 | NF -> H 32 | PC -> B 33 | NO -> N 34 | CN -> P 35 | KF -> B 36 | VF -> S 37 | CC -> K 38 | CF -> N 39 | PS -> S 40 | NK -> N 41 | PB -> H 42 | BP -> O 43 | FK -> N 44 | BO -> S 45 | OH -> C 46 | VB -> S 47 | VP -> F 48 | FO -> V 49 | KB -> C 50 | SK -> H 51 | CO -> H 52 | HV -> H 53 | SV -> B 54 | BF -> O 55 | SS -> K 56 | VK -> S 57 | HS -> B 58 | HF -> P 59 | PK -> F 60 | BS -> O 61 | BB -> O 62 | VC -> P 63 | OP -> F 64 | NS -> P 65 | SB -> C 66 | NN -> K 67 | HC -> S 68 | HH -> B 69 | FN -> P 70 | OO -> V 71 | VO -> N 72 | ON -> P 73 | FV -> K 74 | HK -> S 75 | FS -> V 76 | HO -> V 77 | PN -> B 78 | KH -> B 79 | CH -> C 80 | KP -> S 81 | BH -> O 82 | BK -> B 83 | FB -> H 84 | VS -> S 85 | HP -> O 86 | SP -> P 87 | OV -> F 88 | OF -> H 89 | OC -> V 90 | KN -> H 91 | BC -> F 92 | BN -> F 93 | CK -> K 94 | SN -> P 95 | SF -> K 96 | KC -> C 97 | SC -> C 98 | HN -> V 99 | OK -> O 100 | FF -> V 101 | CV -> V 102 | PP -> V 103 | -------------------------------------------------------------------------------- /2021/inputs/input16.txt: -------------------------------------------------------------------------------- 1 | 620D79802F60098803B10E20C3C1007A2EC4C84136F0600BCB8AD0066E200CC7D89D0C4401F87104E094FEA82B0726613C6B692400E14A305802D112239802125FB69FF0015095B9D4ADCEE5B6782005301762200628012E006B80162007B01060A0051801E200528014002A118016802003801E2006100460400C1A001AB3DED1A00063D0E25771189394253A6B2671908020394359B6799529E69600A6A6EB5C2D4C4D764F7F8263805531AA5FE8D3AE33BEC6AB148968D7BFEF2FBD204CA3980250A3C01591EF94E5FF6A2698027A0094599AA471F299EA4FBC9E47277149C35C88E4E3B30043B315B675B6B9FBCCEC0017991D690A5A412E011CA8BC08979FD665298B6445402F97089792D48CF589E00A56FFFDA3EF12CBD24FA200C9002190AE3AC293007A0A41784A600C42485F0E6089805D0CE517E3C493DC900180213D1C5F1988D6802D346F33C840A0804CB9FE1CE006E6000844528570A40010E86B09A32200107321A20164F66BAB5244929AD0FCBC65AF3B4893C9D7C46401A64BA4E00437232D6774D6DEA51CE4DA88041DF0042467DCD28B133BE73C733D8CD703EE005CADF7D15200F32C0129EC4E7EB4605D28A52F2C762BEA010C8B94239AAF3C5523CB271802F3CB12EAC0002FC6B8F2600ACBD15780337939531EAD32B5272A63D5A657880353B005A73744F97D3F4AE277A7DA8803C4989DDBA802459D82BCF7E5CC5ED6242013427A167FC00D500010F8F119A1A8803F0C62DC7D200CAA7E1BC40C7401794C766BB3C58A00845691ADEF875894400C0CFA7CD86CF8F98027600ACA12495BF6FFEF20691ADE96692013E27A3DE197802E00085C6E8F30600010882B18A25880352D6D5712AE97E194E4F71D279803000084C688A71F440188FB0FA2A8803D0AE31C1D200DE25F3AAC7F1BA35802B3BE6D9DF369802F1CB401393F2249F918800829A1B40088A54F25330B134950E0 2 | -------------------------------------------------------------------------------- /2021/inputs/input17.txt: -------------------------------------------------------------------------------- 1 | target area: x=85..145, y=-163..-108 2 | -------------------------------------------------------------------------------- /2021/inputs/input23.txt: -------------------------------------------------------------------------------- 1 | ############# 2 | #...........# 3 | ###D#A#C#D### 4 | #C#A#B#B# 5 | ######### 6 | -------------------------------------------------------------------------------- /2023/day01.go: -------------------------------------------------------------------------------- 1 | // --- Day 1: Trebuchet?! --- 2 | 3 | package main 4 | 5 | import ( 6 | "bufio" 7 | "fmt" 8 | "os" 9 | "strconv" 10 | "strings" 11 | ) 12 | 13 | var Numbers = map[string]int{ 14 | "one": 1, 15 | "two": 2, 16 | "three": 3, 17 | "four": 4, 18 | "five": 5, 19 | "six": 6, 20 | "seven": 7, 21 | "eight": 8, 22 | "nine": 9, 23 | } 24 | 25 | func readInput() []string { 26 | lines := []string{} 27 | scanner := bufio.NewScanner(os.Stdin) 28 | for scanner.Scan() { 29 | line := scanner.Text() 30 | lines = append(lines, line) 31 | } 32 | 33 | return lines 34 | } 35 | 36 | func calibrationValueFromLine(line string, all bool) int { 37 | x, y := numbersFromLine(line, all) 38 | return x * 10 + y 39 | } 40 | 41 | func calibrationValue(lines []string, all bool) int { 42 | sum := 0 43 | 44 | for _, line := range lines { 45 | sum += calibrationValueFromLine(line, all) 46 | } 47 | 48 | return sum 49 | } 50 | 51 | func firstAndLastIndices(line string, substring string) (int, int) { 52 | return strings.Index(line, substring), strings.LastIndex(line, substring) 53 | } 54 | 55 | 56 | func numbersFromLine(line string, all bool) (int, int) { 57 | x, y := 0, 0 58 | min := len(line) 59 | max := -1 60 | 61 | for n, i := range Numbers { 62 | c := strconv.Itoa(i) 63 | first1, last1 := firstAndLastIndices(line, c) 64 | first2, last2 := first1, last1 65 | 66 | if all { 67 | first2, last2 = firstAndLastIndices(line, n) 68 | } 69 | 70 | first := first1 71 | if first < 0 || first2 >= 0 && first2 < first { 72 | first = first2 73 | } 74 | 75 | last := last1 76 | if last < 0 || last2 > last { 77 | last = last2 78 | } 79 | 80 | if first >= 0 && first < min { 81 | min = first 82 | x = i 83 | } 84 | if last >= 0 && last > max { 85 | max = last 86 | y = i 87 | } 88 | } 89 | 90 | return x, y 91 | } 92 | 93 | func main() { 94 | input := readInput() 95 | 96 | // Part One 97 | fmt.Println(calibrationValue(input, false)) 98 | 99 | // Part Two 100 | fmt.Println(calibrationValue(input, true)) 101 | } 102 | -------------------------------------------------------------------------------- /2024/day01.exs: -------------------------------------------------------------------------------- 1 | # --- Day 1: Historian Hysteria --- 2 | 3 | defmodule Locations do 4 | def total_distance({locations1, locations2}) do 5 | Enum.zip_reduce(Enum.sort(locations1), Enum.sort(locations2), 0, fn x, y, acc -> abs(x - y) + acc end) 6 | end 7 | def total_distance(path), do: lines(path) |> total_distance() 8 | 9 | def similarity_score({locations1, locations2}) do 10 | Enum.map(locations1, fn x -> x * Map.get(Enum.frequencies(locations2), x, 0) end) 11 | |> Enum.sum 12 | end 13 | def similarity_score(path), do: lines(path) |> similarity_score() 14 | 15 | defp lines(path) do 16 | File.read!(path) 17 | |> String.split(~r{\n}, trim: true) 18 | |> Enum.map(&parse_line/1) 19 | |> Enum.unzip 20 | end 21 | 22 | defp parse_line(line) do 23 | String.split(line, ~r{\s}, trim: true) 24 | |> Enum.map(&String.to_integer/1) 25 | |> List.to_tuple() 26 | end 27 | end 28 | 29 | Locations.total_distance("inputs/input01.txt") |> IO.puts 30 | 31 | # --- Part Two --- 32 | Locations.similarity_score("inputs/input01.txt") |> IO.puts 33 | -------------------------------------------------------------------------------- /2024/day02.exs: -------------------------------------------------------------------------------- 1 | # --- Day 2: Red-Nosed Reports --- 2 | 3 | defmodule Reports do 4 | def count_safes(file), do: lines(file) |> Enum.count(&safe?/1) 5 | def count_safes_with_dampener(file), do: lines(file) |> Enum.count(&safe_with_dampener?/1) 6 | 7 | defp safe_with_dampener?([a|l]), do: safe?([a|l]) or safe?(l) or safe_with_dampener?([a], l) 8 | defp safe_with_dampener?(l, [a|r]), do: safe?(l ++ r) or safe_with_dampener?(l ++ [a], r) 9 | defp safe_with_dampener?(l, []), do: safe?(l) 10 | 11 | defp safe?([a, b|l]), do: safe?([b|l], a - b) 12 | defp safe?(_, n) when n == 0 or abs(n) > 3, do: false 13 | defp safe?([_], _), do: true 14 | defp safe?([a, b|_], n) when (a - b < 0 and n > 0) or (a - b > 0 and n < 0), do: false 15 | defp safe?([a, b|l], _), do: safe?([b|l], a - b) 16 | 17 | defp lines(path) do 18 | File.read!(path) 19 | |> String.split(~r{\n}, trim: true) 20 | |> Enum.map(&parse_report/1) 21 | end 22 | 23 | defp parse_report(line) do 24 | String.split(line, ~r{\s}, trim: true) 25 | |> Enum.map(&String.to_integer/1) 26 | end 27 | end 28 | 29 | Reports.count_safes("inputs/input02.txt") |> IO.puts 30 | 31 | # --- Part Two --- 32 | Reports.count_safes_with_dampener("inputs/input02.txt") |> IO.puts 33 | -------------------------------------------------------------------------------- /2024/day03.exs: -------------------------------------------------------------------------------- 1 | # --- Day 3: Mull It Over --- 2 | 3 | defmodule Memory do 4 | def extract_all_muls(path), do: input(path) |> scan |> all_muls |> calculate 5 | 6 | def extract_applicable_muls(path), do: input(path) |> scan |> applicable_muls |> calculate 7 | 8 | defp all_muls(region), do: Enum.filter(region, &is_tuple/1) 9 | 10 | defp applicable_muls(region), do: applicable_muls(region, [], :do) 11 | defp applicable_muls([], muls, _), do: muls 12 | defp applicable_muls([:dont|region], muls, _), do: applicable_muls(region, muls, :dont) 13 | defp applicable_muls([:do|region], muls, _), do: applicable_muls(region, muls, :do) 14 | defp applicable_muls([mul|region], muls, :do), do: applicable_muls(region, muls ++ [mul], :do) 15 | defp applicable_muls([_|region], muls, :dont), do: applicable_muls(region, muls, :dont) 16 | 17 | defp calculate(muls), do: Enum.map(muls, fn {x, y} -> x * y end) |> Enum.sum 18 | 19 | defp scan(region) do 20 | Regex.scan(~r/mul\(\d{1,3},\d{1,3}\)|don\'t\(\)|do\(\)/, region) 21 | |> Enum.map(&hd/1) 22 | |> Enum.map(&parse_op/1) 23 | end 24 | 25 | defp parse_op("don't()"), do: :dont 26 | defp parse_op("do()"), do: :do 27 | defp parse_op(mul) do 28 | [_, d1, d2] = Regex.run(~r/mul\((\d{1,3}),(\d{1,3})\)/, mul) 29 | {String.to_integer(d1), String.to_integer(d2)} 30 | end 31 | 32 | defp input(path) do 33 | File.read!(path) 34 | end 35 | end 36 | 37 | Memory.extract_all_muls("inputs/input03.txt") |> IO.puts 38 | 39 | # --- Part Two --- 40 | 41 | Memory.extract_applicable_muls("inputs/input03.txt") |> IO.puts 42 | -------------------------------------------------------------------------------- /2024/day04.exs: -------------------------------------------------------------------------------- 1 | # --- Day 4: Ceres Search --- 2 | 3 | defmodule XmasSearch do 4 | @target [ "X", "M", "A", "S" ] 5 | 6 | def find_xmas(puzzle) do 7 | rows = Enum.to_list(1..length(puzzle) - 2) 8 | columns = Enum.to_list(1..length(hd(puzzle)) - 2) 9 | 10 | Enum.map(rows, fn i -> Enum.map(columns, fn j -> check_xmas(puzzle, i, j) end) |> Enum.sum end) 11 | |> Enum.sum 12 | end 13 | 14 | defp check_xmas(puzzle, i, j) do 15 | case get(puzzle, i, j) do 16 | "A" -> check_ms(puzzle, i, j) 17 | _ -> 0 18 | end 19 | end 20 | 21 | defp check_ms(puzzle, i, j) do 22 | v1 = get(puzzle, i - 1, j - 1) 23 | v2 = get(puzzle, i + 1, j + 1) 24 | v3 = get(puzzle, i - 1, j + 1) 25 | v4 = get(puzzle, i + 1, j - 1) 26 | 27 | if Enum.sort([v1, v2]) == ["M", "S"] and Enum.sort([v3, v4]) == ["M", "S"] do 28 | 1 29 | else 30 | 0 31 | end 32 | end 33 | 34 | def solve(puzzle) do 35 | rows = Enum.to_list(0..length(puzzle) - 1) 36 | columns = Enum.to_list(0..length(hd(puzzle)) - 1) 37 | 38 | Enum.map(rows, fn row -> 39 | compute(puzzle, {1, 0}, {row, 0}, @target) + 40 | compute(puzzle, {1, 1}, {row, 0}, @target) + 41 | compute(puzzle, {-1, 1}, {row, length(hd(puzzle)) - 1}, @target) 42 | end) ++ 43 | Enum.map(columns, fn col -> 44 | compute(puzzle, {0, 1}, {0, col}, @target) end) ++ 45 | Enum.map(tl(columns), fn col -> 46 | compute(puzzle, {1, 1}, {0, col}, @target) 47 | end) ++ 48 | Enum.map(tl(Enum.reverse(columns)), fn col -> 49 | compute(puzzle, {-1, 1}, {0, col}, @target) 50 | end) 51 | |> Enum.sum 52 | end 53 | 54 | def compute(puzzle, increments, start, target) do 55 | check(puzzle, increments, start, target, target, 0) + 56 | check(puzzle, increments, start, Enum.reverse(target), Enum.reverse(target), 0) 57 | end 58 | 59 | def check(puzzle, {x, y}, {i, j}, [], target, total) do 60 | check(puzzle, {x, y}, {i, j}, target, target, total + 1) 61 | end 62 | 63 | def check(puzzle, _, {i, j}, _, _, total) when j >= length(hd(puzzle)) or i >= length(puzzle) or i < 0 or j < 0, do: total 64 | 65 | def check(puzzle, {x, y}, {i, j}, [n|found], [m|target], total) do 66 | case get(puzzle, i, j) do 67 | ^n -> check(puzzle, {x, y}, {i + y, j + x}, found, [m|target], total) 68 | ^m -> check(puzzle, {x, y}, {i + y, j + x}, target, [m|target], total) 69 | _ -> check(puzzle, {x, y}, {i + y, j + x}, [m|target], [m|target], total) 70 | end 71 | end 72 | 73 | defp get(puzzle, i, j), do: Enum.at(puzzle, i) |> Enum.at(j) 74 | 75 | def read(file) do 76 | File.read!(file) 77 | |> String.split(~r{\n}, trim: true) 78 | |> Enum.map(&String.graphemes/1) 79 | end 80 | end 81 | 82 | XmasSearch.read("inputs/input04.txt") |> XmasSearch.solve |> IO.puts 83 | 84 | # --- Part Two --- 85 | 86 | XmasSearch.read("inputs/input04.txt") |> XmasSearch.find_xmas |> IO.puts 87 | -------------------------------------------------------------------------------- /2024/day05.exs: -------------------------------------------------------------------------------- 1 | # --- Day 5: Print Queue --- 2 | 3 | defmodule SafetyUpdates do 4 | def fix_invalid_updates({rules, updates}) do 5 | Enum.filter(updates, fn update -> !valid_update?(update, rules) end) 6 | |> Enum.map(fn update -> fix(update, rules) end) 7 | end 8 | 9 | def valid_updates({rules, updates}), do: Enum.filter(updates, fn update -> valid_update?(update, rules) end) 10 | 11 | def middle_pages_sum(updates), do: Enum.map(updates, &middle_page/1) |> Enum.sum 12 | 13 | def parse_input(file) do 14 | [rules, updates] = read(file) 15 | {parse_rules(rules), parse_updates(updates)} 16 | end 17 | 18 | defp fix(update, rules) do 19 | applicable_rules(update, rules) 20 | |> sort(Map.keys(update)) 21 | end 22 | 23 | defp applicable_rules(update, rules), do: Enum.filter(rules, fn {x, y} -> Map.has_key?(update, x) and Map.has_key?(update, y) end) 24 | 25 | defp sort(rules, update), do: Enum.sort(update, fn n1, n2 -> sorter(n1, n2, rules) end) 26 | 27 | defp sorter(n1, n2, rules) do 28 | rule = Enum.find(rules, fn rule -> rule == {n1, n2} or rule == {n2, n1} end) 29 | 30 | is_nil(rule) or rule == {n1, n2} 31 | end 32 | 33 | defp middle_page(update) when is_map(update) do 34 | middle = map_size(update) / 2 |> trunc 35 | Map.to_list(update) |> Enum.find(fn {_, v} -> v == middle end) |> elem(0) |> String.to_integer 36 | end 37 | 38 | defp middle_page(update) when is_list(update) do 39 | middle = length(update) / 2 |> trunc 40 | Enum.at(update, middle) |> String.to_integer 41 | end 42 | 43 | defp valid_update?(_, []), do: true 44 | defp valid_update?(update, [rule|rules]), do: satisfies_rule?(update, rule) and valid_update?(update, rules) 45 | 46 | defp satisfies_rule?(update, {x, y}) do 47 | v1 = Map.get(update, x) 48 | v2 = Map.get(update, y) 49 | 50 | is_nil(v1) or is_nil(v2) or v1 < v2 51 | end 52 | 53 | defp parse_rules(rules), do: Enum.map(rules, &parse_rule/1) 54 | defp parse_rule(rule), do: String.split(rule, "|") |> List.to_tuple() 55 | 56 | defp parse_updates(updates), do: Enum.map(updates, &parse_update/1) 57 | defp parse_update(update) do 58 | list = String.split(update, ",") 59 | Enum.zip(list, (0..length(list) - 1)) |> Enum.into(%{}) 60 | end 61 | 62 | defp read(file) do 63 | File.read!(file) 64 | |> String.split(~r{\n\n}, trim: true) 65 | |> Enum.map(fn lines -> String.split(lines, ~r{\n}, trim: true) end) 66 | end 67 | end 68 | 69 | SafetyUpdates.parse_input("inputs/input05.txt") |> SafetyUpdates.valid_updates |> SafetyUpdates.middle_pages_sum |> IO.puts 70 | 71 | # --- Part Two --- 72 | 73 | SafetyUpdates.parse_input("inputs/input05.txt") |> SafetyUpdates.fix_invalid_updates |> SafetyUpdates.middle_pages_sum |> IO.puts 74 | 75 | -------------------------------------------------------------------------------- /2024/day06.exs: -------------------------------------------------------------------------------- 1 | # --- Day 6: Guard Gallivant --- 2 | 3 | defmodule GuardRoute do 4 | def simulate({current={i, j, _}, m, n, grid}), do: simulate(current, m, n, grid, %{{i, j} => true}, %{current => true}) 5 | 6 | def simulate(current, m, n, grid, visited, loop) do 7 | next = {i, j, _} = advance(current, grid) 8 | cond do 9 | Map.has_key?(loop, next) -> :infinite_loop 10 | i < 0 or j < 0 or i >= m or j >= n -> map_size(visited) 11 | true -> simulate(next, m, n, grid, Map.put(visited, {i, j}, true), Map.put(loop, next, true)) 12 | end 13 | end 14 | 15 | def find_all_infinite_loops({current, m, n, grid}) do 16 | is = Enum.to_list(0..m - 1) 17 | js = Enum.to_list(0..n - 1) 18 | 19 | Enum.map(is, fn i -> Enum.map(js, fn j -> infinite_loop_score({i, j}, current, m, n, grid) end) |> Enum.sum end) |> Enum.sum 20 | end 21 | 22 | def infinite_loop_score(obstacle, current={ci, cj, _}, m, n, grid) do 23 | cond do 24 | obstacle == {ci, cj} -> 0 25 | Map.has_key?(grid, obstacle) -> 0 26 | simulate({current, m, n, Map.put(grid, obstacle, "#")}) == :infinite_loop -> 1 27 | true -> 0 28 | end 29 | end 30 | 31 | defp advance({i, j, direction}, grid) do 32 | {ni, nj} = move(i, j, direction) 33 | case Map.get(grid, {ni, nj}) do 34 | "#" -> {i, j, turn(direction)} 35 | nil -> {ni, nj, direction} 36 | end 37 | end 38 | 39 | defp move(i, j, "^"), do: { i - 1, j } 40 | defp move(i, j, ">"), do: { i, j + 1 } 41 | defp move(i, j, "v"), do: { i + 1, j } 42 | defp move(i, j, "<"), do: { i, j - 1} 43 | 44 | defp turn("^"), do: ">" 45 | defp turn(">"), do: "v" 46 | defp turn("v"), do: "<" 47 | defp turn("<"), do: "^" 48 | 49 | def read_map(file) do 50 | File.read!(file) 51 | |> String.split(~r{\n}, trim: true) 52 | |> Enum.map(&String.graphemes/1) 53 | |> parse_map() 54 | end 55 | 56 | defp parse_map(lines), do: parse_map(lines, {nil, length(lines), length(hd(lines)), %{}}, 0) 57 | defp parse_map([], map, _), do: map 58 | defp parse_map([row | rows], map, i) do 59 | updated_map = parse_map(row, map, i, 0) 60 | parse_map(rows, updated_map, i + 1) 61 | end 62 | 63 | defp parse_map([], map, _, _), do: map 64 | defp parse_map(["." | row], map, i, j), do: parse_map(row, map, i, j + 1) 65 | defp parse_map(["#" | row], {start, m, n, grid}, i, j) do 66 | parse_map(row, {start, m, n, Map.put(grid, {i, j}, "#")}, i, j + 1) 67 | end 68 | defp parse_map(["^" | row], {nil, m, n, grid}, i, j) do 69 | parse_map(row, {{i, j, "^"}, m, n, grid}, i, j + 1) 70 | end 71 | end 72 | 73 | GuardRoute.read_map("inputs/input06.txt") |> GuardRoute.simulate |> IO.puts 74 | 75 | # --- Part Two --- 76 | 77 | GuardRoute.read_map("inputs/input06.txt") |> GuardRoute.find_all_infinite_loops |> IO.puts 78 | -------------------------------------------------------------------------------- /2024/day07.exs: -------------------------------------------------------------------------------- 1 | # --- Day 7: Bridge Repair --- 2 | 3 | defmodule BridgeOperations do 4 | def total_calibration(equations, operators) do 5 | Enum.filter(equations, &(fixable?(&1, operators))) 6 | |> Enum.map(&(elem(&1, 0))) 7 | |> Enum.sum 8 | end 9 | 10 | defp fixable?({result, [op|operands]}, operators), do: test_operators(result, operands, op, operators) 11 | 12 | defp test_operators(result, [], result, _), do: true 13 | defp test_operators(_, [], _, _), do: false 14 | defp test_operators(result, [op|operands], total, operators) do 15 | Enum.any?(operators, fn operator -> test_operators(result, operands, operator.(total, op), operators) end) 16 | end 17 | 18 | def concat(a, b), do: "#{a}#{b}" |> String.to_integer 19 | 20 | def read_equations(path) do 21 | File.read!(path) 22 | |> String.split(~r{\n}, trim: true) 23 | |> Enum.map(&parse_equation/1) 24 | end 25 | 26 | defp parse_equation([result|operands]), do: {result, operands} 27 | # 3267: 81 40 27 28 | defp parse_equation(line) do 29 | String.split(line, ~r{:?\s}, trim: true) 30 | |> Enum.map(&String.to_integer/1) 31 | |> parse_equation 32 | end 33 | end 34 | 35 | sum = &Kernel.+/2 36 | mul = &Kernel.*/2 37 | con = &BridgeOperations.concat/2 38 | 39 | BridgeOperations.read_equations("./inputs/input07.txt") |> BridgeOperations.total_calibration([sum, mul]) |> IO.inspect 40 | 41 | # --- Part Two --- 42 | BridgeOperations.read_equations("./inputs/input07.txt") |> BridgeOperations.total_calibration([sum, mul, con]) |> IO.inspect 43 | 44 | -------------------------------------------------------------------------------- /2024/day08.exs: -------------------------------------------------------------------------------- 1 | # --- Day 8: Resonant Collinearity --- 2 | 3 | defmodule Antennas do 4 | def antinodes(map = {_, _, antennas}, harmonics \\ false) do 5 | Map.keys(antennas) 6 | |> Enum.map(fn antenna -> antinodes(antenna, map, harmonics) end) 7 | |> List.flatten 8 | |> Enum.into(MapSet.new()) 9 | end 10 | 11 | def antinodes(antenna, {m, n, antennas}, harmonics), do: antinodes(m, n, Map.get(antennas, antenna), harmonics) 12 | def antinodes(m, n, positions, harmonics) do 13 | for x <- positions, y <- positions, x != y, do: antinodes(m, n, x, y, harmonics) 14 | end 15 | 16 | def antinodes(m, n, x, y, false), do: antinodes(m, n, x, y, 1) 17 | def antinodes(m, n, x, y, true), do: antinodes(m, n, x, y, 0, []) 18 | 19 | def antinodes(m, n, {xi, xj}, {yi, yj}, h) do 20 | nx = within_bounds(m, n, {xi + h*(xi - yi), xj + h*(xj - yj)}) 21 | ny = within_bounds(m, n, {yi + h*(yi - xi), yj + h*(yj - xj)}) 22 | 23 | [nx, ny] |> Enum.reject(&is_nil/1) 24 | end 25 | 26 | def antinodes(m, n, x, y, h, antinodes) do 27 | new_set = antinodes(m, n, x, y, h) 28 | if Enum.empty?(new_set), do: antinodes, else: antinodes(m, n, x, y, h + 1, antinodes ++ new_set) 29 | end 30 | 31 | def within_bounds(m, n, {i, j}) do 32 | if i >= 0 and j >= 0 and i < m and j < n, do: {i, j}, else: nil 33 | end 34 | 35 | def read_map(file) do 36 | File.read!(file) 37 | |> String.split(~r{\n}, trim: true) 38 | |> Enum.map(&String.graphemes/1) 39 | |> parse_map() 40 | end 41 | 42 | defp parse_map(lines), do: parse_map(lines, {length(lines), length(hd(lines)), %{}}, 0) 43 | defp parse_map([], map, _), do: map 44 | defp parse_map([row | rows], map, i) do 45 | parse_map(rows, parse_map(row, map, i, 0), i + 1) 46 | end 47 | 48 | defp parse_map([], map, _, _), do: map 49 | defp parse_map(["." | row], map, i, j), do: parse_map(row, map, i, j + 1) 50 | defp parse_map([antenna | row], {m, n, antennas}, i, j) do 51 | parse_map(row, {m, n, update_antennas(antennas, antenna, i, j)}, i, j + 1) 52 | end 53 | defp update_antennas(antennas, antenna, i, j) do 54 | Map.update(antennas, antenna, [{i, j}], fn x -> x ++ [{i, j}] end) 55 | end 56 | end 57 | 58 | Antennas.read_map("inputs/input08.txt") |> Antennas.antinodes() |> MapSet.size() |> IO.puts 59 | 60 | # --- Part Two --- 61 | 62 | Antennas.read_map("inputs/input08.txt") |> Antennas.antinodes(true) |> MapSet.size() |> IO.puts 63 | -------------------------------------------------------------------------------- /2024/day10.exs: -------------------------------------------------------------------------------- 1 | # --- Day 10: Hoof It --- 2 | 3 | defmodule HikingGuide do 4 | def trailheads_score(map) do 5 | trailheads(map) 6 | |> Enum.map(fn trailhead -> find_all_nines(map, trailhead) end) 7 | |> Enum.map(&Enum.uniq/1) 8 | |> Enum.map(&length/1) 9 | |> Enum.sum 10 | end 11 | 12 | def trailheads_rating(map) do 13 | trailheads(map) 14 | |> Enum.map(fn trailhead -> find_all_nines(map, trailhead) end) 15 | |> Enum.map(&length/1) 16 | |> Enum.sum 17 | end 18 | 19 | def find_all_nines(map, trailhead), do: find_all_nines(map, [trailhead], []) 20 | def find_all_nines(_, [], nines), do: nines 21 | def find_all_nines(map, [x|steps], nines) do 22 | v = Map.get(map, x) 23 | next = find_next_steps(map, x, v) 24 | 25 | if v == 8 do 26 | find_all_nines(map, steps, nines ++ next) 27 | else 28 | find_all_nines(map, next ++ steps, nines) 29 | end 30 | end 31 | 32 | def find_next_steps(map, {i, j}, v) do 33 | # Up, right, down, left 34 | [{i-1, j}, {i, j+1}, {i+1, j}, {i, j-1}] 35 | |> Enum.filter(fn x -> Map.get(map, x) == v + 1 end) 36 | end 37 | 38 | def trailheads(map), do: Map.keys(map) |> Enum.filter(fn x -> Map.get(map, x) == 0 end) 39 | 40 | def read_map(file) do 41 | File.read!(file) 42 | |> String.split(~r{\n}, trim: true) 43 | |> Enum.map(&String.graphemes/1) 44 | |> parse_map() 45 | end 46 | 47 | defp parse_map(lines), do: parse_map(lines, %{}, 0) 48 | defp parse_map([], map, _), do: map 49 | defp parse_map([row | rows], map, i) do 50 | updated_map = parse_map(row, map, i, 0) 51 | parse_map(rows, updated_map, i + 1) 52 | end 53 | 54 | defp parse_map([], map, _, _), do: map 55 | defp parse_map(["." | row], map, i, j), do: parse_map(row, map, i, j+1) 56 | defp parse_map([cell | row], map, i, j) do 57 | parse_map(row, Map.put(map, {i, j}, String.to_integer(cell)), i, j + 1) 58 | end 59 | end 60 | 61 | HikingGuide.read_map("inputs/input10.txt") |> HikingGuide.trailheads_score() |> IO.puts 62 | 63 | # --- Part Two --- 64 | 65 | HikingGuide.read_map("inputs/input10.txt") |> HikingGuide.trailheads_rating() |> IO.puts 66 | -------------------------------------------------------------------------------- /2024/day11.exs: -------------------------------------------------------------------------------- 1 | # --- Day 11: Plutonian Pebbles --- 2 | 3 | require Integer 4 | 5 | defmodule Stones do 6 | def size(stones), do: Map.values(stones) |> Enum.sum 7 | 8 | def simulate(stones, 0), do: stones 9 | def simulate(stones, blinks), do: blink(stones) |> simulate(blinks - 1) 10 | 11 | defp blink(stones), do: present(stones) |> blink(stones, %{}) 12 | 13 | defp blink([], _, transformed), do: transformed 14 | defp blink([0|stones], previous, transformed), do: blink(stones, previous, replace(previous, transformed, 0, 1)) 15 | defp blink([n|stones], previous, transformed) do 16 | if even_digits?(n) do 17 | blink(stones, previous, replace(previous, transformed, n, split_digits(n))) 18 | else 19 | blink(stones, previous, replace(previous, transformed, n, n * 2024)) 20 | end 21 | end 22 | 23 | defp present(stones) do 24 | Map.filter(stones, fn {_, v} -> v > 0 end) |> Map.keys() 25 | end 26 | 27 | defp replace(from, into, n, [r1, r2]), do: replace(from, replace(from, into, n, r1), n, r2) 28 | defp replace(from, into, n, r) do 29 | %{^n => v} = from 30 | increment(into, r, v) 31 | end 32 | 33 | defp increment(stones, key, value), do: Map.update(stones, key, value, fn v -> v + value end) 34 | 35 | defp even_digits?(n), do: Integer.to_string(n) |> String.length() |> Integer.is_even() 36 | 37 | defp split_digits(n) do 38 | s = Integer.to_string(n) 39 | String.split_at(s, trunc(String.length(s)/2)) 40 | |> Tuple.to_list() 41 | |> Enum.map(&String.to_integer/1) 42 | end 43 | 44 | def read(file) do 45 | File.read!(file) 46 | |> String.trim() 47 | |> String.split(~r{\s}, trim: true) 48 | |> Enum.map(&String.to_integer/1) 49 | |> Enum.frequencies() 50 | end 51 | end 52 | 53 | Stones.read("inputs/input11.txt") |> Stones.simulate(25) |> Stones.size() |> IO.puts 54 | 55 | # --- Part Two --- 56 | 57 | Stones.read("inputs/input11.txt") |> Stones.simulate(75) |> Stones.size() |> IO.puts 58 | -------------------------------------------------------------------------------- /2024/day13.exs: -------------------------------------------------------------------------------- 1 | # --- Day 12: Garden Groups --- 2 | 3 | defmodule Claw do 4 | alias Claw.Machine 5 | 6 | def all_prizes(machines), do: Enum.map(machines, &Machine.solve/1) |> Enum.sum() 7 | 8 | defp machine([button_a, button_b, prize], extra), do: Machine.new(button_a, button_b, prize, extra) 9 | 10 | def read_machines(file, extra \\ 0) do 11 | File.read!(file) 12 | |> String.split(~r{\n}, trim: true) 13 | |> Enum.chunk_every(3) 14 | |> Enum.map(fn m -> machine(m, extra) end) 15 | end 16 | end 17 | 18 | defmodule Claw.Machine do 19 | defstruct( 20 | button_a: {0, 0}, 21 | button_b: {0, 0}, 22 | prize: {0, 0} 23 | ) 24 | 25 | def new(button_a, button_b, prize, extra) do 26 | %Claw.Machine{ 27 | button_a: parse_button(button_a), 28 | button_b: parse_button(button_b), 29 | prize: parse_prize(prize, extra) 30 | } 31 | end 32 | def solve(machine) do 33 | {a1, a2} = machine.button_a 34 | gcd = Integer.gcd(a1, a2) 35 | m1 = Integer.floor_div(a1, gcd) 36 | m2 = Integer.floor_div(a2, gcd) 37 | 38 | {b1, b2} = machine.button_b 39 | {c1, c2} = machine.prize 40 | 41 | y = (c1*m2 - c2*m1) / (b1*m2 - b2*m1) 42 | x = (c1 - b1*y) / a1 43 | if floor(y) == y and floor(x) == x and y > 0 and x > 0 do 44 | floor(3*x + y) 45 | else 46 | 0 47 | end 48 | end 49 | 50 | defp parse_button(line) do 51 | [_, x, y] = Regex.run(~r/X\+(\d+), Y\+(\d+)/, line) 52 | tuple(x, y) 53 | end 54 | 55 | defp parse_prize(line, extra) do 56 | [_, x, y] = Regex.run(~r/X=(\d+), Y=(\d+)/, line) 57 | {p, q} = tuple(x, y) 58 | {p + extra, q + extra} 59 | end 60 | 61 | defp tuple(x, y), do: {String.to_integer(x), String.to_integer(y)} 62 | end 63 | 64 | Claw.read_machines("inputs/input13.txt") |> Claw.all_prizes() |> IO.puts 65 | 66 | # --- Part Two --- 67 | 68 | Claw.read_machines("inputs/input13.txt", 10000000000000) |> Claw.all_prizes() |> IO.puts 69 | -------------------------------------------------------------------------------- /2024/day18.exs: -------------------------------------------------------------------------------- 1 | # --- Day 18: RAM Run --- 2 | 3 | defmodule Ram do 4 | def first_blocking_byte([byte|bytes], grid, m, n) do 5 | updated_grid = Map.put(grid, byte, "#") 6 | 7 | if shortest_path(updated_grid, m, n) == :no_path do 8 | byte |> Tuple.to_list() |> Enum.join(",") 9 | else 10 | first_blocking_byte(bytes, updated_grid, m, n) 11 | end 12 | end 13 | 14 | def shortest_path(grid, m, n), do: shortest_path(grid, [{0, 0}], {m, n}, %{{0,0} => 0}, %{}) 15 | def shortest_path(_, [v|_], v, distances, _), do: Map.get(distances, v) 16 | def shortest_path(_, [], _, _, _), do: :no_path 17 | def shortest_path(grid, [v|coords], target, distances, path) do 18 | neighbours = neighbours(grid, v, distances, target) 19 | updated_distances = update_map(neighbours, Map.get(distances, v) + 1, distances) 20 | updated_path = update_map(neighbours, v, path) 21 | 22 | shortest_path(grid, coords ++ neighbours, target, updated_distances, updated_path) 23 | end 24 | 25 | defp neighbours(grid, {x, y}, distances, dims) do 26 | [{x-1, y}, {x, y+1}, {x+1, y}, {x, y-1}] 27 | |> Enum.filter(fn u -> !Map.has_key?(distances, u) and !corrupted?(grid, u) and within_bounds?(u, dims) end) 28 | end 29 | 30 | defp corrupted?(grid, u), do: Map.get(grid, u) == "#" 31 | defp within_bounds?({x, y}, {m, n}), do: x >= 0 and y >= 0 and x <= m and y <= n 32 | 33 | defp update_map(keys, value, map) do 34 | Enum.map(keys, fn key -> {key, value} end) 35 | |> Enum.into(%{}) 36 | |> Map.merge(map) 37 | end 38 | 39 | def print(grid, m, n) do 40 | (for x <- 0..m, y <- 0..n, do: Map.get(grid, {y, x}, ".")) 41 | |> Enum.chunk_every(m+1) 42 | |> Enum.map(&Enum.join/1) 43 | |> Enum.join("\n") 44 | |> IO.puts() 45 | end 46 | 47 | def read(file) do 48 | File.read!(file) 49 | |> String.split(~r{\n}, trim: true) 50 | |> parse_positions([]) 51 | end 52 | 53 | def build_grid(positions), do: update_map(positions, "#", %{}) 54 | 55 | defp parse_positions([], parsed), do: parsed 56 | defp parse_positions([line|lines], parsed) do 57 | pos = String.split(line, ",", trim: true) 58 | |> Enum.map(&String.to_integer/1) 59 | |> List.to_tuple() 60 | 61 | parse_positions(lines, parsed ++ [pos]) 62 | end 63 | end 64 | 65 | bytes = Ram.read("inputs/input18.txt") 66 | memory = Ram.build_grid(Enum.take(bytes, 1024)) 67 | Ram.shortest_path(memory, 70, 70) |> IO.puts() 68 | 69 | # --- Part Two --- 70 | 71 | Ram.first_blocking_byte(Enum.drop(bytes, 1024), memory, 70, 70) |> IO.puts() 72 | -------------------------------------------------------------------------------- /2024/day19.exs: -------------------------------------------------------------------------------- 1 | # --- Day 19: Linen Layout --- 2 | 3 | defmodule Towels do 4 | def count_possibles({patterns, designs}), do: Enum.count(designs, &(possible?(&1, patterns))) 5 | 6 | def sum_all_combinations({patterns, designs}), do: Enum.map(designs, &(count_combinations(&1, patterns))) |> Enum.sum() 7 | 8 | defp possible?(design, patterns), do: count_combinations(design, patterns) > 0 9 | defp count_combinations(design, patterns), do: count_combinations(design, patterns, %{}) |> elem(1) 10 | 11 | def read(file) do 12 | [patterns, designs] = File.read!(file) 13 | |> String.split(~r{\n\n}, trim: true) 14 | 15 | {parse_patterns(patterns), parse_designs(designs)} 16 | end 17 | 18 | def parse_patterns(patterns) do 19 | String.split(patterns, ", ", trim: true) 20 | |> Enum.map(fn d -> {d, 1} end) 21 | |> Enum.into(%{}) 22 | end 23 | 24 | defp count_combinations(design, patterns, counted) do 25 | count_combinations(design, patterns, counted, 1, Map.get(patterns, design, 0)) 26 | end 27 | 28 | def count_combinations(design, patterns, counted, split, total) do 29 | if split >= String.length(design) do 30 | {counted, total} 31 | else 32 | {s1, s2} = String.split_at(design, split) 33 | if Map.has_key?(patterns, s1) do 34 | if Map.has_key?(counted, s2) do 35 | count_combinations(design, patterns, counted, split + 1, Map.get(counted, s2) + total) 36 | else 37 | {updated_counted, v2} = count_combinations(s2, patterns, counted) 38 | count_combinations(design, patterns, Map.put(updated_counted, s2, v2), split + 1, total + v2) 39 | end 40 | else 41 | count_combinations(design, patterns, counted, split + 1, total) 42 | end 43 | end 44 | end 45 | 46 | defp parse_designs(designs) do 47 | String.split(designs, ~r{\n}, trim: true) 48 | end 49 | end 50 | 51 | Towels.read("inputs/input19.txt") |> Towels.count_possibles() |> IO.puts() 52 | 53 | # --- Part Two --- 54 | 55 | Towels.read("inputs/input19.txt") |> Towels.sum_all_combinations() |> IO.puts() 56 | -------------------------------------------------------------------------------- /2024/day22.exs: -------------------------------------------------------------------------------- 1 | # --- Day 22: Monkey Market --- 2 | 3 | defmodule Market do 4 | import Bitwise 5 | 6 | def max_bananas(sequences) do 7 | all_keys = Map.values(sequences) |> Enum.flat_map(&Map.keys/1) 8 | Enum.map(all_keys, fn seq -> bananas(sequences, seq) end) |> Enum.max() 9 | end 10 | 11 | def bananas(sequences, seq) do 12 | Enum.map(sequences, fn {_, bans} -> if is_nil(Map.get(bans, seq)), do: 0, else: Map.get(bans, seq) end) |> Enum.sum() 13 | end 14 | 15 | def sum_secret_numbers(generated), do: Enum.map(generated, fn {_, {n, _}} -> n end) |> Enum.sum() 16 | 17 | defp sequences([p|prices]), do: sequences(p, {}, prices, %{}) 18 | defp sequences(p, {}, [p1, p2, p3, p4|prices], seqs) do 19 | diffs = {_, d2, d3, d4} = {p1 - p, p2 - p1, p3 - p2, p4 - p3} 20 | sequences({d2, d3, d4}, [p2, p3, p4|prices], Map.put_new(seqs, diffs, p4)) 21 | end 22 | defp sequences(_, prices, seqs) when length(prices) < 4, do: seqs 23 | defp sequences({d1, d2, d3}, [_, p2, p3, p4|prices], seqs) do 24 | diffs = {d1, d2, d3, p4 - p3} 25 | sequences({d2, d3, p4 - p3}, [p2, p3, p4|prices], Map.put_new(seqs, diffs, p4)) 26 | end 27 | 28 | def all_sequences(generated), do: Enum.map(generated, fn {n, {_, prices}} -> {n, sequences(prices)} end) |> Enum.into(%{}) 29 | 30 | def generate(numbers, times) when is_list(numbers) do 31 | Enum.map(numbers, fn n -> {n, generate(n, times)} end) 32 | |> Enum.into(%{}) 33 | end 34 | 35 | def generate(number, 0), do: {number, [Integer.mod(number, 10)]} 36 | def generate(number, times) do 37 | n = evolve(number) 38 | {m, list} = generate(n, times - 1) 39 | {m, [Integer.mod(number, 10)|list]} 40 | end 41 | 42 | defp evolve(number) do 43 | n1 = (number <<< 6) |> mix(number) |> prune() 44 | n2 = (n1 >>> 5) |> mix(n1) |> prune() 45 | (n2 <<< 11) |> mix(n2) |> prune() 46 | end 47 | 48 | defp mix(n1, n2), do: bxor(n1, n2) 49 | defp prune(n), do: Integer.mod(n, 16777216) 50 | 51 | def read_numbers(path) do 52 | File.read!(path) 53 | |> String.split(~r{\n}, trim: true) 54 | |> Enum.map(&String.to_integer/1) 55 | end 56 | end 57 | 58 | generated = Market.read_numbers("inputs/input22.txt") |> Market.generate(2000) 59 | Market.sum_secret_numbers(generated) |> IO.puts() 60 | 61 | # --- Part Two --- 62 | 63 | Market.all_sequences(generated) |> Market.max_bananas() |> IO.puts() 64 | 65 | -------------------------------------------------------------------------------- /2024/day23.exs: -------------------------------------------------------------------------------- 1 | # --- Day 23: LAN Party --- 2 | 3 | defmodule LAN do 4 | def maximal_clique(graph), do: maximal_clique(graph, Map.keys(graph), [], MapSet.new(), MapSet.new()) 5 | def maximal_clique(_, [], [], _, maximal), do: maximal 6 | def maximal_clique(graph, [v|starters], [], current, maximal) do 7 | if MapSet.size(current) > MapSet.size(maximal) do 8 | maximal_clique(graph, starters, Map.keys(graph), MapSet.new([v]), current) 9 | else 10 | maximal_clique(graph, starters, Map.keys(graph), MapSet.new([v]), maximal) 11 | end 12 | end 13 | def maximal_clique(graph, starters, [v|nodes], current, maximal) do 14 | if connected_to_all?(graph, v, current) do 15 | maximal_clique(graph, starters, nodes, MapSet.put(current, v), maximal) 16 | else 17 | maximal_clique(graph, starters, nodes, current, maximal) 18 | end 19 | end 20 | 21 | defp connected_to_all?(graph, v, nodes), do: Enum.all?(nodes, fn u -> connected?(graph, u, v) end) 22 | 23 | defp connected?(graph, u, v), do: u in Map.get(graph, v) 24 | 25 | defp groups_of_three(graph), do: groups_of_three(graph, Map.keys(graph), MapSet.new()) 26 | defp groups_of_three(_, [], groups), do: groups 27 | defp groups_of_three(graph, [v|nodes], groups) do 28 | updated_groups = MapSet.union(groups, interconnected(graph, v, Map.get(graph, v))) 29 | groups_of_three(graph, nodes, updated_groups) 30 | end 31 | 32 | defp interconnected(graph, v, cons), do: interconnected(graph, v, cons, MapSet.new()) 33 | defp interconnected(_, _, [], groups), do: groups 34 | defp interconnected(graph, v, [c|cons], groups) do 35 | new_group = Map.get(graph, c) |> Enum.filter(fn d -> v in Map.get(graph, d) end) 36 | |> Enum.map(fn d -> Enum.sort([v, c, d]) end) 37 | |> MapSet.new() 38 | interconnected(graph, v, cons, MapSet.union(groups, new_group)) 39 | end 40 | 41 | def one_with_t(graph) do 42 | groups_of_three(graph) 43 | |> Enum.filter(fn c -> Enum.any?(c, fn x -> String.starts_with?(x, "t") end) end) 44 | end 45 | 46 | defp build_graph(edges), do: build_graph(edges, %{}) 47 | defp build_graph([], graph), do: graph 48 | defp build_graph([[v1, v2]|edges], graph) do 49 | updated_graph = Map.update(graph, v1, [v2], fn ns -> ns ++ [v2] end) 50 | |> Map.update(v2, [v1], fn ns -> ns ++ [v1] end) 51 | 52 | build_graph(edges, updated_graph) 53 | end 54 | 55 | def read_connections(path) do 56 | File.read!(path) 57 | |> String.split(~r{\n}, trim: true) 58 | |> Enum.map(&(String.split(&1, "-"))) 59 | |> build_graph() 60 | end 61 | end 62 | 63 | LAN.read_connections("inputs/input23.txt") |> LAN.one_with_t() |> Enum.count() |> IO.puts() 64 | 65 | # --- Part Two --- 66 | 67 | LAN.read_connections("inputs/input23.txt") |> LAN.maximal_clique() |> Enum.sort() |> Enum.join(",") |> IO.puts() 68 | -------------------------------------------------------------------------------- /2024/day25.exs: -------------------------------------------------------------------------------- 1 | # --- Day 25: Code Chronicle --- 2 | 3 | defmodule Locks do 4 | def fitting_pairs(schematics) do 5 | %{lock: locks, key: keys} = Enum.group_by(schematics, &type/1) 6 | 7 | (for l <- locks, k <- keys, do: {l, k}) 8 | |> Enum.count(fn {l, k} -> fit?(l, k) end) 9 | end 10 | 11 | defp fit?(lock, key), do: Enum.zip(heights(lock), heights(key)) |> Enum.all?(fn {h1, h2} -> h1 + h2 <= 7 end) 12 | 13 | defp heights(schematic), do: Enum.map(0..4, fn j -> height(schematic, j) end) 14 | 15 | defp height(schematic, j), do: Enum.count(0..6, fn i -> Map.get(schematic, {i, j}) == "#" end) 16 | 17 | defp type(schematic) do 18 | cond do 19 | lock?(schematic) -> :lock 20 | key?(schematic) -> :key 21 | end 22 | end 23 | 24 | defp lock?(schematic), do: first_row_all?(schematic, "#") and bottom_row_all?(schematic, ".") 25 | defp key?(schematic), do: first_row_all?(schematic, ".") and bottom_row_all?(schematic, "#") 26 | 27 | defp first_row_all?(schematic, c), do: Enum.all?(0..4, fn j -> Map.get(schematic, {0, j}) == c end) 28 | defp bottom_row_all?(schematic, c), do: Enum.all?(0..4, fn j -> Map.get(schematic, {6, j}) == c end) 29 | 30 | def read_schematics(file) do 31 | File.read!(file) 32 | |> String.split(~r{\n\n}, trim: true) 33 | |> Enum.map(&parse_schematic/1) 34 | |> Enum.uniq() 35 | end 36 | 37 | defp parse_schematic(line), do: String.split(line, ~r{\n}, trim: true) |> parse_schematic(%{}, 0) 38 | defp parse_schematic([], schematic, _), do: schematic 39 | defp parse_schematic([line | lines], schematic, i) do 40 | updated_schematic = parse_schematic(String.graphemes(line), schematic, i, 0) 41 | parse_schematic(lines, updated_schematic, i + 1) 42 | end 43 | 44 | defp parse_schematic([], schematic, _, _), do: schematic 45 | defp parse_schematic([c | row], schematic, i, j), do: parse_schematic(row, Map.put(schematic, {i, j}, c), i, j + 1) 46 | end 47 | 48 | Locks.read_schematics("inputs/input25.txt") |> Locks.fitting_pairs() |> IO.inspect 49 | 50 | # --- Part Two --- 51 | 52 | # There's no part two! 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Advent of Code 2 | 3 | Advent of Code is an Advent calendar of small programming puzzles that can be solved in any language. It's been happening every December since 2015, and it's a lot of fun. Every day from 1st December to 25th December, a new puzzle with 2 parts gets unblocked. The goal is to solve all of them to save Christmas. The big story linking the problems together is great too. I've used AoC to learn some Elixir, it's a nice way to get some practice in a new language or to even try several ones! 4 | 5 | Learn more and sign up for next edition at the [official website](https://adventofcode.com/). 6 | --------------------------------------------------------------------------------