add 2020 and some cleanup

This commit is contained in:
2021-11-30 17:09:36 -08:00
parent efd1fa7e57
commit 804d6b6b92
41 changed files with 658 additions and 13309 deletions

35
2020/day01.nim Normal file
View File

@ -0,0 +1,35 @@
import std/[sets, strformat]
import lib/loader
let data = loadInts(1).toHashSet()
iterator sumPairs(sum: int): (int, int) =
for i in 0..(sum div 2):
yield (i, sum - i)
iterator sumTriples(sum: int): (int, int, int) =
for x in 0..(sum div 3):
for y, z in sumPairs(sum - x):
yield (x, y, z)
proc partOne() =
for x, y in sumPairs(2020):
if data.contains(x) and data.contains(y):
echo fmt"{x} * {y} = {x * y}"
break
proc partTwo() =
for x, y, z in sumTriples(2020):
if data.contains(x) and data.contains(y) and data.contains(z):
echo fmt"{x} * {y} * {z} = {x * y * z}"
break
when isMainModule:
partOne()
partTwo()

48
2020/day02.nim Normal file
View File

@ -0,0 +1,48 @@
import std/[strutils]
import lib/loader
type
PasswordItem = object
min: int
max: int
letter: char
pwd: string
var data: seq[PasswordItem]
for line in loadStrings(2):
let parts = line.split()
let minmax = parts[0].split('-')
let item = PasswordItem(
min: parseInt(minmax[0]),
max: parseInt(minmax[1]),
letter: parts[1].strip(chars = {':'})[0],
pwd: parts[2]
)
data.add(item)
proc partOne() =
var total = 0
for item in data:
let c = item.pwd.count(item.letter)
if c >= item.min and c <= item.max:
total += 1
echo "One: ", total
proc partTwo() =
var total = 0
for item in data:
let first = (item.pwd[item.min - 1] == item.letter)
let last = (item.pwd[item.max - 1] == item.letter)
if (first and not last) or (last and not first):
total += 1
echo "Two: ", total
when isMainModule:
partOne()
partTwo()

45
2020/day03.nim Normal file
View File

@ -0,0 +1,45 @@
import lib/loader
type
Map = object
rows: seq[string]
proc `[]`(m: Map, x, y: int): bool =
let row = m.rows[y]
let colNum = x mod row.len
result = (row[colNum] == '#')
let data = Map(rows: loadStrings(3))
proc partOne(dX, dY: int): int =
var count, x, y = 0
while y < data.rows.len:
if data[x, y]:
count += 1
x += dX
y += dY
result = count
proc partTwo(): int =
let slopes = @[
(1, 1),
(3, 1),
(5, 1),
(7, 1),
(1, 2),
]
var total = 1
for slope in slopes:
total *= partOne(slope[0], slope[1])
result = total
when isMainModule:
echo "One: ", partOne(3, 1)
echo "Two: ", partTwo()

120
2020/day04.nim Normal file
View File

@ -0,0 +1,120 @@
import std/[options, strutils]
import lib/loader
type Passport = object
byr, iyr, eyr, hgt, hcl, ecl, pid, cid: Option[string]
proc addField(p: var Passport, field: string) =
let kv = field.split(':')
case kv[0]
of "byr": p.byr = some(kv[1])
of "iyr": p.iyr = some(kv[1])
of "eyr": p.eyr = some(kv[1])
of "hgt": p.hgt = some(kv[1])
of "hcl": p.hcl = some(kv[1])
of "ecl": p.ecl = some(kv[1])
of "pid": p.pid = some(kv[1])
of "cid": p.cid = some(kv[1])
else: discard
var data = @[Passport()] # start with one empty passport
for line in loadStrings(4):
let pairs = line.splitWhitespace()
if pairs.len == 0:
data.add(Passport())
else:
for pair in pairs:
data[^1].addField(pair)
proc partOne(data: seq[Passport]): int =
for p in data:
if (
p.byr.isSome() and
p.iyr.isSome() and
p.eyr.isSome() and
p.hgt.isSome() and
p.hcl.isSome() and
p.ecl.isSome() and
p.pid.isSome()
):
inc result
func validateYr(year: string; min, max: int): bool =
if year.len != 4:
return false
try:
let n = parseInt(year)
result = (n >= min and n <= max)
except ValueError:
result = false
func validateHgt(hgt: string): bool =
if hgt.len < 3: # need both value and unit
return false
let value = try:
parseInt(hgt[0 .. ^3])
except ValueError:
return false
let unit = hgt[^2 .. ^1]
if unit == "in":
return value >= 59 and value <= 76
elif unit == "cm":
return value >= 150 and value <= 193
else:
return false
func validateHcl(hcl: string): bool =
if hcl.len != 7 or hcl[0] != '#':
return false
for c in hcl[2 .. ^1]:
if c notin {
'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
}:
return false
return true
func validateEcl(ecl: string): bool =
case ecl:
of "amb", "blu", "brn", "gry", "grn", "hzl", "oth":
return true
else:
return false
func validatePid(pid: string): bool =
if pid.len != 9:
return false
for c in pid:
if c notin {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}:
return false
return true
proc partTwo(data: seq[Passport]): int =
for p in data:
if (
validateYr(p.byr.get(""), 1920, 2002) and
validateYr(p.iyr.get(""), 2010, 2020) and
validateYr(p.eyr.get(""), 2020, 2030) and
validateHgt(p.hgt.get("")) and
validateHcl(p.hcl.get("")) and
validateEcl(p.ecl.get("")) and
validatePid(p.pid.get(""))
):
inc result
when isMainModule:
echo "One: ", partOne(data)
echo "Two: ", partTwo(data)

90
2020/day05.nim Normal file
View File

@ -0,0 +1,90 @@
import std/[algorithm, math]
import lib/loader
type
BinSet = object
start: int
stop: int
Half = enum
hLow,
hHigh
Seat = object
row: int
col: int
proc newBinSet(size: int): BinSet =
# check to make sure size is a power of 2
var q = size
while q mod 2 == 0:
q = q div 2
if q != 1:
raise newException(ValueError, "BinSet must have a size that is a power of 2.")
result = BinSet(start: 0, stop: size - 1)
proc size(bs: BinSet): int = bs.stop - bs.start + 1 # bounds are inclusive
func seatId(s: Seat): int = s.row * 8 + s.col
func seatCmp(s1, s2: Seat): int =
let i1 = s1.seatId()
let i2 = s2.seatId()
if i1 < i2:
return -1
elif i1 == i2:
return 0
else:
return 1
proc split(bs: var BinSet, h: Half) =
if h == hLow:
bs.stop -= (bs.size div 2)
else:
bs.start += (bs.size div 2)
proc parseBinSet(spec: string; lowChar, hiChar: char): int =
var bset = newBinSet(2 ^ spec.len)
for c in spec:
if c == lowChar:
bset.split(hLow)
elif c == hiChar:
bset.split(hHigh)
else:
raise newException(ValueError, "Bad BinSet specifier: " & spec)
assert(bset.start == bset.stop) # this should never be false, but just in case
result = bset.start
proc loadSeats(): seq[Seat] =
for line in loadStrings(5):
let row = parseBinSet(line[0..6], 'F', 'B')
let col = parseBinSet(line[7..^1], 'L', 'R')
result.add(Seat(row: row, col: col))
result.sort(seatCmp)
proc partOne(seats: seq[Seat]): int =
for s in seats:
if s.seatId() > result:
result = s.seatId()
proc partTwo(seats: seq[Seat]): int =
for i, s in seats:
let curId = s.seatId()
let nextId = seats[i + 1].seatId()
if nextId - curId != 1: # i.e. if there's a gap
return curId + 1
when isMainModule:
let seats = loadSeats()
echo "One: ", partOne(seats)
echo "Two: ", partTwo(seats)

51
2020/day06.nim Normal file
View File

@ -0,0 +1,51 @@
import lib/loader
proc toSet(s: string): set[char] =
for c in s:
result = result + {c}
proc answersIncl(data: seq[string]): seq[set[char]] =
var s: set[char]
for line in data:
if line.len > 0:
s = s + line.toSet()
else:
result.add(s)
s = {}
if s.len > 0: # in case of trailing newline
result.add(s)
proc answersExcl(data: seq[string]): seq[set[char]] =
var s: set[char]
var first = true
for line in data:
if first:
s = line.toSet()
first = false
elif line.len > 0:
s = s * line.toSet()
else:
result.add(s)
s = {}
first = true
if s.len > 0:
result.add(s)
proc partOne(data: seq[string]): int =
for group in answersIncl(data):
result += group.len
proc partTwo(data: seq[string]): int =
for group in answersExcl(data):
result += group.len
when isMainModule:
let data = loadStrings(6)
echo "One: ", partOne(data)
echo "Two: ", partTwo(data)

86
2020/day07.nim Normal file
View File

@ -0,0 +1,86 @@
import std/[strutils, tables]
import lib/[loader, util]
type
Bag = object
adj: string
color: string
BagCount = object
bag: Bag
count: int
Rule = object
outer: Bag
contents: seq[BagCount]
proc `==`(a, b: Bag): bool =
a.adj == b.adj and a.color == b.color
func parseBag(spec: string): Bag =
[adj, color] <- spec.split()
result = Bag(adj: adj, color: color)
func parseRule(spec: string): Rule =
[outer, inner] <- spec.split(" bags contain ")
let outerBag = parseBag(outer)
var bagCounts: seq[BagCount]
if inner != "no other bags.":
# make sure to strip "bags." off the end of the contents
for bc_spec in inner[0..^6].split(", "):
[count, bag_spec] <- bc_spec.split(maxsplit = 1)
let bc = BagCount(
bag: parseBag(bag_spec),
count: parseInt(count)
)
bagCounts.add(bc)
result = Rule(
outer: parseBag(outer),
contents: bagCounts
)
proc loadRules(): Table[Bag, Rule] =
for line in loadStrings(7):
let rule = parseRule(line)
result[rule.outer] = rule
proc partOne(rules: Table[Bag, Rule]): int =
# inner function so we can capture the "rules" var I guess?
proc find(rule: Rule, target: Bag): bool =
for bagCount in rule.contents:
if bagCount.bag == target:
return true
elif find(rules[bagCount.bag], target):
return true
return false
let b = Bag(adj: "shiny", color: "gold")
for _, r in rules:
if find(r, b):
result += 1
proc partTwo(rules: Table[Bag, Rule]): int =
# ok so that worked, let's do it again
proc sumContents(bag: Bag): int =
for bagCount in rules[bag].contents:
result += bagCount.count # for the inner bag itself
result += bagCount.count * sumContents(bagCount.bag)
# "x contains no other bag" is a no-op, so returns default 0
let b = Bag(adj: "shiny", color: "gold")
result = sumContents(b)
when isMainModule:
let rules = loadRules()
echo "One: ", partOne(rules)
echo "Two: ", partTwo(rules)

74
2020/day08.nim Normal file
View File

@ -0,0 +1,74 @@
import lib/[loader, util]
import std/[strutils, sugar]
type
OpCode = enum
acc, jmp, nop
ProgramLine = object
op: OpCode
arg: int
visited: bool
proc loadProgram(): seq[ProgramLine] =
for line in loadStrings(8):
[strOp, strArg] <- line.split()
let pl = ProgramLine(
op: parseEnum[OpCode](strOp),
arg: parseInt(strArg),
visited: false
)
result.add(pl)
proc partOne(prog: seq[ProgramLine]): (int, bool) =
var prog = prog # mutable copy
var pos: int
var accum: int
while pos < prog.len and not prog[pos].visited:
prog[pos].visited = true
let line = prog[pos]
case line.op
of acc:
accum += line.arg
of jmp:
pos += line.arg - 1
of nop:
discard
pos += 1
if pos == prog.len:
result = (accum, true)
else:
result = (accum, false)
proc partTwo(prog: seq[ProgramLine]): int =
var prog = prog
for i, line in prog:
let orig = line.op
case line.op
of acc:
discard
of jmp:
prog[i].op = nop
of nop:
prog[i].op = jmp
let (accum, finished) = partOne(prog)
if finished:
return accum
prog[i].op = orig
raise newException(ValueError, "Couldn't find a working variation.")
when isMainModule:
let program = loadProgram()
let (accum, finished) = partOne(program)
echo "One: ", accum
echo "Two: ", partTwo(program)

43
2020/day09.nim Normal file
View File

@ -0,0 +1,43 @@
import lib/[loader, util]
proc isValid(numbers: seq[int], idx: int, size = 25): bool =
# find two previous numbers that sum to the given number
let slice = (idx - size) .. (idx - 1)
for a in slice:
for b in slice:
if a == b:
continue
elif numbers[a] + numbers[b] == numbers[idx]:
return true
return false
proc findRangeWithSum(numbers: seq[int]; target: int): (int, int) =
for start in 0 .. (numbers.high - 1):
for stop in (start + 1) .. numbers.high:
var total = 0
for i in start .. stop:
total += numbers[i]
if total == target:
return (start, stop)
raise newException(ValueError, "Could not find a range of numbers adding to " & $target)
proc partOne(numbers: seq[int]): int =
for idx in 25 .. numbers.high:
if not isValid(numbers, idx):
return numbers[idx]
raise newException(ValueError, "Could not find an invalid value")
proc partTwo(numbers: seq[int]): int =
let target = partOne(numbers)
let (start, stop) = numbers.findRangeWithSum(target)
return min(numbers[start..stop]) + max(numbers[start..stop])
when isMainModule:
let data = loadInts(9)
echo "One: ", partOne(data)
echo "Two: ", partTwo(data)

20
2020/lib/loader.nim Normal file
View File

@ -0,0 +1,20 @@
import streams, strutils
func getFileName(daynum: int, suffix: string = ""): string =
if daynum < 10:
result = "0"
result = "data/" & result & $daynum & suffix & ".txt"
proc loadStrings*(daynum: int, suffix: string = ""): seq[string] =
var s = openFileStream(getFileName(daynum, suffix))
for line in s.lines():
result.add(line)
proc loadInts*(daynum: int): seq[int] =
var s = openFileStream(getFileName(daynum))
for line in s.lines():
let n = parseInt(line)
result.add(n)

44
2020/lib/util.nim Normal file
View File

@ -0,0 +1,44 @@
import std/macros
macro `<-`*(lhs: untyped, rhs: untyped): untyped =
# ensure that the left-hand side is a valid bracket expression e.g. [a, b, c]
let errmsg = "Invalid syntax for unpack operator: " & lhs.toStrLit().strVal()
if lhs.kind != nnkBracket:
error(errmsg, lhs)
for name in lhs:
if name.kind != nnkIdent:
error(errmsg, lhs)
# the result will be a check to ensure no index defects, followed by assignment statements
result = newNimNode(nnkStmtList, lineInfoFrom = lhs)
# first add a check to ensure that the container being unpacked has enough values
# this has to be done at runtime, since it's potentially unknown at compile time
let numIdents = lhs.len
let strRepr = lhs.toStrLit().strVal() & " <- " & rhs.toStrLit().strVal()
let lenCheck = quote do:
if `rhs`.len < `numIdents`:
raise newException(ValueError, "Not enough values to unpack: \"" & `strRepr` & "\"")
result.add(lenCheck)
var assign_from = rhs
# if the right-hand side is an expression, it might
# be expensive, so we save it to a temp variable
if rhs.kind != nnkIdent:
# repoint assign_src so that we end up using a temp variable
# instead of repeating the original expression a bunch of times
assign_from = ident("unpack_tmp_src")
# e.g. "let unpack_tmp_src = somestring.split()"
let declare_assign_src = newLetStmt(assign_from, rhs)
result.add(declare_assign_src)
# finally, inject the actual assignments
for i, name in lhs:
let assignment = quote do:
# expanded to e.g. "let a = someseq[0]"
let `name` = `assign_from`[`i`]
result.add(assignment)
proc showAddr*[T](x: T) = echo cast[uint64](unsafeAddr x)