--- /dev/null
+Cargo.lock
+target/
+data/
+__pycache__/
+.pytest_cache/
--- /dev/null
+[package]
+name = "day1"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+clap = { version = "4.5.22", features = ["derive"] }
--- /dev/null
+extern crate clap;
+
+use std::error::Error;
+use std::fs::File;
+use std::io::{prelude::*, BufReader};
+use std::collections::BTreeMap;
+
+use clap::{Parser, ArgAction};
+
+/// Advent of Code 2023 day 4
+#[derive(Parser, Default, Debug)]
+struct Arguments {
+ /// specify the input data filename
+ filename: String,
+ #[arg(short, long, action=ArgAction::SetTrue)]
+ /// enable additional debug output
+ debug: Option<bool>,
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct ParseRowError;
+
+fn from_str(s: &str) -> Result<(i64, i64), ParseRowError> {
+ let (a, b) = s.trim().split_once(' ').ok_or(ParseRowError)?;
+ let l = a.trim().parse::<i64>().unwrap();
+ let r = b.trim().parse::<i64>().unwrap();
+ Ok((l, r))
+}
+
+fn main() -> Result<(), Box<dyn Error>> {
+ let args = Arguments::parse();
+
+ let input = File::open(args.filename).expect("no such file");
+ let buffered = BufReader::new(input);
+
+ let (mut a, mut b): (Vec<_>,Vec<_>) = buffered.lines()
+ .map(|x| x.expect("invalid line"))
+ .map(|line| from_str(&line).unwrap())
+ .into_iter()
+ .unzip();
+ a.sort();
+ b.sort();
+ let cols = a.iter().zip(b).collect::<Vec<_>>();
+ let part1 = cols.iter().map(|(a, b)| (b - *a).abs()).sum::<i64>();
+ println!("part 1: {}", part1);
+
+ //let mut lset = BTreeMap::new();
+ let mut rset = BTreeMap::new();
+ for (_, r) in cols {
+ // lset.entry(l).and_modify(|v| *v += 1).or_insert(1_i64);
+ rset.entry(r).and_modify(|v| *v += 1).or_insert(1_i64);
+ }
+
+ let mut part2 = 0_i64;
+ for k in a {
+ let count = *match rset.get(&k) {
+ Some(val) => val,
+ None => &0_i64
+ };
+ let s = k * count;
+ if args.debug.unwrap() {
+ println!("k:{} n:{} s:{}", k, count, s);
+ }
+ part2 += s;
+ }
+ println!("part 2: {}", part2);
+
+ Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_lists_parse_rows() {
+ let input = "3 4\n4 3\n2 5\n1 3\n3 9\n3 3";
+
+ let (mut a, mut b): (Vec<_>,Vec<_>) = input.split('\n')
+ .map(|line| line.trim().split_once(' ').unwrap())
+ .map(|(a, b)| (a.trim().parse::<i64>().unwrap(), b.trim().parse::<i64>().unwrap()))
+ .into_iter()
+ .unzip();
+
+ a.sort();
+ assert_eq!(a, vec![1, 2, 3, 3, 3, 4]);
+ b.sort();
+ assert_eq!(b, vec![3, 3, 3, 4, 5, 9]);
+
+ let mut cols = a.into_iter().zip(b);
+ //assert_eq!(cols.next(), Some((1_i64, 3_i64)));
+ //assert_eq!(cols.next(), Some((2, 3)));
+ //assert_eq!(cols.next(), Some((3, 3)));
+ //assert_eq!(cols.next(), Some((3, 4)));
+ //assert_eq!(cols.next(), Some((3, 5)));
+ //assert_eq!(cols.next(), Some((4, 9)));
+
+ let sum = cols.map(|(a, b)| (b - a).abs()).sum::<i64>();
+ assert_eq!(sum, 11);
+ }
+
+ //vec![1, 2, 3, 3, 3, 4],
+ //vec![3, 3, 3, 4, 5, 9],
+}
\ No newline at end of file
--- /dev/null
+[package]
+name = "day2"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+clap = { version = "4.5.22", features = ["derive"] }
--- /dev/null
+from typing import List
+from dataclasses import dataclass
+
+
+def is_safe(diff) -> bool:
+ rising = all([d > 0 for d in diff])
+ falling = all([d < 0 for d in diff])
+ small = all([abs(d) < 4 for d in diff])
+ return (rising or falling) and small
+
+
+@dataclass
+class Record:
+ data: List[int]
+ diff: List[int]
+
+ def is_safe(self) -> bool:
+ return is_safe(self.diff)
+
+ def is_safe_dampened(self, debug=False) -> bool:
+ safe = False
+ for index, value in enumerate(self.data):
+ test = self.data.copy()
+ del test[index] # remove element by index
+ diff = Record.difference(test)
+ safe = is_safe(diff)
+ if debug:
+ print(f"value: {value} test:{test} diff:{diff} result:{safe}")
+ if safe:
+ break
+ return safe
+
+ @staticmethod
+ def difference(data: List[int]) -> List[int]:
+ diff = []
+ a = data[0]
+ for d in data[1:]:
+ diff.append(d - a)
+ a = d
+ return diff
+
+ @staticmethod
+ def from_str(s: str) -> 'Record':
+ data = [int(v) for v in s.strip().split(' ')]
+ diff = Record.difference(data)
+ return Record(data, diff)
+
+
+@dataclass
+class Problem:
+ records: List[Record]
+
+ def run(self, part2=False, debug=False) -> int:
+ safe = 0
+ for record in self.records:
+ if record.is_safe():
+ safe += 1
+ elif part2 and record.is_safe_dampened(debug=debug):
+ safe += 1
+ return safe
+
+ @staticmethod
+ def from_stream(stream):
+ return Problem([Record.from_str(line) for line in stream])
+
\ No newline at end of file
--- /dev/null
+import sys
+import argparse
+from . import Problem
+
+
+def main(args=None):
+ parser = argparse.ArgumentParser(description="AOC 2024 day 2")
+ parser.add_argument('filename', type=str)
+ parser.add_argument('-d', '--debug', action='store_true')
+ parser.add_argument('-2', '--part2', action='store_true')
+ options = parser.parse_args(args)
+
+ with open(options.filename) as f:
+ problem = Problem.from_stream(f)
+ # if options.debug:
+ # for record in problem.records:
+ # print(record.data, record.diff, record.is_safe())
+
+ if options.part2:
+ print(f"part2: {problem.run(part2=True, debug=options.debug)} safe reports.")
+ else:
+ print(f"part1: {problem.run()} safe reports.")
+
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv[1:]))
--- /dev/null
+extern crate clap;
+
+pub mod reports;
+use reports::Report;
+
+use std::error::Error;
+use std::fs::File;
+use std::io::{prelude::*, BufReader};
+use std::str::FromStr;
+
+use clap::{Parser, ArgAction};
+
+/// Advent of Code 2024 day 2
+#[derive(Parser, Default, Debug)]
+struct Arguments {
+ /// specify the input file name
+ filename: String,
+ #[arg(short, long, action=ArgAction::SetTrue)]
+ /// enable debug output
+ debug: Option<bool>,
+}
+
+
+fn parse_input(filename: &str) -> Vec<Report> {
+ let input = File::open(filename).expect("file not found");
+ let buffered = BufReader::new(input);
+ buffered.lines()
+ .map(|line| Report::from_str(line.unwrap().as_str()).unwrap())
+ .collect()
+}
+
+fn part1(data: &Vec<Report>, debug: bool) -> i64 {
+ if debug {
+ for datum in data {
+ for d in datum.data.iter() {
+ print!("{} ", d);
+ }
+ println!("");
+ }
+ }
+ 0_i64
+}
+
+fn main() -> Result<(), Box<dyn Error>> {
+ let args = Arguments::parse();
+ let debug = args.debug.unwrap_or(false);
+ let input = parse_input(&args.filename);
+ println!("part 1: {}", part1(&input, debug));
+ //println!("part 2: {}", part2(&args.filename));
+
+ Ok(())
+}
--- /dev/null
+use std::str::FromStr;
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct ParseReportError;
+
+pub type Level = i64;
+// pub type Report = Vec<Level>;
+
+#[derive(Debug, PartialEq)]
+pub struct Report {
+ pub data: Vec<Level>
+}
+
+impl FromStr for Report {
+ type Err = ParseReportError;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ let report = s
+ .trim()
+ .split_whitespace()
+ .map(|x| x.parse::<Level>().unwrap())
+ .collect::<Vec<Level>>();
+ Ok( Report {data: report })
+ }
+}
+
+impl Report {
+ pub fn is_safe(&self) -> bool {
+
+ let mut i = self.data.iter();
+ let a = i.next();
+ let y = i.scan(a, |state, &x| {
+ let xx = x - *state;
+ *state = x;
+ Some(xx)
+ });
+
+ for x in y {
+ print!("{}, ", x);
+ }
+
+ let deltas = self.data
+ .chunks_exact(2)
+ .map(|x| x.to_vec())
+ .map(|x| x[1] - x[0])
+ .collect::<Vec<i64>>();
+
+ print!("[ ");
+ for x in deltas.clone() {
+ print!("{} ", x);
+ }
+
+ let allinc = deltas.clone().iter().all(|x| x > &0);
+ let alldec = deltas.clone().iter().all(|x| x < &0);
+ let allsmall = deltas.clone().iter().all(|x| x.abs() < 3);
+ println!("] inc {} dec {} small {}", allinc, alldec, allsmall);
+ (allinc || alldec) && allsmall
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_report_from_strln() {
+ let input = "7 6 4 2 1\n";
+ let result = Report::from_str(input).unwrap();
+ assert_eq!(result.data, vec![7_i64, 6, 4, 2, 1]);
+ }
+ #[test]
+ fn test_report_from_str() {
+ let input = "7 6 4 2 1";
+ let result = Report::from_str(input).unwrap();
+ assert_eq!(result.data, vec![7_i64, 6, 4, 2, 1]);
+ }
+ #[test]
+ fn test_report_is_safe_safe() {
+ let input = "7 6 4 2 1";
+ let result = Report::from_str(input).unwrap();
+ assert_eq!(result.is_safe(), true);
+ }
+ #[test]
+ fn test_report_is_safe_unsafe() {
+ let input = "1 2 7 8 9";
+ let result = Report::from_str(input).unwrap();
+ assert_eq!(result.is_safe(), false);
+ }
+}
--- /dev/null
+from enum import Enum\r
+from dataclasses import dataclass\r
+\r
+\r
+class State(Enum):\r
+ Junk = 0,\r
+ M = 1,\r
+ U = 2,\r
+ L = 3,\r
+ Open = 4,\r
+ Second = 5\r
+ D = 6\r
+ Oh = 7\r
+ N = 8\r
+ Apos = 9\r
+ T = 10\r
+ Open2 = 11\r
+\r
+\r
+@dataclass\r
+class Problem:\r
+ data: list\r
+\r
+ def run(self) -> int:\r
+ return sum([a * b for (a, b) in self.data])\r
+\r
+ @staticmethod\r
+ def from_stream(stream, part2=False):\r
+ data = []\r
+ state = State.Junk\r
+ dostate = State.Junk\r
+ enabled = True\r
+ while True:\r
+ c = stream.read(1)\r
+ if not c:\r
+ break\r
+ if part2 and state == State.Junk:\r
+ if c == 'd' and dostate == State.Junk:\r
+ dostate = State.D\r
+ elif c == 'o' and dostate == State.D:\r
+ dostate = State.Oh\r
+ elif c == '(' and dostate == State.Oh:\r
+ dostate = State.Open\r
+ elif c == ')' and dostate == State.Open:\r
+ enabled = True\r
+ dostate = State.Junk\r
+ elif c == 'n' and dostate == State.Oh:\r
+ dostate = State.N\r
+ elif c == '\'' and dostate == State.N:\r
+ dostate = State.Apos\r
+ elif c == 't' and dostate == State.Apos:\r
+ dostate = State.T\r
+ elif c == '(' and dostate == State.T:\r
+ dostate = State.Open2\r
+ elif c == ')' and dostate == State.Open2:\r
+ dostate = State.Junk\r
+ enabled = False\r
+ elif c == 'm':\r
+ state = State.M\r
+ elif state == State.Junk and c == 'm':\r
+ state = State.M\r
+ elif state == State.M and c == 'u':\r
+ state = State.U\r
+ elif state == State.U and c == 'l':\r
+ state = State.L\r
+ elif state == State.L and c == '(':\r
+ state = State.Open\r
+ pair = ["", ""]\r
+ elif state == State.Open:\r
+ if c == ',':\r
+ state = State.Second\r
+ elif c.isdigit() and len(pair[0]) < 3:\r
+ pair[0] += c\r
+ else:\r
+ state = State.Junk\r
+ elif state == State.Second:\r
+ if c == ')':\r
+ if enabled:\r
+ step = int(pair[0]), int(pair[1])\r
+ data.append(step)\r
+ state = State.Junk\r
+ elif c.isdigit() and len(pair[1]) < 3:\r
+ pair[1] += c\r
+ else:\r
+ state = State.Junk\r
+ else:\r
+ state = State.Junk\r
+\r
+ return Problem(data)\r
--- /dev/null
+import sys
+import argparse
+from . import Problem
+
+
+def main(args=None):
+ parser = argparse.ArgumentParser(description="AOC 2024 day 3")
+ parser.add_argument('filename', type=str)
+ parser.add_argument('-d', '--debug', action='store_true')
+ parser.add_argument('-2', '--part2', action='store_true')
+ options = parser.parse_args(args)
+
+ with open(options.filename) as f:
+ problem = Problem.from_stream(f, options.part2)
+ if options.debug:
+ for row in problem.data:
+ print(row)
+ print(f"result {problem.run()}")
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv[1:]))
--- /dev/null
+from enum import Enum\r
+from dataclasses import dataclass\r
+from typing import List\r
+\r
+\r
+@dataclass\r
+class Problem:\r
+ data: List[str]\r
+ word: str = 'XMAS'\r
+\r
+ def run(self, part2: bool = False) -> int:\r
+ if part2:\r
+ return self.run_part2()\r
+ else:\r
+ return self.run_part1()\r
+\r
+ def run_part1(self) -> int:\r
+ result = 0\r
+ rows = len(self.data)\r
+ cols = len(self.data[0])\r
+ for r in range(rows):\r
+ for c in range(cols):\r
+ result += self.wordcount(r, c)\r
+ return result\r
+\r
+ def wordcount(self, row: int, col: int) -> int:\r
+ """if the current position is the start of our word """\r
+ result = 0\r
+ if self.data[row][col] == self.word[0]:\r
+ result += self.find_word(row, col, self.word[1])\r
+ return result\r
+\r
+ def find_word(self, row: int, col: int, want: str) -> int:\r
+ """check the Moore neighbourhood for the given letter and return\r
+ the number of words starting here."""\r
+ count = 0\r
+ for dr in [-1, 0, 1]:\r
+ r = row + dr\r
+ if r >= 0 and r < len(self.data):\r
+ for dc in [-1, 0, 1]:\r
+ c = col + dc\r
+ if c >= 0 and c < len(self.data[0]):\r
+ letter = self.data[r][c]\r
+ if letter == want:\r
+ dir = (dr, dc)\r
+ if self.verify((r, c), dir, 2):\r
+ count += 1\r
+ return count\r
+\r
+ def verify(self, pos: tuple, dir: tuple, start: int) -> bool:\r
+ """Given a position and direction vector check that we have a full\r
+ word returning True if so or False if not."""\r
+ for n in range(start, len(self.word)):\r
+ newr = pos[0] + dir[0]\r
+ newc = pos[1] + dir[1]\r
+ if newr >= 0 and newr < len(self.data) and newc >= 0 and newc < len(self.data[0]):\r
+ pos = (newr, newc)\r
+ check = self.data[pos[0]][pos[1]]\r
+ if self.word[n] != check:\r
+ return False\r
+ else:\r
+ return False\r
+ return True\r
+\r
+ def run_part2(self) -> int:\r
+ result = 0\r
+ rows = len(self.data)\r
+ cols = len(self.data[0])\r
+ for r in range(rows):\r
+ for c in range(cols):\r
+ if self.data[r][c] == 'A':\r
+ result += self.find_mas(r, c)\r
+ return result\r
+\r
+ def find_mas(self, row: int, col: int) -> int:\r
+ count = 0\r
+ for dr in [-1, 1]:\r
+ r = row + dr\r
+ r2 = row - dr\r
+ if r >= 0 and r < len(self.data) and r2 >= 0 and r2 < len(self.data):\r
+ for dc in [-1, 1]:\r
+ c = col + dc\r
+ c2 = col - dc\r
+ if c >= 0 and c < len(self.data[0]) and c2 >= 0 and c2 < len(self.data):\r
+ letter = self.data[r][c]\r
+ opposite = self.data[r2][c2]\r
+ if letter == 'M' and opposite == 'S':\r
+ # print((row, col), (r, c), (r2, c2), letter, 'A', opposite)\r
+ count += 1\r
+ return 1 if count == 2 else 0\r
+\r
+ @staticmethod\r
+ def from_stream(stream) -> 'Problem':\r
+ data = [line.strip() for line in stream.readlines()]\r
+ return Problem(data)\r
--- /dev/null
+import sys
+import argparse
+from . import Problem
+
+
+def main(args=None):
+ parser = argparse.ArgumentParser(description="AOC 2024 day 4")
+ parser.add_argument('filename', type=str)
+ parser.add_argument('-d', '--debug', action='store_true')
+ parser.add_argument('-2', '--part2', action='store_true')
+ options = parser.parse_args(args)
+
+ with open(options.filename) as f:
+ problem = Problem.from_stream(f)
+ if options.debug:
+ for row in problem.data:
+ print(row)
+
+ print(f"result {problem.run(part2=options.part2)}")
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv[1:]))
--- /dev/null
+from dataclasses import dataclass
+from enum import Enum
+from collections import defaultdict
+from typing import List
+
+
+class State(Enum):
+ Rules = 0,
+ Updates = 1
+
+
+@dataclass
+class Problem:
+ rules: List[int]
+ updates: List[int]
+ preceding: defaultdict
+
+ _disordered = []
+
+ def run(self) -> int:
+ result = sum([self.validate(ndx, pageset) \
+ for ndx, pageset in enumerate(self.updates)])
+ result2 = [self.revalidate(ndx) for ndx in self._disordered]
+ return result
+
+ def validate(self, update_index: int, pageset: List[int]) -> int:
+ """If the pageset is valid, return the middle page number or 0 if invalid"""
+ # pageset = self.updates[update_index]
+ disallowed = set()
+ for page in pageset:
+ if page in disallowed:
+ self._disordered.append(update_index)
+ return 0
+ disallowed |= self.preceding[page]
+ return pageset[len(pageset)//2]
+
+ def revalidate(self, update_index: int) -> int:
+ pageset = self.updates[update_index]
+ # for page in pageset:
+ pass
+
+ @staticmethod
+ def from_stream(stream) -> 'Problem':
+ state = State.Rules
+ rules = []
+ updates = []
+ for line in stream:
+ line = line.strip()
+ if len(line) == 0:
+ if state == State.Rules:
+ state = State.Updates
+ continue
+ if state == State.Rules:
+ rule = [int(a) for a in [x for x in line.split('|')]]
+ rules.append(rule)
+ elif state == State.Updates:
+ pageset = [int(p) for p in line.split(',')]
+ updates.append(pageset)
+
+ preceding = defaultdict(set)
+ for a, b in rules:
+ preceding[b].add(a)
+ return Problem(rules, updates, preceding)
--- /dev/null
+import sys
+import argparse
+from . import Problem
+
+
+def main(args=None):
+ parser = argparse.ArgumentParser(description="AOC 2024 day 5")
+ parser.add_argument('filename', type=str)
+ parser.add_argument('-d', '--debug', action='store_true')
+ parser.add_argument('-2', '--part2', action='store_true')
+ options = parser.parse_args(args)
+
+ with open(options.filename) as f:
+ problem = Problem.from_stream(f)
+ if options.debug:
+ for rule in problem.rules:
+ print(rule)
+ for update in problem.updates:
+ print(update)
+
+ print(f"result {problem.run()}")
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv[1:]))
--- /dev/null
+import unittest
+import os
+from . import Problem
+
+
+class TestProblem(unittest.TestCase):
+ testfile = os.path.join(os.path.dirname(__file__), r'data', r'test_input')
+ def test_from_str(self):
+ with open(self.testfile, 'rt') as fd:
+ problem = Problem.from_stream(fd)
+ self.assertEqual(len(problem.rules), 21)
+ self.assertSequenceEqual(problem.rules[0], [47, 53])
+ self.assertEqual(len(problem.updates), 6)
+ self.assertSequenceEqual(problem.updates[0], [75, 47, 61, 53, 29])
+
+
+if __name__ == '__main__':
+ unittest.main()
--- /dev/null
+from dataclasses import dataclass
+from typing import List, Tuple
+from copy import copy, deepcopy
+from enum import IntFlag
+
+
+class Direction(IntFlag):
+ Left = 1
+ Up = 2
+ Right = 4
+ Down = 8
+
+
+@dataclass
+class Guard:
+ row: int
+ col: int
+ dir: str
+
+ def move(self):
+ if self.dir == '<':
+ self.col -= 1
+ elif self.dir == '^':
+ self.row -= 1
+ elif self.dir == '>':
+ self.col += 1
+ elif self.dir == 'v':
+ self.row += 1
+
+DIRMAP = {
+ '<': Direction.Left,
+ '^': Direction.Up,
+ '>': Direction.Right,
+ 'v': Direction.Down
+}
+
+@dataclass
+class Problem:
+ data: List[int]
+ start: Guard
+
+ def run(self, trace=False) -> int:
+ count = 1
+ guard = copy(self.start)
+ self.data[guard.row][guard.col] = '0'
+ while True:
+ g = copy(guard)
+ g.move()
+ if self.outside(g):
+ break
+ next_tile = self.data[g.row][g.col]
+ if next_tile == '#':
+ if guard.dir == '^':
+ guard.dir = '>'
+ elif guard.dir == '>':
+ guard.dir = 'v'
+ elif guard.dir == 'v':
+ guard.dir = '<'
+ elif guard.dir == '<':
+ guard.dir = '^'
+ else:
+ if self.data[g.row][g.col] in ('.', '0'):
+ count += 1
+ self.data[g.row][g.col] = '%c' % 0x60
+
+ # if the new tile has already been visited in the same
+ # direction then we are on a loop.
+ tileval = ord(self.data[g.row][g.col])
+ if tileval & DIRMAP[g.dir]:
+ self.data[g.row][g.col] = 'O'
+ return -1
+
+ tileval |= DIRMAP[g.dir]
+ self.data[g.row][g.col] = '%c' % tileval # mark as visited
+ guard = g
+
+ if self.outside(guard):
+ break
+ return count
+
+ def next(self) -> str:
+ g = copy(self.guard)
+ g.move()
+ return self.data[g.row][g.col]
+
+ def outside(self, guard: Guard) -> bool:
+ return (
+ guard.row < 0
+ or guard.col < 0
+ or guard.row >= len(self.data)
+ or guard.col >= len(self.data[0])
+ )
+
+ def unblocked(self):
+ for row, tiles in enumerate(self.data):
+ for col, tile in enumerate(tiles):
+ if tile == '.':
+ yield (row, col)
+
+ def print_map(self, score) -> None:
+ print("".join(self.data[0]), score)
+ for row in self.data[1:]:
+ print("".join(row))
+ print()
+
+ def run_part2(self, debug=False) -> int:
+ count = 0
+ old = deepcopy(self.data)
+ for coord in self.unblocked():
+ self.data = deepcopy(old)
+ row, col = coord
+ self.data[row][col] = '#'
+ score = self.run(trace=debug)
+ if debug:
+ self.print_map(score)
+ if score < 0:
+ count += 1
+ return count
+
+ @staticmethod
+ def from_stream(stream) -> 'Problem':
+ data = []
+ start = None
+ for row, line in enumerate(stream):
+ line = line.strip()
+ col = [n for n, c in enumerate(line) if c in ('<', '^', '>', 'v')]
+ if col:
+ start = (row, col[0])
+ data.append([c for c in line])
+ assert data
+ assert start is not None
+ guard = Guard(start[0], start[1], data[start[0]][start[1]])
+ return Problem(data, guard)
--- /dev/null
+import sys
+import argparse
+from . import Problem
+
+
+def main(args=None):
+ parser = argparse.ArgumentParser(description="AOC 2024 day 6")
+ parser.add_argument('filename', type=str)
+ parser.add_argument('-d', '--debug', action='store_true')
+ parser.add_argument('-t', '--trace', action='store_true')
+ parser.add_argument('-1', '--part1', action='store_true')
+ parser.add_argument('-2', '--part2', action='store_true')
+ options = parser.parse_args(args)
+
+ with open(options.filename) as f:
+ problem = Problem.from_stream(f)
+ if options.debug and options.part1:
+ print(problem.start)
+ for row in problem.data:
+ print("".join(row))
+
+ if options.part1:
+ print(f"result {problem.run(trace=options.trace)}")
+ if options.part2:
+ print(f"result {problem.run_part2(debug=options.debug)}")
+
+
+ if options.debug and options.part1:
+ for row in problem.data:
+ print("".join(row))
+
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv[1:]))
--- /dev/null
+from dataclasses import dataclass
+from typing import List, Tuple
+
+
+__title__ = 'Day 7: Bridge Repair'
+
+
+@dataclass
+class Problem:
+ data: List[Tuple[int, List[int]]]
+
+ def run(self, part2=False) -> int:
+ total = 0
+ for result, nums in self.data:
+ total += Problem.validate(result, nums, part2)
+ return total
+
+ @staticmethod
+ def validate(result: int, nums: List[int], with_concat=False) -> int:
+ queue = [(1, nums[0])]
+ while queue:
+ index, value = queue.pop()
+ if index == len(nums):
+ if value == result:
+ return result
+ continue
+ calcs = [value + nums[index],
+ value * nums[index]]
+ if with_concat:
+ calcs.append(
+ int(str(value) + str(nums[index])))
+ for calc in calcs:
+ if calc <= result:
+ queue.append( (index + 1, calc))
+ return 0
+
+ @staticmethod
+ def from_stream(stream) -> 'Problem':
+ data = []
+ for line in stream:
+ result, nums = line.strip().split(':')
+ result = int(result)
+ nums = [int(n) for n in nums.strip().split(' ')]
+ data.append((result, tuple(nums)))
+ return Problem(data)
--- /dev/null
+import sys
+import argparse
+from . import Problem
+
+
+def main(args=None):
+ parser = argparse.ArgumentParser(description="AOC 2024 day 7")
+ parser.add_argument('filename', type=str)
+ parser.add_argument('-d', '--debug', action='store_true')
+ parser.add_argument('-2', '--part2', action='store_true')
+ options = parser.parse_args(args)
+
+ with open(options.filename) as f:
+ problem = Problem.from_stream(f)
+ if options.debug:
+ for row in problem.data:
+ print(row)
+
+ print(f"result {problem.run(part2=options.part2)}")
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv[1:]))
--- /dev/null
+from dataclasses import dataclass
+from typing import List
+
+
+@dataclass
+class Problem:
+ data: List[int]
+
+ def run(self) -> int:
+ return 0
+
+ @staticmethod
+ def from_stream(stream) -> 'Problem':
+ data = [line.strip() for line in stream]
+ return Problem(data)
--- /dev/null
+import sys
+import argparse
+from . import Problem
+
+
+def main(args=None):
+ parser = argparse.ArgumentParser(description="AOC 2024 day ?")
+ parser.add_argument('filename', type=str)
+ parser.add_argument('-d', '--debug', action='store_true')
+ options = parser.parse_args(args)
+
+ with open(options.filename) as f:
+ problem = Problem.from_stream(f)
+ if options.debug:
+ for row in problem.data:
+ print(row)
+
+ print(f"result {problem.run()}")
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv[1:]))