Compare commits

...

27 Commits

Author SHA1 Message Date
a7cda04308 add 2024 day 4 2024-12-10 20:55:29 -05:00
ec3b438f40 2024 day 3 part 2 2024-12-06 20:39:16 -05:00
e1496aa25a add 2024 day 3 part 1 2024-12-06 11:44:57 -05:00
8b265bc3ec add 2024 day 2 2024-12-03 15:45:52 -05:00
1f6d987c53 add 2024 day 1 2024-12-02 09:58:45 -05:00
9ef31dde55 finish day 9 2022-12-09 22:41:21 -08:00
Joseph Montanaro
9236e70abe broken implementation of day 9 2022-12-09 17:49:56 -08:00
Joseph Montanaro
2e2d668269 add day 8 2022-12-08 14:29:47 -08:00
Joseph Montanaro
6abb28f96f finish day7 2022-12-08 10:14:48 -08:00
393db1d3bf broken implementation of 7.1 2022-12-07 22:06:52 -08:00
Joseph Montanaro
a998e68b71 half-baked partial start to day 7 2022-12-07 16:43:51 -08:00
166008ebb9 suspiciously straightforward 2022-12-06 18:48:04 -08:00
3b9a3eed0c it's ugly but it works 2022-12-05 20:12:57 -08:00
Joseph Montanaro
a10ff091da start day 5 2022-12-05 16:46:00 -08:00
19d28ea991 day 4 2022-12-04 21:43:02 -08:00
3bc97158bc day 3 2022-12-04 20:38:46 -08:00
Joseph Montanaro
86eb94514e day 2 2022-12-02 16:11:46 -08:00
Joseph Montanaro
c7c444bce9 aoc 2022 day 1 2022-12-02 14:52:51 -08:00
8c6ffc2eb9 finish day 18 2022-01-08 19:50:46 -08:00
Joseph Montanaro
61374b8655 completely rework day 18 2022-01-07 14:57:22 -08:00
54053779aa work on exploding and splitting 2022-01-06 21:37:13 -08:00
Joseph Montanaro
63818ee2ee day 18 data parsing 2022-01-06 16:05:19 -08:00
Joseph Montanaro
5abd82efff day 17 2022-01-05 16:03:23 -08:00
6492fd5c03 day 16 2022-01-02 10:55:42 -08:00
5633d37773 day 15 2021-12-31 08:25:28 -08:00
33e9af89be day 14 2021-12-29 13:51:59 -08:00
0f6f5cfc76 remove sample data from day 13 2021-12-29 07:05:22 -08:00
25 changed files with 2380 additions and 30 deletions

2
.gitignore vendored
View File

@@ -1,3 +1,5 @@
*/data/*
*/target/*
**.exe
*.out

View File

@@ -92,29 +92,6 @@ fn show(grid: &Vec2<bool>) {
}
const SAMPLE: &str = "6,10
0,14
9,10
0,3
10,4
4,11
6,0
6,12
4,1
0,13
10,12
3,4
3,0
8,4
1,10
2,14
8,10
9,0
fold along y=7
fold along x=5";
pub fn run(data: &str) -> eyre::Result<(usize, usize)> {
let (grid, folds) = load(data)?;

83
2021/src/day14.rs Normal file
View File

@@ -0,0 +1,83 @@
use std::collections::HashMap;
use color_eyre::eyre;
use crate::lib::{IterExt, Counter};
type Pairs = Counter<(char, char)>;
type Rules = HashMap<(char, char), char>;
fn load(data: &str) -> eyre::Result<(Pairs, Rules)> {
let mut lines = data.lines();
let template = lines.next().unwrap().to_string();
let mut pairs = Counter::new();
for i in 1..template.len() {
let a = template.as_bytes()[i - 1] as char;
let b = template.as_bytes()[i] as char;
pairs.inc((a, b));
}
lines.next();
let mut rules = HashMap::new();
for line in &mut lines {
let (pair_s, tgt_s) = line.split(" -> ").take_pair()?;
let pair = pair_s.chars().take_pair()?;
let tgt = tgt_s.chars().next().unwrap();
rules.insert(pair, tgt);
}
Ok((pairs, rules))
}
fn expand(src: &Pairs, rules: &Rules) -> Pairs {
let mut dst = Counter::new();
for (&(a, b), count) in src.iter() {
if let Some(&tween) = rules.get(&(a, b)) {
dst.inc_by((a, tween), count);
dst.inc_by((tween, b), count);
}
}
dst
}
fn score(pairs: &Pairs) -> usize {
// "score" is defined as the number of occurences of the most common
// character minus the number of occurrences of the least common
let mut firsts = Counter::new();
let mut lasts = Counter::new();
for (pair, count) in pairs.iter() {
let (a, b) = *pair;
firsts.inc_by(a, count);
lasts.inc_by(b, count);
}
// the count of a given character is the number of times it appears
// in either first or last place of a pair, whichever is greater
let mut counts = Counter::new();
for (c, count) in firsts.iter().chain(lasts.iter()) {
if count > counts.get(c) {
counts.set(*c, count);
}
}
let max = counts.iter().map(|(_c, n)| n).max().unwrap();
let min = counts.iter().map(|(_c, n)| n).min().unwrap();
max - min
}
pub fn run(data: &str) -> eyre::Result<(usize, usize)> {
let (mut pairs, rules) = load(data)?;
for _i in 0..10 {pairs = expand(&pairs, &rules);}
let one = score(&pairs);
for _i in 0..30 {pairs = expand(&pairs, &rules);}
let two = score(&pairs);
Ok((one, two))
}

105
2021/src/day15.rs Normal file
View File

@@ -0,0 +1,105 @@
use color_eyre::eyre;
use std::collections::{HashMap, BinaryHeap};
use std::cmp::{Ord, PartialOrd, Ordering};
use crate::lib::Vec2;
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
struct Node {
row: usize,
col: usize,
cost: usize,
}
impl Ord for Node {
fn cmp(&self, other: &Self) -> Ordering {
other.cost.cmp(&self.cost)
.then_with(|| self.row.cmp(&other.row))
.then_with(|| self.col.cmp(&other.col))
}
}
impl PartialOrd for Node {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
fn search(graph: &Vec2<usize>) -> usize {
// lifted shamelessly from https://doc.rust-lang.org/std/collections/binary_heap/index.html
let mut q = BinaryHeap::new();
let start = Node {row: 0, col: 0, cost: 0};
q.push(start);
let mut costs = HashMap::new();
while let Some(node) = q.pop() {
if node.row == graph.row_count() - 1 && node.col == graph.col_count() - 1 {
return node.cost;
}
if &node.cost > costs.get(&(node.row, node.col)).unwrap_or(&usize::MAX) {
continue;
}
for (row, col) in graph.rect_neighbors(node.row, node.col) {
let hop_cost = graph.get(row, col);
let neighbor = Node {row, col, cost: node.cost + hop_cost};
if &neighbor.cost < costs.get(&(row, col)).unwrap_or(&usize::MAX) {
q.push(neighbor);
costs.insert((row, col), neighbor.cost);
}
}
}
panic!("Could not find a path");
}
fn load(data: &str) -> eyre::Result<Vec2<usize>> {
let width = data.lines().next().unwrap().len();
let mut grid = Vec2::new(width);
for line in data.lines() {
let chars = line.chars().map(|c| c.to_digit(10).unwrap() as usize);
grid.push(chars);
}
Ok(grid)
}
fn tile(grid: &Vec2<usize>) -> Vec2<usize> {
let old_cols = grid.col_count();
let new_cols = old_cols * 5;
let mut result = Vec2::new(new_cols);
for row in grid.rows() {
let values = (0..new_cols).map(|i| {
let v = row[i % old_cols] + i / old_cols;
(v - 1) % 9 + 1
});
result.push(values);
}
let old_rows = grid.row_count();
let new_rows = old_rows * 5;
for row_n in old_rows..new_rows {
let values = (0..new_cols).map(|c| {
let r = row_n % old_rows;
let v = result.get(r, c) + row_n / old_rows;
(v - 1) % 9 + 1
})
.collect::<Vec<usize>>();
result.push(values.into_iter());
}
result
}
pub fn run(data: &str) -> eyre::Result<(usize, usize)> {
let grid = load(data)?;
let grid2 = tile(&grid);
let one = search(&grid);
let two = search(&grid2);
Ok((one, two))
}

127
2021/src/day16.rs Normal file
View File

@@ -0,0 +1,127 @@
use color_eyre::eyre;
use crate::lib::{BitVec, BitSlice};
#[derive(Debug)]
struct Packet {
version: u8,
type_id: u8,
content: PacketContent,
}
#[derive(Debug)]
enum PacketContent {
Literal(u64),
Operator(Vec<Packet>),
}
struct Parser {
data: BitVec,
idx: usize,
}
impl Parser {
fn from_hex(hex: &str) -> eyre::Result<Self> {
let mut data = BitVec::with_capacity(hex.len() * 4);
for i in 0 .. (hex.len() / 2) { // we are assuming valid hex
let chunk = &hex[i * 2 .. (i + 1) * 2];
let byte = u8::from_str_radix(chunk, 16)?;
data.push_byte(byte);
}
Ok(Parser {data, idx: 0})
}
fn next(&mut self, n: usize) -> BitSlice {
let range = self.idx .. self.idx + n;
self.idx += n;
self.data.slice(range)
}
fn parse_value(&mut self) -> u64 {
let mut value = 0;
loop {
let group = self.next(5);
value = (value << 4) | group.slice(1..5).as_u64();
if !group[0] {break;}
}
value
}
fn parse_children(&mut self) -> Vec<Packet> {
let mut children = Vec::new();
if self.next(1).as_bool() {
// count refers to number of sub-packets
let count = self.next(11).as_u16();
children = Vec::with_capacity(count as usize);
for _i in 0..count {
children.push(self.parse());
}
}
else {
// len refers to content length in bits
let len = self.next(15).as_u16();
let end = self.idx + (len as usize);
while self.idx < end {
children.push(self.parse());
}
}
children
}
fn parse(&mut self) -> Packet {
let version = self.next(3).as_u8();
let type_id = self.next(3).as_u8();
let content = match type_id {
4 => PacketContent::Literal(self.parse_value()),
_ => PacketContent::Operator(self.parse_children()),
};
Packet {version, type_id, content}
}
}
fn sum_versions(root: &Packet) -> usize {
let mut total = root.version as usize;
if let PacketContent::Operator(children) = &root.content {
for child in children {
total += sum_versions(child);
}
}
total
}
fn evaluate(packet: &Packet) -> usize {
let children = match &packet.content {
PacketContent::Literal(value) => {return *value as usize;}, // early exit here
PacketContent::Operator(children) => children,
};
let mut values = children.iter().map(|p| evaluate(p));
match packet.type_id {
0 => values.sum(),
1 => values.product(),
2 => values.min().unwrap(),
3 => values.max().unwrap(),
// it's guaranteed that comparisons will have exactly two children
5 => if values.next().unwrap() > values.next().unwrap() {1} else {0},
6 => if values.next().unwrap() < values.next().unwrap() {1} else {0},
7 => if values.next().unwrap() == values.next().unwrap() {1} else {0},
// the version number was a 3-bit value, so:
_ => unreachable!(),
}
}
pub fn run(data: &str) -> eyre::Result<(usize, usize)> {
let mut parser = Parser::from_hex(data)?;
let root = parser.parse();
let one = sum_versions(&root);
let two = evaluate(&root);
Ok((one, two))
}

81
2021/src/day17.rs Normal file
View File

@@ -0,0 +1,81 @@
use std::ops::Range;
use color_eyre::eyre;
#[derive(Default, Debug)]
struct Pair {
x: i64,
y: i64
}
#[derive(Debug)]
struct ProbeState {
pos: Pair,
vel: Pair,
}
struct Zone {
x: Range<i64>,
y: Range<i64>,
}
impl ProbeState {
fn new(vx: i64, vy: i64) -> Self {
let vel = Pair {x: vx, y: vy};
ProbeState {pos: Pair::default(), vel}
}
fn step(&mut self) {
self.pos.x += self.vel.x;
self.pos.y += self.vel.y;
self.vel.y -= 1;
if self.vel.x > 0 {
self.vel.x -= 1;
}
}
fn is_in(&self, zone: Zone) -> bool {
zone.x.contains(&self.pos.x) && zone.y.contains(&self.pos.y)
}
fn is_past(&self, zone: Zone) -> bool {
self.pos.x >= zone.x.end || self.pos.y <= zone.y.start
}
}
const VX_MIN: i64 = 23;
const VX_MAX: i64 = 293;
const VY_MIN: i64 = -69;
const VY_MAX: i64 = 68;
const TARGET: Zone = Zone {x: 269..293, y: -68..-43};
//sample data
// const VX_MIN: i64 = 6;
// const VX_MAX: i64 = 31;
// const VY_MIN: i64 = -11;
// const VY_MAX: i64 = 10;
// const TARGET: Zone = Zone {x: 20..31, y: -10..-4};
pub fn run(_data: &str) -> eyre::Result<(usize, usize)> {
let mut total = 0;
// just brute force it, there are only ~35k possibilities
for vx in VX_MIN..VX_MAX {
for vy in VY_MIN..VY_MAX {
let mut state = ProbeState::new(vx, vy);
while !state.is_past(TARGET) {
state.step();
if state.is_in(TARGET) {
total += 1;
break;
}
}
}
}
Ok((2278, total))
}

188
2021/src/day18.rs Normal file
View File

@@ -0,0 +1,188 @@
use color_eyre::eyre;
#[derive(Debug, Clone)]
enum Node {
Leaf(u8),
Branch {
left: Box<Node>,
right: Box<Node>,
}
}
use Node::*;
impl Node {
fn unwrap_branch(&self) -> (&Node, &Node) {
match self {
Node::Leaf(_) => panic!("Attempted to unwrap branches from a leaf node"),
Node::Branch {left, right} => (left, right),
}
}
fn leftmost_value_mut(&mut self) -> &mut u8 {
match self {
Node::Leaf(val) => val,
Node::Branch {left, ..} => left.leftmost_value_mut(),
}
}
fn rightmost_value_mut(&mut self) -> &mut u8 {
match self {
Node::Leaf(val) => val,
Node::Branch {right, ..} => right.rightmost_value_mut(),
}
}
fn magnitude(&self) -> usize {
match self {
Node::Leaf(v) => *v as usize,
Node::Branch {left, right} => 3 * left.magnitude() + 2 * right.magnitude(),
}
}
}
struct Parser<'a> {
chars: std::str::Chars<'a>,
}
impl<'a> Parser<'a> {
fn from(src: &'a str) -> Parser<'a> {
let mut chars = src.chars();
chars.next(); // throw away the opening bracket to avoid nesting too deeply
Parser {chars}
}
fn parse(&mut self) -> Node {
let mut left = None;
loop {
let node = match self.chars.next() {
Some('[') => self.parse(),
Some(','|']') => {continue;},
Some(c) => Node::Leaf(c.to_digit(10).unwrap() as u8),
None => {dbg!(left); panic!("Ran out of characters while parsing");},
};
if left.is_none() {
left = Some(node);
}
else {
return Node::Branch {
left: Box::new(left.unwrap()),
right: Box::new(node),
};
}
}
}
}
fn load(data: &str) -> Vec<Node> {
let mut nodes = Vec::new();
for line in data.lines() {
let mut parser = Parser::from(line);
nodes.push(parser.parse());
}
nodes
}
fn explode(node: &mut Node, depth: usize) -> Option<(u8, u8)> {
// only branch nodes can explode
if let Leaf(_) = node {return None;}
if depth >= 4 {
if let (Leaf(v1), Leaf(v2)) = node.unwrap_branch() {
let result = (*v1, *v2);
*node = Leaf(0);
return Some(result);
}
}
if let Branch {left, right} = node {
if let Some((v1, v2)) = explode(left, depth + 1) {
if v2 > 0 {
*right.leftmost_value_mut() += v2;
return Some((v1, 0));
}
return Some((v1, v2));
}
if let Some((v1, v2)) = explode(right, depth + 1) {
if v1 > 0 {
*left.rightmost_value_mut() += v1;
return Some((0, v2));
}
return Some((v1, v2));
}
}
return None;
}
fn split(node: &mut Node) -> bool {
match node {
Leaf(v) if *v > 9 => {
*node = Branch {
left: Box::new(Leaf(*v / 2)),
right: Box::new(Leaf((*v + 1) / 2)),
};
return true;
},
Leaf(_) => return false,
Branch {left, right} => {
if split(left) {
return true;
}
else {
return split(right);
}
}
}
}
fn reduce(node: &mut Node) {
loop {
if let Some(..) = explode(node, 0) {
continue;
}
if split(node) {
continue;
}
break;
}
}
fn add(left: Node, right: Node) -> Node {
let mut sum = Node::Branch {
left: Box::new(left),
right: Box::new(right),
};
reduce(&mut sum);
sum
}
pub fn run(data: &str) -> eyre::Result<(usize, usize)> {
let nodes = load(data);
let one = nodes.into_iter().reduce(|sum, next| add(sum, next)).unwrap();
// I'm sure there's a more efficient way to do this, but
let mut max = 0;
let nodes2 = load(data);
for i in 0..nodes2.len() {
for j in 0..nodes2.len() {
if i == j {continue;}
let a = nodes2[i].clone();
let b = nodes2[j].clone();
let magnitude = add(a, b).magnitude();
if magnitude > max {
max = magnitude;
}
}
}
Ok((one.magnitude(), max))
}

View File

@@ -1,9 +1,11 @@
use std::io::Read;
use std::collections::VecDeque;
use std::str::FromStr;
use std::fs::File;
use std::collections::HashMap;
use std::fmt::{Display, Formatter};
use std::ops::{Index, IndexMut};
use std::fs::File;
use std::hash::Hash;
use std::ops::{Index, IndexMut, Range};
use std::str::FromStr;
use color_eyre::{eyre};
use eyre::eyre;
@@ -145,6 +147,26 @@ impl Iterator for NeighborCoords {
}
pub struct RectNeighbors {
nc: NeighborCoords,
}
impl Iterator for RectNeighbors {
type Item = (usize, usize);
fn next(&mut self) -> Option<Self::Item> {
if let Some((row, col)) = self.nc.next() {
if row == self.nc.start_row || col == self.nc.start_col {
return Some((row, col));
}
else {
return self.next();
}
}
return None
}
}
#[allow(dead_code)]
impl<T> Vec2<T> {
pub fn new(columns: usize) -> Vec2<T> {
@@ -203,6 +225,10 @@ impl<T> Vec2<T> {
}
}
pub fn rect_neighbors(&self, row: usize, col: usize) -> RectNeighbors {
RectNeighbors {nc: self.neighbor_coords(row, col)}
}
pub fn get(&self, row: usize, col: usize) -> &T {
&self.data[self.offset(row, col)]
}
@@ -256,7 +282,7 @@ impl<T: Display> Display for Vec2<T> {
for row in self.rows() {
write!(f, "[")?;
for (i, v) in row.iter().enumerate() {
if i > 0 {write!(f, ", ")?;}
if i > 0 {write!(f, "")?;}
write!(f, "{}", v)?;
}
write!(f, "]\n")?;
@@ -264,3 +290,179 @@ impl<T: Display> Display for Vec2<T> {
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct Counter<K> {
data: HashMap<K, usize>
}
#[allow(dead_code)]
impl<K> Counter<K>
where K: Eq + Hash,
{
pub fn new() -> Self {
Counter {data: HashMap::<K, usize>::new()}
}
pub fn inc(&mut self, key: K) {
let v = self.data.entry(key).or_insert(0);
*v += 1;
}
pub fn inc_by(&mut self, key: K, increment: usize) {
let v = self.data.entry(key).or_insert(0);
*v += increment;
}
pub fn dec(&mut self, key: K) {
let v = self.data.entry(key).or_insert(0);
if *v > 0 {
*v -= 1;
}
}
pub fn dec_by(&mut self, key: K, decrement: usize) {
let v = self.data.entry(key).or_insert(0);
if *v >= decrement {
*v -= decrement;
}
}
pub fn get(&mut self, key: &K) -> usize {
*self.data.get(key).unwrap_or(&0)
}
pub fn set(&mut self, key: K, value: usize) {
self.data.insert(key, value);
}
pub fn iter(&self) -> impl Iterator<Item =(&'_ K, usize)> {
self.data.iter().map(|(k, v)| (k, *v))
}
}
pub struct BitVec {
data: Vec<u8>,
len: usize,
}
#[allow(dead_code)]
impl BitVec {
fn new() -> Self {
BitVec {data: vec![], len: 0}
}
pub fn with_capacity(cap: usize) -> Self {
let true_cap = (cap + 8 - 1) / 8; // ceiling division
BitVec {
data: Vec::with_capacity(true_cap),
len: 0
}
}
pub fn len(&self) -> usize {
self.len
}
pub fn push(&mut self, b: bool) {
let offset = self.len % 8;
let v = (b as u8) << (7 - offset);
match offset {
0 => self.data.push(v),
_ => *self.data.last_mut().unwrap() |= v,
}
self.len += 1;
}
pub fn push_byte(&mut self, byte: u8) {
if self.len() % 8 != 0 {
panic!("Attempted to push full byte across byte boundary");
}
self.data.push(byte);
self.len += 8;
}
pub fn slice(&self, range: Range<usize>) -> BitSlice {
if range.end > self.len {
panic!("Slice out of bounds: {}..{}", range.start, range.end);
}
BitSlice {
src: self,
start: range.start,
end: range.end,
}
}
}
impl Index<usize> for BitVec {
type Output = bool;
fn index(&self, index: usize) -> &bool {
let (idx, offset) = (index / 8, index % 8);
let bit = self.data[idx] & (128 >> offset);
if bit == 0 {&false} else {&true}
}
}
#[derive(Copy, Clone)]
pub struct BitSlice<'a> {
src: &'a BitVec,
start: usize,
end: usize,
}
#[allow(dead_code)]
impl BitSlice<'_> {
pub fn len(&self) -> usize {
self.end - self.start
}
pub fn as_u64(&self) -> u64 {
if self.len() > 64 {panic!("Slice too large for u64");}
let mut value = 0;
for i in self.start..self.end {
let offset = self.end - 1 - i;
value |= (self.src[i] as u64) << offset;
}
value
}
pub fn as_u16(&self) -> u16 {
if self.len() > 16 {panic!("Slice too large for u16");}
self.as_u64() as u16
}
pub fn as_u8(&self) -> u8 {
if self.len() > 8 {panic!("Slice too large for u8");}
self.as_u64() as u8
}
pub fn as_bool(&self) -> bool {
if self.len() != 1 {
panic!("Only slices of length 1 can be converted to bool");
}
self.as_u64() == 1
}
pub fn slice(&self, range: Range<usize>) -> BitSlice {
if range.end > self.len() {
panic!("Slice out of bounds: {}..{}", range.start, range.end);
}
BitSlice {
src: &self.src,
start: self.start + range.start,
end: self.start + range.end,
}
}
}
impl Index<usize> for BitSlice<'_> {
type Output = bool;
fn index(&self, index: usize) -> &bool {
self.src.index(self.start + index)
}
}

View File

@@ -4,14 +4,14 @@ use color_eyre::eyre;
mod lib;
use lib::load;
mod day13;
mod day18;
fn main() -> eyre::Result<()> {
let data = load("data/13.txt")?;
let data = load("data/18.txt")?;
let start = Instant::now();
let (one, two) = day13::run(&data)?;
let (one, two) = day18::run(&data)?;
let (dur, unit) = format_ns(start.elapsed().as_nanos());
let precision = 2.0 - dur.log10().floor();

49
2022/Cargo.lock generated Normal file
View File

@@ -0,0 +1,49 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "advent-2022"
version = "0.1.0"
dependencies = [
"lazy_static",
"regex",
]
[[package]]
name = "aho-corasick"
version = "0.7.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
dependencies = [
"memchr",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "regex"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"

50
2022/Cargo.toml Normal file
View File

@@ -0,0 +1,50 @@
[package]
name = "advent-2022"
version = "0.1.0"
edition = "2021"
[lib]
name = "advent_2022"
path = "src/lib.rs"
[[bin]]
name = "day1"
path = "src/day1.rs"
[[bin]]
name = "day2"
path = "src/day2.rs"
[[bin]]
name = "day3"
path = "src/day3.rs"
[[bin]]
name = "day4"
path = "src/day4.rs"
[[bin]]
name = "day5"
path = "src/day5.rs"
[[bin]]
name = "day6"
path = "src/day6.rs"
[[bin]]
name = "day7"
path = "src/day7.rs"
[[bin]]
name = "day8"
path = "src/day8.rs"
[[bin]]
name = "day9"
path = "src/day9.rs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
regex = "1.7.0"
lazy_static = "1.4.0"

28
2022/src/day1.rs Normal file
View File

@@ -0,0 +1,28 @@
const DATA: &'static str = include_str!("../data/day1.txt");
fn main() {
let mut counts = DATA.trim()
.split("\n\n")
.map(|lines| {
lines.split("\n")
.map(|c| c.parse::<u64>().unwrap())
.collect::<Vec<_>>()
})
.collect::<Vec<_>>();
counts.sort_by_key(|values| values.iter().sum::<u64>());
let max_total_cals: u64 = counts.last()
.unwrap()
.iter()
.sum();
let top3_total_cals: &u64 = &counts[(counts.len() - 3)..]
.iter()
.flatten()
.sum();
println!("Part 1: {max_total_cals}");
println!("Part 2: {top3_total_cals}");
}

108
2022/src/day2.rs Normal file
View File

@@ -0,0 +1,108 @@
use std::str::FromStr;
const DATA: &'static str = include_str!("../data/day2.txt");
// const DATA: &'static str = "
// A Y
// B X
// C Z
// ";
enum RPS {
Rock = 1,
Paper = 2,
Scissors = 3,
}
impl FromStr for RPS {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"A" | "X" => Ok(Rock),
"B" | "Y" => Ok(Paper),
"C" | "Z" => Ok(Scissors),
_ => Err(())
}
}
}
enum Outcome {
Win = 6,
Loss = 0,
Draw = 3,
}
impl FromStr for Outcome {
type Err = ();
fn from_str(s: &str) -> Result<Self, ()> {
match s {
"X" => Ok(Loss),
"Y" => Ok(Draw),
"Z" => Ok(Win),
_ => Err(())
}
}
}
use RPS::*;
use Outcome::*;
fn compare(me: &RPS, them: &RPS) -> Outcome {
match (me, them) {
(Rock, Rock) => Draw,
(Rock, Paper) => Loss,
(Rock, Scissors) => Win,
(Paper, Rock) => Win,
(Paper, Paper) => Draw,
(Paper, Scissors) => Loss,
(Scissors, Rock) => Loss,
(Scissors, Paper) => Win,
(Scissors, Scissors) => Draw,
}
}
fn get_response(opponent: &RPS, outcome: &Outcome) -> RPS {
match (opponent, outcome) {
(Rock, Win) => Paper,
(Rock, Loss) => Scissors,
(Rock, Draw) => Rock,
(Paper, Win) => Scissors,
(Paper, Loss) => Rock,
(Paper, Draw) => Paper,
(Scissors, Win) => Rock,
(Scissors, Loss) => Paper,
(Scissors, Draw) => Scissors,
}
}
fn score(me: RPS, them: RPS) -> u64 {
let outcome = compare(&me, &them);
(me as u64) + (outcome as u64)
}
fn main() {
let mut total_1 = 0;
for pair in DATA.trim().split("\n") {
let mut it = pair.split(" ")
.map(|v| v.parse::<RPS>().unwrap());
let (them, me) = (it.next().unwrap(), it.next().unwrap());
total_1 += score(me, them);
}
println!("Part 1: {total_1}");
let mut total_2 = 0;
for pair in DATA.trim().split("\n") {
let mut it = pair.split(" ");
let them = it.next().unwrap().parse::<RPS>().unwrap();
let outcome = it.next().unwrap().parse::<Outcome>().unwrap();
let me = get_response(&them, &outcome);
total_2 += me as u64;
total_2 += outcome as u64
}
println!("Part 2: {total_2}");
}

103
2022/src/day3.rs Normal file
View File

@@ -0,0 +1,103 @@
use std::str::FromStr;
const DATA: &'static str = include_str!("../data/day3.txt");
// const DATA: &'static str = "
// vJrwpWtwJgWrhcsFMMfFFhFp
// jqHRNqRjqzjGDLGLrsFMfFZSrLrFZsSL
// PmmdzqPrVvPwwTWBwg
// wMqvLMZHhHMvwLHjbvcjnnSBnvTQFn
// ttgJtRGJQctTZtZT
// CrZsJsPPZsGzwwsLwLmpwMDw
// ";
#[derive(Debug)]
enum DataError {
Imbalanced,
UnknownValue,
NoDuplicate,
NoTriplicate,
ShortGroup,
}
struct Rucksack {
left: Vec<u8>,
right: Vec<u8>,
}
impl Rucksack {
fn find_duplicate(&self) -> Option<u8> {
// pretty sure this is faster than hash set, given the sizes involved
self.left.iter()
.find(|c| self.right.contains(c))
.map(|c| *c)
}
fn iter_all(&self) -> impl Iterator<Item = &u8> {
self.left.iter().chain(self.right.iter())
}
fn contains(&self, v: u8) -> bool {
self.left.contains(&v) || self.right.contains(&v)
}
}
impl FromStr for Rucksack {
type Err = DataError;
fn from_str(line: &str) -> Result<Rucksack, DataError> {
let line = line.trim();
if line.len() % 2 != 0 {
return Err(DataError::Imbalanced);
}
let (a, b) = line.as_bytes().split_at(line.len() / 2);
Ok(Rucksack {
left: a.into(),
right: b.into(),
})
}
}
fn priority(c: u8) -> Result<u64, DataError> {
match c {
// lowercase
97..=122 => Ok(c as u64 - 96),
// uppercase
65..=90 => Ok(c as u64 - 38),
_ => Err(DataError::UnknownValue),
}
}
fn main() -> Result<(), DataError> {
let mut dup_total = 0;
let rucks = DATA.trim()
.split("\n")
.map(|line| Rucksack::from_str(line))
.collect::<Result<Vec<Rucksack>, DataError>>()?;
for ruck in rucks.iter() {
let dup = ruck.find_duplicate()
.ok_or(DataError::NoDuplicate)?;
dup_total += priority(dup)?;
}
println!("Part 1: {dup_total}");
let mut groups_total = 0;
for (idx, ruck) in rucks.iter().enumerate().step_by(3) {
if idx > rucks.len() - 3 {
return Err(DataError::ShortGroup);
}
let badge = *ruck.iter_all()
.find(|&v| rucks[idx + 1].contains(*v) && rucks[idx + 2].contains(*v))
.ok_or(DataError::NoTriplicate)?;
groups_total += priority(badge)?;
}
println!("Part 2: {groups_total}");
Ok(())
}

89
2022/src/day4.rs Normal file
View File

@@ -0,0 +1,89 @@
const DATA: &'static str = include_str!("../data/day4.txt");
// const DATA: &'static str = "
// 2-4,6-8
// 2-3,4-5
// 5-7,7-9
// 2-8,3-7
// 6-6,4-6
// 2-6,4-8
// ";
#[derive(Debug)]
enum DataError {
NotANumber,
NotAPair,
NotARange,
}
struct Range {
start: u32,
end: u32,
}
impl Range {
fn contains(&self, other: &Range) -> bool {
self.start <= other.start && self.end >= other.end
}
fn overlaps(&self, other: &Range) -> bool {
// what if self.start == other.start or self.end == other.end?
if self.start <= other.start && self.end >= other.start {
return true;
}
if self.start <= other.end && self.end >= other.end {
return true;
}
false
}
}
impl std::str::FromStr for Range {
type Err = DataError;
fn from_str(s: &str) -> Result<Range, DataError> {
let mut bounds = s.split('-');
let start = bounds.next()
.ok_or(DataError::NotARange)?
.parse::<u32>()
.map_err(|_e| DataError::NotANumber)?;
let end = bounds.next()
.ok_or(DataError::NotARange)?
.parse::<u32>()
.map_err(|_e| DataError::NotANumber)?;
Ok(Range {start, end})
}
}
fn main() -> Result<(), DataError> {
let ranges = DATA.trim()
.lines()
.map(|line| {
let mut pairs = line.split(',');
let left: Range = pairs.next()
.ok_or(DataError::NotAPair)?
.parse()?;
let right: Range = pairs.next()
.ok_or(DataError::NotAPair)?
.parse()?;
Ok((left, right))
})
.collect::<Result<Vec<(Range, Range)>, DataError>>()?;
let total_supersets = ranges.iter()
.filter(|(a, b)| a.contains(b) || b.contains(a))
.count();
println!("Part 1: {total_supersets}");
let total_overlaps = ranges.iter()
.filter(|(a, b)| a.overlaps(b) || b.contains(a) || a.contains(b))
.count();
println!("Part 2: {total_overlaps}");
Ok(())
}

113
2022/src/day5.rs Normal file
View File

@@ -0,0 +1,113 @@
use core::num::ParseIntError;
const DATA: &'static str = include_str!("../data/day5.txt");
// const DATA: &'static str = "
// [D]
// [N] [C]
// [Z] [M] [P]
// 1 2 3
// move 1 from 2 to 1
// move 3 from 1 to 3
// move 2 from 2 to 1
// move 1 from 1 to 2
// ";
enum BadLine {
NotEnoughWords,
ParseError(ParseIntError),
}
#[derive(Debug)]
struct State {
stacks: [Vec<char>; 9],
}
impl State {
fn process(&mut self, op: &Instruction) {
for _ in 0..(op.count) {
let item = self.stacks[op.src as usize - 1].pop().unwrap();
self.stacks[op.dst as usize - 1].push(item);
}
}
fn process_inorder(&mut self, op: &Instruction) {
let src_stack = &mut self.stacks[op.src as usize - 1];
let split_idx = src_stack.len() - op.count as usize;
let mut substack = src_stack.split_off(split_idx);
self.stacks[op.dst as usize - 1].append(&mut substack);
}
fn get_tops(&self) -> String {
let mut res = String::with_capacity(9);
for stack in &self.stacks {
res.push(*stack.last().unwrap());
}
res
}
}
#[derive(Debug)]
struct Instruction {
count: u32,
src: u32,
dst: u32,
}
fn parse_map(map: &str) -> State {
let rows: Vec<&str> = map.lines().collect();
let mut stacks: [Vec<char>; 9] = Default::default();
// skip the first row, because it's just the stack numbers
for row in rows.iter().rev().skip(1) {
for (col_num, c) in row.chars().enumerate() {
if c.is_ascii_uppercase() {
let stack_idx = (col_num - 1) / 4;
stacks[stack_idx].push(c);
}
}
}
State { stacks }
}
fn parse_instruction(data: &str) -> Result<Instruction, BadLine> {
let mut words = data.split(' ');
Ok(Instruction {
count: words.nth(1).ok_or(BadLine::NotEnoughWords)?.parse().map_err(|e| BadLine::ParseError(e))?,
src: words.nth(1).ok_or(BadLine::NotEnoughWords)?.parse().map_err(|e| BadLine::ParseError(e))?,
dst: words.nth(1).ok_or(BadLine::NotEnoughWords)?.parse().map_err(|e| BadLine::ParseError(e))?,
})
}
fn main() -> Result<(), String> {
let mut splat = DATA.trim_end().split("\n\n");
let map = splat.next().unwrap();
let instruction_lines = splat.next().unwrap();
let mut state = parse_map(map);
let instructions: Vec<Instruction> = instruction_lines.lines()
.map(|line| parse_instruction(line).map_err(|_e| format!("Bad line: {line}")))
.collect::<Result<Vec<Instruction>, String>>()?;
for i in &instructions {
state.process(i);
}
println!("Part 1: {}", state.get_tops());
let mut state2 = parse_map(map);
for i in &instructions {
state2.process_inorder(i);
}
println!("Part 2: {}", state2.get_tops());
Ok(())
}

58
2022/src/day6.rs Normal file
View File

@@ -0,0 +1,58 @@
// const DATA: &'static str = "zcfzfwzzqfrljwzlrfnpqdbhtmscgvjw";
const DATA: &'static str = include_str!("../data/day6.txt");
struct CharCounts {
chars: [u8; 26],
}
impl CharCounts {
fn new() -> Self {
CharCounts { chars: [0; 26]}
}
fn idx(c: u8) -> usize {
(c as usize) - (b'a' as usize)
}
fn inc(&mut self, c: u8) {
self.chars[Self::idx(c)] += 1;
}
fn dec(&mut self, c: u8) {
self.chars[Self::idx(c)] -= 1;
}
fn max(&self) -> u8 {
// max only returns None if the iterator is empty
*self.chars.iter().max().unwrap()
}
}
fn find_marker(data: &[u8], window_size: usize) -> Option<usize> {
let mut window = CharCounts::new();
for (i, v) in data.iter().enumerate() {
window.inc(*v);
if i > (window_size - 1) {
let dropout = data[i - window_size];
window.dec(dropout);
}
if i > (window_size - 1) && window.max() == 1 {
return Some(i + 1);
}
}
None
}
fn main() {
let bytes = DATA.trim().as_bytes();
let m1 = find_marker(&bytes, 4).unwrap();
println!("Part 1: {m1}");
let m2 = find_marker(&bytes, 14).unwrap();
println!("Part 2: {m2}");
}

229
2022/src/day7.rs Normal file
View File

@@ -0,0 +1,229 @@
use std::collections::HashMap;
use lazy_static::lazy_static;
use regex::Regex;
struct Directory {
name: String,
children: Vec<Node>,
}
struct File {
name: String,
size: usize,
}
enum Node {
Branch(Directory),
Leaf(File),
}
lazy_static! {
static ref CMD_CD: Regex = Regex::new(r"^\$ cd (\S+)$").unwrap();
static ref CMD_LS: Regex = Regex::new(r"^\$ ls$").unwrap();
static ref DIR_LISTING: Regex = Regex::new(r"^dir (\S+)$").unwrap();
static ref FILE_LISTING: Regex = Regex::new(r"^(\d+) (\S+)$").unwrap();
}
struct DirListing {
name: String,
children: Vec<usize>,
parent: Option<usize>,
}
enum NodeListing {
Branch(DirListing),
Leaf(File),
}
fn build_map(data: &str) -> Result<HashMap<usize, NodeListing>, String> {
let root = DirListing {
name: "".to_string(),
children: vec![],
parent: None,
};
let mut map = HashMap::new();
map.insert(0, NodeListing::Branch(root));
let mut stack: Vec<usize> = vec![];
stack.push(0);
'lines: for line in data.trim().lines() {
if CMD_LS.is_match(line) {continue;}
if let Some(c) = CMD_CD.captures(line) {
let name = c.get(1).unwrap().as_str();
if name == "/" {
stack.truncate(1);
continue;
}
if name == ".." {
stack.pop();
continue;
}
let current_dir = match map.get(&stack.last().unwrap()).unwrap() {
NodeListing::Branch(dir) => dir,
_ => unreachable!(),
};
for id in &current_dir.children {
let child_name = match map.get(&id).unwrap() {
NodeListing::Branch(dir) => &dir.name,
NodeListing::Leaf(file) => &file.name,
};
if child_name == name {
stack.push(*id);
continue 'lines;
}
}
return Err(format!("Attempted to cd into directory before ls-ing it: {}", line.to_string()));
}
if let Some(c) = DIR_LISTING.captures(line) {
let parent_id = stack.last().unwrap();
let child_id = map.len();
let parent = match map.get_mut(parent_id).unwrap() {
NodeListing::Branch(dir) => dir,
_ => unreachable!(),
};
parent.children.push(child_id);
let dir = DirListing {
name: c.get(1).unwrap().as_str().to_string(),
children: vec![],
parent: Some(*parent_id),
};
map.insert(child_id, NodeListing::Branch(dir));
continue;
}
if let Some(c) = FILE_LISTING.captures(line) {
let name = c.get(2).unwrap().as_str().to_string();
let size = c.get(1).unwrap().as_str().parse::<usize>()
.map_err(|_e| format!("Invalid file size: {}", line.to_string()))?;
let file_id = map.len();
let file = File {name, size};
map.insert(file_id, NodeListing::Leaf(file));
let parent = match map.get_mut(stack.last().unwrap()).unwrap() {
NodeListing::Branch(dir) => dir,
_ => unreachable!(),
};
parent.children.push(file_id);
continue;
}
return Err(format!("Could not interpret line: {line}"));
}
Ok(map)
}
fn build_tree(map: &mut HashMap<usize, NodeListing>, root_listing: &DirListing) -> Directory {
let mut root_children = Vec::new();
for child_id in &root_listing.children {
match map.remove(&child_id).unwrap() {
NodeListing::Leaf(file) => {
root_children.push(Node::Leaf(file));
},
NodeListing::Branch(dir_listing) => {
let dir = build_tree(map, &dir_listing);
root_children.push(Node::Branch(dir))
},
}
}
Directory {
name: root_listing.name.clone(),
children: root_children,
}
}
fn get_sizes(root: &Directory, mut sizes: Vec<usize>) -> (usize, Vec<usize>) {
let mut dir_size = 0;
for child in &root.children {
match child {
Node::Leaf(file) => dir_size += file.size,
Node::Branch(dir) => {
let (child_size, rs) = get_sizes(dir, sizes);
sizes = rs;
dir_size += child_size;
},
}
}
sizes.push(dir_size);
(dir_size, sizes)
}
fn main() -> Result<(), String> {
let mut map = build_map(DATA)?;
let root = match map.remove(&0).unwrap() {
NodeListing::Branch(dir) => dir,
_ => unreachable!(),
};
let tree = build_tree(&mut map, &root);
let (total_size, dir_sizes) = get_sizes(&tree, vec![]);
let part1_sizes: usize = dir_sizes.iter()
.filter(|&&s| s <= 100000)
.sum();
println!("Part 1: {part1_sizes}");
let available = 70_000_000 - total_size;
if available >= 30_000_000 {
return Err("Weird, there's already enough space available".to_string());
}
let target_size = 30_000_000 - available;
let delete_size = dir_sizes.iter()
.filter(|&&s| s >= target_size)
.min()
.unwrap();
println!("Part 2: {delete_size}");
Ok(())
}
const DATA: &'static str = include_str!("../data/day7.txt");
// const DATA: &'static str = "
// $ cd /
// $ ls
// dir a
// 14848514 b.txt
// 8504156 c.dat
// dir d
// $ cd a
// $ ls
// dir e
// 29116 f
// 2557 g
// 62596 h.lst
// $ cd e
// $ ls
// 584 i
// $ cd ..
// $ cd ..
// $ cd d
// $ ls
// 4060174 j
// 8033020 d.log
// 5626152 d.ext
// 7214296 k
// ";

93
2022/src/day8.rs Normal file
View File

@@ -0,0 +1,93 @@
const DATA: &'static str = include_str!("../data/day8.txt");
// const DATA: &'static str = "
// 30373
// 25512
// 65332
// 33549
// 35390
// ";
// O(N * (2 * (sqrt(N) - 1))), so roughly N^1.5? Where N is number of elements in the grid
fn is_visible(grid: &Vec<Vec<usize>>, x: usize, y: usize) -> bool {
let height = grid[y][x];
let rows = grid.len();
let cols = grid[x].len();
// search horizontal
let vis_left = (0..x).all(|cmp_x| grid[y][cmp_x] < height);
let vis_right = ((x + 1)..cols).all(|cmp_x| grid[y][cmp_x] < height);
let vis_top = (0..y).all(|cmp_y| grid[cmp_y][x] < height);
let vis_bottom = ((y + 1)..rows).all(|cmp_y| grid[cmp_y][x] < height);
vis_left || vis_right || vis_top || vis_bottom
}
fn scenic_score(grid: &Vec<Vec<usize>>, x: usize, y: usize) -> usize {
let height = grid[y][x];
let rows = grid.len();
let cols = grid[x].len();
let sc_left = (0..x).rev()
.position(|cmp_x| grid[y][cmp_x] >= height) // 0-indexed position of the first blocking tree
.map(|p| p + 1) // +1 since we include the blocking tree in the count
.unwrap_or(x); // if no blocking trees, score is how many are to the left of current
let sc_right = ((x + 1)..cols)
.position(|cmp_x| grid[y][cmp_x] >= height)
.map(|p| p + 1)
.unwrap_or(cols - x - 1); // e.g. if x is 4 and there are 5 cols, this is rightmost, so 5 - 4 - 1 = 0
let sc_top = (0..y).rev()
.position(|cmp_y| grid[cmp_y][x] >= height)
.map(|p| p + 1)
.unwrap_or(y);
let sc_bottom = ((y + 1)..rows)
.position(|cmp_y| grid[cmp_y][x] >= height)
.map(|p| p + 1)
.unwrap_or(rows - y - 1);
sc_left * sc_right * sc_top * sc_bottom
}
fn build_grid(data: &str) -> Result<Vec<Vec<usize>>, String> {
data.trim()
.lines()
.map(|line| {
line.chars()
.map(|c| c.to_digit(10).map(|c| c as usize).ok_or_else(|| format!("Failed to convert to integer: {c}") ))
.collect::<Result<Vec<usize>, String>>()
})
.collect::<Result<Vec<Vec<usize>>, String>>()
}
fn main() -> Result<(), String> {
let grid = build_grid(DATA)?;
let mut visible_trees = 0;
for y in 0..grid.len() {
for x in 0..grid[y].len() {
if is_visible(&grid, x, y) {
visible_trees += 1;
}
}
}
println!("Part 1: {visible_trees}");
let mut max_score = 0;
for y in 0..grid.len() {
for x in 0..grid[y].len() {
let score = scenic_score(&grid, x, y);
if score > max_score {
max_score = score;
}
}
}
println!("Part 2: {max_score}");
Ok(())
}

184
2022/src/day9.rs Normal file
View File

@@ -0,0 +1,184 @@
use std::collections::HashSet;
use std::fmt::{Display, Formatter};
use std::str::FromStr;
#[derive(Copy, Clone, Debug)]
enum Direction {
Left,
Right,
Up,
Down,
}
impl FromStr for Direction {
type Err = String;
fn from_str(s: &str) -> Result<Self, String> {
match s {
"L" => Ok(Direction::Left),
"R" => Ok(Direction::Right),
"U" => Ok(Direction::Up),
"D" => Ok(Direction::Down),
_ => Err(format!("Not a valid direction: {s}")),
}
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
struct Pos {
x: isize,
y: isize,
}
#[derive(Debug)]
struct Rope<const L: usize> {
knots: [Pos; L],
tail_visited: HashSet<Pos>,
}
impl<const L: usize> Rope<L> {
fn new() -> Self {
Rope {
knots: [Pos {x: 0, y: 0}; L],
tail_visited: HashSet::new()
}
}
fn do_move(&mut self, dir: Direction, count: isize) {
for _ in 0..count {
let head = &mut self.knots[0];
match dir {
Direction::Left => head.x -= 1,
Direction::Right => head.x += 1,
Direction::Up => head.y += 1,
Direction::Down => head.y -= 1,
}
for i in 1..(self.knots.len()) {
self.move_follower(i);
}
self.tail_visited.insert(*self.knots.last().unwrap());
}
}
fn move_follower(&mut self, i: usize) {
let leader = self.knots[i - 1];
let follower = &mut self.knots[i];
let dx = leader.x - follower.x;
let dy = leader.y - follower.y;
// check to make sure that we didn't do anything wrong previously
if dx.abs() + dy.abs() > 4
|| dx.abs() == 0 && dy.abs() > 2
|| dx.abs() > 2 && dy.abs() == 0
{
panic!("Invalid head and tail positions: {dx}, {dy}");
}
// head and tail are in line vertically
if dx == 0 && dy.abs() == 2 {
follower.y += dy / 2; // use integer division to keep the sign of dy
}
// in line horizontally
if dy == 0 && dx.abs() == 2 {
follower.x += dx / 2; // similarly
}
// head and tail are offset by 1 horizontaly and 2 vertically
if dx.abs() == 1 && dy.abs() == 2 {
follower.x += dx;
follower.y += dy / 2;
}
// offset by 1 vertically and 2 horizontally
if dy.abs() == 1 && dx.abs() == 2 {
follower.y += dy;
follower.x += dx / 2;
}
// offset by 2 in both dimensions
if dx.abs() == 2 && dy.abs() == 2 {
follower.x += dx / 2;
follower.y += dy / 2;
}
}
}
use std::convert::TryFrom;
impl<const L: usize> Display for Rope<L> {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
let min_x = self.knots.iter().map(|pos| pos.x).min().unwrap();
let max_x = self.knots.iter().map(|pos| pos.x).max().unwrap();
let min_y = self.knots.iter().map(|pos| pos.y).min().unwrap();
let max_y = self.knots.iter().map(|pos| pos.y).max().unwrap();
let n_rows = usize::try_from(max_y - min_y + 1).unwrap();
let n_cols = usize::try_from(max_x - min_x + 1).unwrap();
let mut grid = vec![vec!['.'; n_cols]; n_rows];
for (i, kt) in self.knots.iter().enumerate() {
let rel_x = usize::try_from(kt.x - min_x).unwrap();
let rel_y = usize::try_from(max_y - kt.y).unwrap();
let tgt = &mut grid[rel_y][rel_x];
if *tgt == '.' {
*tgt = char::from_digit(i as u32, 10).unwrap();
}
}
for row in grid {
write!(f, "{}\n", row.iter().collect::<String>())?;
}
Ok(())
}
}
fn main() -> Result<(), String> {
let instructions = DATA.trim()
.lines()
.map(|line| {
let mut splat = line.split(' ');
match (splat.next(), splat.next()) {
(Some(dir), Some(dist)) => {
let dist = dist.parse().map_err(|_e| format!("Not an integer: {dist}"));
Ok((dir.parse()?, dist?))
},
_ => Err(format!("Could not parse move instruction from line: {line}"))
}
})
.collect::<Result<Vec<(Direction, isize)>, String>>()?;
let mut rope1 = Rope::<2>::new();
for &(dir, dist) in &instructions {
rope1.do_move(dir, dist);
}
println!("Part 1: {}", rope1.tail_visited.len());
let mut rope2 = Rope::<10>::new();
for &(dir, dist) in &instructions {
rope2.do_move(dir, dist);
}
println!("Part 2: {}", rope2.tail_visited.len());
Ok(())
}
const DATA: &'static str = include_str!("../data/day9.txt");
// const DATA: &'static str = "
// R 5
// U 8
// L 8
// D 3
// R 17
// D 10
// L 25
// U 20
// ";

67
2022/src/lib.rs Normal file
View File

@@ -0,0 +1,67 @@
#![allow(dead_code)]
use std::ops::{Index, IndexMut};
struct Grid<T> {
rows: Vec<Vec<T>>
}
impl<T> Grid<T> {
fn new() -> Self {
Grid { rows: vec![] }
}
fn with_default_values<D>(width: usize, height: usize) -> Self
where T: Default + Clone
{
let rows = (0..height)
.map(|_i| vec![Default::default(); width])
.collect();
Grid { rows }
}
fn from_rows<I>(rows: I) -> Self
where I: Iterator<Item = Vec<T>>
{
Grid { rows: rows.collect() }
}
fn from_chars<F, E>(src: &str, conv: F) -> Result<Self, E>
where F: Fn(char) -> Result<T, E>
{
let rows: Result<Vec<Vec<T>>, E> = src.lines()
.map(|line| {
line.chars()
.map(|c| conv(c))
.collect()
})
.collect();
Ok(Grid { rows: rows? })
}
fn iter_rows(&self) -> impl Iterator<Item = &Vec<T>> {
self.rows.iter()
}
}
impl<T> Index<[usize; 2]> for Grid<T> {
type Output = T;
fn index(&self, idx: [usize; 2]) -> &T {
let [x, y] = idx;
&self.rows[y][x]
}
}
impl<T> IndexMut<[usize; 2]> for Grid<T> {
fn index_mut(&mut self, idx: [usize; 2]) -> &mut T {
let [x, y] = idx;
&mut self.rows[y][x]
}
}

79
2024/01.c Normal file
View File

@@ -0,0 +1,79 @@
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
int cmp(const void *a, const void *b) {
int *_a = (int *)a;
int *_b = (int *)b;
return *_a - *_b;
}
int count_occurrences(int num, int arr[], int len) {
int total = 0;
for (int i = 0; i < len; i++) {
if (arr[i] == num) {
total++;
}
}
return total;
}
int part1(int nums_l[], int nums_r[], int len) {
int sum = 0;
for (int i = 0; i < len; i++) {
int diff = nums_l[i] - nums_r[i];
if (diff < 0) {
diff = diff * -1;
}
sum += diff;
}
return sum;
}
int part2(int nums_l[], int nums_r[], int len) {
int score = 0;
for (int i = 0; i < len; i++) {
score += nums_l[i] * count_occurrences(nums_l[i], nums_r, len);
}
return score;
}
int main() {
FILE *file = fopen("data/01.txt", "r");
char *data = (char *)malloc(16384);
size_t data_len = fread(data, 1, 16384, file);
int nums_l[1000];
int nums_r[1000];
int nlines = 0;
while (1) {
char* line = strsep(&data, "\n");
int left = strtol(line, &line, 10);
int right = strtol(line, NULL, 10);
// if `strtol` fails, it apparently just returns 0, how helpful
if (left == 0 && right == 0) {
break;
}
nums_l[nlines] = left;
nums_r[nlines] = right;
nlines++;
}
qsort(nums_l, nlines, 4, cmp);
qsort(nums_r, nlines, 4, cmp);
int solution_1 = part1(nums_l, nums_r, nlines);
printf("Part 1: %i\n", solution_1);
int solution_2 = part2(nums_l, nums_r, nlines);
printf("Part 2: %i\n", solution_2);
}

77
2024/02.cpp Normal file
View File

@@ -0,0 +1,77 @@
#include <fstream>
#include <iostream>
#include <string>
#include <vector>
using namespace std;
vector<int> parse_line(string line) {
int start = 0;
vector<int> result;
while (start < line.length()) {
int end = line.find(" ", start);
string word = line.substr(start, end - start);
int n = stoi(word);
result.push_back(n);
start = end + 1;
if (end == -1) {
break;
}
}
return result;
}
bool is_valid(vector<int> report) {
int prev_diff = 0;
for (int i = 1; i < report.size(); i++) {
int diff = report[i] - report[i - 1];
if (diff < -3 || diff == 0 || diff > 3) {
return false;
}
// on the first iteration, we can't compare to the previous difference
if (i == 1) {
prev_diff = diff;
continue;
}
if ((diff > 0 && prev_diff < 0) || (diff < 0 && prev_diff > 0)) {
return false;
}
prev_diff = diff;
}
return true;
}
int main() {
ifstream file("data/02.txt");
string line;
int count_part1 = 0;
int count_part2 = 0;
while (getline(file, line)) {
auto report = parse_line(line);
if (is_valid(report)) {
count_part1++;
count_part2++;
}
else {
for (int i = 0; i < report.size(); i++) {
int n = report[i];
report.erase(report.begin() + i);
if (is_valid(report)) {
count_part2++;
break;
}
report.insert(report.begin() + i, n);
}
}
}
cout << "Part 1: " << count_part1 << "\n";
cout << "Part 2: " << count_part2 << "\n";
}

87
2024/03.fs Normal file
View File

@@ -0,0 +1,87 @@
: load-data ( addr1 u1 -- addr2 u2 )
20000 allocate drop \ allocate a 20k buffer, drop the status code
dup 2swap \ duplicate the buffer addr and stick both copies under the addr/length of filename
r/o open-file drop \ open file, drop the status code
20000 swap \ stick the number of bytes to be read in between the buffer addr and file id
read-file drop \ read the file, drop the status code
;
: chop-prefix ( addr u u2 -- addr2 u2 )
\ chop the first `u2` bytes off the beginning of the string at `addr u`
tuck \ duplicate `u2` and store it "under" the length of the string
- \ subtract `u2` from the length of the string
-rot \ stick the new string length underneath the start pointer
+ \ increment the start pointer by `u2`
swap \ put them back in the right order
;
require regexp.fs
: mul-instr ( addr u -- flag )
\ match a string of the form `mul(x,y)` where x and y are integers and capture those integers
(( =" mul(" \( {++ \d ++} \) ` , \( {++ \d ++} \) ` ) )) ;
: get-product ( addr u -- u2 )
mul-instr \ match the string from `addr u` against the above regex
if \ if the regex matches, then:
\1 s>number drop \ convert the first capture from string to number, drop the status code (we already know it will succeed)
\2 s>number drop \ convert the second capture from string to number, drop the status code
* \ multiply, and leave the answer on the stack
else
0 \ otherwise, leave 0 on the stack
then
;
variable result \ initialize `result` with 0
0 result !
variable enabled
-1 enabled ! \ idiomatically -1 is "true" (really anything other than 0 is true)
: handle-mul ( addr u -- )
get-product \ pass those to get-product above
result @ + \ load `result` and add to product
result ! \ store this new value back in `result`
;
: sum-mul-instrs ( addr u -- u2 )
\ we want to loop from addr to (addr + u - 8), because 8 is the min length of a valid mul(x,y) instruction
\ we also want to have addr + u on the top of the stack when we enter the loop,
\ so that we can use that to compute the remaining length of the string from our current address
over + \ copy addr to top of stack and add to length
dup 8 - \ duplicate, then subtract 8 from the top value
rot \ move original addr to top of stack
( stack at this point: [ addr + u, addr + u - 8, addr ] )
( i.e. [ end-of-string, loop-limit, loop-start ] )
do \ start looping
I 4 s" do()" str= \ compare the length-4 substring starting at I to the string "do()"
if \ if valid do() instruction,
-1 enabled ! \ set enabled=true
then
I 7 s" don't()" str= \ compare length-7 substring to "don't()"
if \ if valid don't() instruction,
0 enabled ! \ set enabled=false
then
I 4 s" mul(" str= \ compare length-4 substring to "mul("
enabled @ and \ combine with current value of `enabled`
if \ if a candidate for `mul(x,y)` instruction, and enabled=true, then
dup I - \ subtract current string pointer from end-of-string pointer to get length of remaining string
I swap handle-mul \ put current pointer onto stack again, swap so stack is ( addr len), and handle
then
loop
drop \ get rid of end-of-string pointer
result @ \ return value of result
;
s" data/03.txt" load-data
sum-mul-instrs .
bye

171
2024/04.f90 Normal file
View File

@@ -0,0 +1,171 @@
program advent04
implicit none
character, dimension(146, 146) :: grid
integer :: row, col, part1, part2
real :: start, end
call cpu_time(start)
grid = load("data/04.txt", 140, 140)
part1 = 0
part2 = 0
do col = 4, 143
do row = 4, 143
if (grid(row, col) == 'X') then
part1 = part1 + count_xmas(row, col)
end if
if (grid(row, col) == 'A') then
part2 = part2 + count_mas(row, col)
end if
end do
end do
call cpu_time(end)
print *, "Part 1:", part1
print *, "Part 2:", part2
print *, "Execution time:", end - start
contains
function load(path, n_rows, n_cols) result(grid)
implicit none
character(*), intent(in) :: path
integer, intent(in) :: n_rows, n_cols
integer :: handle, row, col
character, dimension(:, :), allocatable :: grid
character(n_cols) :: line
allocate(grid(n_rows + 6, n_cols + 6))
grid = '.'
open(newunit=handle, file=path, status="old", action="read")
do row = 4, n_rows + 3
read(handle, *) line
do col = 1, n_cols
grid(row, col + 3) = line(col:col)
end do
end do
close(handle)
end function load
integer function count_xmas(row, col) result(count)
implicit none
integer, intent(in) :: row, col
integer :: i
integer(8) :: prod
integer(8), dimension(8) :: primes
character, dimension(7, 7) :: test_grid, window
integer(8), dimension(7, 7) :: prime_mask, matches, matches_prime
test_grid = reshape( &
[&
'S', '.', '.', 'S', '.', '.', 'S', &
'.', 'A', '.', 'A', '.', 'A', '.', &
'.', '.', 'M', 'M', 'M', '.', '.', &
'S', 'A', 'M', 'X', 'M', 'A', 'S', &
'.', '.', 'M', 'M', 'M', '.', '.', &
'.', 'A', '.', 'A', '.', 'A', '.', &
'S', '.', '.', 'S', '.', '.', 'S' &
], &
shape(test_grid) &
)
primes = [2, 3, 5, 7, 11, 13, 17, 19]
prime_mask = reshape( &
[ &
2, 1, 1, 3, 1, 1, 5, &
1, 2, 1, 3, 1, 5, 1, &
1, 1, 2, 3, 5, 1, 1, &
19, 19, 19, 1, 7, 7, 7, &
1, 1, 17, 13, 11, 1, 1, &
1, 17, 1, 13, 1, 11, 1, &
17, 1, 1, 13, 1, 1, 11 &
], &
shape(prime_mask) &
)
window = grid(row - 3:row + 3, col - 3:col + 3)
matches = logical_to_int64(window == test_grid)
matches_prime = matches * prime_mask
prod = product(zero_to_one(matches_prime))
count = 0
do i = 1, 8
if (mod(prod, primes(i) ** 3) == 0) then
count = count + 1
end if
end do
end function count_xmas
integer function count_mas(row, col) result(count)
implicit none
integer, intent(in) :: row, col
integer :: i
character, dimension(3, 3) :: window, t1, t2, t3, t4
t1 = reshape( &
[ &
'M', '.', 'S', &
'.', 'A', '.', &
'M', '.', 'S' &
], &
shape(t1) &
)
t2 = t1(3:1:-1, :) ! flip t1 top-to-bottom
t3 = transpose(t1) ! swap t1 rows for columns
t4 = t3(:, 3:1:-1) ! flip t3 lef-to-right
window = grid(row - 1:row + 1, col - 1:col + 1)
if ( &
count_matches(window, t1) == 5 &
.or. count_matches(window, t2) == 5 &
.or. count_matches(window, t3) == 5 &
.or. count_matches(window, t4) == 5 &
) then
count = 1
else
count = 0
end if
end function count_mas
integer function count_matches(a1, a2) result(matches)
implicit none
character, dimension(:, :) :: a1, a2
matches = count(a1 == a2)
end function count_matches
elemental integer(8) function logical_to_int64(b) result(i)
implicit none
logical, intent(in) :: b
if (b) then
i = 1
else
i = 0
end if
end function logical_to_int64
elemental integer(8) function zero_to_one(x) result(y)
implicit none
integer(8), intent(in) :: x
if (x == 0) then
y = 1
else
y = x
end if
end function zero_to_one
end program advent04