Compare commits
3 Commits
9df3c2662d
...
12fde697d3
Author | SHA1 | Date |
---|---|---|
|
12fde697d3 | |
|
1aa90ba9db | |
|
3ca6745fb1 |
|
@ -0,0 +1,271 @@
|
|||
use crate::util::maps::Direction;
|
||||
use std::collections::HashMap;
|
||||
|
||||
const DPADS: usize = 25;
|
||||
|
||||
pub fn day21() {
|
||||
let sequences: Vec<String> = crate::input(21).lines().map(|l| l.to_string()).collect();
|
||||
|
||||
let mut memo: HashMap<(String, usize), u64> = HashMap::new();
|
||||
fn deeper(s: String, depth: usize, memo: &mut HashMap<(String, usize), u64>) -> u64 {
|
||||
// if this is already in deep lut, return
|
||||
if let Some(&expanded_size) = memo.get(&(s.to_string(), depth)) {
|
||||
return expanded_size;
|
||||
}
|
||||
// if this is two characters at the end, add expanded size to deep lut, and return
|
||||
if depth == 1 {
|
||||
let expanded_size = expand_dpad_string(&s).len() as u64;
|
||||
memo.insert((s, depth), expanded_size);
|
||||
return expanded_size;
|
||||
}
|
||||
// otherwise 1. expand, 2. split into substrings, and 3. recur for each substring
|
||||
let expanded = expand_dpad_string(&s);
|
||||
// 2. get substrings split by A (but including the A characters at the end of each substring)
|
||||
let substrings: Vec<String> = expanded
|
||||
.split_terminator("A")
|
||||
.map(|substring| {
|
||||
let mut substring = substring.to_string();
|
||||
substring.push_str("A");
|
||||
substring
|
||||
})
|
||||
.collect();
|
||||
let mut total: u64 = 0;
|
||||
for substring in substrings {
|
||||
let final_expanded_size = deeper(substring, depth - 1, memo);
|
||||
total += final_expanded_size;
|
||||
}
|
||||
memo.insert((s, depth), total);
|
||||
total
|
||||
}
|
||||
|
||||
let mut complexity: u64 = 0;
|
||||
for sequence in sequences.iter() {
|
||||
let first_dpad_steps = expand_numpad_string(sequence);
|
||||
let length = deeper(first_dpad_steps.clone(), DPADS, &mut memo);
|
||||
let numeric = sequence
|
||||
.chars()
|
||||
.filter(|c| c.is_numeric())
|
||||
.collect::<String>()
|
||||
.parse::<u64>()
|
||||
.unwrap();
|
||||
complexity += length * numeric;
|
||||
}
|
||||
|
||||
println!("Total Complexity: {}", complexity);
|
||||
}
|
||||
|
||||
fn expand_dpad_string(s: &str) -> String {
|
||||
let sequence: Vec<usize> = s.chars().map(char_to_directional_keypad_index).collect();
|
||||
sequence_to_dpad_steps(
|
||||
&directional_keypad(),
|
||||
sequence,
|
||||
char_to_directional_keypad_index('A'),
|
||||
)
|
||||
}
|
||||
|
||||
fn expand_numpad_string(s: &str) -> String {
|
||||
let sequence: Vec<usize> = s.chars().map(char_to_numerical_keypad_index).collect();
|
||||
sequence_to_dpad_steps(
|
||||
&numerical_keypad(),
|
||||
sequence,
|
||||
char_to_numerical_keypad_index('A'),
|
||||
)
|
||||
}
|
||||
|
||||
fn sequence_to_dpad_steps(keypad: &Keypad, sequence: Vec<usize>, start: usize) -> String {
|
||||
let mut sequence = sequence.clone();
|
||||
sequence.insert(0, start);
|
||||
|
||||
let mut total_path = "".to_string();
|
||||
|
||||
for w in sequence.windows(2) {
|
||||
let best_path = shortest_paths(keypad, w[0], w[1])
|
||||
.into_iter()
|
||||
// convert paths to strings
|
||||
.map(|path| path.iter().map(direction_to_char).collect::<String>())
|
||||
// get the most efficient path (there are certain arbitrary rules for efficiency)
|
||||
.max_by_key(|path| efficiency_score(path.as_str()))
|
||||
.unwrap();
|
||||
total_path.push_str(&best_path);
|
||||
total_path.push_str("A");
|
||||
}
|
||||
|
||||
total_path
|
||||
}
|
||||
|
||||
fn shortest_paths(keypad: &Keypad, start: usize, end: usize) -> Vec<Vec<Direction>> {
|
||||
fn path_costs(keypad: &Keypad, p: usize, cost_so_far: u64, costs: &mut HashMap<usize, u64>) {
|
||||
if cost_so_far < costs.get(&p).copied().unwrap_or(u64::MAX) {
|
||||
costs.insert(p, cost_so_far);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
for &adjacent in keypad.adjacents(p) {
|
||||
path_costs(keypad, adjacent, cost_so_far + 1, costs);
|
||||
}
|
||||
}
|
||||
let mut costs: HashMap<usize, u64> = HashMap::new();
|
||||
path_costs(keypad, end, 0, &mut costs);
|
||||
|
||||
fn find_paths(
|
||||
keypad: &Keypad,
|
||||
start: usize,
|
||||
costs: &HashMap<usize, u64>,
|
||||
) -> Vec<Vec<Direction>> {
|
||||
if costs[&start] == 0 {
|
||||
return vec![vec![]];
|
||||
}
|
||||
keypad.buttons[start]
|
||||
.links
|
||||
.iter()
|
||||
.filter(|(_, adjacent)| costs[adjacent] < costs[&start])
|
||||
.flat_map(|(&direction, &adjacent)| {
|
||||
let mut paths = find_paths(keypad, adjacent, costs);
|
||||
paths.iter_mut().for_each(|path| {
|
||||
path.insert(0, direction);
|
||||
});
|
||||
paths
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
find_paths(keypad, start, &costs)
|
||||
}
|
||||
|
||||
fn efficiency_score(s: &str) -> u64 {
|
||||
let repetition_score = s
|
||||
.chars()
|
||||
.collect::<Vec<char>>()
|
||||
.windows(2)
|
||||
.filter(|w| w[0] == w[1])
|
||||
.count() as u64;
|
||||
let mut order_mishaps: u64 = 0;
|
||||
for ss in s.split("A") {
|
||||
order_mishaps += ss.matches(">v").count() as u64;
|
||||
order_mishaps += ss.matches("^<").count() as u64;
|
||||
order_mishaps += ss.matches("v<").count() as u64;
|
||||
order_mishaps += ss.matches(">^").count() as u64;
|
||||
}
|
||||
|
||||
(repetition_score << 32) | (u32::MAX as u64 - order_mishaps)
|
||||
}
|
||||
|
||||
struct Keypad {
|
||||
buttons: Vec<Button>,
|
||||
}
|
||||
|
||||
impl Keypad {
|
||||
fn adjacents(&self, button: usize) -> impl Iterator<Item = &usize> {
|
||||
self.buttons[button].links.values()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
struct Button {
|
||||
links: HashMap<Direction, usize>,
|
||||
}
|
||||
|
||||
fn char_to_numerical_keypad_index(c: char) -> usize {
|
||||
match c {
|
||||
'7' => 0,
|
||||
'8' => 1,
|
||||
'9' => 2,
|
||||
'4' => 3,
|
||||
'5' => 4,
|
||||
'6' => 5,
|
||||
'1' => 6,
|
||||
'2' => 7,
|
||||
'3' => 8,
|
||||
'0' => 9,
|
||||
'A' => 10,
|
||||
_ => panic!("The given char {c} has no place on a numerical keypad."),
|
||||
}
|
||||
}
|
||||
|
||||
fn char_to_directional_keypad_index(c: char) -> usize {
|
||||
match c {
|
||||
'^' => 0,
|
||||
'A' => 1,
|
||||
'<' => 2,
|
||||
'v' => 3,
|
||||
'>' => 4,
|
||||
_ => panic!("The given char {c} has no place on a directional keypad."),
|
||||
}
|
||||
}
|
||||
|
||||
fn numerical_keypad() -> Keypad {
|
||||
let mut buttons = vec![Button::default(); 11];
|
||||
// 7
|
||||
buttons[0].links.insert(Direction::East, 1);
|
||||
buttons[0].links.insert(Direction::South, 3);
|
||||
// 8
|
||||
buttons[1].links.insert(Direction::West, 0);
|
||||
buttons[1].links.insert(Direction::East, 2);
|
||||
buttons[1].links.insert(Direction::South, 4);
|
||||
// 9
|
||||
buttons[2].links.insert(Direction::West, 1);
|
||||
buttons[2].links.insert(Direction::South, 5);
|
||||
// 4
|
||||
buttons[3].links.insert(Direction::East, 4);
|
||||
buttons[3].links.insert(Direction::North, 0);
|
||||
buttons[3].links.insert(Direction::South, 6);
|
||||
// 5
|
||||
buttons[4].links.insert(Direction::West, 3);
|
||||
buttons[4].links.insert(Direction::East, 5);
|
||||
buttons[4].links.insert(Direction::South, 7);
|
||||
buttons[4].links.insert(Direction::North, 1);
|
||||
// 6
|
||||
buttons[5].links.insert(Direction::West, 4);
|
||||
buttons[5].links.insert(Direction::South, 8);
|
||||
buttons[5].links.insert(Direction::North, 2);
|
||||
// 1
|
||||
buttons[6].links.insert(Direction::North, 3);
|
||||
buttons[6].links.insert(Direction::East, 7);
|
||||
// 2
|
||||
buttons[7].links.insert(Direction::West, 6);
|
||||
buttons[7].links.insert(Direction::East, 8);
|
||||
buttons[7].links.insert(Direction::North, 4);
|
||||
buttons[7].links.insert(Direction::South, 9);
|
||||
// 3
|
||||
buttons[8].links.insert(Direction::North, 5);
|
||||
buttons[8].links.insert(Direction::West, 7);
|
||||
buttons[8].links.insert(Direction::South, 10);
|
||||
// 0
|
||||
buttons[9].links.insert(Direction::North, 7);
|
||||
buttons[9].links.insert(Direction::East, 10);
|
||||
// A
|
||||
buttons[10].links.insert(Direction::West, 9);
|
||||
buttons[10].links.insert(Direction::North, 8);
|
||||
|
||||
Keypad { buttons }
|
||||
}
|
||||
|
||||
fn directional_keypad() -> Keypad {
|
||||
let mut buttons = vec![Button::default(); 5];
|
||||
// ^
|
||||
buttons[0].links.insert(Direction::East, 1);
|
||||
buttons[0].links.insert(Direction::South, 3);
|
||||
// A
|
||||
buttons[1].links.insert(Direction::West, 0);
|
||||
buttons[1].links.insert(Direction::South, 4);
|
||||
// <
|
||||
buttons[2].links.insert(Direction::East, 3);
|
||||
// v
|
||||
buttons[3].links.insert(Direction::East, 4);
|
||||
buttons[3].links.insert(Direction::North, 0);
|
||||
buttons[3].links.insert(Direction::West, 2);
|
||||
// >
|
||||
buttons[4].links.insert(Direction::North, 1);
|
||||
buttons[4].links.insert(Direction::West, 3);
|
||||
|
||||
Keypad { buttons }
|
||||
}
|
||||
|
||||
fn direction_to_char(direction: &Direction) -> char {
|
||||
match direction {
|
||||
Direction::North => '^',
|
||||
Direction::South => 'v',
|
||||
Direction::West => '<',
|
||||
Direction::East => '>',
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
type Sequence = (i8, i8, i8, i8);
|
||||
|
||||
pub fn day22() {
|
||||
let initial_numbers: Vec<u64> = crate::input(22)
|
||||
.lines()
|
||||
.map(|x| x.parse().unwrap())
|
||||
.collect();
|
||||
|
||||
let mut total_sequence_values: HashMap<Sequence, u64> = HashMap::new();
|
||||
for &vendor_secret in initial_numbers.iter() {
|
||||
let price_data = price_data(vendor_secret);
|
||||
let mut seen_sequences: HashSet<Sequence> = HashSet::new();
|
||||
for window in price_data.windows(4) {
|
||||
let sequence: Sequence = (window[0].1, window[1].1, window[2].1, window[3].1);
|
||||
let price = window[3].0;
|
||||
if !seen_sequences.contains(&sequence) {
|
||||
*total_sequence_values.entry(sequence.clone()).or_insert(0) += price as u64;
|
||||
seen_sequences.insert(sequence);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let (best_sequence, best_price) = total_sequence_values
|
||||
.into_iter()
|
||||
.max_by_key(|&(_, price)| price)
|
||||
.unwrap();
|
||||
println!("Best Sequence: {:?}, Price: {}", best_sequence, best_price);
|
||||
|
||||
let mut total: u64 = 0;
|
||||
for mut number in initial_numbers {
|
||||
for _ in 0..2000 {
|
||||
number = evolve_secret_number(number);
|
||||
}
|
||||
total += number;
|
||||
}
|
||||
println!("Sum of Final Secret Numbers: {total}");
|
||||
}
|
||||
|
||||
fn price_data(mut secret: u64) -> Vec<(u8, i8)> {
|
||||
let mut prices = vec![last_digit(secret)];
|
||||
for _ in 0..2000 {
|
||||
secret = evolve_secret_number(secret);
|
||||
prices.push(last_digit(secret));
|
||||
}
|
||||
let mut ret: Vec<(u8, i8)> = vec![];
|
||||
for i in 1..prices.len() {
|
||||
let change = prices[i] as i8 - prices[i - 1] as i8;
|
||||
ret.push((prices[i], change));
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
fn last_digit(number: u64) -> u8 {
|
||||
(number % 10) as u8
|
||||
}
|
||||
|
||||
fn evolve_secret_number(mut number: u64) -> u64 {
|
||||
number = (number ^ (64 * number)) % 16777216;
|
||||
number = (number ^ (number / 32)) % 16777216;
|
||||
number = (number ^ (number * 2048)) % 16777216;
|
||||
number
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
use itertools::Itertools;
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
fmt::{Display, Write},
|
||||
};
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Ord, PartialOrd)]
|
||||
struct Computer(char, char);
|
||||
|
||||
pub fn day23() {
|
||||
let input_connections: Vec<(Computer, Computer)> = parse_input(&crate::input(23));
|
||||
let connections = {
|
||||
let mut connections: HashMap<Computer, Vec<Computer>> = HashMap::new();
|
||||
for &(a, b) in input_connections.iter() {
|
||||
connections.entry(a).or_insert(vec![]).push(b);
|
||||
connections.entry(b).or_insert(vec![]).push(a);
|
||||
}
|
||||
connections
|
||||
};
|
||||
|
||||
fn collect_cliques(
|
||||
connections: &HashMap<Computer, Vec<Computer>>,
|
||||
clique: &Vec<Computer>,
|
||||
visited: &mut HashSet<Vec<Computer>>,
|
||||
) -> Vec<Vec<Computer>> {
|
||||
let mut bigger_cliques: Vec<Vec<Computer>> = vec![];
|
||||
bigger_cliques.push(clique.clone());
|
||||
for (computer, friends) in connections.iter() {
|
||||
if clique.contains(computer) {
|
||||
continue;
|
||||
}
|
||||
if clique.iter().all(|groupie| friends.contains(groupie)) {
|
||||
let mut new_clique = clique.clone();
|
||||
new_clique.push(*computer);
|
||||
new_clique.sort();
|
||||
if visited.contains(&new_clique) {
|
||||
continue;
|
||||
} else {
|
||||
visited.insert(new_clique.clone());
|
||||
}
|
||||
let mut new_bigger_cliques = collect_cliques(connections, &new_clique, visited);
|
||||
bigger_cliques.append(&mut new_bigger_cliques);
|
||||
}
|
||||
}
|
||||
bigger_cliques
|
||||
}
|
||||
let mut visited = HashSet::new();
|
||||
let mut all_cliques: Vec<Vec<Computer>> = connections
|
||||
.keys()
|
||||
.map(|&c| vec![c])
|
||||
.flat_map(|seed| collect_cliques(&connections, &seed, &mut visited))
|
||||
.collect();
|
||||
all_cliques.sort_by_key(|group| group.len());
|
||||
let mut maximum_clique = all_cliques.last().unwrap().clone();
|
||||
println!("The biggest clique's size is {}.", maximum_clique.len());
|
||||
maximum_clique.sort();
|
||||
println!(
|
||||
"The biggest clique's password is {}",
|
||||
maximum_clique.iter().map(|c| format!("{c}")).join(",")
|
||||
);
|
||||
|
||||
let t_trios = all_cliques
|
||||
.iter()
|
||||
.filter(|clique| clique.len() == 3)
|
||||
.filter(|trio| trio.iter().any(|c| c.0 == 't'))
|
||||
.count();
|
||||
println!("{t_trios} trios contain a friend who starts with t.");
|
||||
}
|
||||
|
||||
impl Computer {
|
||||
fn from_string(s: &str) -> Self {
|
||||
Self(s.chars().nth(0).unwrap(), s.chars().nth(1).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Computer {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_char(self.0)?;
|
||||
f.write_char(self.1)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_input(input: &str) -> Vec<(Computer, Computer)> {
|
||||
input
|
||||
.lines()
|
||||
.map(|line| {
|
||||
line.split("-")
|
||||
.map(Computer::from_string)
|
||||
.collect_tuple()
|
||||
.unwrap()
|
||||
})
|
||||
.collect()
|
||||
}
|
|
@ -24,8 +24,14 @@ mod day18;
|
|||
mod day19;
|
||||
#[allow(dead_code)]
|
||||
mod day2;
|
||||
#[allow(dead_code)]
|
||||
mod day20;
|
||||
#[allow(dead_code)]
|
||||
mod day21;
|
||||
#[allow(dead_code)]
|
||||
mod day22;
|
||||
mod day23;
|
||||
#[allow(dead_code)]
|
||||
mod day3;
|
||||
#[allow(dead_code)]
|
||||
mod day4;
|
||||
|
@ -41,7 +47,7 @@ mod day8;
|
|||
mod day9;
|
||||
|
||||
fn main() {
|
||||
day20::day20();
|
||||
day23::day23();
|
||||
}
|
||||
|
||||
pub fn input(day: u8) -> String {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::{fmt::Display, intrinsics::atomic_cxchgweak_acquire_acquire};
|
||||
use std::fmt::Display;
|
||||
|
||||
use itertools::Itertools;
|
||||
|
||||
|
|
Loading…
Reference in New Issue