summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--12/Cargo.toml15
-rw-r--r--12/input.txt41
-rw-r--r--12/src/part-1.rs120
-rw-r--r--12/src/part-2.rs120
-rw-r--r--12/src/tensor.rs171
-rw-r--r--12/test.txt5
6 files changed, 472 insertions, 0 deletions
diff --git a/12/Cargo.toml b/12/Cargo.toml
new file mode 100644
index 0000000..04aaa89
--- /dev/null
+++ b/12/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "day-12"
+version = "0.1.0"
+authors = ["Gard Spreemann <gspr@nonempty.org>"]
+edition = "2021"
+
+[[bin]]
+name = "part-1"
+path = "src/part-1.rs"
+
+[[bin]]
+name = "part-2"
+path = "src/part-2.rs"
+
+[dependencies]
diff --git a/12/input.txt b/12/input.txt
new file mode 100644
index 0000000..c2ec0e6
--- /dev/null
+++ b/12/input.txt
@@ -0,0 +1,41 @@
+abccccccccccccccccccccaaaaaaaacccccccccccccaacaaaaacccccccccccccccccccccaaaaaacccccaaaaaccccccccccccccccccccaaaccccccccccccccccccccccccccccccccccccccccccccccccccccccccccaaaa
+abcccccccccccccccccccaaaaaaaaacccccccccccccaaaaaaaaccccccccccccccaacccccaaaaaaaaaaaaaaaaccccccccccccccccccccaaaccccccccccccccccccccccccaccaccccccccccccccccccccccccccccaaaaaa
+abccccccccccccccccccaaaaaaaaaacccccaacccccccaaaaaccccccccccccccaaaaaacccaaaaaaaaaaaaaaaaccccccccccccccccccaacaaaaacccccccccccccccccccccaaaaccccccccccccccccccccccccccccaaaaaa
+abccccccccccaaacaaacaaacaaacccccacaaaccccccccaaaaacccccccccccccaaaaaacaaaaaaaaaaaaaaaaaaccccccccccccccccccaaaaaaaaccccccccccccccccccccaaaaaccccccccaaaccccaaaccccccccccaaacaa
+abccccccccaaaaaccaaaaaccaaaccccaaaaaaaacccccaaacaaccccccccccccccaaaaccaaaaaaaaccccaaaaaacccccccccccccccccccaaaaaccccccccccccccccccccccaaaaaacccccccaaaacccaaaccccccccccccccaa
+abccccccccaaaaaaccaaaaaaaaaaaccaaaaaaaaccccccaacccccccccccccccccaaaacaaaacaaaacccccaaacccccccaacaaccccccccccaaaaacccaaccccccccccccccccaaaaaaccccccccaaaaaaaacccccccccccccccaa
+abccccccccaaaaaaaaaaaaaaaaaaacccaaaaaaccccccccccccccccccccccccccaccaccccccaaaaaccccccccccccccaaaaacccccccccaaacaacaaaaaaccccccccccccccccaacccccccckkkkkkaaaaccccccccccccccccc
+abccccccccaaaaacaaaaaccaaaaaaaacaaaaaccccccccccccccccccccccccccccccccccccccaaaccccccccccccccccaaaaacccccccccaaccccaaaaaaccccccccccccccccccccccccckkkkkkklaaccccccccaacccccccc
+abccccccccaaaaacaacaaacaaaaaaaacaaaaaacccccccccccccccccccccaacccccccccccccccaaaccccccccccccccaaaaaacccccccccccccccaaaaaacccccccccccccccccccccccckkkkkkkklllccccccccccaaaacccc
+abcaaccccccccccccccaaaccaaaaaaaccccaaccccccccccccccccccaaccaacccccccccccccccccccaacccccccccccaaaacccccccccccccccccaaaaacccccccaaaacccccccccccccckkkoppppllllllccccccccaaccccc
+abcaacccccccccccccccccccaaaaaccccccccccccccccccccccccccaaaaaacccccccccccccccccaaaaaacccccccccccaaccccccccccccccccccaaaacccccccaaaaacccccccccccckkkooppppplllllllllccccdaccccc
+abaaaccccccaaacccccccccaaaaaaccccccccaaaccccccccccccccccaaaaaaacccccccccccccccaaaaaacccccccccccccccccccccccccccccccccccccccccaaaaaaccccccccccccjkoooopuppplllllllmmmddddacccc
+abaaaaaccccaaaaacccccccccccaaaaacccccaaaaccccccccccccccccaaaaaaccccccccccccccccaaaacccccccccccccccccaaaccccccccccccccccccccccaaaaaacccccccccccjjjooouuuuppppppqqmmmmmdddacccc
+abaaaaacccaaaaaacccaacaaacccaaaaaacccaaaacccccccccccccccaaaaaccccccccccaaccccccaaaaccccccaacccccacccaaccccccccccaacaaccccccccaaaaaacccaaaccccjjjjoouuuuuuppppqqqqqmmmdddacccc
+abaaccacccaaaaaacccaaaaaacccaaaaaacccaaacccccccccccccccaaaaaaccccccccaaaaaaccccaccacccccaaaacccaaaaaaaccccccccccaaaaaccccccccccaacacccaaccccjjjjooouuuxuuupppqqqqqmmmdddccccc
+abaaaccccccaaaaacccaaaaaacccaaaaaccccccccccccccccccccccccccaaccccccccaaaaaacccccccccccccaaaaccccaaaaaaaaccccccccaaaaaaccccccccccccaaaaaaacjjjjjoooouuxxxuuvvvvvvqqqmmdddccccc
+abaaaccccccaacaacccaaaaaaacccaaaaacccccccccccccccaaacccccccccccccccccaaaaaccccaaacccccccaaaaccccaaaaaaaaacccccccaaaaaaccccccccccccaaaaaacjjjjjoooouuuxxxuuvvvvvvqqqmmdddccccc
+abccccccccccccccccaaaaaaaacccaaaaacccccccccccccccaaaccccccccccccccccccaaaaacccaaacacccccccccccccaaaaaaaaacccccccaaaaaccccccaacccccaaaaaaajjjnoooottuuxxxxvyyyvvvqqmmmdddccccc
+abccccccccccccccccaaaaaaaacccccccccccccccccaaaaaaaaaccaaccccccccccccccaaaaacaacaaaaaccccccccccccaaaaaaaaccccccccccaaacccccaaaaccccaaaaaajjjnnnntttttxxxxyyyyyvvvqqmmmdddccccc
+abccccccaaaccccccccccaaacccaaccccccccccccccaaaaaaaaaaaaaaaccccccccaaacccccccaaaaaaaacccccccccccaaaaaaaaccccccccccccaacccccaaaaacacaaaaaaiiinnntttxxxxxxxyyyyyvvqqqmmdddcccccc
+SbccccccaaaaaccccccccaaccccaaaccccccccccccccaaaaaaaaaaaaaacccccccaaaaaaccccccaaaaaccccccccacccaaaccaaacccccccccaaacaaccaaaaaaaaaaaaaaaaaiiinnntttxxxEzzzzyyyvvqqqmmmeeecccccc
+abcccccaaaaaacccccccccccaaaaaaaaccccccccccccaaaaaaaaaaaaaccccccccaaaaaacccccccaaaaacccccccaacaaaccccaaacccccccccaaaaaccaaaaaaaaaacaccaaaiiinnntttxxxxxyyyyyvvvqqqnnneeecccccc
+abaacccaaaaaacccccccccccaaaaaaaaccccaaaccccaaaaaaaaaaaaaaacccccccaaaaaccccccccaacaaaaaccccaaaaacccccccccccccccccaaaaaaaccaaaaaacccccccaaiiinnnttttxxxxyyyyyyvvvrrnnneeecccccc
+abaaaaaaaaaaaccccccccccccaaaaaaccccaaaacccaaaaaaaaaaaaacaaccccccccaaaaacccccccaaccccaaaacccaaaaaacaacaaccccccccaaaaaaaaccaaaaaaccccccccciiiinnnttttxxwyywyyyyvvrrrnneeecccccc
+abaaaaacaacaaccccccccccccaaaaaaccccaaaacccaaacaaaacaaaccccccccccccaacaaccccaacccccaaaaaacaaaaaaaacaaaaacccccaaaaaaaaaaaccaaaaaacccccccccciiiinnnttttwwyywwywwwvrrrnneeecccccc
+abaaaacccccccccccccccccccaaaaaacccccaaacccccccaaacccacaaccccccccccccccccccaaccccccaaaaaccaaaaaaaaaaaaaccccccaaaaaaaaacccaaaaaaaacccccccccciiinnnnntswwywwwwwwwwrrrnnneecccccc
+abaaaaaccccccccccccccccccaacaaacccccccccccccccaacaaacaaacccccccccccccccaaaaacaaccccaaaaaccccaacccaaaaaacccccaaacaaaaaccccacccccccaaacccccciiiiinnmsswwwwwswwwwrrrrnnneecccccc
+abaaaaaaccaaaccccccccccccccccccccccccccccccccccccaaaaaaaccccccccaaaccccaaaaaaaaccccaaccaccccaacccccaaaaccaaaaaaaaaaccccccccccccccaaaaacccccciihmmmsswwwwssrrrrrrrrnnneecccccc
+abaaccaacaaaaccccccccccccccccccccccccccccccccccccaaaaaacccccccccaaaaaccccaaaaacccccccccccccccccccccacccccaaaaaaaaacccccccaaccaacaaaaacccccccchhhmmssswwsssrrrrrrrnnneeecccccc
+abaacccccaaaacccccccccccccccccccccccccccccccccccccaaaaaaaacccccaaaaaacccaaaaacccccccccccccccccccccccccccccaaaaaaaccccccccaaaaaacaaaaacccccccchhhmmssssssslllllllnnnnfeecccccc
+abccccccccaaacccccccccccccccaaccccccccccccccaaaccaaaaaaaaacccccaaaaaacccaacaaaccccccccccccccccccccccccaacccaaaaaaccccccccaaaaacccaaaaaccccccchhhmmmssssslllllllllnnfffeaacccc
+abcccccccccccccccccccccccacaaaccccccccccccccaaaaaaaaaaaaaaccaaccaaaaaccccccaaccaacccccccccccccccccccccaaaccaaaaaaacccccccaaaaaaccaaccccccccccchhhmmmmsmllllllllllfffffaaacccc
+abccccccccccccccccccccccaaaaaaaaccccccccccccaaaaaaacaaacaaaaaaccaaaaccccccccccaaaacccccccccccccccccaaaaaaaaaaacaaaccccccaaaaaaaacccccccccccccchhhmmmmmmlllggfffffffffaacccccc
+abccccccccccccccccccccccaaaaaaaaccccccccccccaaacccccaaacaaaaacccccccccccaaacccaaaacccccccccccccccccaaaaaaaaaacccccccccccaaaaaaaaccccccccccccccchhhmmmmmlggggffffffffaaacccccc
+abccccccccccccccccaaaacccaaaaaacccccccccccccccccccccaaacaaaaaaccccccccccaaacccaaaaccccccacccccccccccaaaaaacccccccccccccccccaacccaaccccccccccccchhhhgmgggggggffaccccccaacccccc
+abccccccccccccccccaaaacccaaaaacccccccccccccccccccccccccaaaaaaaacccccccccaaaaccaaacccccccaaacaaacccccaaaaaacccccccccccccccccaaccaaccccccccccccccchhhgggggggaaaaacccccccccccccc
+abccccccccccccccccaaaacccaaaaaaccccccccccccccccccccccccaaaaaaaaccccccccccaaaaaaaaaccccccaaaaaaacccccaaaaaaccccccccccccccccccaaaaacaaccccccccccccchggggggaacccccccccccccccccca
+abcccccccccccccccccaacccccccaaccccccccccccccccccccccccccccaacaccaaacccaacaaaaaaaacccccccaaaaaaccccccaaccaacaaaccacccccaaccccaaaaaaaaccccccccccccccccccaaaccccccccccccccccccca
+abcccccaaccccccccccccccccaaccccccccaacccccccccaaacccccccccaacaaaaaacccaaacaaaaaacccccccaaaaaaacccccccccccccaaaaaacccaaaaccccccaaaaaccccaaacccccccccccccaaccccccccccccccaaaaaa
+abccccaaaacccccccccccccccaaaacccaaaaccccccccccaaaacccccccccccaaaaaaccaaaaaaaaaacccccccaaaaaaaaaaccccccccccccaaaaacccaaaaaacccaaaaacccccaaaacccccccccccaaccccccccccccccccaaaaa
+abccccaaaacccccccccccccaaaaaacccaaaaaaccccccccaaaaccccccccccaaaaaaaacaaaaaaaaaaaaaccccaaaaaaaaaaccccccccccaaaaaaaacccaaaaccccaacaaaccccaaaacccccccccccccccccccccccccccccaaaaa
diff --git a/12/src/part-1.rs b/12/src/part-1.rs
new file mode 100644
index 0000000..2e70ef9
--- /dev/null
+++ b/12/src/part-1.rs
@@ -0,0 +1,120 @@
+mod tensor;
+
+use std::collections::{BinaryHeap};
+use std::io::{BufRead};
+
+use tensor::{Index, Shape, Tensor};
+
+#[derive(Debug)]
+struct Node {
+ idx: [usize; 2],
+ cost: usize
+}
+
+impl Node {
+ fn new(idx: [usize; 2], cost: usize) -> Self {
+ Self {
+ idx: idx,
+ cost: cost
+ }
+ }
+}
+
+impl PartialEq for Node {
+ fn ne(self: & Self, other: & Self) -> bool {
+ self.cost != other.cost
+ }
+ fn eq(self: & Self, other: & Self) -> bool {
+ self.cost == other.cost
+ }
+}
+
+impl Eq for Node {
+}
+
+impl PartialOrd for Node {
+ fn partial_cmp(self: & Self, other: & Self) -> Option<std::cmp::Ordering> {
+ other.cost.partial_cmp(& self.cost)
+ }
+}
+
+impl Ord for Node {
+ fn cmp(self: & Self, other: & Self) -> std::cmp::Ordering {
+ other.cost.cmp(& self.cost)
+ }
+}
+
+fn main() {
+ let stdin = std::io::stdin();
+ let mut handle = stdin.lock();
+
+ let mut buf: Vec<u8> = Vec::new();
+
+ let mut tmp: Vec<(u8, bool)> = Vec::new();
+ let mut m: usize = 0;
+ let mut n: usize = 0;
+ let mut n_check: usize = 0;
+
+ loop {
+ let buf = handle.fill_buf().expect("IO error");
+ let bytes_read = buf.len();
+ if bytes_read == 0 { break; }
+
+ for & b in buf.into_iter() {
+ if b == b'\n' {
+ m += 1;
+ assert_eq!(n_check, n);
+ n_check = 0;
+ }
+ else {
+ tmp.push((b, false));
+ if m == 0 {
+ n += 1;
+ }
+ n_check += 1;
+ }
+ }
+
+ handle.consume(bytes_read);
+ }
+ let mut terrain: Tensor<(u8, bool), 2> = Tensor::new_from([m, n], tmp);
+
+ let mut unvisited: BinaryHeap<Node> = BinaryHeap::new();
+ let mut done: bool = false;
+ let mut res: usize = usize::MAX;
+
+ for i in 0..m {
+ for j in 0..n {
+ if terrain[[i, j]].0 == b'S' {
+ unvisited.push(Node::new([i, j], 0));
+ }
+ }
+ }
+
+ while let Some(node) = unvisited.pop() {
+ let idx = node.idx;
+ if !terrain[idx].1 {
+ let elev = terrain[idx].0;
+ let neighbor_idxs = [[idx[0].wrapping_sub(1), idx[1] ],
+ [idx[0] + 1 , idx[1] ],
+ [idx[0] , idx[1].wrapping_sub(1)],
+ [idx[0] , idx[1] + 1 ]];
+ for neighbor_idx in neighbor_idxs.into_iter() {
+ if let Some(& (x, v)) = terrain.el(& neighbor_idx) {
+ if x == b'E' && (elev == b'y' || elev == b'z') { // The end node has elevation z.
+ res = node.cost + 1;
+ done = true;
+ break;
+ }
+ if !v && (x < elev || x - elev <= 1 || elev == b'S') {
+ unvisited.push(Node::new(neighbor_idx.clone(), node.cost + 1));
+ }
+ }
+ }
+ terrain[idx].1 = true;
+ }
+ if done { break; }
+ }
+
+ println!("{}", res);
+}
diff --git a/12/src/part-2.rs b/12/src/part-2.rs
new file mode 100644
index 0000000..aa14ef8
--- /dev/null
+++ b/12/src/part-2.rs
@@ -0,0 +1,120 @@
+mod tensor;
+
+use std::collections::{BinaryHeap};
+use std::io::{BufRead};
+
+use tensor::{Index, Shape, Tensor};
+
+#[derive(Debug)]
+struct Node {
+ idx: [usize; 2],
+ cost: usize
+}
+
+impl Node {
+ fn new(idx: [usize; 2], cost: usize) -> Self {
+ Self {
+ idx: idx,
+ cost: cost
+ }
+ }
+}
+
+impl PartialEq for Node {
+ fn ne(self: & Self, other: & Self) -> bool {
+ self.cost != other.cost
+ }
+ fn eq(self: & Self, other: & Self) -> bool {
+ self.cost == other.cost
+ }
+}
+
+impl Eq for Node {
+}
+
+impl PartialOrd for Node {
+ fn partial_cmp(self: & Self, other: & Self) -> Option<std::cmp::Ordering> {
+ other.cost.partial_cmp(& self.cost)
+ }
+}
+
+impl Ord for Node {
+ fn cmp(self: & Self, other: & Self) -> std::cmp::Ordering {
+ other.cost.cmp(& self.cost)
+ }
+}
+
+fn main() {
+ let stdin = std::io::stdin();
+ let mut handle = stdin.lock();
+
+ let mut buf: Vec<u8> = Vec::new();
+
+ let mut tmp: Vec<(u8, bool)> = Vec::new();
+ let mut m: usize = 0;
+ let mut n: usize = 0;
+ let mut n_check: usize = 0;
+
+ loop {
+ let buf = handle.fill_buf().expect("IO error");
+ let bytes_read = buf.len();
+ if bytes_read == 0 { break; }
+
+ for & b in buf.into_iter() {
+ if b == b'\n' {
+ m += 1;
+ assert_eq!(n_check, n);
+ n_check = 0;
+ }
+ else {
+ tmp.push((b, false));
+ if m == 0 {
+ n += 1;
+ }
+ n_check += 1;
+ }
+ }
+
+ handle.consume(bytes_read);
+ }
+ let mut terrain: Tensor<(u8, bool), 2> = Tensor::new_from([m, n], tmp);
+
+ let mut unvisited: BinaryHeap<Node> = BinaryHeap::new();
+ let mut done: bool = false;
+ let mut res: usize = usize::MAX;
+
+ for i in 0..m {
+ for j in 0..n {
+ if terrain[[i, j]].0 == b'E' {
+ unvisited.push(Node::new([i, j], 0));
+ }
+ }
+ }
+
+ while let Some(node) = unvisited.pop() {
+ let idx = node.idx;
+ if !terrain[idx].1 {
+ let elev = if terrain[idx].0 == b'E' { b'z' } else { terrain[idx].0 };
+ let neighbor_idxs = [[idx[0].wrapping_sub(1), idx[1] ],
+ [idx[0] + 1 , idx[1] ],
+ [idx[0] , idx[1].wrapping_sub(1)],
+ [idx[0] , idx[1] + 1 ]];
+ for neighbor_idx in neighbor_idxs.into_iter() {
+ if let Some(& (x, v)) = terrain.el(& neighbor_idx) {
+ if (x == b'S' || x == b'a') && (elev == b'a' || elev == b'b') {
+ res = node.cost + 1;
+ done = true;
+ break;
+ }
+ if !v && (x > elev || elev - x <= 1) {
+ unvisited.push(Node::new(neighbor_idx.clone(), node.cost + 1));
+ }
+ }
+ }
+ terrain[idx].1 = true;
+ }
+ if done { break; }
+ }
+
+ println!("{}", res);
+}
diff --git a/12/src/tensor.rs b/12/src/tensor.rs
new file mode 100644
index 0000000..2635886
--- /dev/null
+++ b/12/src/tensor.rs
@@ -0,0 +1,171 @@
+#![allow(dead_code)]
+
+pub type Shape<const D: usize> = [usize; D];
+pub type Index<const D: usize> = Shape<D>;
+
+pub struct Tensor<T, const D: usize> {
+ data: Vec<T>,
+ shape: Shape<D>,
+ strides: Shape<D>
+}
+
+pub type Tensor1<T> = Tensor<T, 1>;
+pub type Tensor2<T> = Tensor<T, 2>;
+pub type Tensor3<T> = Tensor<T, 3>;
+pub type Tensor4<T> = Tensor<T, 4>;
+pub type Index1 = Index<1>;
+pub type Index2 = Index<2>;
+pub type Index3 = Index<3>;
+pub type Index4 = Index<4>;
+
+
+impl<T: Copy, const D: usize> Tensor<T, D> {
+ pub fn new(shape: Shape<D>, x: T) -> Self {
+ if D == 0 { panic!("Empty shape not allowed."); }
+
+ let mut len = shape[D-1];
+ let mut strides: Shape<D> = [0; D];
+ strides[D-1] = 1;
+ for d in (1..D).rev() { // d=D-1, …, 1.
+ strides[d-1] = shape[d]*strides[d];
+ len *= shape[d-1];
+ }
+ if len == 0 { panic!("Empty dimensions not allowed."); }
+
+ Self {
+ data: vec![x; len],
+ shape: shape,
+ strides: strides,
+ }
+ }
+
+ pub fn new_from(shape: Shape<D>, x: Vec<T>) -> Self {
+ if D == 0 { panic!("Empty shape not allowed."); }
+
+ let mut len = shape[D-1];
+ let mut strides: Shape<D> = [0; D];
+ strides[D-1] = 1;
+ for d in (1..D).rev() { // d=D-1, …, 1.
+ strides[d-1] = shape[d]*strides[d];
+ len *= shape[d-1];
+ }
+ if len == 0 { panic!("Empty dimensions not allowed."); }
+
+ if len != x.len() { panic!("Vector of length {} cannot fill tensor with {} entries.", x.len(), len); }
+
+ Self {
+ data: x,
+ shape: shape,
+ strides: strides,
+ }
+ }
+
+
+ #[inline(always)]
+ fn flatten_idx(self: & Self, idx: & Index<D>) -> usize {
+ // NOTE: This is a very hot code path. Should benchmark versus explicit loop.
+ idx.iter().zip(self.strides.iter()).fold(0, |sum, (i, s)| sum + i*s)
+ }
+
+ fn bound_check_panic(self: & Self, idx: & Index<D>) -> () {
+ for d in 0..D {
+ let i = *(unsafe { idx.get_unchecked(d) });
+ if i >= self.shape[d] {
+ panic!("{}-dimensional tensor index is out of bounds in dimension {} ({} >= {}).", D, d, i, self.shape[d])
+ }
+ }
+ }
+
+ pub fn in_bounds(self: & Self, idx: & Index<D>) -> bool {
+ for d in 0..D {
+ let i = *(unsafe { idx.get_unchecked(d) });
+ if i >= self.shape[d] {
+ return false;
+ }
+ }
+ true
+ }
+
+ pub fn shape(self: & Self) -> & Shape<D> { &self.shape }
+
+ pub fn el(self: & Self, idx: & Index<D>) -> Option<& T> {
+ if self.in_bounds(idx) { Some(unsafe { self.el_unchecked(idx) }) }
+ else { None }
+ }
+
+ pub unsafe fn el_unchecked(self: & Self, idx: & Index<D>) -> & T { self.data.get_unchecked(self.flatten_idx(idx)) }
+
+ pub unsafe fn el_unchecked_mut(self: &mut Self, idx: & Index<D>) -> &mut T {
+ let flat_idx = self.flatten_idx(idx);
+ self.data.get_unchecked_mut(flat_idx)
+ }
+
+ pub fn flat_len(self: & Self) -> usize { self.data.len() }
+ pub fn size(self: & Self) -> usize { self.flat_len()*std::mem::size_of::<T>() }
+
+ pub fn fill_with(self: &mut Self, x: & [T]) -> () {
+ // Already panics on size mismatch.
+ self.data.copy_from_slice(x)
+ }
+
+ pub fn fill(self: &mut Self, x: T) -> () {
+ self.data.fill(x);
+ }
+
+ pub fn data(self: & Self) -> & [T] { & self.data }
+}
+
+impl<T: Copy + std::fmt::Display> Tensor<T, 2> {
+ pub fn dirty_print(self: & Self) {
+ for i in 0..self.shape[0] {
+ for j in 0..self.shape[1] {
+ print!("{} ", self[[i, j]]);
+ }
+ println!("");
+ }
+ }
+}
+
+impl<T: Copy, const D: usize> std::ops::Index<Index<D>> for Tensor<T, D> {
+ type Output = T;
+
+ fn index(self: & Self, idx: Index<D>) -> & Self::Output {
+ self.bound_check_panic(&idx);
+ unsafe { self.el_unchecked(&idx) }
+ }
+}
+
+impl<T: Copy, const D: usize> std::ops::Index<& Index<D>> for Tensor<T, D> {
+ type Output = T;
+
+ fn index(self: & Self, idx: & Index<D>) -> & Self::Output {
+ self.bound_check_panic(idx);
+ unsafe { self.el_unchecked(idx) }
+ }
+}
+
+impl<T: Copy, const D: usize> std::ops::IndexMut<Index<D>> for Tensor<T, D> {
+ fn index_mut(self: &mut Self, idx: Index<D>) -> &mut Self::Output {
+ self.bound_check_panic(&idx);
+ unsafe { self.el_unchecked_mut(&idx) }
+ }
+}
+
+impl<T: Copy, const D: usize> std::ops::IndexMut<& Index<D>> for Tensor<T, D> {
+ fn index_mut(self: &mut Self, idx: & Index<D>) -> &mut Self::Output {
+ self.bound_check_panic(idx);
+ unsafe { self.el_unchecked_mut(idx) }
+ }
+}
+
+impl<T: Copy, const D: usize> IntoIterator for Tensor<T, D> {
+ type Item = T;
+ type IntoIter = std::vec::IntoIter<Self::Item>;
+
+ fn into_iter(self) -> Self::IntoIter { self.data.into_iter() }
+}
+
+
+// FIXME: Should have a proper IntoIter implementing IntoIter for &'a Tensor<T, D>.
+
+// Note: Tensor is also sliceable (due to the Index implementations)
diff --git a/12/test.txt b/12/test.txt
new file mode 100644
index 0000000..86e9cac
--- /dev/null
+++ b/12/test.txt
@@ -0,0 +1,5 @@
+Sabqponm
+abcryxxl
+accszExk
+acctuvwj
+abdefghi