add 2020 and some cleanup
This commit is contained in:
35
2020/day01.nim
Normal file
35
2020/day01.nim
Normal 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
48
2020/day02.nim
Normal 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
45
2020/day03.nim
Normal 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
120
2020/day04.nim
Normal 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
90
2020/day05.nim
Normal 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
51
2020/day06.nim
Normal 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
86
2020/day07.nim
Normal 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
74
2020/day08.nim
Normal 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
43
2020/day09.nim
Normal 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
20
2020/lib/loader.nim
Normal 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
44
2020/lib/util.nim
Normal 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)
|
Reference in New Issue
Block a user