Add 2024 Day 23

This commit is contained in:
Burnus 2024-12-23 11:16:09 +01:00
parent c99638f28d
commit 7520d4956b
5 changed files with 3655 additions and 0 deletions

View file

@ -0,0 +1,13 @@
[package]
name = "day23_lan_party"
version = "0.1.0"
edition = "2021"
[dependencies]
[dev-dependencies]
# criterion = "0.5.1"
[[bench]]
name = "test_benchmark"
harness = false

View file

@ -0,0 +1,109 @@
As The Historians wander around a secure area at Easter Bunny HQ, you come across posters for a [LAN party](https://en.wikipedia.org/wiki/LAN_party) scheduled for today! Maybe you can find it; you connect to a nearby [datalink port](/2016/day/9) and download a map of the local network (your puzzle input).
The network map provides a list of every *connection between two computers*. For example:
```
kh-tc
qp-kh
de-cg
ka-co
yn-aq
qp-ub
cg-tb
vc-aq
tb-ka
wh-tc
yn-cg
kh-ub
ta-co
de-co
tc-td
tb-wq
wh-td
ta-ka
td-qp
aq-cg
wq-ub
ub-vc
de-ta
wq-aq
wq-vc
wh-yn
ka-de
kh-ta
co-tc
wh-qp
tb-vc
td-yn
```
Each line of text in the network map represents a single connection; the line `kh-tc` represents a connection between the computer named `kh` and the computer named `tc`. Connections aren't directional; `tc-kh` would mean exactly the same thing.
LAN parties typically involve multiplayer games, so maybe you can locate it by finding groups of connected computers. Start by looking for *sets of three computers* where each computer in the set is connected to the other two computers.
In this example, there are `12` such sets of three inter-connected computers:
```
aq,cg,yn
aq,vc,wq
co,de,ka
co,de,ta
co,ka,ta
de,ka,ta
kh,qp,ub
qp,td,wh
tb,vc,wq
tc,td,wh
td,wh,yn
ub,vc,wq
```
If the Chief Historian is here, *and* he's at the LAN party, it would be best to know that right away. You're pretty sure his computer's name starts with `t`, so consider only sets of three computers where at least one computer's name starts with `t`. That narrows the list down to `*7*` sets of three inter-connected computers:
```
co,de,ta
co,ka,ta
de,ka,ta
qp,td,wh
tb,vc,wq
tc,td,wh
td,wh,yn
```
Find all the sets of three inter-connected computers. *How many contain at least one computer with a name that starts with `t`?*
Your puzzle answer was `1304`.
\--- Part Two ---
----------
There are still way too many results to go through them all. You'll have to find the LAN party another way and go there yourself.
Since it doesn't seem like any employees are around, you figure they must all be at the LAN party. If that's true, the LAN party will be the *largest set of computers that are all connected to each other*. That is, for each computer at the LAN party, that computer will have a connection to every other computer at the LAN party.
In the above example, the largest set of computers that are all connected to each other is made up of `co`, `de`, `ka`, and `ta`. Each computer in this set has a connection to every other computer in the set:
```
ka-co
ta-co
de-co
ta-ka
de-ta
ka-de
```
The LAN party posters say that the *password* to get into the LAN party is the name of every computer at the LAN party, sorted alphabetically, then joined together with commas. (The people running the LAN party are clearly a bunch of nerds.) In this example, the password would be `*co,de,ka,ta*`.
*What is the password to get into the LAN party?*
Your puzzle answer was `ao,es,fe,if,in,io,ky,qq,rd,rn,rv,vc,vl`.
Both parts of this puzzle are complete! They provide two gold stars: \*\*
At this point, you should [return to your Advent calendar](/2024) and try another puzzle.
If you still want to see it, you can [get your puzzle input](23/input).

View file

@ -0,0 +1,121 @@
use core::fmt::Display;
use std::collections::HashSet;
#[derive(Debug, PartialEq, Eq)]
pub enum ParseError<'a> {
ComputerName(&'a str),
LineMalformed(&'a str),
}
impl Display for ParseError<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::ComputerName(e) => write!(f, "Computer name doesn't consist of two ascii characters: \"{e}\"."),
Self::LineMalformed(v) => write!(f, "Line is malformed: {v}"),
}
}
}
type Connection = u32;
fn try_connection_from(value: &str) -> Result<Connection, ParseError> {
if let Some((lhs, rhs)) = value.split_once('-') {
if lhs.len() != 2 {
return Err(ParseError::ComputerName(lhs));
}
let lhs_bytes = lhs.as_bytes();
let lhs = ((lhs_bytes[0] as u32) << 8) | (lhs_bytes[1] as u32);
if rhs.len() != 2 {
return Err(ParseError::ComputerName(rhs));
}
let rhs_bytes = rhs.as_bytes();
let rhs = ((rhs_bytes[0] as u32) << 8) | rhs_bytes[1] as u32;
Ok((lhs.min(rhs) << 16) | (lhs.max(rhs)))
} else {
Err(ParseError::LineMalformed(value))
}
}
fn triples(conns: &HashSet<Connection>) -> Vec<(u16, u16, u16)> {
let mut res = Vec::new();
conns.iter().for_each(|conn| {
let (lhs, rhs) = ((conn >> 16) as u16, (conn & 0xFFFF) as u16);
conns.iter().filter(|&&other| (other >> 16) as u16 == lhs).for_each(|new| {
let new = new & 0xFFFF;
if conns.contains(&(((rhs as u32) << 16) | new)) {
res.push((lhs, rhs, new as u16));
}
});
});
res
}
fn largest_clique(triples: &[(u16, u16, u16)], conns: &HashSet<u32>) -> Vec<u16> {
let mut cliques: Vec<_> = triples.iter().map(|(l, m, r)| vec![*l, *m, *r]).collect();
let mut next_cliques = Vec::with_capacity(cliques.len());
while cliques.len() > 1 {
for clique in cliques.iter() {
conns.iter()
.filter(|&&conn| (conn >> 16) as u16 == clique[0])
.for_each(|&conn| {
let new = (conn & 0xFFFF) as u16;
if clique.iter().skip(1).all(|old| conns.contains(&((*old as u32) << 16 | new as u32))) {
let mut new_clique = clique.to_vec();
new_clique.push(new);
next_cliques.push(new_clique);
}
});
}
std::mem::swap(&mut cliques, &mut next_cliques);
next_cliques.clear();
}
cliques[0].to_vec()
}
fn password(computers: &[u16]) -> String {
let computers = computers.to_vec();
computers
.iter()
.map(|n| String::from_utf8(vec![(n >> 8) as u8, (n & 0xFF) as u8]).unwrap())
.collect::<Vec<_>>()
.join(",")
}
pub fn run(input: &str) -> Result<(usize, String), ParseError> {
const FIRST_LETTER_T: u16 = (b't' as u16) << 8;
let conns: HashSet<_> = input.lines().map(try_connection_from).collect::<Result<HashSet<_>, _>>()?;
let triples = triples(&conns);
let first = triples
.iter()
.filter(|(l, m, r)|
l & 0xFF00 == FIRST_LETTER_T ||
m & 0xFF00 == FIRST_LETTER_T ||
r & 0xFF00 == FIRST_LETTER_T)
.count();
let party = largest_clique(&triples, &conns);
let second = password(&party);
Ok((first, second))
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs::read_to_string;
fn read_file(name: &str) -> String {
read_to_string(name).expect(&format!("Unable to read file: {name}")[..]).trim().to_string()
}
#[test]
fn test_sample() {
let sample_input = read_file("tests/sample_input");
assert_eq!(run(&sample_input), Ok((7, "co,de,ka,ta".to_string())));
}
#[test]
fn test_challenge() {
let challenge_input = read_file("tests/challenge_input");
assert_eq!(run(&challenge_input), Ok((1304, "ao,es,fe,if,in,io,ky,qq,rd,rn,rv,vc,vl".to_string())));
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,32 @@
kh-tc
qp-kh
de-cg
ka-co
yn-aq
qp-ub
cg-tb
vc-aq
tb-ka
wh-tc
yn-cg
kh-ub
ta-co
de-co
tc-td
tb-wq
wh-td
ta-ka
td-qp
aq-cg
wq-ub
ub-vc
de-ta
wq-aq
wq-vc
wh-yn
ka-de
kh-ta
co-tc
wh-qp
tb-vc
td-yn