diff --git a/Day-01/rust/duckulus/day01.rs b/Day-01/rust/duckulus/day01.rs new file mode 100644 index 0000000..2406c84 --- /dev/null +++ b/Day-01/rust/duckulus/day01.rs @@ -0,0 +1,53 @@ +use crate::common::{InputType, read_lines}; + +const DAY: usize = 1; + +pub fn part_one(input_type: InputType) { + let lines = read_lines(DAY, &input_type); + let mut dial = 50; + let mut password = 0; + for line in lines { + let direction = if &line.as_str()[..1] == "L" { -1 } else { 1 }; + dial += direction * line.as_str()[1..].parse::().unwrap(); + dial = dial % 100; + while dial < 0 { + dial = 100 + dial + } + if dial == 0 { + password += 1; + } + } + println!("{}", password); +} + +pub fn part_two(input_type: InputType) { + let lines = read_lines(DAY, &input_type); + let mut dial = 50; + let mut password = 0; + for line in lines { + let direction = if &line.as_str()[..1] == "L" { -1 } else { 1 }; + let change = line.as_str()[1..].parse::().unwrap(); + + if direction == -1 { + let t = dial; + dial -= change; + if dial < 0 && t == 0 { + password -= 1; + } + while dial < 0 { + dial = 100 + dial; + password += 1; + } + if dial == 0 { + password += 1; + } + } else { + dial += change; + if dial >= 100 { + password += dial / 100; + dial = dial % 100; + } + } + } + println!("{}", password); +} diff --git a/Day-02/rust/duckulus/day02.rs b/Day-02/rust/duckulus/day02.rs new file mode 100644 index 0000000..04d3f5b --- /dev/null +++ b/Day-02/rust/duckulus/day02.rs @@ -0,0 +1,62 @@ +use crate::common::{InputType, read_input}; +use std::ops::{Range, RangeInclusive}; + +const DAY: usize = 2; + +pub fn part_one(input_type: InputType) { + let ranges = read_ranges(input_type); + + let mut sum = 0; + for range in ranges { + for i in range { + let s = i.to_string(); + let str = s.as_str(); + let len = str.len(); + + if len % 2 != 0 { + continue + } + + if str[..len / 2] == str[len / 2..] { + sum += i; + } + } + } + println!("{}", sum); +} + +pub fn part_two(input_type: InputType) { + let ranges = read_ranges(input_type); + + let mut sum = 0; + for range in ranges { + for i in range { + let s = i.to_string(); + let str = s.as_str(); + let len = str.len(); + for pref in 1..=len / 2 { + if len % pref != 0 { + continue; + } + let reps = len / pref; + if str[..len / reps].repeat(reps) == str { + sum += i; + break; + } + } + } + } + println!("{}", sum); +} + +fn read_ranges(input_type: InputType) -> Vec> { + let ranges: Vec<_> = read_input(DAY, &input_type) + .as_str() + .split(",") + .map(|s| { + let split: Vec<_> = s.split("-").collect(); + split[0].trim().parse::().unwrap()..=split[1].trim().parse::().unwrap() + }) + .collect(); + ranges +} diff --git a/Day-03/rust/duckulus/day03.rs b/Day-03/rust/duckulus/day03.rs new file mode 100644 index 0000000..b63b037 --- /dev/null +++ b/Day-03/rust/duckulus/day03.rs @@ -0,0 +1,51 @@ +use crate::common::{InputType, read_input}; + +const DAY: usize = 3; + +pub fn part_one(input_type: &InputType) { + let value: u64 = get_banks(input_type) + .into_iter() + .map(|bank| max_joltage(bank, 2, 0)) + .sum(); + println!("{}", value) +} + +pub fn part_two(input_type: &InputType) { + let value: u64 = get_banks(&input_type) + .into_iter() + .map(|bank| max_joltage(bank, 12, 0)) + .sum(); + println!("{}", value) +} + +fn max_joltage(bank: Vec, digits: usize, start: usize) -> u64 { + if digits == 0 { + return 0; + } + let mut max = bank[start]; + let mut maxi = start; + for i in start + 1..=bank.len() - digits { + if bank[i] > max { + max = bank[i]; + maxi = i; + } + } + + let current_digit_value = (bank[maxi] as u64) * 10u64.pow((digits-1) as u32); + let remaining_digits_value = max_joltage(bank, digits - 1, maxi + 1); + + current_digit_value + remaining_digits_value +} + +fn get_banks(input_type: &InputType) -> Vec> { + read_input(DAY, input_type) + .as_str() + .trim() + .split("\n") + .map(|line| { + line.chars() + .map(|c| c.to_digit(10).unwrap() as u8) + .collect() + }) + .collect() +} diff --git a/Day-04/rust/duckulus/day04.rs b/Day-04/rust/duckulus/day04.rs new file mode 100644 index 0000000..a643c8d --- /dev/null +++ b/Day-04/rust/duckulus/day04.rs @@ -0,0 +1,86 @@ +use crate::common::{InputType, read_input}; + +const DAY: usize = 4; + +pub fn part_one(input_type: &InputType) { + let mut grid = get_grid(input_type); + let value = remove_unaccessible(&mut grid); + println!("{}", value) +} + +pub fn part_two(input_type: &InputType) { + let mut grid = get_grid(input_type); + let mut sum = 0; + let mut value = 0; + value = remove_unaccessible(&mut grid); + while value > 0 { + sum += value; + value = remove_unaccessible(&mut grid); + } + println!("{}", sum) +} + +fn remove_unaccessible(grid: &mut Vec>) -> usize { + let mut remove: Vec<(usize, usize)> = Vec::new(); + + let rows = grid.len() as i32; + for y in 0..rows { + let cols = grid[y as usize].len() as i32; + for x in 0..cols { + if grid[y as usize][x as usize] != Cell::Roll { + continue; + } + let mut neighbors = 0; + for dy in -1..=1 { + let ny = (y) + dy; + if (ny < 0 || ny >= rows) { + continue; + } + for dx in -1..=1 { + if (dx == 0 && dy == 0) { + continue; + } + let nx = (x) + dx; + if (nx < 0 || nx >= cols) { + continue; + } + if grid[ny as usize][nx as usize] == Cell::Roll { + neighbors += 1; + } + } + } + if neighbors < 4 { + remove.push((x as usize, y as usize)); + } + } + } + + for (x, y) in remove.iter() { + grid[*y][*x] = Cell::Empty; + } + + remove.len() +} + +#[derive(PartialEq)] +enum Cell { + Roll, + Empty, +} + +fn to_cell(c: char) -> Cell { + match c { + '.' => Cell::Empty, + '@' => Cell::Roll, + _ => panic!("Found invalid cell: {}", c), + } +} + +fn get_grid(input_type: &InputType) -> Vec> { + read_input(DAY, input_type) + .as_str() + .trim() + .lines() + .map(|line| line.trim().chars().map(to_cell).collect()) + .collect() +} diff --git a/Day-05/rust/duckulus/day05.rs b/Day-05/rust/duckulus/day05.rs new file mode 100644 index 0000000..1056611 --- /dev/null +++ b/Day-05/rust/duckulus/day05.rs @@ -0,0 +1,93 @@ +use crate::common::{InputType, read_input, Range}; +use std::cmp::{max, min}; +use std::collections::HashSet; + +const DAY: usize = 5; + +fn parse(input_type: &InputType) -> (Vec, Vec) { + let input = read_input(DAY, input_type); + let (ranges, ingredients) = input.split_once("\n\n").unwrap(); + + let ranges: Vec<_> = ranges + .lines() + .map(|line: &str| { + let (l, h) = line.split_once("-").unwrap(); + Range { + l: l.parse::().unwrap(), + h: h.parse::().unwrap(), + } + }) + .collect(); + + let ingredients: Vec<_> = ingredients + .lines() + .map(|line: &str| line.parse::().unwrap()) + .collect(); + + (ranges, ingredients) +} + +pub fn part_one(input_type: &InputType) { + let (ranges, ingredients) = parse(input_type); + + let mut sum = 0; + for ingredient in ingredients { + for range in &ranges { + if range.l <= ingredient && range.h >= ingredient { + sum += 1; + break; + } + } + } + println!("{}", sum); +} + +pub fn part_two(input_type: &InputType) { + let (ranges, _) = parse(input_type); + + let mut range_list = Vec::new(); + + for range in ranges { + if range_list.is_empty() { + range_list.push(range); + continue; + } + + let mut intersecting: Vec = range_list + .iter() + .filter(|r| r.intersection(&range).is_some()) + .map(|r| r.clone()) + .collect(); + intersecting.push(range.clone()); + + let l = intersecting.iter().map(|r| r.l).min().unwrap(); + let h = intersecting.iter().map(|r| r.h).max().unwrap(); + let union = Range { l, h }; + + range_list.retain(|r| r.intersection(&range).is_none()); + range_list.push(union); + } + + let sum: i64 = range_list.iter().map(|r| r.len()).sum(); + println!("{}", sum); +} + +#[cfg(test)] +mod test { + use crate::day05::Range; + + #[test] + fn test_range_intersect() { + let r1 = Range { l: 3, h: 5 }; + let r2 = Range { l: 6, h: 8 }; + let r3 = Range { l: 4, h: 9 }; + let r4 = Range { l: 1, h: 4 }; + let r5 = Range { l: 5, h: 6 }; + + assert_eq!(0, r1.intersect(&r2)); + assert_eq!(2, r1.intersect(&r3)); + assert_eq!(2, r1.intersect(&r4)); + assert_eq!(1, r1.intersect(&r5)); + assert_eq!(3, r1.intersect(&r1)); + } +} diff --git a/Day-06/rust/duckulus/day06.rs b/Day-06/rust/duckulus/day06.rs new file mode 100644 index 0000000..b105c56 --- /dev/null +++ b/Day-06/rust/duckulus/day06.rs @@ -0,0 +1,80 @@ +use crate::common::{InputType, read_lines}; + +const DAY: usize = 6; + +pub fn part_one(input_type: &InputType) { + let lines = read_lines(DAY, input_type); + let mut rows = Vec::new(); + let mut operations = Vec::new(); + for line in &lines { + if line.contains("+") { + operations.append(&mut line.trim().split_whitespace().collect::>()); + break; + } + rows.push( + line.trim() + .split_whitespace() + .map(|s| s.parse::().unwrap()) + .collect::>(), + ) + } + + let mut results = rows.remove(0); + for row in rows { + for i in 0..row.len() { + match operations[i] { + "+" => results[i] += row[i], + "*" => results[i] *= row[i], + _ => panic!("Unexpected operation {}", operations[i]), + } + } + } + println!("{}", results.iter().sum::()) +} + +pub fn part_two(input_type: &InputType) { + let mut lines = read_lines(DAY, input_type); + let last_line = lines.remove(lines.len() - 1); + let operations = last_line.trim().split_whitespace().collect::>(); + let max_length = lines.iter().map(|l| l.len()).max().unwrap(); + lines + .iter_mut() + .for_each(|l| l.push_str(" ".repeat(max_length - l.len()).as_str())); + + let mut iters = lines.iter().map(|l| l.chars()).collect::>(); + let mut results: Vec = Vec::new(); + let mut current_group = Vec::new(); + for i in 0..max_length { + let chars = iters + .iter_mut() + .map(|iter| iter.next().unwrap()) + .collect::>(); + if chars.iter().all(|c| *c == ' ') { + let result = match operations[results.len()] { + "+" => current_group.iter().fold(0, |a, b| a + b), + "*" => current_group.iter().fold(1, |a, b| a * b), + _ => panic!("Unexpected operation {}", operations[results.len()]), + }; + results.push(result); + current_group.clear(); + continue; + } + + let mut num = 0; + for c in chars.iter() { + if *c != ' ' { + num *= 10; + num += c.to_digit(10).unwrap() as u64 + } + } + current_group.push(num); + } + let result = match operations[results.len()] { + "+" => current_group.iter().fold(0, |a, b| a + b), + "*" => current_group.iter().fold(1, |a, b| a * b), + _ => panic!("Unexpected operation {}", operations[results.len()]), + }; + results.push(result); + + println!("{}", results.iter().sum::()) +} diff --git a/Day-07/rust/duckulus/day07.rs b/Day-07/rust/duckulus/day07.rs new file mode 100644 index 0000000..595b532 --- /dev/null +++ b/Day-07/rust/duckulus/day07.rs @@ -0,0 +1,70 @@ +use crate::common::{InputType, read_lines}; +use std::collections::{HashMap, HashSet}; + +const DAY: usize = 7; + +pub fn part_one(input_type: &InputType) { + let grid = read_lines(DAY, input_type) + .iter() + .map(|s| s.chars().collect::>()) + .collect::>(); + let start = grid[0].iter().position(|c| *c == 'S').unwrap(); + + let mut beams = HashSet::new(); + beams.insert(start); + + let mut splits = 0; + for i in 1..grid.len() { + let row = &grid[i]; + for beam in beams.clone() { + if row[beam] == '^' { + beams.remove(&beam); + beams.insert(beam - 1); + beams.insert(beam + 1); + splits += 1; + } + } + } + + println!("{}", splits); +} + +pub fn part_two(input_type: &InputType) { + let grid = read_lines(DAY, input_type) + .iter() + .map(|s| s.chars().collect::>()) + .collect::>(); + let start = grid[0].iter().position(|c| *c == 'S').unwrap(); + + let mut memo = HashMap::new(); + println!("{}", timelines(&mut memo, &grid, 2, start)); +} + +fn timelines( + memo: &mut HashMap<(usize, usize), i64>, + grid: &Vec>, + row: usize, + col: usize, +) -> i64 { + if let Some(tl) = memo.get(&(row, col)) { + return *tl; + } + + let mut left_timelines = 1; + for i in row + 1..grid.len() { + if grid[i][col - 1] == '^' { + left_timelines = timelines(memo, grid, i, col - 1); + break; + } + } + let mut right_timelines = 1; + for i in row + 1..grid.len() { + if grid[i][col + 1] == '^' { + right_timelines = timelines(memo, grid, i, col + 1); + break; + } + } + let timelines = left_timelines + right_timelines; + memo.insert((row, col), timelines); + timelines +} diff --git a/Day-09/rust/duckulus/day09.rs b/Day-09/rust/duckulus/day09.rs new file mode 100644 index 0000000..18a9970 --- /dev/null +++ b/Day-09/rust/duckulus/day09.rs @@ -0,0 +1,105 @@ +use crate::common::{DisjointRangeSet, InputType, Range, read_lines}; +use std::cmp::{max, min}; +use std::collections::{HashMap, HashSet}; + +const DAY: usize = 9; + +pub fn part_one(input_type: &InputType) { + let tiles = read_lines(DAY, input_type) + .iter() + .map(|s| { + let (l, r) = s.split_once(",").unwrap(); + (l.parse::().unwrap(), r.parse::().unwrap()) + }) + .collect::>(); + + let mut max_area = 0; + for i in 0..tiles.len() { + let tile_a = tiles[i]; + for j in i + 1..tiles.len() { + let tile_b = tiles[j]; + let width = (tile_a.0 - tile_b.0).abs() + 1; + let height = (tile_a.1 - tile_b.1).abs() + 1; + max_area = max(max_area, width * height); + } + } + + println!("{}", max_area) +} + +pub fn part_two(input_type: &InputType) { + let tiles = read_lines(DAY, input_type) + .iter() + .map(|s| { + let (l, r) = s.split_once(",").unwrap(); + (l.parse::().unwrap(), r.parse::().unwrap()) + }) + .collect::>(); + + let mut max_area = 0; + for i in 0..tiles.len() { + let tile_a = tiles[i]; + for j in i + 1..tiles.len() { + let tile_b = tiles[j]; + let min_x = min(tile_a.0, tile_b.0); + let max_x = max(tile_a.0, tile_b.0); + let min_y = min(tile_a.1, tile_b.1); + let max_y = max(tile_a.1, tile_b.1); + let x_range = Range::new(min_x, max_x); + let y_range = Range::new(min_y, max_y); + let width = max_x - min_x + 1; + let height = max_y - min_y + 1; + let area = width * height; + if area <= max_area { + continue; + } + + let mut left = DisjointRangeSet::new(); + let mut right = DisjointRangeSet::new(); + let mut top = DisjointRangeSet::new(); + let mut bottom = DisjointRangeSet::new(); + for k in 0..tiles.len() { + let corner1 = tiles[k]; + let corner2 = tiles[(k + 1) % tiles.len()]; + + if corner1.0 == corner2.0 { + //vertical edge + if corner1.0 <= min_x { + let edge_range = Range::from_unordered(corner1.1, corner2.1); + if let Some(intersect) = edge_range.intersection(&y_range) { + left.add_range(intersect); + } + } else if corner1.0 >= max_x { + let edge_range = Range::from_unordered(corner1.1, corner2.1); + if let Some(intersect) = edge_range.intersection(&y_range) { + right.add_range(intersect); + } + } + } else if corner1.1 == corner2.1 { + // horizontal edge + if corner1.1 <= min_y { + let edge_range = Range::from_unordered(corner1.0, corner2.0); + if let Some(intersect) = edge_range.intersection(&x_range) { + top.add_range(intersect); + } + } else if corner1.1 >= max_y { + let edge_range = Range::from_unordered(corner1.0, corner2.0); + if let Some(intersect) = edge_range.intersection(&x_range) { + bottom.add_range(intersect); + } + } + } else { + panic!("Expected all corners to be adjacent") + } + } + if left.len() == 1 && left.min() ==min_y && left.max() == max_y + && right.len() == 1 && right.min() ==min_y && right.max() == max_y + && top.len() == 1 && top.min() ==min_x && top.max() == max_x + && bottom.len() == 1 && bottom.min() ==min_x && bottom.max() == max_x { + max_area = area; + } + } + } + + println!("{}", max_area) +} diff --git a/Day-11/rust/duckulus/day11.rs b/Day-11/rust/duckulus/day11.rs new file mode 100644 index 0000000..7e95101 --- /dev/null +++ b/Day-11/rust/duckulus/day11.rs @@ -0,0 +1,74 @@ +use crate::common::{Graph, InputType, read_lines}; +use std::collections::HashMap; + +const DAY: usize = 11; + +fn build_graph(input_type: &InputType) -> Graph { + let lines = read_lines(DAY, input_type); + let mut graph = Graph::new(); + for line in &lines { + let (source, _) = line.trim().split_once(": ").unwrap(); + graph.add_node(source.to_string()) + } + graph.add_node("out".to_string()); + for line in &lines { + let (source, dest) = line.trim().split_once(": ").unwrap(); + let target_nodes = dest.split(" ").collect::>(); + for target in target_nodes.iter().rev() { + graph.add_edge(&source.to_string(), &target.to_string()); + } + } + graph +} + +pub fn part_one(input_type: &InputType) { + let graph = build_graph(input_type); + println!("{}", count_paths_one(&graph, "you", "out")) +} + +fn count_paths_one(graph: &Graph, src: &str, dest: &str) -> i64 { + if (src == dest) { + return 1; + } + let mut sum = 0; + for succ in graph.successors(&src.to_string()) { + sum += count_paths_one(graph, succ.as_str(), dest); + } + sum +} + +pub fn part_two(input_type: &InputType) { + let graph = build_graph(input_type); + let mut cache = HashMap::new(); + let mut path = Vec::new(); + println!( + "{}", + count_paths_two(&mut cache, &graph, "svr", "out", &mut path) + ) +} + +fn count_paths_two( + cache: &mut HashMap<(String, (bool, bool)), i64>, + graph: &Graph, + src: &str, + dest: &str, + path: &mut Vec, +) -> i64 { + let b1 = path.contains(&"dac".to_string()); + let b2 = path.contains(&"fft".to_string()); + if (src == dest) { + return if b1 && b2 { 1 } else { 0 }; + } + let key = (src.to_string(), (b1, b2)); + if let Some(paths) = cache.get(&key) { + return *paths; + } + let mut sum = 0; + path.push(src.to_string()); + for succ in graph.successors(&src.to_string()) { + sum += count_paths_two(cache, graph, succ.as_str(), dest, path); + } + assert_eq!(src, path.pop().unwrap()); + cache.insert(key , sum); + sum +} diff --git a/Day-12/rust/duckulus/day12.rs b/Day-12/rust/duckulus/day12.rs new file mode 100644 index 0000000..4f1c0a7 --- /dev/null +++ b/Day-12/rust/duckulus/day12.rs @@ -0,0 +1,77 @@ +use crate::common::{Graph, InputType, read_input, read_lines}; +use std::arch::x86_64::_mm256_lddqu_si256; +use std::collections::HashMap; + +const DAY: usize = 12; + +#[derive(Debug)] +struct Region { + width: i64, + height: i64, + quantities: Vec, +} + +pub fn part_one(input_type: &InputType) { + let (shapes, regions) = parse(input_type); + let mut possible_regions = 0; + for region in ®ions { + let mut used_area = 0; + for i in 0..shapes.len() { + let shape_size = shapes[i].iter().filter(|b| **b).count() as i64; + used_area += region.quantities[i] * shape_size; + } + let available_area = region.width * region.height; + if used_area <= available_area { + possible_regions += 1; + } + } + + println!("Regions possible {}/{}", possible_regions, regions.len()); +} + +pub fn part_two(input_type: &InputType) {} + +fn parse(input_type: &InputType) -> (Vec<[bool; 9]>, Vec) { + let input = read_input(DAY, input_type); + let mut split = input.trim().split("\n\n").collect::>(); + + let regions = split + .pop() + .unwrap() + .split("\n") + .map(|line| { + let (left, right) = line.trim().split_once(": ").unwrap(); + let (w, h) = left.split_once("x").unwrap(); + let width = w.parse::().unwrap(); + let height = h.parse::().unwrap(); + let quantities = right + .trim() + .split(" ") + .map(|s| s.parse::().unwrap()) + .collect(); + Region { + width, + height, + quantities, + } + }) + .collect::>(); + let mut shapes: Vec<[bool; 9]> = Vec::new(); + for s in split { + let mut shape = [false; 9]; + let lines = s.trim().split("\n").skip(1).collect::>(); + let mut i = 0; + for line in lines { + for c in line.trim().chars() { + if c == '#' { + shape[i] = true; + } else if c != '.' { + panic!("Unexpected shape char {}", c); + } + i += 1; + } + } + shapes.push(shape); + } + (shapes, regions) +} diff --git a/shared/duckulus/common.rs b/shared/duckulus/common.rs new file mode 100644 index 0000000..2ce7265 --- /dev/null +++ b/shared/duckulus/common.rs @@ -0,0 +1,264 @@ +use std::borrow::Borrow; +use std::cmp::{max, min}; +use std::collections::HashMap; +use std::fs::{File, read_to_string}; +use std::hash::Hash; +use std::io::BufRead; +use std::sync::Arc; + +pub enum InputType { + Example, + Real, +} + +impl InputType { + fn get_file_name(&self) -> &'static str { + match self { + InputType::Example => "example", + InputType::Real => "input", + } + } +} + +fn get_path(day: usize, input_type: &InputType) -> String { + format!( + "./{}-{}.txt", + input_type.get_file_name(), + left_pad_zero(day.to_string().as_str(), 2) + ) +} + +pub fn read_input(day: usize, input_type: &InputType) -> String { + read_to_string(get_path(day, input_type)) + .unwrap() + .as_str() + .trim() + .to_string() +} + +pub fn read_lines(day: usize, input_type: &InputType) -> Vec { + let lines = read_to_string(get_path(day, input_type)).unwrap(); + + let mut output = Vec::new(); + for line in lines.lines() { + output.push(line.to_string()); + } + output +} + +fn left_pad_zero(str: &str, length: usize) -> String { + left_pad(str, '0', length) +} + +fn left_pad(str: &str, c: char, length: usize) -> String { + if str.len() < length { + String::from(c).as_str().repeat(length - str.len()) + str + } else { + str.to_string() + } +} + +#[derive(Debug, Clone)] +pub struct Range { + pub l: i64, + pub h: i64, +} + +impl Range { + pub fn new(l: i64, h: i64) -> Self { + Self { l, h } + } + + pub fn from_unordered(l: i64, h: i64) -> Self { + Self { + l: min(l, h), + h: max(l, h), + } + } + pub fn len(&self) -> i64 { + self.h - self.l + 1 + } + + pub fn intersect(&self, other: &Range) -> i64 { + self.intersection(other).map(|r| r.len()).unwrap_or(0) + } + + pub fn intersection(&self, other: &Range) -> Option { + let l = max(self.l, other.l); + let h = min(self.h, other.h); + if l > h { None } else { Some(Range { l, h }) } + } +} + +pub struct DisjointRangeSet { + ranges: Vec, +} + +impl DisjointRangeSet { + pub fn new() -> Self { + Self { ranges: Vec::new() } + } + + pub fn add_range(&mut self, range: Range) { + if self.ranges.is_empty() { + self.ranges.push(range); + return; + } + + let mut intersecting: Vec = self + .ranges + .iter() + .filter(|r| r.intersection(&range).is_some()) + .map(|r| r.clone()) + .collect(); + intersecting.push(range.clone()); + + let l = intersecting.iter().map(|r| r.l).min().unwrap(); + let h = intersecting.iter().map(|r| r.h).max().unwrap(); + let union = Range::new(l, h); + + self.ranges.retain(|r| r.intersection(&range).is_none()); + self.ranges.push(union); + } + + pub fn len(&self) -> usize { + self.ranges.len() + } + + pub fn min(&self) -> i64 { + self.ranges.iter().map(|r| r.l).min().unwrap() + } + + pub fn max(&self) -> i64 { + self.ranges.iter().map(|r| r.h).max().unwrap() + } +} + +type NodeIndex = usize; +type EdgeIndex = usize; + +pub struct Graph { + node_values: Vec>, + node_index: HashMap, NodeIndex>, + + nodes: Vec, + edges: Vec, +} + +struct NodeData { + first_edge: Option, +} + +struct EdgeData { + target_node: NodeIndex, + next_edge: Option, +} + +impl Graph { + pub fn new() -> Self { + Self { + node_index: HashMap::new(), + node_values: Vec::new(), + + nodes: Vec::new(), + edges: Vec::new(), + } + } + + pub fn add_node(&mut self, node: T) { + assert_eq!(self.nodes.len(), self.node_values.len()); + + let index = self.nodes.len(); + self.nodes.push(NodeData { first_edge: None }); + let arc = Arc::new(node); + self.node_values.push(arc.clone()); + self.node_index.insert(arc, index); + } + + pub fn add_edge(&mut self, start: &T, end: &T) { + let start_node_idx = *self.node_index.get(start).unwrap(); + let start_node = &mut self.nodes[start_node_idx]; + let end_node_idx = *self.node_index.get(end).unwrap(); + + let edge_index = self.edges.len(); + self.edges.push(EdgeData { + target_node: end_node_idx, + next_edge: start_node.first_edge, + }); + start_node.first_edge = Some(edge_index) + } + + pub fn successors(&'_ self, node: &T) -> Successors<'_, T> { + let node_idx = *self.node_index.get(node).unwrap(); + let first_edge = self.nodes[node_idx].first_edge; + Successors { + graph: self, + current_edge_idx: first_edge, + } + } +} + +pub struct Successors<'g, T: Eq + Hash> { + graph: &'g Graph, + current_edge_idx: Option, +} + +impl<'g, T: Eq + Hash> Iterator for Successors<'g, T> { + type Item = &'g T; + + fn next(&mut self) -> Option { + match self.current_edge_idx { + None => None, + Some(idx) => { + let data = &self.graph.edges[idx]; + self.current_edge_idx = data.next_edge; + + let node = &self.graph.node_values[data.target_node]; + Some(node.as_ref()) + } + } + } +} + +#[cfg(test)] +mod test { + use crate::common::{Graph, left_pad, left_pad_zero}; + + #[test] + fn test_left_pad() { + assert_eq!("1", left_pad_zero("1", 1)); + assert_eq!("01", left_pad_zero("1", 2)); + assert_eq!("001", left_pad_zero("1", 3)); + assert_eq!("100", left_pad_zero("100", 3)); + assert_eq!("1000", left_pad_zero("1000", 3)); + } + + #[test] + fn test_graph() { + let mut graph = Graph::new(); + graph.add_node("a".to_string()); + graph.add_node("b".to_string()); + graph.add_node("c".to_string()); + graph.add_node("d".to_string()); + + graph.add_edge(&"a".into(), &"b".into()); + graph.add_edge(&"a".into(), &"c".into()); + graph.add_edge(&"a".into(), &"d".into()); + + graph.add_edge(&"b".into(), &"c".into()); + + assert_eq!( + vec![&"d", &"c", &"b"], + graph.successors(&"a".into()).collect::>() + ); + assert_eq!( + vec![&"c"], + graph.successors(&"b".into()).collect::>() + ); + let empty: Vec<&String> = Vec::new(); + assert_eq!( + empty, + graph.successors(&"c".into()).collect::>() + ); + } +}