Skip to content

Commit 1fa77f3

Browse files
committed
day23 πŸ”‘
1 parent 0d7acd7 commit 1fa77f3

File tree

7 files changed

+276
-11
lines changed

7 files changed

+276
-11
lines changed

β€Ž2024/README.mdβ€Ž

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
# Advent of Code in Rust πŸ¦€
22

33
![AoC2024](https://img.shields.io/badge/Advent_of_Code-2024-8A2BE2)
4-
![Stars: 44](https://img.shields.io/badge/Stars-44⭐-blue)
5-
![Rust: 23](https://img.shields.io/badge/Rust-23-cyan?logo=Rust)
6-
![Python: 8](https://img.shields.io/badge/Python-8-cyan?logo=Python)
4+
![Stars: 46](https://img.shields.io/badge/Stars-46⭐-blue)
5+
![Rust: 24](https://img.shields.io/badge/Rust-24-cyan?logo=Rust)
6+
![Python: 9](https://img.shields.io/badge/Python-9-cyan?logo=Python)
77

8-
## 2024 ([Calendar](https://adventofcode.com/2024)) ([Solutions](../2024/)) : 44⭐
8+
## 2024 ([Calendar](https://adventofcode.com/2024)) ([Solutions](../2024/)) : 46⭐
99

1010
Puzzle | Stars | Languages
1111
---------------------------------------------------------------------- | ----- | -----------
@@ -31,3 +31,4 @@ Puzzle | Stars |
3131
[Day 20: Race Condition](https://adventofcode.com/2024/day/20) | ⭐⭐ | [![Rust](../scripts/assets/rust.png)](../2024/day20/day20.rs)
3232
[Day 21: Keypad Conundrum](https://adventofcode.com/2024/day/21) | ⭐⭐ | [![Rust](../scripts/assets/rust.png)](../2024/day21/day21.rs)
3333
[Day 22: Monkey Market](https://adventofcode.com/2024/day/22) | ⭐⭐ | [![Rust](../scripts/assets/rust.png)](../2024/day22/day22.rs)
34+
[Day 23: LAN Party](https://adventofcode.com/2024/day/23) | ⭐⭐ | [![Rust](../scripts/assets/rust.png)](../2024/day23/day23.rs) [![Python](../scripts/assets/python.png)](../2024/day23/day23.py)

β€Ž2024/day23/Cargo.tomlβ€Ž

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[package]
2+
name = "day23"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
aoc = { path = "../../aoc" }
8+
rustworkx-core = "*"
9+
petgraph = "*"
10+
11+
[[bin]]
12+
name = "day23"
13+
path = "day23.rs"

β€Ž2024/day23/day23.pyβ€Ž

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#!/usr/bin/env python3
2+
# [Day 23: LAN Party](https://adventofcode.com/2024/day/23)
3+
4+
from argparse import ArgumentParser
5+
from pathlib import Path
6+
7+
import networkx as nx
8+
9+
parser = ArgumentParser()
10+
parser.add_argument("-v", "--verbose", action="store_true")
11+
parser.add_argument("-t", "--test", action="store_true")
12+
parser.add_argument("filename", nargs="?", type=Path, default="input.txt")
13+
args = parser.parse_args()
14+
if args.test:
15+
args.filename = Path("test.txt")
16+
17+
data = args.filename.read_text().strip()
18+
lines = data.splitlines()
19+
20+
# init
21+
connections = []
22+
for line in lines:
23+
connections.append(tuple(line.strip().split("-")))
24+
25+
G = nx.Graph()
26+
G.add_edges_from(connections)
27+
28+
# part 1
29+
triangles = [list(triangle) for triangle in nx.enumerate_all_cliques(G) if len(triangle) == 3]
30+
print(sum(1 for triangle in triangles if any(node.startswith("t") for node in triangle)))
31+
32+
# part 2
33+
largest_clique = max(nx.find_cliques(G), key=len)
34+
password = ",".join(sorted(largest_clique))
35+
print(password)

β€Ž2024/day23/day23.rsβ€Ž

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
//! [Day 23: LAN Party](https://adventofcode.com/2024/day/23)
2+
3+
use petgraph::graph::{NodeIndex, UnGraph};
4+
use std::collections::{HashMap, HashSet};
5+
6+
// Bron-Kerbosch recursive algorithm to find cliques
7+
fn bron_kerbosch(
8+
graph: &UnGraph<(), ()>,
9+
r: &mut HashSet<NodeIndex>,
10+
p: &mut HashSet<NodeIndex>,
11+
x: &mut HashSet<NodeIndex>,
12+
cliques: &mut Vec<Vec<NodeIndex>>,
13+
) {
14+
if p.is_empty() && x.is_empty() {
15+
cliques.push(r.iter().copied().collect());
16+
return;
17+
}
18+
19+
let p_clone = p.clone();
20+
for &v in &p_clone {
21+
let mut r_new = r.clone();
22+
r_new.insert(v);
23+
24+
let neighbors: HashSet<_> = graph.neighbors(v).collect();
25+
let mut p_new: HashSet<NodeIndex> = p.intersection(&neighbors).copied().collect();
26+
let mut x_new: HashSet<NodeIndex> = x.intersection(&neighbors).copied().collect();
27+
28+
bron_kerbosch(graph, &mut r_new, &mut p_new, &mut x_new, cliques);
29+
30+
p.remove(&v);
31+
x.insert(v);
32+
}
33+
}
34+
35+
struct Puzzle {
36+
connections: Vec<(String, String)>,
37+
}
38+
39+
impl Puzzle {
40+
fn new() -> Self {
41+
Self {
42+
connections: Vec::new(),
43+
}
44+
}
45+
46+
/// Get the puzzle input.
47+
fn configure(&mut self, path: &str) {
48+
let data = std::fs::read_to_string(path).unwrap_or_else(|_| {
49+
eprintln!("cannot read input file {path}");
50+
std::process::exit(1);
51+
});
52+
53+
for line in data.lines() {
54+
if let Some((from, to)) = line.split_once('-') {
55+
self.connections.push((from.to_string(), to.to_string()));
56+
}
57+
}
58+
}
59+
60+
/// Solve part one.
61+
fn part1(&self) -> usize {
62+
let mut graph: HashMap<String, HashSet<String>> = HashMap::new();
63+
let mut triangles: HashSet<[&String; 3]> = HashSet::new();
64+
65+
for (n1, n2) in &self.connections {
66+
graph
67+
.entry(n1.to_string())
68+
.or_default()
69+
.insert(n2.to_string());
70+
71+
graph
72+
.entry(n2.to_string())
73+
.or_default()
74+
.insert(n1.to_string());
75+
}
76+
77+
for (node, neighbors) in &graph {
78+
for n1 in neighbors {
79+
for n2 in neighbors {
80+
if n1 != n2 && graph[n1].contains(n2) {
81+
let mut triangle = [node, n1, n2];
82+
triangle.sort_unstable();
83+
84+
triangles.insert(triangle);
85+
}
86+
}
87+
}
88+
}
89+
90+
triangles
91+
.iter()
92+
.filter(|triangle| triangle.iter().any(|node| node.starts_with('t')))
93+
.count()
94+
}
95+
96+
/// Solve part two.
97+
fn part2(&self) -> String {
98+
// type G = Graph<(), (), Undirected>;
99+
100+
// let mut graph = G::new_undirected();
101+
// let mut nodes = HashMap::new();
102+
103+
// for (n1, n2) in &self.connections {
104+
// let i1 = *nodes.entry(n1).or_insert_with(|| graph.add_node(()));
105+
// let i2 = *nodes.entry(n2).or_insert_with(|| graph.add_node(()));
106+
107+
// graph.add_edge(i1, i2, ());
108+
// }
109+
110+
let mut graph = UnGraph::<(), ()>::new_undirected();
111+
112+
let mut nodes = HashMap::new();
113+
114+
for (n1, n2) in &self.connections {
115+
let i1 = *nodes
116+
.entry(n1.clone())
117+
.or_insert_with(|| graph.add_node(()));
118+
let i2 = *nodes
119+
.entry(n2.clone())
120+
.or_insert_with(|| graph.add_node(()));
121+
graph.add_edge(i1, i2, ());
122+
}
123+
124+
let mut node_names: HashMap<&NodeIndex, &str> = HashMap::new();
125+
for (k, v) in &nodes {
126+
node_names.insert(v, k);
127+
}
128+
129+
// find the largest clique
130+
let mut cliques = Vec::new();
131+
let mut r = HashSet::new();
132+
let mut p: HashSet<NodeIndex> = graph.node_indices().collect();
133+
let mut x = HashSet::new();
134+
135+
bron_kerbosch(&graph, &mut r, &mut p, &mut x, &mut cliques);
136+
137+
let largest_clique = cliques.into_iter().max_by_key(std::vec::Vec::len);
138+
139+
if let Some(largest_clique) = largest_clique {
140+
let mut clique_names = largest_clique
141+
.iter()
142+
.map(|idx| node_names[idx])
143+
.collect::<Vec<_>>();
144+
clique_names.sort_unstable();
145+
146+
return clique_names.join(",").to_string();
147+
}
148+
149+
String::new()
150+
}
151+
}
152+
153+
fn main() {
154+
let args = aoc::parse_args();
155+
let mut puzzle = Puzzle::new();
156+
puzzle.configure(args.path.as_str());
157+
println!("{}", puzzle.part1());
158+
println!("{}", puzzle.part2());
159+
}
160+
161+
/// Test from puzzle input
162+
#[cfg(test)]
163+
mod test {
164+
use super::*;
165+
166+
#[test]
167+
fn test_part1() {
168+
let mut puzzle = Puzzle::new();
169+
puzzle.configure("test.txt");
170+
assert_eq!(puzzle.part1(), 7);
171+
}
172+
173+
#[test]
174+
fn test_part2() {
175+
let mut puzzle = Puzzle::new();
176+
puzzle.configure("test.txt");
177+
assert_eq!(puzzle.part2(), "co,de,ka,ta");
178+
}
179+
}

β€Ž2024/day23/test.txtβ€Ž

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
kh-tc
2+
qp-kh
3+
de-cg
4+
ka-co
5+
yn-aq
6+
qp-ub
7+
cg-tb
8+
vc-aq
9+
tb-ka
10+
wh-tc
11+
yn-cg
12+
kh-ub
13+
ta-co
14+
de-co
15+
tc-td
16+
tb-wq
17+
wh-td
18+
ta-ka
19+
td-qp
20+
aq-cg
21+
wq-ub
22+
ub-vc
23+
de-ta
24+
wq-aq
25+
wq-vc
26+
wh-yn
27+
ka-de
28+
kh-ta
29+
co-tc
30+
wh-qp
31+
tb-vc
32+
td-yn

β€ŽREADME.mdβ€Ž

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
# [Advent of Code](https://adventofcode.com) in Rust πŸ¦€
22

3-
![Stars: 494](https://img.shields.io/badge/Stars-494⭐-blue)
4-
![Rust: 200](https://img.shields.io/badge/Rust-200-cyan?logo=Rust)
5-
![Python: 121](https://img.shields.io/badge/Python-121-cyan?logo=Python)
3+
![Stars: 496](https://img.shields.io/badge/Stars-496⭐-blue)
4+
![Rust: 201](https://img.shields.io/badge/Rust-201-cyan?logo=Rust)
5+
![Python: 122](https://img.shields.io/badge/Python-122-cyan?logo=Python)
66

77
Solutions of [Advent of Code](https://adventofcode.com/) in [Rust](https://www.rust-lang.org), and sometimes in [Python](https://www.python.org/) and other languages.
88

99
Made for fun 😎 and to practice Rust. Many thanks to [Eric Wastl](https://twitter.com/ericwastl).
1010

11-
## 2024 (current event) ([Calendar](https://adventofcode.com/2024)) ([Solutions](2024/)) : 44⭐
11+
## 2024 (current event) ([Calendar](https://adventofcode.com/2024)) ([Solutions](2024/)) : 46⭐
1212

1313
Puzzle | Stars | Languages
1414
---------------------------------------------------------------------- | ----- | -----------
@@ -34,12 +34,13 @@ Puzzle | Stars |
3434
[Day 20: Race Condition](https://adventofcode.com/2024/day/20) | ⭐⭐ | [![Rust](./scripts/assets/rust.png)](./2024/day20/day20.rs)
3535
[Day 21: Keypad Conundrum](https://adventofcode.com/2024/day/21) | ⭐⭐ | [![Rust](./scripts/assets/rust.png)](./2024/day21/day21.rs)
3636
[Day 22: Monkey Market](https://adventofcode.com/2024/day/22) | ⭐⭐ | [![Rust](./scripts/assets/rust.png)](./2024/day22/day22.rs)
37+
[Day 23: LAN Party](https://adventofcode.com/2024/day/23) | ⭐⭐ | [![Rust](./scripts/assets/rust.png)](./2024/day23/day23.rs) [![Python](./scripts/assets/python.png)](./2024/day23/day23.py)
3738

3839
## All years
3940

4041
Calendar | Solutions | Stars | Rust | Python | πŸŽ„
4142
-------- | --------- | ----- | ---- | ------ | --
42-
[Advent of Code 2024](https://adventofcode.com/2024) | [Solutions](2024/README.md) | 44⭐ | 22 | 8 | 3
43+
[Advent of Code 2024](https://adventofcode.com/2024) | [Solutions](2024/README.md) | 46⭐ | 23 | 9 | 3
4344
[Advent of Code 2023](https://adventofcode.com/2023) | [Solutions](2023/README.md) | 50⭐ | 25 | 11 | 1
4445
[Advent of Code 2022](https://adventofcode.com/2022) | [Solutions](2022/README.md) | 50⭐ | 24 | 18 |
4546
[Advent of Code 2021](https://adventofcode.com/2021) | [Solutions](2021/README.md) | 50⭐ | 24 | 12 |

β€Žscripts/aoc.pyβ€Ž

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,13 +116,17 @@ def aoc_install(ctx: click.Context):
116116

117117

118118
@aoc.command(name="private-leaderboard")
119+
@click.option("-y", "--year", type=int, help="Year")
119120
@click.argument("id", type=int)
120121
@click.pass_context
121-
def aoc_private_leaderboard(ctx: click.Context, id: int):
122+
def aoc_private_leaderboard(ctx: click.Context, year: int, id: int):
122123
"""
123124
Show the state of a private leaderboard.
124125
"""
125-
subprocess.run(["aoc-cli", "private-leaderboard", str(id)])
126+
cmd = ["aoc-cli", "private-leaderboard", str(id)]
127+
if year:
128+
cmd.extend(["--year", str(year)])
129+
subprocess.run(cmd)
126130

127131

128132
@aoc.command(name="calendar", context_settings=dict(ignore_unknown_options=True, allow_extra_args=True))

0 commit comments

Comments
Β (0)