From d8a562145aa6c8f83dfb0f33305fec39f027d6f2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tristan=20Dani=C3=ABl=20Maat?= <tm@tlater.net>
Date: Sat, 12 Dec 2020 21:42:16 +0000
Subject: [PATCH] Re-implement day 11.1 with graphs

---
 day-11/Cargo.lock  |  39 +++++++++
 day-11/Cargo.toml  |   1 +
 day-11/src/main.rs | 193 +++++++++++++++++++++------------------------
 3 files changed, 129 insertions(+), 104 deletions(-)

diff --git a/day-11/Cargo.lock b/day-11/Cargo.lock
index 5e329eb..097af7b 100644
--- a/day-11/Cargo.lock
+++ b/day-11/Cargo.lock
@@ -1,10 +1,39 @@
 # This file is automatically @generated by Cargo.
 # It is not intended for manual editing.
+[[package]]
+name = "autocfg"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
+
 [[package]]
 name = "day-11"
 version = "0.1.0"
 dependencies = [
  "indoc",
+ "petgraph",
+]
+
+[[package]]
+name = "fixedbitset"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d"
+
+[[package]]
+name = "hashbrown"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
+
+[[package]]
+name = "indexmap"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "55e2e4c765aa53a0424761bf9f41aa7a6ac1efa87238f59560640e27fca028f2"
+dependencies = [
+ "autocfg",
+ "hashbrown",
 ]
 
 [[package]]
@@ -30,6 +59,16 @@ dependencies = [
  "unindent",
 ]
 
+[[package]]
+name = "petgraph"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7"
+dependencies = [
+ "fixedbitset",
+ "indexmap",
+]
+
 [[package]]
 name = "proc-macro-hack"
 version = "0.5.19"
diff --git a/day-11/Cargo.toml b/day-11/Cargo.toml
index 5cf3b01..115407c 100644
--- a/day-11/Cargo.toml
+++ b/day-11/Cargo.toml
@@ -8,3 +8,4 @@ edition = "2018"
 
 [dependencies]
 indoc = "0.3"
+petgraph = "0.5"
diff --git a/day-11/src/main.rs b/day-11/src/main.rs
index 886261d..813bb24 100644
--- a/day-11/src/main.rs
+++ b/day-11/src/main.rs
@@ -1,5 +1,8 @@
 use std::fs;
 
+use petgraph::graph::{DiGraph, Graph};
+use petgraph::visit::IntoNodeIdentifiers;
+
 fn main() -> Result<(), Box<dyn std::error::Error>> {
     let input = fs::read_to_string("input")?;
     let layout = parse_layout(&input)?;
@@ -11,6 +14,20 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
     Ok(())
 }
 
+type Layout = DiGraph<Seating, Direction>;
+
+#[derive(Copy, Clone, Debug)]
+enum Direction {
+    NorthWest,
+    North,
+    NorthEast,
+    West,
+    East,
+    SouthWest,
+    South,
+    SouthEast,
+}
+
 #[derive(Copy, Clone, Debug, PartialEq)]
 enum Seating {
     Occupied,
@@ -29,124 +46,92 @@ impl Seating {
     }
 }
 
-fn parse_layout(input: &str) -> Result<Vec<Vec<Seating>>, String> {
-    input
+fn parse_layout(input: &str) -> Result<Layout, String> {
+    let mut output: Layout = Graph::new();
+
+    let grid: Vec<Vec<_>> = input
         .lines()
-        .map(|line| line.chars().map(Seating::new).collect())
-        .collect()
-}
-
-fn print_layout(layout: &Vec<Vec<Seating>>) {
-    let output: String = layout
-        .iter()
-        .map(|row| {
-            let mut row: String = row
-                .iter()
-                .map(|c| match c {
-                    Seating::Occupied => '#',
-                    Seating::Empty => 'L',
-                    Seating::Floor => '.',
-                })
-                .collect();
-            row.push('\n');
-            row
-        })
-        .collect();
-
-    println!("{}", output);
-}
-
-fn get_adjacent(layout: &Vec<Vec<Seating>>, i: usize, j: usize) -> Vec<Option<&Seating>> {
-    let adjacent: [[usize; 2]; 8] = [
-        [0, 0],
-        [0, 1],
-        [0, 2],
-        [1, 0],
-        [1, 2],
-        [2, 0],
-        [2, 1],
-        [2, 2],
-    ];
-
-    adjacent
-        .iter()
-        .map(|[nudge_row, nudge_col]| {
-            if let Some(row) = (i + nudge_row).checked_sub(1) {
-                if let Some(row) = layout.get(row) {
-                    if let Some(col) = (j + nudge_col).checked_sub(1) {
-                        row.get(col)
-                    } else {
-                        None
-                    }
-                } else {
-                    None
-                }
-            } else {
-                None
-            }
-        })
-        .collect()
-}
-
-fn simulate_people(layout: &Vec<Vec<Seating>>) -> (Vec<Vec<Seating>>, usize) {
-    let mut changes = 0;
-
-    let layout = layout
-        .iter()
-        .enumerate()
-        .map(|(i, row)| {
-            row.iter()
-                .enumerate()
-                .map(|(j, seat)| match seat {
-                    Seating::Occupied => {
-                        if get_adjacent(layout, i, j)
-                            .iter()
-                            .filter(|seat| **seat == Some(&Seating::Occupied))
-                            .count()
-                            >= 4
-                        {
-                            changes += 1;
-                            Seating::Empty
-                        } else {
-                            Seating::Occupied
-                        }
-                    }
-                    Seating::Empty => {
-                        if get_adjacent(layout, i, j)
-                            .iter()
-                            .any(|seat| *seat == Some(&Seating::Occupied))
-                        {
-                            Seating::Empty
-                        } else {
-                            changes += 1;
-                            Seating::Occupied
-                        }
-                    }
-                    Seating::Floor => Seating::Floor,
-                })
+        .map(|line| {
+            line.chars()
+                .map(|c| Ok(output.add_node(Seating::new(c)?)))
                 .collect()
         })
-        .collect();
+        .collect::<Result<_, String>>()?;
 
-    (layout, changes)
+    for i in 0..grid.len() {
+        for j in 0..grid[0].len() {
+            if let Some(node) = grid.get(i).and_then(|row| row.get(j)) {
+                for ((c, r), direction) in [
+                    ((0, 0), Direction::NorthWest),
+                    ((0, 1), Direction::North),
+                    ((0, 2), Direction::NorthEast),
+                    ((1, 0), Direction::West),
+                    ((1, 2), Direction::East),
+                    ((2, 0), Direction::SouthWest),
+                    ((2, 1), Direction::South),
+                    ((2, 2), Direction::SouthEast),
+                ]
+                .iter()
+                {
+                    if let (Some(c), Some(r)) = ((i + c).checked_sub(1), (j + r).checked_sub(1)) {
+                        if let Some(target) = grid.get(c).and_then(|row| row.get(r)) {
+                            output.add_edge(*node, *target, *direction);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    Ok(output)
 }
 
-fn count_eventually_occupied_seats(layout: &Vec<Vec<Seating>>) -> usize {
-    let mut loop_layout = layout.clone();
+fn count_eventually_occupied_seats(layout: &Layout) -> usize {
+    let mut layout = layout.clone();
 
     loop {
-        let (res, changes) = simulate_people(&loop_layout);
-        loop_layout = res;
+        let mut changes = 0;
+
+        layout = layout.map(
+            |node, seat| match seat {
+                Seating::Occupied => {
+                    if layout
+                        .neighbors(node)
+                        .filter(|seat| layout[*seat] == Seating::Occupied)
+                        .count()
+                        >= 4
+                    {
+                        changes += 1;
+                        Seating::Empty
+                    } else {
+                        *seat
+                    }
+                }
+                Seating::Empty => {
+                    if !layout
+                        .neighbors(node)
+                        .any(|seat| layout[seat] == Seating::Occupied)
+                    {
+                        changes += 1;
+                        Seating::Occupied
+                    } else {
+                        *seat
+                    }
+                }
+                Seating::Floor => Seating::Floor,
+            },
+            |_, direction| *direction,
+        );
 
         if changes == 0 {
             break;
         }
     }
 
-    loop_layout
-        .iter()
-        .flatten()
-        .filter(|seat| **seat == Seating::Occupied)
+    layout
+        .node_identifiers()
+        .map(|id| layout[id])
+        .filter(|seat| *seat == Seating::Occupied)
         .count()
 }