|
1 | | -pub type Domino = (u8, u8); |
| 1 | +//! This exercise is about finding a [Eulerian path]. The "dots" are the |
| 2 | +//! vertices of the graph, while the dominoes are the edges. |
| 3 | +//! |
| 4 | +//! [Eulerian path]: https://en.wikipedia.org/wiki/Eulerian_path |
2 | 5 |
|
3 | | -/// A table keeping track of available dominoes. |
4 | | -/// |
5 | | -/// Effectively a 6x6 matrix. Each position denotes whether a domino is available with that column |
6 | | -/// dots and row dots. Positions are mirrored ((3,4) == (4,3)), except for positions with equal row |
7 | | -/// and column numbers. |
8 | | -struct AvailabilityTable { |
9 | | - m: Vec<u8>, |
10 | | -} |
| 6 | +type Domino = (u8, u8); |
11 | 7 |
|
12 | | -impl AvailabilityTable { |
13 | | - fn new() -> AvailabilityTable { |
14 | | - AvailabilityTable { |
15 | | - m: std::iter::repeat_n(0, 6 * 6).collect(), |
16 | | - } |
| 8 | +pub fn chain(input: &[Domino]) -> Option<Vec<Domino>> { |
| 9 | + let mut bag = DominoBag::default(); |
| 10 | + for domino in input.iter().copied() { |
| 11 | + bag.insert(domino); |
17 | 12 | } |
18 | 13 |
|
19 | | - fn get(&self, x: u8, y: u8) -> u8 { |
20 | | - self.m[((x - 1) * 6 + (y - 1)) as usize] |
21 | | - } |
| 14 | + let mut chain = Vec::with_capacity(input.len()); |
| 15 | + let mut tail = vec![]; // used for temporary storage |
22 | 16 |
|
23 | | - fn set(&mut self, x: u8, y: u8, v: u8) { |
24 | | - let m = &mut self.m[..]; |
25 | | - m[((x - 1) * 6 + (y - 1)) as usize] = v; |
26 | | - } |
| 17 | + // start with any domino. (default will cause empty chain to be returned) |
| 18 | + let (mut first_dots, mut current_dots) = (0..7) |
| 19 | + .find_map(|i| bag.take_neighbor(i)) |
| 20 | + .inspect(|&d| chain.push(d)) |
| 21 | + .unwrap_or_default(); |
27 | 22 |
|
28 | | - fn add(&mut self, x: u8, y: u8) { |
29 | | - if x == y { |
30 | | - let n = self.get(x, y); |
31 | | - self.set(x, y, n + 1) // Along the diagonal |
32 | | - } else { |
33 | | - let m = self.get(x, y); |
34 | | - self.set(x, y, m + 1); |
35 | | - let n = self.get(y, x); |
36 | | - self.set(y, x, n + 1); |
| 23 | + loop { |
| 24 | + while let Some(next_domoino) = bag.take_neighbor(current_dots) { |
| 25 | + chain.push(next_domoino); |
| 26 | + current_dots = next_domoino.1; |
37 | 27 | } |
38 | | - } |
39 | | - |
40 | | - fn remove(&mut self, x: u8, y: u8) { |
41 | | - if self.get(x, y) > 0 { |
42 | | - if x == y { |
43 | | - let n = self.get(x, y); |
44 | | - self.set(x, y, n - 1) // Along the diagonal |
45 | | - } else { |
46 | | - let m = self.get(x, y); |
47 | | - self.set(x, y, m - 1); |
48 | | - let n = self.get(y, x); |
49 | | - self.set(y, x, n - 1); |
50 | | - } |
51 | | - } else { |
52 | | - // For this toy code hard explicit fail is best |
53 | | - panic!("remove for 0 stones: ({x:?}, {y:?})") |
| 28 | + if current_dots != first_dots { |
| 29 | + return None; // unbalanced |
54 | 30 | } |
55 | | - } |
| 31 | + // reintegrate second chain half from previous loop iteration |
| 32 | + chain.append(&mut tail); |
56 | 33 |
|
57 | | - fn pop_first(&mut self, x: u8) -> Option<u8> { |
58 | | - if self.get(x, x) > 0 { |
59 | | - self.remove(x, x); |
60 | | - return Some(x); |
| 34 | + if bag.is_empty() { |
| 35 | + return Some(chain); |
61 | 36 | } |
| 37 | + // We have found a path that ends where it started, but not all dominoes |
| 38 | + // are used up. We must find a location in the current chain where we |
| 39 | + // could've taken a different path. |
| 40 | + let (fork_point, next_domino) = chain |
| 41 | + .iter() |
| 42 | + .enumerate() |
| 43 | + .find_map(|(i, &(x, _))| bag.take_neighbor(x).map(|d| (i, d)))?; |
62 | 44 |
|
63 | | - for y in 1..7 { |
64 | | - if self.get(x, y) > 0 { |
65 | | - self.remove(x, y); |
66 | | - return Some(y); |
67 | | - } |
68 | | - } |
69 | | - None |
| 45 | + // put aside second half of first chain |
| 46 | + tail.extend(chain.drain(fork_point..)); |
| 47 | + |
| 48 | + chain.push(next_domino); |
| 49 | + // Treat the domino after the fork point as the first domino, to search |
| 50 | + // for a path that ends up back at the fork point. |
| 51 | + (first_dots, current_dots) = next_domino; |
70 | 52 | } |
71 | 53 | } |
72 | 54 |
|
73 | | -pub fn chain(dominoes: &[Domino]) -> Option<Vec<Domino>> { |
74 | | - match dominoes.len() { |
75 | | - 0 => Some(vec![]), |
76 | | - 1 => { |
77 | | - if dominoes[0].0 == dominoes[0].1 { |
78 | | - Some(vec![dominoes[0]]) |
79 | | - } else { |
80 | | - None |
81 | | - } |
82 | | - } |
83 | | - _ => { |
84 | | - // First check if the total number of each amount of dots is even, if not it's not |
85 | | - // possible to complete a cycle. This follows from that it's an Eulerian path. |
86 | | - let mut v: Vec<u8> = vec![0, 0, 0, 0, 0, 0]; |
87 | | - // Keep the mutable borrow in a small scope here to allow v.iter(). |
88 | | - { |
89 | | - let vs = &mut v[..]; |
90 | | - for dom in dominoes.iter() { |
91 | | - vs[dom.0 as usize - 1] += 1; |
92 | | - vs[dom.1 as usize - 1] += 1; |
93 | | - } |
94 | | - } |
95 | | - for n in v.iter() { |
96 | | - if n % 2 != 0 { |
97 | | - return None; |
98 | | - } |
99 | | - } |
100 | | - let chain = chain_worker(dominoes); |
101 | | - if chain.len() == dominoes.len() { |
102 | | - Some(chain) |
103 | | - } else { |
104 | | - None |
105 | | - } |
106 | | - } |
| 55 | +/// The domino bag stores all "untraversed edges" of the graph, using an |
| 56 | +/// adjacency matrix. |
| 57 | +#[derive(Default)] |
| 58 | +struct DominoBag([[u8; 7]; 7]); |
| 59 | + |
| 60 | +impl DominoBag { |
| 61 | + fn is_empty(&self) -> bool { |
| 62 | + self.0.iter().flatten().all(|d| *d == 0) |
107 | 63 | } |
108 | | -} |
109 | 64 |
|
110 | | -fn chain_worker(dominoes: &[Domino]) -> Vec<Domino> { |
111 | | - let mut doms = dominoes.to_vec(); |
112 | | - let first = doms.pop().unwrap(); |
113 | | - let mut t = AvailabilityTable::new(); |
114 | | - for dom in doms.iter() { |
115 | | - t.add(dom.0, dom.1) |
| 65 | + fn insert(&mut self, d: Domino) { |
| 66 | + let (i, j) = (d.0 as usize, d.1 as usize); |
| 67 | + self.0[i][j] += 1; |
| 68 | + self.0[j][i] += 1; |
116 | 69 | } |
117 | | - let mut v: Vec<Domino> = Vec::new(); |
118 | | - v.push(first); |
119 | | - let mut n = first.1; // Number to connect to |
120 | | - while let Some(m) = t.pop_first(n) { |
121 | | - v.push((n, m)); |
122 | | - n = m; |
| 70 | + |
| 71 | + /// Takes a domino connecting `i` to any neighbor. |
| 72 | + fn take_neighbor(&mut self, i: u8) -> Option<Domino> { |
| 73 | + (0..7).map(|j| (i, j)).find(|d| { |
| 74 | + let (i, j) = (d.0 as usize, d.1 as usize); |
| 75 | + if self.0[i][j] == 0 { |
| 76 | + return false; |
| 77 | + }; |
| 78 | + self.0[i][j] -= 1; |
| 79 | + self.0[j][i] -= 1; |
| 80 | + true |
| 81 | + }) |
123 | 82 | } |
124 | | - v |
125 | 83 | } |
0 commit comments