use super::intcode;\r
+use itertools::Itertools;\r
use std::collections::HashSet;\r
use std::convert::TryFrom;\r
+use std::ops::Range;\r
\r
#[derive(PartialEq, Eq, Copy, Clone, Debug)]\r
enum Direction { Up, Left, Down, Right }\r
#[derive(PartialEq, Eq, Copy, Clone, Debug)]\r
struct MovementCommand { mov: Movement, steps: u32 }\r
\r
-pub struct RobotTrackingSystem {\r
- output: Vec<i64>,\r
+struct RobotTrackingSystem {\r
board: Vec<Vec<char>>,\r
start_position: (i32, i32),\r
start_dir: Direction,\r
- crossings: Vec<(i32, i32)>,\r
-\r
dir_commands: Vec<MovementCommand>,\r
- commands_sequences: [Vec<(i32, i32)>; 3], // 3 sequences: A, B and C.\r
+ crossings: Vec<(i32, i32)>,\r
}\r
\r
impl RobotTrackingSystem {\r
fn new() -> Self {\r
RobotTrackingSystem {\r
- output: Vec::new(),\r
board: Vec::<Vec<char>>::new(),\r
start_position: (0, 0),\r
start_dir: Direction::Up,\r
- crossings: Vec::<(i32, i32)>::new(),\r
dir_commands: Vec::<MovementCommand>::new(),\r
- commands_sequences: [Vec::<(i32, i32)>::new(), Vec::<(i32, i32)>::new(), Vec::<(i32, i32)>::new()],\r
- }\r
- }\r
-\r
- fn get(&self, x: i32, y: i32) -> Option<char> {\r
- if x < self.board[0].len() as i32 && x >= 0 && y < self.board.len() as i32 && y >= 0 {\r
- Some(self.board[y as usize][x as usize])\r
- } else {\r
- None\r
+ crossings: Vec::<(i32, i32)>::new(),\r
}\r
}\r
\r
- fn build_board_from_output(&mut self) {\r
- // If the board has already been read.\r
- if !self.board.is_empty() {\r
- return;\r
- }\r
-\r
+ fn from(output: &[i64]) -> Self {\r
+ let mut rts = RobotTrackingSystem::new();\r
let mut current_line = Vec::<char>::new();\r
let mut current_x = 0;\r
- for c in self.output.iter() {\r
+ for c in output.iter() {\r
if *c == 10 {\r
- self.board.push(current_line);\r
+ rts.board.push(current_line);\r
current_line = Vec::<char>::new();\r
current_x = 0;\r
} else {\r
let c = (*c as u8) as char;\r
if let Ok(dir) = Direction::try_from(c) {\r
- self.start_position = (current_x, self.board.len() as i32);\r
- self.start_dir = dir\r
+ rts.start_position = (current_x, rts.board.len() as i32);\r
+ rts.start_dir = dir\r
}\r
\r
current_line.push(c);\r
current_x += 1;\r
}\r
}\r
+ rts.run_through();\r
+ rts\r
+ }\r
\r
- self.output.clear();\r
- self.run_through();\r
+ fn get(&self, x: i32, y: i32) -> Option<char> {\r
+ if x < self.board[0].len() as i32 && x >= 0 && y < self.board.len() as i32 && y >= 0 {\r
+ Some(self.board[y as usize][x as usize])\r
+ } else {\r
+ None\r
+ }\r
}\r
\r
// Run the path and find the crossings and define the movements.\r
break;\r
}\r
}\r
+}\r
\r
- fn find_sequences(&mut self) {\r
- if !self.commands_sequences[0].is_empty() { return; }\r
+struct CommandSequences {\r
+ commands: Vec<(usize, Range<usize>)> // Each range is associated with a sequence number (first tuple value).\r
+}\r
\r
+fn is_overlapping<T : PartialOrd>(r1: &Range<T>, r2: &Range<T>) -> bool {\r
+ r1.start < r2.start && r1.end > r2.start || r2.start < r1.start && r2.end > r1.start\r
+}\r
\r
+impl CommandSequences {\r
+ fn new() -> Self {\r
+ CommandSequences {\r
+ commands: Vec::new()\r
+ }\r
}\r
+\r
+ fn find_sequences(&mut self, movements: &[MovementCommand]) {\r
+ // For each sequence length we try to match them agains the movements.\r
+ let len_min = 3;\r
+ let len_max = 6;\r
+\r
+ for l1 in len_min ..= len_max {\r
+ for l2 in len_min ..= len_max {\r
+ for l3 in len_min ..= len_max {\r
+ self.commands.clear();\r
+ let mut position: usize = 0;\r
+ for seq_num in 0 .. 3 {\r
+ let l = match seq_num { 0 => l1, 1 => l2, _ => l3 };\r
+ let range = position .. position + l;\r
+ self.commands.push((seq_num, range.clone()));\r
+ // Try to find the sequence elsewhere in 'movements'.\r
+ let mut position2 = position + l;\r
+ while position2 <= movements.len() - l {\r
+ let range2 = position2 .. position2 + l;\r
+ if !self.commands.iter().any(|(_, r)| is_overlapping(&r, &range2)) && movements.get(range.clone()) == movements.get(range2.clone()) {\r
+ self.commands.push((seq_num, range2));\r
+ position2 += l;\r
+ } else {\r
+ position2 += 1;\r
+ }\r
+ }\r
+\r
+ // Update position to the next free position.\r
+ while self.commands.iter().any(|(_, r)| r.contains(&position)) {\r
+ position += 1;\r
+ }\r
+ }\r
+\r
+ // Check if all movements are included into a sequence.\r
+ if self.commands.iter().fold(0, |sum, (_, range)| sum + range.len()) == movements.len() {\r
+ return;\r
+ }\r
+ }\r
+\r
+ }\r
+ }\r
+ }\r
+}\r
+\r
+struct Part1 { output: Vec<i64>, }\r
+\r
+impl Part1 { fn new() -> Self { Part1 { output: Vec::<i64>::new() } }}\r
+\r
+struct Part2 {\r
+ output: Vec<i64>,\r
+ rts: Option<RobotTrackingSystem>,\r
+ commands_sequences: CommandSequences,\r
+ input: Vec<i64>,\r
+ input_position: usize,\r
+ dust_collected: i64,\r
}\r
\r
-impl intcode::IO for RobotTrackingSystem {\r
+impl Part2 { fn new() -> Self { Part2 { output: Vec::<i64>::new(), rts: None, commands_sequences: CommandSequences::new(), input: Vec::new(), input_position: 0, dust_collected: 0 } } }\r
+\r
+impl intcode::IO for Part1 {\r
// Read instructions\r
+ fn read(&mut self) -> i64 { 0 }\r
+\r
+ // Send to the output channel.\r
+ fn write(&mut self, value: i64) {\r
+ self.output.push(value);\r
+ }\r
+}\r
+\r
+impl intcode::IO for Part2 {\r
+ // Read instructions.\r
fn read(&mut self) -> i64 {\r
- self.build_board_from_output();\r
- self.find_sequences();\r
- 42\r
+ if self.rts.is_none() {\r
+ self.rts = Some(RobotTrackingSystem::from(&self.output));\r
+ self.commands_sequences.find_sequences(&self.rts.as_ref().unwrap().dir_commands);\r
+\r
+ // 1: add the movements sequences: "A,B,C,A\n" // Max: 10 sequence calls.\r
+ for (i, (seq_num, _)) in self.commands_sequences.commands.iter().sorted_by(|(_, r1), (_, r2)| r1.start.cmp(&r2.start)).enumerate() {\r
+ if i > 0 { self.input.push(44); }\r
+ self.input.push(*seq_num as i64 + 65);\r
+ }\r
+ self.input.push(10);\r
+\r
+ // 2: Add the sequence A, B and C: "R,8,L,2,R,1\n", Max: ~6 movements.\r
+ for seq_num in 0 .. 3 {\r
+ let (_, sequence) = self.commands_sequences.commands.iter().find(|(s, _)| *s == seq_num).unwrap();\r
+ for (i, movement_command) in self.rts.as_ref().unwrap().dir_commands.get(sequence.clone()).unwrap().iter().enumerate() {\r
+\r
+ if i > 0 { self.input.push(44); }\r
+ if movement_command.mov == Movement::Left { self.input.push(76); } else { self.input.push(82); }\r
+ self.input.push(44);\r
+ for c in movement_command.steps.to_string().as_bytes() {\r
+ self.input.push(*c as i64);\r
+ }\r
+ }\r
+ self.input.push(10);\r
+ }\r
+\r
+ // 3: Add "y\n" (continuous video feed activated) or "n\n" (no video).\r
+ self.input.push(110);\r
+ self.input.push(10);\r
+ }\r
+\r
+ self.input_position += 1;\r
+ self.input[self.input_position - 1]\r
}\r
\r
// Send to the output channel.\r
}\r
\r
fn finished(&mut self) {\r
- self.build_board_from_output();\r
+ self.dust_collected = *self.output.last().unwrap();\r
}\r
}\r
\r
pub fn scaffold_intersections(code: &[i64]) -> i32 {\r
- let mut rts = RobotTrackingSystem::new();\r
- intcode::execute_op_code_with_custom_io(code, &mut rts);\r
+ let mut part1 = Part1::new();\r
+ intcode::execute_op_code_with_custom_io(code, &mut part1);\r
+ let rts = RobotTrackingSystem::from(&part1.output);\r
rts.crossings.iter().fold(0, |sum, crossing| sum + crossing.0 * crossing.1)\r
}\r
\r
-pub fn part2(code: &[i64]) {\r
- let mut rts = RobotTrackingSystem::new();\r
- intcode::execute_op_code_with_custom_io(code, &mut rts);\r
-\r
-}\r
-\r
-#[cfg(test)]\r
-mod tests {\r
- use super::*;\r
- use itertools::Itertools;\r
-\r
- #[test]\r
- fn foo() {\r
- let array = vec![1, 2, 3];\r
- let s: i32 = array.iter().sum();\r
- dbg!(s);\r
- let mut combinations: Vec<Vec<i32>> = (3..=7).combinations_with_replacement(3).sorted_by(|v1, v2| {\r
- let s1: i32 = v1.iter().sum();\r
- let s2: i32 = v2.iter().sum();\r
- s1.cmp(&s2)\r
- }).collect();\r
- println!("{:?}", combinations);\r
- }\r
+pub fn collected_dust(code: &[i64]) -> i64 {\r
+ let mut part2 = Part2::new();\r
+ intcode::execute_op_code_with_custom_io(code, &mut part2);\r
+ part2.dust_collected\r
}
\ No newline at end of file