├── .gitignore ├── LICENSE ├── README.md ├── addlist ├── Cargo.toml ├── README.md └── src │ └── lib.rs ├── bestpath ├── Cargo.toml ├── README.md └── src │ └── lib.rs ├── fizzbuzz ├── Cargo.toml ├── README.md └── src │ └── main.rs ├── i18n ├── README.md ├── naive │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── prefix │ ├── Cargo.toml │ └── src │ │ └── main.rs └── sets │ ├── Cargo.toml │ └── src │ └── main.rs ├── minstack ├── Cargo.toml ├── README.md └── src │ └── lib.rs ├── oddman ├── Cargo.toml ├── README.md └── src │ └── lib.rs ├── pancake ├── Cargo.toml ├── README.md └── src │ └── lib.rs ├── printmult ├── Cargo.toml ├── README.md └── src │ └── main.rs ├── reversewords ├── Cargo.toml ├── README.md └── src │ └── lib.rs ├── substring ├── Cargo.toml ├── README.md └── src │ └── lib.rs ├── sumfile ├── Cargo.toml ├── README.md ├── numbers.txt └── src │ └── main.rs ├── targetsum ├── Cargo.toml ├── README.md └── src │ └── lib.rs └── validbtree ├── Cargo.toml ├── README.md └── src └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled files 2 | *.o 3 | *.so 4 | *.rlib 5 | *.dll 6 | 7 | # Executables 8 | *.exe 9 | 10 | # Generated by Cargo 11 | Cargo.lock 12 | **/target/ 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Brian Quinlan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Learning Rust through Interview Questions 2 | 3 | This reposity is the product of my first in earnest attempt to 4 | learn [Rust](http://www.rust-lang.org/). 5 | 6 | I didn't have a particular probem to tackle so I used job interview 7 | coding questions that I found on the internet. In particular, 8 | ["Hacking a Google Interview"](http://courses.csail.mit.edu/iap/interview/materials.php) 9 | contains a lot of interesting questions. 10 | 11 | Since this was a learning project, some of the code might not be 12 | idiomatic (or even sane). Corrections are welcome and can be 13 | sent in the form of pull requests or an email to brian@sweetapp.com. 14 | 15 | ## Understandability 16 | 17 | Assuming that you are a programmer unfamiliar with Rust, here is my 18 | arrangement of problems in order of increasing difficult to understand 19 | algorithms and Rust idioms. 20 | 21 | * [printmult](https://github.com/brianquinlan/learn-rust/tree/master/printmult) - 22 | Print the 12x12 multiplication table. 23 | * [fizzbuzz](https://github.com/brianquinlan/learn-rust/tree/master/fizzbuzz) - 24 | Count to 100 using the words "Fizz", "Buzz" and "FizzBuzz". 25 | * [oddman](https://github.com/brianquinlan/learn-rust/blob/master/oddman) - 26 | Find the missing integer in a list. 27 | * [substring](https://github.com/brianquinlan/learn-rust/tree/master/substring) - 28 | Determine if a string is a substring of another. 29 | * [sumfile](https://github.com/brianquinlan/learn-rust/tree/master/sumfile) - 30 | Sum the contents of a text file. 31 | * [targetsum](https://github.com/brianquinlan/learn-rust/tree/master/targetsum) - 32 | Find pairs in a list that sum to a value. 33 | * [addlist](https://github.com/brianquinlan/learn-rust/tree/master/addlist) - 34 | Increment a number represented as a linked-list. 35 | * [pancake](https://github.com/brianquinlan/learn-rust/tree/master/pancake) - 36 | Sort a list using a function that reverse the list between [0..n]. 37 | * [validbtree](https://github.com/brianquinlan/learn-rust/tree/master/validbtree) - 38 | Determine if a binary tree is valid. 39 | * [reversewords](https://github.com/brianquinlan/learn-rust/tree/master/reversewords) - 40 | Reverse the words in a string. 41 | * [minstack](https://github.com/brianquinlan/learn-rust/tree/master/minstack) - 42 | Implement a stack with O(1) getMinimumValue. 43 | * [bestpath](https://github.com/brianquinlan/learn-rust/tree/master/bestpath) - 44 | Return the fastest path through a maze. 45 | * [i18n](https://github.com/brianquinlan/learn-rust/tree/master/i18n) - 46 | Expand patterns like "i18n". 47 | -------------------------------------------------------------------------------- /addlist/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "addlist" 3 | version = "0.1.0" 4 | authors = ["Brian Quinlan "] 5 | -------------------------------------------------------------------------------- /addlist/README.md: -------------------------------------------------------------------------------- 1 | # Increment a Number Presented as a Linked-List 2 | 3 | ## Problem 4 | 5 | Increment a number represented as a linked list. Each node contains a single digit 6 | and the most significant digit is stored in the head of the list. 7 | 8 | Do *not* reverse the linked-list before performing the increment. 9 | 10 | For example: 11 | 12 | [ 1 ] --> [ 2 ] --> [ 3 ] => [ 1 ] --> [ 2 ] --> [ 4 ] 13 | [ 1 ] --> [ 9 ] --> [ 9 ] => [ 2 ] --> [ 0 ] --> [ 0 ] 14 | 15 | ## Source: 16 | 17 | https://ashayraut.files.wordpress.com/2014/05/interview-preparation-best-100-ashay-raut.pdf 18 | -------------------------------------------------------------------------------- /addlist/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | pub struct Node { 3 | pub value: u8, 4 | pub next: Link, 5 | } 6 | 7 | pub type Link = Option>; 8 | 9 | impl PartialEq for Node { 10 | fn eq(&self, other: &Node) -> bool { 11 | self.value == other.value && match self.next { 12 | None => other.next.is_none(), 13 | Some(ref node) => match other.next { 14 | None => false, 15 | Some(ref n2) => node == n2, 16 | }, 17 | } 18 | } 19 | } 20 | 21 | /// Increment an integer whose digits are represented as seperate nodes in 22 | /// a linked list. Return whether the increment resulted in overflow. 23 | /// 24 | /// # Examples 25 | /// 26 | /// ``` 27 | /// use addlist::{Node, increment}; 28 | /// 29 | /// let mut node = Node{ 30 | /// value: 1, 31 | /// next: Some(Box::new(Node{value: 9, next: None}))}; 32 | /// assert!(!increment(&mut node)); 33 | /// assert_eq!( 34 | /// node, 35 | /// Node{value: 2, next: Some(Box::new(Node{value: 0, next: None}))}); 36 | /// ``` 37 | #[inline] 38 | pub fn increment(n: &mut Node) -> bool { 39 | if let Some(ref mut node) = n.next { 40 | if increment(&mut *node) { 41 | n.value += 1; 42 | } 43 | 44 | } else { 45 | n.value += 1; 46 | } 47 | 48 | if n.value == 10 { 49 | n.value = 0; 50 | return true; 51 | } 52 | false 53 | } 54 | 55 | #[test] 56 | fn eq_empty() { 57 | assert!(Node{value: 1, next: None} == Node{value: 1, next: None}); 58 | assert!(Node{value: 1, next: None} != Node{value: 2, next: None}); 59 | } 60 | 61 | #[test] 62 | fn eq_left_empty() { 63 | assert!(Node{value: 1, next: None} != 64 | Node{value: 1, next: Some(Box::new(Node{value: 9, next: None}))}); 65 | } 66 | 67 | #[test] 68 | fn eq_right_empty() { 69 | assert!(Node{value: 1, next: Some(Box::new(Node{value: 9, next: None}))} != 70 | Node{value: 1, next: None}); 71 | } 72 | 73 | #[test] 74 | fn eq_some_next() { 75 | assert!(Node{value: 1, next: Some(Box::new(Node{value: 1, next: None}))} == 76 | Node{value: 1, next: Some(Box::new(Node{value: 1, next: None}))}); 77 | assert!(Node{value: 1, next: Some(Box::new(Node{value: 1, next: None}))} != 78 | Node{value: 1, next: Some(Box::new(Node{value: 2, next: None}))}); 79 | } 80 | 81 | #[test] 82 | fn increment_0() { 83 | let mut node = Node{value: 0, next: None}; 84 | assert!(!increment(&mut node)); 85 | assert_eq!(Node{value: 1, next: None}, node); 86 | } 87 | 88 | #[test] 89 | fn increment_9() { 90 | let mut node = Node{value: 9, next: None}; 91 | assert!(increment(&mut node)); 92 | assert_eq!(Node{value: 0, next: None}, node); 93 | } 94 | 95 | #[test] 96 | fn increment_099() { 97 | let mut node = Node{value: 0, next: Some(Box::new( 98 | Node{value: 9, next: Some(Box::new( 99 | Node{value: 9, next: None}))}))}; 100 | assert!(!increment(&mut node)); 101 | assert_eq!( 102 | Node{value: 1, 103 | next: Some(Box::new( 104 | Node{value: 0, next: Some(Box::new( 105 | Node{value: 0, next: None}))}))}, node); 106 | 107 | } 108 | 109 | #[test] 110 | fn increment_123() { 111 | let mut node = Node{value: 1, next: Some(Box::new( 112 | Node{value: 2, next: Some(Box::new( 113 | Node{value: 3, next: None}))}))}; 114 | assert!(!increment(&mut node)); 115 | assert_eq!( 116 | Node{value: 1, 117 | next: Some(Box::new( 118 | Node{value: 2, next: Some(Box::new( 119 | Node{value: 4, next: None}))}))}, node); 120 | 121 | } 122 | 123 | #[test] 124 | fn increment_999() { 125 | let mut node = Node{value: 9, next: Some(Box::new( 126 | Node{value: 9, next: Some(Box::new( 127 | Node{value: 9, next: None}))}))}; 128 | assert!(increment(&mut node)); 129 | assert_eq!( 130 | Node{value: 0, 131 | next: Some(Box::new( 132 | Node{value: 0, next: Some(Box::new( 133 | Node{value: 0, next: None}))}))}, node); 134 | 135 | } 136 | -------------------------------------------------------------------------------- /bestpath/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bestpath" 3 | version = "0.1.0" 4 | authors = ["Brian Quinlan "] 5 | -------------------------------------------------------------------------------- /bestpath/README.md: -------------------------------------------------------------------------------- 1 | # Find a Path through a Grid. 2 | 3 | ## Problem 4 | 5 | Given a square grid find the optimal path from a given start point to a 6 | given destination. The grid may contain obstacles which must be 7 | circumvented. 8 | 9 | ## Source: 10 | 11 | http://courses.csail.mit.edu/iap/interview/Hacking_a_Google_Interview_Practice_Questions_Person_B.pdf 12 | -------------------------------------------------------------------------------- /bestpath/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Reference: http://www.redblobgames.com/pathfinding/a-star/introduction.html 2 | 3 | use std::collections::BinaryHeap; 4 | use std::collections::HashMap; 5 | use std::cmp::Ordering; 6 | 7 | #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] 8 | pub struct Position { 9 | pub x: usize, 10 | pub y: usize 11 | } 12 | 13 | // Used to maintain a priority queue of positions to explore. 14 | #[derive(Copy, Clone, Eq, PartialEq, Debug)] 15 | struct PositionPriority { 16 | minimum_cost: usize, 17 | position: Position, 18 | } 19 | 20 | // The priority queue depends on `Ord`. 21 | impl Ord for PositionPriority { 22 | // Reverse the comparison so the position with the lowest cost is 23 | // explored first. 24 | fn cmp(&self, other: &PositionPriority) -> Ordering { 25 | other.minimum_cost.cmp(&self.minimum_cost) 26 | } 27 | } 28 | 29 | impl PartialOrd for PositionPriority { 30 | fn partial_cmp(&self, other: &PositionPriority) -> Option { 31 | Some(self.cmp(other)) 32 | } 33 | } 34 | 35 | #[inline] 36 | fn min_distance_heuristic(p1: Position, p2: Position) -> usize { 37 | let x_diff = match p1.x > p2.x { 38 | true => p1.x - p2.x, 39 | false => p2.x - p1.x, 40 | }; 41 | let y_diff = match p1.y > p2.y { 42 | true => p1.y - p2.y, 43 | false => p2.y - p1.y, 44 | }; 45 | 46 | x_diff + y_diff // Since diagonal moves aren't allowed. 47 | } 48 | 49 | /// Return the navigable neighbouring cells to the given one. 50 | #[inline] 51 | fn neighbours(grid: &[Vec], pos: Position) -> Vec { 52 | let mut v = Vec::new(); 53 | 54 | if pos.x > 0 && grid[pos.y][pos.x-1] == ' ' { 55 | v.push(Position{x: pos.x-1, y: pos.y}); 56 | } 57 | if pos.x + 1 < grid[pos.y].len() && grid[pos.y][pos.x+1] == ' ' { 58 | v.push(Position{x: pos.x+1, y: pos.y}); 59 | } 60 | 61 | if pos.y > 0 && pos.x < grid[pos.y-1].len() && grid[pos.y-1][pos.x] == ' ' { 62 | v.push(Position{x: pos.x, y: pos.y-1}); 63 | } 64 | if pos.y + 1 < grid.len() && 65 | pos.x < grid[pos.y+1].len() && 66 | grid[pos.y+1][pos.x] == ' ' { 67 | v.push(Position{x: pos.x, y: pos.y+1}); 68 | } 69 | v 70 | } 71 | 72 | /// Generate the optimal path from start to goal given a mapping from positions 73 | /// to the position that lead to that position. 74 | #[inline] 75 | fn reconstruct(came_from: &HashMap, 76 | start: Position, 77 | goal: Position) -> Vec { 78 | let mut current = goal; 79 | let mut path = vec![current]; 80 | while current != start { 81 | current = came_from[¤t]; 82 | path.push(current); 83 | } 84 | path.reverse(); 85 | path 86 | } 87 | 88 | /// Find the optimal path from a starting position to a goal position. Return 89 | /// None if no path is possible. The input grid is a 2-dimensional grid of 90 | /// characters. Any character other than a space is considered an impassable 91 | /// obstacle. 92 | /// 93 | /// # Examples 94 | /// 95 | /// ``` 96 | /// use bestpath::{find_path, Position}; 97 | /// 98 | /// let grid = vec![vec![' ', '*', '*', ' '], 99 | /// vec![' ', ' ', ' ', '*'], 100 | /// vec!['*', '*', ' ', ' '], 101 | /// vec![' ', '*', ' ', ' ']]; 102 | /// assert_eq!(Some(vec![Position{x: 0, y: 0}, 103 | /// Position{x: 0, y: 1}, 104 | /// Position{x: 1, y: 1}, 105 | /// Position{x: 2, y: 1}, 106 | /// Position{x: 2, y: 2}]), 107 | /// find_path(&grid, Position{x: 0, y: 0}, Position{x: 2, y: 2})); 108 | /// assert_eq!(None, 109 | /// find_path(&grid, Position{x: 0, y: 0}, Position{x: 3, y: 0})); 110 | /// ``` 111 | #[inline] 112 | pub fn find_path(grid: &[Vec], start: Position, goal: Position) 113 | -> Option> { 114 | let mut frontier = BinaryHeap::new(); 115 | frontier.push(PositionPriority { minimum_cost: 0, position: start }); 116 | 117 | let mut came_from = HashMap::new(); 118 | let mut cost_so_far = HashMap::new(); 119 | cost_so_far.insert(start, 0); 120 | 121 | while let Some(PositionPriority { minimum_cost, position }) = 122 | frontier.pop() { 123 | if position == goal { 124 | return Some(reconstruct(&came_from, start, goal)); 125 | } 126 | let cost = cost_so_far[&position]; 127 | 128 | for next_position in neighbours(grid, position) { 129 | let new_cost = cost + 1; 130 | if let Some(existing_cost) = cost_so_far.get(&next_position) { 131 | if new_cost >= *existing_cost { 132 | continue; 133 | } 134 | } 135 | cost_so_far.insert(next_position, new_cost); 136 | frontier.push(PositionPriority { 137 | minimum_cost: new_cost + 138 | min_distance_heuristic(goal, next_position), 139 | position: next_position }); 140 | came_from.insert(next_position, position); 141 | } 142 | } 143 | None 144 | } 145 | 146 | /// Generate a copy of the given grid with the path filled in. The starting 147 | /// position is shown with a '@' , the goal postiion with a 'X' and all other 148 | /// points on the path with a 'o'. 149 | /// 150 | /// # Examples 151 | /// 152 | /// ``` 153 | /// use bestpath::{find_path, format_path_map, Position}; 154 | /// 155 | /// let grid = vec![vec![' ', ' ', ' ', ' '], 156 | /// vec![' ', '*', ' ', '*'], 157 | /// vec!['*', '*', ' ', ' '], 158 | /// vec![' ', '*', ' ', ' ']]; 159 | /// let path = find_path(&grid, 160 | /// Position{x: 0, y: 1}, 161 | /// Position{x: 3, y: 2}).unwrap(); 162 | /// assert_eq!(vec![vec!['o', 'o', 'o', ' '], 163 | /// vec!['@', '*', 'o', '*'], 164 | /// vec!['*', '*', 'o', 'X'], 165 | /// vec![' ', '*', ' ', ' ']], 166 | /// format_path_map(&grid, &path)); 167 | /// ``` 168 | #[inline] 169 | pub fn format_path_map(grid: &[Vec], path: &[Position]) 170 | -> Vec> { 171 | let mut m = grid.to_vec(); 172 | let last_path = path.len() - 1; 173 | for (i, p) in path.iter().enumerate() { 174 | m[p.y][p.x] = match i { 175 | 0 => '@', 176 | _ if i == last_path => 'X', 177 | _ => 'o', 178 | } 179 | } 180 | m 181 | } 182 | 183 | #[cfg(test)] 184 | fn print_grid(grid: &[Vec]) { 185 | println!(""); 186 | for row in grid { 187 | for ch in row { 188 | print!(" {} ", ch); 189 | } 190 | println!(""); 191 | } 192 | } 193 | 194 | #[test] 195 | fn no_path() { 196 | let map = 197 | vec![vec![' ', ' ', '█', ' ', ' ', ' ', ' '], 198 | vec!['█', '█', ' ', ' ', '█', '█', ' '], 199 | vec![' ', '█', ' ', '█', '█', ' ', '█'], 200 | vec![' ', ' ', ' ', '█', ' ', '█', ' '], 201 | vec!['█', ' ', ' ', ' ', ' ', '█', ' '], 202 | vec![' ', '█', '█', '█', '█', ' ', ' '], 203 | vec![' ', ' ', ' ', ' ', ' ', ' ', ' ']]; 204 | 205 | let path = find_path(&map, Position { x: 0, y: 0}, Position { x: 6, y: 6}); 206 | assert_eq!(None, path); 207 | } 208 | 209 | #[test] 210 | fn multiple_complex_paths_to_goal() { 211 | let map = 212 | vec![vec![' ', ' ', ' ', ' ', ' ', ' ', ' '], 213 | vec![' ', '█', '█', '█', '█', ' ', ' '], 214 | vec![' ', ' ', ' ', '█', '█', ' ', '█'], 215 | vec![' ', '█', ' ', '█', ' ', ' ', ' '], 216 | vec![' ', '█', ' ', '█', ' ', ' ', '█'], 217 | vec![' ', ' ', ' ', '█', ' ', ' ', ' '], 218 | vec![' ', '█', ' ', ' ', ' ', '█', ' '], 219 | vec![' ', ' ', ' ', ' ', ' ', '█', ' '], 220 | vec![' ', ' ', ' ', ' ', '█', ' ', ' ']]; 221 | 222 | let path = find_path(&map, 223 | Position { x: 0, y: 0}, 224 | Position { x: 6, y: 8}).unwrap(); 225 | let path_map = format_path_map(&map, &path); 226 | print_grid(&path_map); 227 | assert_eq!( 228 | vec![vec!['@', 'o', 'o', 'o', 'o', 'o', ' '], 229 | vec![' ', '█', '█', '█', '█', 'o', ' '], 230 | vec![' ', ' ', ' ', '█', '█', 'o', '█'], 231 | vec![' ', '█', ' ', '█', ' ', 'o', ' '], 232 | vec![' ', '█', ' ', '█', ' ', 'o', '█'], 233 | vec![' ', ' ', ' ', '█', ' ', 'o', 'o'], 234 | vec![' ', '█', ' ', ' ', ' ', '█', 'o'], 235 | vec![' ', ' ', ' ', ' ', ' ', '█', 'o'], 236 | vec![' ', ' ', ' ', ' ', '█', ' ', 'X']], 237 | path_map); 238 | } 239 | 240 | #[test] 241 | fn verticle_map() { 242 | let map = 243 | vec![vec![' '], 244 | vec![' '], 245 | vec![' '], 246 | vec![' ']]; 247 | 248 | let path = find_path(&map, 249 | Position { x: 0, y: 0}, 250 | Position { x: 0, y: 3}).unwrap(); 251 | let path_map = format_path_map(&map, &path); 252 | print_grid(&path_map); 253 | assert_eq!( 254 | vec![vec!['@'], 255 | vec!['o'], 256 | vec!['o'], 257 | vec!['X']], 258 | path_map); 259 | } 260 | 261 | #[test] 262 | fn horizontal_map() { 263 | let map = 264 | vec![vec![' ', ' ', ' ', ' ']]; 265 | 266 | let path = find_path(&map, 267 | Position { x: 0, y: 0}, 268 | Position { x: 3, y: 0}).unwrap(); 269 | let path_map = format_path_map(&map, &path); 270 | print_grid(&path_map); 271 | assert_eq!( 272 | vec![vec!['@', 'o', 'o', 'X']], 273 | path_map); 274 | } 275 | 276 | #[test] 277 | fn start_at_goal() { 278 | let map = 279 | vec![vec![' ', ' ', ' ', ' ']]; 280 | 281 | let path = find_path(&map, 282 | Position { x: 0, y: 0}, 283 | Position { x: 0, y: 0}).unwrap(); 284 | assert_eq!(vec![Position{x: 0, y: 0 }], path); 285 | } 286 | 287 | #[test] 288 | fn straight_diagonal_to_goal() { 289 | let map = 290 | vec![vec![' ', ' ', ' ', ' ', ' ', ' ', ' '], 291 | vec![' ', ' ', ' ', ' ', ' ', ' ', ' '], 292 | vec![' ', ' ', ' ', ' ', ' ', ' ', ' '], 293 | vec![' ', ' ', ' ', ' ', ' ', ' ', ' '], 294 | vec![' ', ' ', ' ', ' ', ' ', ' ', ' '], 295 | vec![' ', ' ', ' ', ' ', ' ', ' ', ' '], 296 | vec![' ', ' ', ' ', ' ', ' ', ' ', ' ']]; 297 | 298 | let path = find_path(&map, 299 | Position { x: 0, y: 0}, 300 | Position { x: 6, y: 6}).unwrap(); 301 | let path_map = format_path_map(&map, &path); 302 | print_grid(&path_map); 303 | assert_eq!(13, path.len()) // There are many equivalent paths. 304 | } 305 | 306 | #[test] 307 | fn single_walled_path_to_goal() { 308 | let map = 309 | vec![vec![' ', ' ', ' ', ' ', ' ', ' ', ' '], 310 | vec!['█', '█', '█', '█', '█', '█', ' '], 311 | vec![' ', ' ', ' ', ' ', ' ', ' ', ' '], 312 | vec![' ', '█', '█', '█', '█', '█', '█'], 313 | vec![' ', ' ', ' ', ' ', ' ', ' ', ' '], 314 | vec!['█', '█', '█', '█', '█', '█', ' '], 315 | vec![' ', ' ', ' ', ' ', ' ', ' ', ' ']]; 316 | 317 | let path = find_path(&map, 318 | Position { x: 0, y: 0}, 319 | Position { x: 0, y: 6}).unwrap(); 320 | let path_map = format_path_map(&map, &path); 321 | print_grid(&path_map); 322 | assert_eq!( 323 | vec![vec!['@', 'o', 'o', 'o', 'o', 'o', 'o'], 324 | vec!['█', '█', '█', '█', '█', '█', 'o'], 325 | vec!['o', 'o', 'o', 'o', 'o', 'o', 'o'], 326 | vec!['o', '█', '█', '█', '█', '█', '█'], 327 | vec!['o', 'o', 'o', 'o', 'o', 'o', 'o'], 328 | vec!['█', '█', '█', '█', '█', '█', 'o'], 329 | vec!['X', 'o', 'o', 'o', 'o', 'o', 'o']], 330 | path_map); 331 | } 332 | 333 | #[test] 334 | fn spiral_wall_path_to_goal() { 335 | let map = 336 | vec![vec![' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], 337 | vec![' ', '█', '█', '█', '█', '█', '█', '█'], 338 | vec![' ', '█', ' ', ' ', ' ', ' ', ' ', ' '], 339 | vec![' ', '█', ' ', '█', '█', '█', '█', ' '], 340 | vec![' ', '█', ' ', '█', ' ', ' ', '█', ' '], 341 | vec![' ', '█', ' ', '█', '█', ' ', '█', ' '], 342 | vec![' ', '█', ' ', ' ', ' ', ' ', '█', ' '], 343 | vec![' ', '█', '█', '█', '█', '█', '█', ' '], 344 | vec![' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ']]; 345 | 346 | let path = find_path(&map, 347 | Position { x: 0, y: 0}, 348 | Position { x: 4, y: 4}).unwrap(); 349 | let path_map = format_path_map(&map, &path); 350 | print_grid(&path_map); 351 | assert_eq!( 352 | vec![vec!['@', ' ', ' ', ' ', ' ', ' ', ' ', ' '], 353 | vec!['o', '█', '█', '█', '█', '█', '█', '█'], 354 | vec!['o', '█', 'o', 'o', 'o', 'o', 'o', 'o'], 355 | vec!['o', '█', 'o', '█', '█', '█', '█', 'o'], 356 | vec!['o', '█', 'o', '█', 'X', 'o', '█', 'o'], 357 | vec!['o', '█', 'o', '█', '█', 'o', '█', 'o'], 358 | vec!['o', '█', 'o', 'o', 'o', 'o', '█', 'o'], 359 | vec!['o', '█', '█', '█', '█', '█', '█', 'o'], 360 | vec!['o', 'o', 'o', 'o', 'o', 'o', 'o', 'o']], 361 | path_map); 362 | } 363 | -------------------------------------------------------------------------------- /fizzbuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fizzbuzz" 3 | version = "0.1.0" 4 | authors = ["Brian Quinlan "] 5 | -------------------------------------------------------------------------------- /fizzbuzz/README.md: -------------------------------------------------------------------------------- 1 | # FizzBuzz 2 | 3 | ## Problem 4 | 5 | Print the numbers from 1 to 100. If the number is a evenly divisible by 6 | 3 then print ```Fizz``` instead. If the number is evenly divisible by 5 7 | then print ```Buzz``` instead. If the number is evenly divisible by both 8 | 3 and 5 then print ```FizzBuzz``` instead. 9 | 10 | For example: 11 | 12 | 1 13 | 2 14 | Fizz 15 | 4 16 | Buzz 17 | Fizz 18 | 7 19 | 8 20 | Fizz 21 | Buzz 22 | 11 23 | Fizz 24 | 13 25 | 14 26 | FizzBuzz 27 | ... 28 | 29 | ## Source: 30 | 31 | http://imranontech.com/2007/01/24/using-fizzbuzz-to-find-developers-who-grok-coding/ 32 | -------------------------------------------------------------------------------- /fizzbuzz/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | for i in 1..101 { 3 | match (i % 3, i % 5) { 4 | (0, 0) => println!("FizzBuzz"), 5 | (0, _) => println!("Fizz"), 6 | (_, 0) => println!("Buzz"), 7 | _ => println!("{}", i) 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /i18n/README.md: -------------------------------------------------------------------------------- 1 | # Expand patterns like "i18n". 2 | 3 | ## Problem 4 | 5 | Given a pattern and a list of words, return all of the words that match 6 | the pattern. The pattern will consist of ASCII letters and/or digits. 7 | Letters must be matched exactly (ignoring case) while digits must be 8 | composed into a number and that number of (any) characters must be matched. 9 | 10 | The objective is to make the matching algorithm efficient when calculating 11 | many matches so the cost of pre-processing the dictionary can be discounted. 12 | 13 | For example: 14 | 15 | match("i18n", ) => 16 | institutionalization 17 | intercrystallization 18 | interdifferentiation 19 | internationalization 20 | 21 | match("24", ) => 22 | formaldehydesulphoxylate 23 | pathologicopsychological 24 | scientificophilosophical 25 | tetraiodophenolphthalein 26 | thyroparathyroidectomize 27 | 28 | match("cat", ) => 29 | Cat 30 | cat 31 | 32 | ## Notes 33 | 34 | I went to town on this problem because I wasn't sure what the best approach was: 35 | 36 | * "naive": Converts the pattern to a regular expression and sequentially scans 37 | a word list. The implementation is pretty straight-forward. 38 | * "sets": Coverts the word dictionary into a mapping of 39 | (*\*, *\*, *\*) => *\* 40 | so matching becomes a series of set intersections. 41 | 42 | For example, the pattern "i18n" would match: 43 | 44 | mapping[('i', 0, 20)] ∩ mapping[('n', 19, 20)]. 45 | 46 | The implementation is understandable and has good performance when at 47 | least one intermediate set is small e.g. "i18n" leads to a single 48 | intersection operation between a length 23 set and a length 11 set. 49 | 50 | * "prefix": Coverts the word dictionary into a mapping of 51 | *\* => *\* so matching involves maintaining 52 | a work queue of prefix trees to traverse in parallel. 53 | 54 | The implementation is somewhat difficult to understand but has 55 | good performance when the number of characters skipped is small or 56 | the prefix tree is sparse where there any many skipped characters e.g. 57 | "c1t", "internation8n". 58 | 59 | ## Source: 60 | 61 | http://www.careercup.com/page?pid=google-interview-questions 62 | -------------------------------------------------------------------------------- /i18n/naive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "naive" 3 | version = "0.1.0" 4 | authors = ["bquinlan"] 5 | 6 | [dependencies] 7 | getopts = "0.2" 8 | regex = "0.1.8" 9 | time = "0.1" 10 | -------------------------------------------------------------------------------- /i18n/naive/src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate getopts; 2 | extern crate regex; 3 | extern crate time; 4 | 5 | use getopts::Options; 6 | use std::env; 7 | use std::error::Error; 8 | use std::fs::File; 9 | use std::io; 10 | use std::io::BufReader; 11 | use std::io::prelude::*; 12 | use std::path::Path; 13 | use regex::Regex; 14 | use time::{Duration, PreciseTime}; 15 | 16 | /// Load a dictionary containing one word per line (e.g. from /usr/share/dict/). 17 | fn load_dictionary(dictionary_path: &Path) -> Vec { 18 | let display = dictionary_path.display(); 19 | 20 | let file = match File::open(dictionary_path) { 21 | Err(why) => panic!("couldn't open {}: {}", 22 | display, 23 | Error::description(&why)), 24 | Ok(file) => file, 25 | }; 26 | let reader = BufReader::new(file); 27 | let mut words = Vec::new(); 28 | 29 | for readline in reader.lines() { 30 | let line = match readline { 31 | Err(why) => panic!("couldn't read from {}: {}", 32 | display, 33 | Error::description(&why)), 34 | Ok(readline) => readline, 35 | }; 36 | 37 | words.push(line.trim().to_string()); 38 | } 39 | return words; 40 | } 41 | 42 | /// Match a pattern against a given list of words and return the subset that 43 | /// match. The pattern must consist of ASCII letters or digits. Letters will be 44 | /// matched exactly (ignoring case) while digits will be composed into numbers 45 | /// (e.g. "18" will be treated as eighteen) and that many characters will be 46 | /// skipped. So "i18n" would be equivalent to the regex pattern "^i.{18}n$". 47 | /// 48 | /// # Examples 49 | /// 50 | /// ``` 51 | /// let words = vec![ 52 | /// "cat", 53 | /// "integer" 54 | /// "compartmentalization".to_string(), 55 | /// "intercrystallization".to_string(), 56 | /// "internationalization".to_string(), 57 | /// "pseudopseudohypoparathyroidism".to_string()]; 58 | /// let mut matches = match_pattern("i18n", &words); 59 | /// matches.sort(); 60 | /// assert_eq!(matches, ["intercrystallization", "internationalization"]); 61 | /// ``` 62 | #[inline] 63 | fn match_pattern<'a>(pattern : &str, words : &'a Vec) 64 | -> Vec<&'a str> { 65 | let pattern_parser = Regex::new( 66 | r"(?P\d+)|(?P[A-Za-z])").unwrap(); 67 | 68 | // Convert the input pattern into a regular expression. 69 | let mut regex_string = "(?i)^".to_string(); 70 | for number_or_letter in pattern_parser.captures_iter(&pattern) { 71 | if number_or_letter.name("number").is_some() { 72 | let number_as_string = number_or_letter.name("number").unwrap(); 73 | regex_string.push_str(".{"); 74 | regex_string.push_str(number_as_string); 75 | regex_string.push('}'); 76 | } else { 77 | let match_word = number_or_letter.name("letter").unwrap(); 78 | let ch = match_word.char_indices().next().unwrap().1; 79 | regex_string.push(ch); 80 | } 81 | } 82 | 83 | regex_string.push('$'); 84 | let pattern_matcher = Regex::new(®ex_string).unwrap(); 85 | let mut matching_words : Vec<&str> = Vec::new(); 86 | 87 | // Try to match the constructed regular expression against every word. 88 | for word in words { 89 | if pattern_matcher.is_match(word) { 90 | matching_words.push(word) 91 | } 92 | } 93 | matching_words 94 | } 95 | 96 | #[test] 97 | fn match_number_only() { 98 | let words = vec![ 99 | "cat".to_string(), 100 | "intercrystallization".to_string(), 101 | "parallelogrammatical".to_string(), 102 | "pseudoanthropological".to_string()]; 103 | let mut matches = match_pattern("20", &words); 104 | matches.sort(); 105 | assert_eq!(matches, ["intercrystallization", "parallelogrammatical"]); 106 | } 107 | 108 | #[test] 109 | fn match_number_only_no_match() { 110 | let words = vec![ 111 | "cat".to_string(), 112 | "parallelogrammatical".to_string(), 113 | "pseudoanthropological".to_string()]; 114 | let matches = match_pattern("2", &words); 115 | assert!(matches.is_empty()); 116 | } 117 | 118 | #[test] 119 | fn match_letters_only() { 120 | let words = vec![ 121 | "cat".to_string(), 122 | "intercrystallization".to_string(), 123 | "parallelogrammatical".to_string(), 124 | "pseudoanthropological".to_string()]; 125 | let mut matches = match_pattern("parallelogrammatical", &words); 126 | matches.sort(); 127 | assert_eq!(matches, ["parallelogrammatical"]); 128 | } 129 | 130 | #[test] 131 | fn match_letters_only_no_match() { 132 | let words = vec![ 133 | "cat".to_string(), 134 | "intercrystallization".to_string(), 135 | "pseudoanthropological".to_string()]; 136 | let matches = match_pattern("caterpillar", &words); 137 | assert!(matches.is_empty()); 138 | } 139 | 140 | #[test] 141 | fn match_letter_number_letter() { 142 | let words = vec![ 143 | "i18n".to_string(), 144 | "in".to_string(), 145 | "intercrystallization".to_string(), 146 | "internationalization".to_string(), 147 | "internationalizationy".to_string()]; 148 | let mut matches = match_pattern("i18n", &words); 149 | matches.sort(); 150 | assert_eq!(matches, ["intercrystallization", "internationalization"]); 151 | } 152 | 153 | #[test] 154 | fn match_letter_number_letter_number_letter() { 155 | let words = vec![ 156 | "institutionalization".to_string(), 157 | "intercrystallization".to_string(), 158 | "internationalization".to_string(), 159 | "internationalizationy".to_string()]; 160 | let mut matches = match_pattern("i1t16n", &words); 161 | matches.sort(); 162 | assert_eq!(matches, ["intercrystallization", "internationalization"]); 163 | } 164 | 165 | #[test] 166 | fn match_number_letter_number_letter_number() { 167 | let words = vec![ 168 | "antianthropomorphism".to_string(), 169 | "institutionalization".to_string(), 170 | "intercrystallization".to_string(), 171 | "internationalization".to_string(), 172 | "internationalizationy".to_string()]; 173 | let mut matches = match_pattern("2t2n14", &words); 174 | matches.sort(); 175 | assert_eq!(matches, ["antianthropomorphism", "internationalization"]); 176 | } 177 | 178 | #[test] 179 | fn match_ignores_case() { 180 | let words = vec![ 181 | "Cat".to_string(), 182 | "cat".to_string(), 183 | "cot".to_string(), 184 | "dog".to_string()]; 185 | let mut matches = match_pattern("c1t", &words); 186 | matches.sort(); 187 | assert_eq!(matches, ["Cat", "cat", "cot"]); 188 | } 189 | 190 | fn print_matches(words : Vec<&str>, num_runs : u32, duration : &Duration) { 191 | if words.is_empty() { 192 | println!("\t"); 193 | } else { 194 | let len = words.len(); 195 | let mut sorted_words : Vec<&str> = words.into_iter().collect(); 196 | sorted_words.sort(); 197 | for word in sorted_words { 198 | println!("\t{}", word); 199 | } 200 | 201 | match num_runs { 202 | 1 => println!("\t => {} results in {}μs", 203 | len, 204 | duration.num_microseconds().unwrap()), 205 | _ => println!("\t => {} results in {}μs ({} runs)", 206 | len, 207 | duration.num_microseconds().unwrap(), 208 | num_runs) 209 | } 210 | } 211 | } 212 | 213 | #[allow(dead_code)] 214 | fn main() { 215 | let args: Vec = env::args().collect(); 216 | 217 | let mut opts = Options::new(); 218 | opts.optopt( 219 | "n", 220 | "num_runs", 221 | "the number of times to run the match per line", 222 | "COUNT"); 223 | let matches = opts.parse(&args[1..]).unwrap(); 224 | let num_runs : u32 = match matches.opt_str("n") { 225 | Some(n) => n.parse().unwrap(), 226 | None => 1 227 | }; 228 | 229 | 230 | let dictionary_path = Path::new("/usr/share/dict/words"); 231 | let words = load_dictionary(&dictionary_path); 232 | 233 | let stdin = io::stdin(); 234 | for line in stdin.lock().lines() { 235 | let start = PreciseTime::now(); 236 | let word = line.unwrap(); 237 | for _ in 0..num_runs { 238 | match_pattern(&word, &words); 239 | } 240 | let matches = match_pattern(&word, &words); 241 | print_matches(matches, num_runs, &start.to(PreciseTime::now())); 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /i18n/prefix/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "prefix" 3 | version = "0.1.0" 4 | authors = ["Brian Quinlan "] 5 | 6 | [dependencies] 7 | getopts = "0.2" 8 | regex = "0.1.8" 9 | time = "0.1" 10 | -------------------------------------------------------------------------------- /i18n/prefix/src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate getopts; 2 | extern crate regex; 3 | extern crate time; 4 | 5 | use getopts::Options; 6 | use std::env; 7 | use std::mem; 8 | use std::ascii::AsciiExt; 9 | use std::error::Error; 10 | use std::fs::File; 11 | use std::io; 12 | use std::io::BufReader; 13 | use std::io::prelude::*; 14 | use std::path::Path; 15 | use std::str::Chars; 16 | use std::collections::HashMap; 17 | use regex::Regex; 18 | use time::{Duration, PreciseTime}; 19 | 20 | /// A node in a prefix-tree (aka trie). 21 | // TODO(brian@sweetapp.com): Since each trie only stores words of the same 22 | // length, this struct is unnecessarily general. 23 | struct Node { 24 | words : Vec, 25 | children: HashMap 26 | } 27 | 28 | /// Load a dictionary containing one word per line (e.g. from /usr/share/dict/). 29 | fn load_dictionary(dictionary_path: &Path) -> Vec { 30 | let display = dictionary_path.display(); 31 | 32 | let file = match File::open(dictionary_path) { 33 | Err(why) => panic!("couldn't open {}: {}", 34 | display, 35 | Error::description(&why)), 36 | Ok(file) => file, 37 | }; 38 | let reader = BufReader::new(file); 39 | let mut words = Vec::new(); 40 | 41 | for readline in reader.lines() { 42 | let line = match readline { 43 | Err(why) => panic!("couldn't read from {}: {}", 44 | display, 45 | Error::description(&why)), 46 | Ok(readline) => readline, 47 | }; 48 | 49 | words.push(line.trim().to_string()); 50 | } 51 | return words; 52 | } 53 | 54 | fn insert_in_trie_internal(word : &str, 55 | remaining: &mut Chars, 56 | node: &mut Node) { 57 | match remaining.next() { 58 | Some(ch) => { 59 | if let Some(next_node) = node.children.get_mut(&ch) { 60 | insert_in_trie_internal(word, remaining, next_node); 61 | return; 62 | } 63 | let mut next_node = Node{words: Vec::new(), 64 | children: HashMap::new()}; 65 | insert_in_trie_internal(word, remaining, &mut next_node); 66 | node.children.insert(ch, next_node); 67 | } 68 | None => node.words.push(word.to_string()) 69 | } 70 | } 71 | 72 | fn insert_in_trie(word : &str, node: &mut Node) { 73 | insert_in_trie_internal(word, &mut word.to_ascii_lowercase().chars(), node); 74 | } 75 | 76 | /// Map each word length to a set of tries containing only words of that length. 77 | fn build_length_to_trie_map(words: &[String]) -> HashMap { 78 | let mut length_to_trie = HashMap::new(); 79 | 80 | for word in words { 81 | let mut trie = length_to_trie.entry(word.len()).or_insert_with( 82 | || Node {words: Vec::new(), children: HashMap::new()}); 83 | insert_in_trie(word, &mut trie); 84 | } 85 | length_to_trie 86 | } 87 | 88 | fn match_pattern<'a>(pattern : &str, 89 | build_length_to_trie_map : &'a HashMap) -> Vec<&'a str> { 90 | let pattern_parser = Regex::new( 91 | r"(?P\d+)|(?P[A-Za-z])").unwrap(); 92 | 93 | // To make it easier to traverse the trie, convert the input pattern into a 94 | // list of tokens. For example, "c2t" => vec![Token::Character('c'), 95 | // Token::Any, 96 | // Token::Any, 97 | // Token::Character('t')] 98 | enum Token { 99 | Character(char), 100 | Any 101 | } 102 | let mut tokens = Vec::new(); 103 | let mut pattern_length: usize = 0; 104 | 105 | for number_or_letter in pattern_parser.captures_iter( 106 | &pattern.to_ascii_lowercase()) { 107 | if number_or_letter.name("number").is_some() { 108 | let number_as_string = number_or_letter.name("number").unwrap(); 109 | let number: usize = number_as_string.parse().unwrap(); 110 | for _ in 0..number { 111 | tokens.push(Token::Any); 112 | } 113 | pattern_length += number; 114 | } else { 115 | let match_word = number_or_letter.name("letter").unwrap(); 116 | let ch = match_word.char_indices().next().unwrap().1; 117 | tokens.push(Token::Character(ch)); 118 | pattern_length += 1; 119 | } 120 | } 121 | 122 | let trie = match build_length_to_trie_map.get(&pattern_length) { 123 | Some(node) => node, 124 | None => return Vec::new() 125 | }; 126 | 127 | let mut nodes : Vec<&Node> = vec![trie]; 128 | // The trie nodes in the next level in the trie. "nodes" and "next_nodes" 129 | // are swapped at the end of the token processing loop. 130 | let mut next_nodes : Vec<&Node> = Vec::new(); 131 | 132 | for token in tokens.iter() { 133 | match *token { 134 | Token::Any => { 135 | // If the token represents any character then push all the 136 | // children of each node in "nodes" onto "next_nodes". 137 | while let Some(node) = nodes.pop() { 138 | for next_node in node.children.values() { 139 | next_nodes.push(next_node); 140 | } 141 | } 142 | } 143 | Token::Character(ch) => { 144 | // If the token represents a single character then push only the 145 | // "ch" children of each node in "nodes" onto "next_nodes". 146 | while let Some(node) = nodes.pop() { 147 | if let Some(next_node) = node.children.get(&ch) { 148 | next_nodes.push(next_node); 149 | } 150 | } 151 | } 152 | } 153 | if next_nodes.len() == 0 { 154 | return Vec::new(); 155 | } 156 | assert_eq!(nodes.len(), 0); 157 | mem::swap(&mut nodes, &mut next_nodes); 158 | } 159 | 160 | // "nodes" now contains leaf nodes. Extract the words from them. 161 | let mut words : Vec<&str> = Vec::new(); 162 | for node in nodes { 163 | for word in node.words.iter() { 164 | words.push(&word); 165 | } 166 | } 167 | words 168 | } 169 | 170 | #[test] 171 | fn match_number_only() { 172 | let words = vec![ 173 | "cat".to_string(), 174 | "intercrystallization".to_string(), 175 | "parallelogrammatical".to_string(), 176 | "pseudoanthropological".to_string()]; 177 | let trie = build_length_to_trie_map(&words); 178 | let mut matches = match_pattern("20", &trie); 179 | matches.sort(); 180 | assert_eq!(matches, ["intercrystallization", "parallelogrammatical"]); 181 | } 182 | 183 | #[test] 184 | fn match_number_only_no_match() { 185 | let words = vec![ 186 | "cat".to_string(), 187 | "parallelogrammatical".to_string(), 188 | "pseudoanthropological".to_string()]; 189 | let trie = build_length_to_trie_map(&words); 190 | let matches = match_pattern("2", &trie); 191 | assert!(matches.is_empty()); 192 | } 193 | 194 | #[test] 195 | fn match_letters_only() { 196 | let words = vec![ 197 | "cat".to_string(), 198 | "intercrystallization".to_string(), 199 | "parallelogrammatical".to_string(), 200 | "pseudoanthropological".to_string()]; 201 | let trie = build_length_to_trie_map(&words); 202 | let mut matches = match_pattern("parallelogrammatical", &trie); 203 | matches.sort(); 204 | assert_eq!(matches, ["parallelogrammatical"]); 205 | } 206 | 207 | #[test] 208 | fn match_letters_only_no_match() { 209 | let words = vec![ 210 | "cat".to_string(), 211 | "intercrystallization".to_string(), 212 | "pseudoanthropological".to_string()]; 213 | let trie = build_length_to_trie_map(&words); 214 | let matches = match_pattern("caterpillar", &trie); 215 | assert!(matches.is_empty()); 216 | } 217 | 218 | #[test] 219 | fn match_letter_number_letter() { 220 | let words = vec![ 221 | "i18n".to_string(), 222 | "in".to_string(), 223 | "intercrystallization".to_string(), 224 | "internationalization".to_string(), 225 | "internationalizationy".to_string()]; 226 | let trie = build_length_to_trie_map(&words); 227 | let mut matches = match_pattern("i18n", &trie); 228 | matches.sort(); 229 | assert_eq!(matches, ["intercrystallization", "internationalization"]); 230 | } 231 | 232 | #[test] 233 | fn match_letter_number_letter_number_letter() { 234 | let words = vec![ 235 | "institutionalization".to_string(), 236 | "intercrystallization".to_string(), 237 | "internationalization".to_string(), 238 | "internationalizationy".to_string()]; 239 | let trie = build_length_to_trie_map(&words); 240 | let mut matches = match_pattern("i1t16n", &trie); 241 | matches.sort(); 242 | assert_eq!(matches, ["intercrystallization", "internationalization"]); 243 | } 244 | 245 | #[test] 246 | fn match_number_letter_number_letter_number() { 247 | let words = vec![ 248 | "antianthropomorphism".to_string(), 249 | "institutionalization".to_string(), 250 | "intercrystallization".to_string(), 251 | "internationalization".to_string(), 252 | "internationalizationy".to_string()]; 253 | let trie = build_length_to_trie_map(&words); 254 | let mut matches = match_pattern("2t2n14", &trie); 255 | matches.sort(); 256 | assert_eq!(matches, ["antianthropomorphism", "internationalization"]); 257 | } 258 | 259 | #[test] 260 | fn match_ignores_case() { 261 | let words = vec![ 262 | "Cat".to_string(), 263 | "cat".to_string(), 264 | "cot".to_string(), 265 | "dog".to_string()]; 266 | let trie = build_length_to_trie_map(&words); 267 | let mut matches = match_pattern("c1t", &trie); 268 | matches.sort(); 269 | assert_eq!(matches, ["Cat", "cat", "cot"]); 270 | } 271 | 272 | fn print_matches(words : Vec<&str>, num_runs : u32, duration : &Duration) { 273 | if words.is_empty() { 274 | println!("\t"); 275 | } else { 276 | let len = words.len(); 277 | let mut sorted_words : Vec<&str> = words.into_iter().collect(); 278 | sorted_words.sort(); 279 | for word in sorted_words { 280 | println!("\t{}", word); 281 | } 282 | 283 | match num_runs { 284 | 1 => println!("\t => {} results in {}μs", 285 | len, 286 | duration.num_microseconds().unwrap()), 287 | _ => println!("\t => {} results in {}μs ({} runs)", 288 | len, 289 | duration.num_microseconds().unwrap(), 290 | num_runs) 291 | } 292 | } 293 | } 294 | 295 | #[allow(dead_code)] 296 | fn main() { 297 | let args: Vec = env::args().collect(); 298 | 299 | let mut opts = Options::new(); 300 | opts.optopt( 301 | "n", 302 | "num_runs", 303 | "the number of times to run the match per line", 304 | "COUNT"); 305 | let matches = opts.parse(&args[1..]).unwrap(); 306 | let num_runs : u32 = match matches.opt_str("n") { 307 | Some(n) => n.parse().unwrap(), 308 | None => 1 309 | }; 310 | 311 | 312 | let dictionary_path = Path::new("/usr/share/dict/words"); 313 | let words = load_dictionary(&dictionary_path); 314 | let length_to_trie = build_length_to_trie_map(&words); 315 | 316 | let stdin = io::stdin(); 317 | for line in stdin.lock().lines() { 318 | let start = PreciseTime::now(); 319 | let word = line.unwrap(); 320 | for _ in 0..num_runs { 321 | match_pattern(&word, &length_to_trie); 322 | } 323 | let matches = match_pattern(&word, &length_to_trie); 324 | print_matches(matches, num_runs, &start.to(PreciseTime::now())); 325 | } 326 | } 327 | -------------------------------------------------------------------------------- /i18n/sets/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sets" 3 | version = "0.1.0" 4 | authors = ["bquinlan"] 5 | 6 | [dependencies] 7 | getopts = "0.2" 8 | regex = "0.1.8" 9 | time = "0.1" 10 | -------------------------------------------------------------------------------- /i18n/sets/src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate getopts; 2 | extern crate regex; 3 | extern crate time; 4 | 5 | use getopts::Options; 6 | use std::env; 7 | use std::ascii::AsciiExt; 8 | use std::error::Error; 9 | use std::fs::File; 10 | use std::io; 11 | use std::io::BufReader; 12 | use std::io::prelude::*; 13 | use std::path::Path; 14 | use std::collections::HashMap; 15 | use std::collections::HashSet; 16 | use regex::Regex; 17 | use time::{Duration, PreciseTime}; 18 | 19 | /// Load a dictionary containing one word per line (e.g. from /usr/share/dict/). 20 | fn load_dictionary(dictionary_path: &Path) -> Vec { 21 | let display = dictionary_path.display(); 22 | 23 | let file = match File::open(dictionary_path) { 24 | Err(why) => panic!("couldn't open {}: {}", 25 | display, 26 | Error::description(&why)), 27 | Ok(file) => file, 28 | }; 29 | let reader = BufReader::new(file); 30 | let mut words = Vec::new(); 31 | 32 | for readline in reader.lines() { 33 | let line = match readline { 34 | Err(why) => panic!("couldn't read from {}: {}", 35 | display, 36 | Error::description(&why)), 37 | Ok(readline) => readline, 38 | }; 39 | 40 | words.push(line.trim().to_string()); 41 | } 42 | return words; 43 | } 44 | 45 | /// Build the data structures needed for the matching algorithm given a list of 46 | /// words. 47 | /// 48 | /// The first hashmap maps (character, index, word length) to a list of words. 49 | /// For example: 50 | /// ('c', 1, 3) => ["can, "cat", "con", etc.] 51 | /// ('t', 3, 3) => ["art, "cat", "mat", etc.] 52 | /// 53 | /// The second hashmap maps word length to a list of words of that length. 54 | /// For example: 55 | /// 1 => ["a", "I", etc.] 56 | /// 3 => ["art", "can", con", "mat", etc.] 57 | fn build_maps(words: &[String]) -> ( 58 | HashMap<(char, usize, usize), HashSet<&str>>, 59 | HashMap>) { 60 | let mut ch_position_length_map : 61 | HashMap<(char, usize, usize), HashSet<&str>> = HashMap::new(); 62 | let mut length_map : HashMap> = HashMap::new(); 63 | 64 | for word in words.iter() { 65 | for (index, ch) in word.to_ascii_lowercase().chars().enumerate() { 66 | let key = (ch, index, word.len()); 67 | 68 | ch_position_length_map.entry(key).or_insert_with( 69 | || HashSet::new()).insert(word); 70 | } 71 | length_map.entry(word.len()).or_insert_with( 72 | || Vec::new()).push(word); 73 | } 74 | (ch_position_length_map, length_map) 75 | } 76 | 77 | /// Match a pattern against a dictionary and return the subset of words that 78 | /// match. The pattern must consist of ASCII letters or digits. Letters will be 79 | /// matched exactly (ignoring case) while digits will be composed into numbers 80 | /// (e.g. "18" will be treated as eighteen) and that many characters will be 81 | /// skipped. So "i18n" would be equivalent to the regex pattern "^i.{18}n$". 82 | /// 83 | /// The dictionary is presented as two HashMaps (see `build_maps`). The first 84 | /// maps (character, index, word length) to a list of words. The second maps 85 | /// word length to a list of words. 86 | fn match_pattern<'a>(pattern : &str, 87 | ch_position_length_map : &HashMap<(char, usize, usize), HashSet<&'a str>>, 88 | length_map : &HashMap>) -> Vec<&'a str> { 89 | let pattern_parser = Regex::new( 90 | r"(?P\d+)|(?P[A-Za-z])").unwrap(); 91 | 92 | let mut pattern_length: usize = 0; 93 | let mut ch_and_index = Vec::new(); // Known characters and their index. 94 | 95 | for number_or_letter in pattern_parser.captures_iter( 96 | &pattern.to_ascii_lowercase()) { 97 | if number_or_letter.name("number").is_some() { 98 | let number_as_string = number_or_letter.name("number").unwrap(); 99 | let number: usize = number_as_string.parse().unwrap(); 100 | pattern_length += number; 101 | } else { 102 | let match_word = number_or_letter.name("letter").unwrap(); 103 | let ch = match_word.char_indices().next().unwrap().1; 104 | ch_and_index.push((ch, pattern_length)); 105 | pattern_length += 1; 106 | } 107 | } 108 | 109 | if ch_and_index.is_empty() { 110 | // No characters were given so return all the words of the specified 111 | // length. This handles the case where the pattern is purely digits. 112 | return match length_map.get(&pattern_length) { 113 | // The division was valid 114 | Some(word_list) => word_list.clone(), 115 | // The division was invalid 116 | None => Vec::new() 117 | } 118 | } 119 | 120 | // Use "ch_and_index" to find all of the word sets that apply to the 121 | // pattern. 122 | let mut word_sets : Vec<&HashSet<&str>> = Vec::new(); 123 | for &(ch, index) in ch_and_index.iter() { 124 | let key = (ch, index as usize, pattern_length as usize); 125 | match ch_position_length_map.get(&key) { 126 | // The division was valid 127 | Some(word_set) => word_sets.push(word_set), 128 | // There are no words of pattern_length with ch at index. 129 | None => return Vec::new() 130 | } 131 | } 132 | 133 | assert!(!word_sets.is_empty()); // Should have exited already in this case. 134 | if word_sets.len() == 1 { 135 | // Handles the case of a single letter specified e.g. "c2". 136 | return word_sets[0].iter().cloned().collect(); 137 | } 138 | 139 | // Intersect the word sets from smallest set to largest set to minimize 140 | // intersection time. 141 | word_sets.sort_by(|a, b| a.len().cmp(&b.len())); 142 | let mut refined_word_set : HashSet<&str> = 143 | word_sets[0].intersection( 144 | word_sets[1]).cloned().collect(); 145 | 146 | for word_set in word_sets.iter().skip(2) { 147 | refined_word_set = refined_word_set.intersection( 148 | word_set).cloned().collect(); 149 | } 150 | 151 | return refined_word_set.iter().cloned().collect(); 152 | } 153 | 154 | #[test] 155 | fn match_number_only() { 156 | let words = vec![ 157 | "cat".to_string(), 158 | "intercrystallization".to_string(), 159 | "parallelogrammatical".to_string(), 160 | "pseudoanthropological".to_string()]; 161 | let (ch_position_length_map, length_map) = build_maps(&words); 162 | let mut matches = match_pattern("20", 163 | &ch_position_length_map, 164 | &length_map); 165 | matches.sort(); 166 | assert_eq!(matches, ["intercrystallization", "parallelogrammatical"]); 167 | } 168 | 169 | #[test] 170 | fn match_number_only_no_match() { 171 | let words = vec![ 172 | "cat".to_string(), 173 | "parallelogrammatical".to_string(), 174 | "pseudoanthropological".to_string()]; 175 | let (ch_position_length_map, length_map) = build_maps(&words); 176 | let matches = match_pattern("2", 177 | &ch_position_length_map, 178 | &length_map); 179 | assert!(matches.is_empty()); 180 | } 181 | 182 | #[test] 183 | fn match_letters_only() { 184 | let words = vec![ 185 | "cat".to_string(), 186 | "intercrystallization".to_string(), 187 | "parallelogrammatical".to_string(), 188 | "pseudoanthropological".to_string()]; 189 | let (ch_position_length_map, length_map) = build_maps(&words); 190 | let mut matches = match_pattern("parallelogrammatical", 191 | &ch_position_length_map, 192 | &length_map); 193 | matches.sort(); 194 | assert_eq!(matches, ["parallelogrammatical"]); 195 | } 196 | 197 | #[test] 198 | fn match_letters_only_no_match() { 199 | let words = vec![ 200 | "cat".to_string(), 201 | "intercrystallization".to_string(), 202 | "pseudoanthropological".to_string()]; 203 | let (ch_position_length_map, length_map) = build_maps(&words); 204 | let matches = match_pattern("caterpillar", 205 | &ch_position_length_map, 206 | &length_map); 207 | assert!(matches.is_empty()); 208 | } 209 | 210 | #[test] 211 | fn match_letter_number_letter() { 212 | let words = vec![ 213 | "i18n".to_string(), 214 | "in".to_string(), 215 | "intercrystallization".to_string(), 216 | "internationalization".to_string(), 217 | "internationalizationy".to_string()]; 218 | let (ch_position_length_map, length_map) = build_maps(&words); 219 | let mut matches = match_pattern("i18n", 220 | &ch_position_length_map, 221 | &length_map); 222 | matches.sort(); 223 | assert_eq!(matches, ["intercrystallization", "internationalization"]); 224 | } 225 | 226 | #[test] 227 | fn match_letter_number_letter_number_letter() { 228 | let words = vec![ 229 | "institutionalization".to_string(), 230 | "intercrystallization".to_string(), 231 | "internationalization".to_string(), 232 | "internationalizationy".to_string()]; 233 | let (ch_position_length_map, length_map) = build_maps(&words); 234 | let mut matches = match_pattern("i1t16n", 235 | &ch_position_length_map, 236 | &length_map); 237 | matches.sort(); 238 | assert_eq!(matches, ["intercrystallization", "internationalization"]); 239 | } 240 | 241 | #[test] 242 | fn match_number_letter_number_letter_number() { 243 | let words = vec![ 244 | "antianthropomorphism".to_string(), 245 | "institutionalization".to_string(), 246 | "intercrystallization".to_string(), 247 | "internationalization".to_string(), 248 | "internationalizationy".to_string()]; 249 | let (ch_position_length_map, length_map) = build_maps(&words); 250 | let mut matches = match_pattern("2t2n14", 251 | &ch_position_length_map, 252 | &length_map); 253 | matches.sort(); 254 | assert_eq!(matches, ["antianthropomorphism", "internationalization"]); 255 | } 256 | 257 | #[test] 258 | fn match_ignores_case() { 259 | let words = vec![ 260 | "Cat".to_string(), 261 | "cat".to_string(), 262 | "cot".to_string(), 263 | "dog".to_string()]; 264 | let (ch_position_length_map, length_map) = build_maps(&words); 265 | let mut matches = match_pattern("c1t", 266 | &ch_position_length_map, 267 | &length_map); 268 | matches.sort(); 269 | assert_eq!(matches, ["Cat", "cat", "cot"]); 270 | } 271 | 272 | fn print_matches(words : Vec<&str>, num_runs : u32, duration : &Duration) { 273 | if words.is_empty() { 274 | println!("\t"); 275 | } else { 276 | let len = words.len(); 277 | let mut sorted_words : Vec<&str> = words.into_iter().collect(); 278 | sorted_words.sort(); 279 | for word in sorted_words { 280 | println!("\t{}", word); 281 | } 282 | 283 | match num_runs { 284 | 1 => println!("\t => {} results in {}μs", 285 | len, 286 | duration.num_microseconds().unwrap()), 287 | _ => println!("\t => {} results in {}μs ({} runs)", 288 | len, 289 | duration.num_microseconds().unwrap(), 290 | num_runs) 291 | } 292 | } 293 | } 294 | 295 | #[allow(dead_code)] 296 | fn main() { 297 | let args: Vec = env::args().collect(); 298 | let mut opts = Options::new(); 299 | opts.optopt( 300 | "n", 301 | "num_runs", 302 | "the number of times to run the match per line", 303 | "COUNT"); 304 | let matches = opts.parse(&args[1..]).unwrap(); 305 | let num_runs : u32 = match matches.opt_str("n") { 306 | Some(n) => n.parse().unwrap(), 307 | None => 1 308 | }; 309 | 310 | let dictionary_path = Path::new("/usr/share/dict/words"); 311 | let words = load_dictionary(&dictionary_path); 312 | let (ch_position_length_map, length_map) = build_maps(&words); 313 | 314 | let stdin = io::stdin(); 315 | for line in stdin.lock().lines() { 316 | let start = PreciseTime::now(); 317 | let word = line.unwrap(); 318 | for _ in 0..num_runs { 319 | match_pattern(&word, &ch_position_length_map, &length_map); 320 | } 321 | let matches = match_pattern(&word, 322 | &ch_position_length_map, 323 | &length_map); 324 | print_matches(matches, num_runs, &start.to(PreciseTime::now())); 325 | } 326 | } 327 | -------------------------------------------------------------------------------- /minstack/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "minstack" 3 | version = "0.1.0" 4 | authors = ["Brian Quinlan "] 5 | -------------------------------------------------------------------------------- /minstack/README.md: -------------------------------------------------------------------------------- 1 | # Implement a stack with O(1) getMin() 2 | 3 | ## Problem 4 | 5 | Implement a stack such that ```push()```, ```pop()``` and 6 | ```getMinimumValue()``` have an amortized complexity of O(1). 7 | 8 | ```push()``` and ```pop()``` have the usual implementation. 9 | ```getMinimumValue()``` returns the minimum value currently stored 10 | in the stack. 11 | 12 | For example: 13 | 14 | stack.push(5) 15 | stack.push(3) 16 | stack.push(27) 17 | stack.getMinimumValue() => 3 18 | 19 | ## Source: 20 | 21 | http://courses.csail.mit.edu/iap/interview/Hacking_a_Google_Interview_Practice_Questions_Person_A.pdf 22 | -------------------------------------------------------------------------------- /minstack/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A stack type with heap-allocated contents. 2 | //! 3 | //! MinStacks have amortized `O(1)` push, pop and min (return the minimum value 4 | //! in the stack). 5 | 6 | pub struct MinStack { 7 | stack: Vec, 8 | // Maintain a parallel stack of locations of minimum values. 9 | // Whenever a value is pushed to `stack`, also push its location to `min_stack` if 10 | // the value is <= the value at the top of `min_stack`. 11 | // Whenever a value is popped from `stack`, also pop `min_stack` if the 12 | // position == the value at the top of `min_stack`. 13 | min_stack: Vec 14 | } 15 | 16 | impl MinStack { 17 | /// Constructs a new, empty `MinStack`. 18 | /// 19 | /// # Examples 20 | /// 21 | /// ``` 22 | /// use minstack::MinStack; 23 | /// 24 | /// let mut stack: MinStack = MinStack::new(); 25 | /// ``` 26 | #[inline] 27 | pub fn new() -> MinStack { 28 | MinStack { 29 | stack: Vec::new(), 30 | min_stack: Vec::new() 31 | } 32 | } 33 | 34 | /// Appends an element to end of the stack. 35 | /// 36 | /// # Panics 37 | /// 38 | /// Panics if the number of elements in the vector overflows a `usize`. 39 | /// 40 | /// # Examples 41 | /// 42 | /// ``` 43 | /// use minstack::MinStack; 44 | /// 45 | /// let mut stack = MinStack::new(); 46 | /// stack.push(3); 47 | /// assert_eq!(Some(3), stack.pop()); 48 | /// ``` 49 | #[inline] 50 | pub fn push(&mut self, value: T) { 51 | let idx = self.stack.len(); 52 | 53 | match self.min_stack.last() { 54 | Some(&min) if value <= self.stack[min] => 55 | self.min_stack.push(idx), 56 | None => 57 | self.min_stack.push(idx), 58 | _ => { } 59 | } 60 | 61 | self.stack.push(value); 62 | } 63 | 64 | /// Removes the last element from the stack and returns it, or `None` if it 65 | /// is empty. 66 | /// 67 | /// # Examples 68 | /// 69 | /// ``` 70 | /// use minstack::MinStack; 71 | /// 72 | /// let mut stack = MinStack::new(); 73 | /// stack.push(3); 74 | /// assert_eq!(Some(3), stack.pop()); 75 | /// assert_eq!(None, stack.pop()); 76 | /// ``` 77 | #[inline] 78 | pub fn pop(&mut self) -> Option { 79 | let value = self.stack.pop(); 80 | 81 | match self.min_stack.last() { 82 | Some(&min) if min == self.stack.len() => { 83 | self.min_stack.pop(); 84 | } 85 | _ => { } 86 | } 87 | 88 | value 89 | } 90 | 91 | /// Returns the smallest element in the stack, or `None` if it is empty. 92 | /// 93 | /// # Examples 94 | /// 95 | /// ``` 96 | /// use minstack::MinStack; 97 | /// 98 | /// let mut stack = MinStack::new(); 99 | /// assert_eq!(None, stack.min()); 100 | /// stack.push(3); 101 | /// stack.push(1); 102 | /// stack.push(83); 103 | /// assert_eq!(Some(&1), stack.min()); 104 | /// ``` 105 | #[inline] 106 | pub fn min(&self) -> Option<&T> { 107 | self.min_stack.last().map(|&n| &self.stack[n]) 108 | } 109 | } 110 | 111 | #[test] 112 | fn empty_stack() { 113 | let mut stack : MinStack = MinStack::new(); 114 | assert_eq!(None, stack.min()); 115 | assert_eq!(None, stack.pop()); 116 | } 117 | 118 | #[test] 119 | fn ascending_push() { 120 | let mut stack : MinStack = MinStack::new(); 121 | stack.push(1); 122 | stack.push(2); 123 | stack.push(3); 124 | assert_eq!(Some(&1), stack.min()); 125 | assert_eq!(Some(3), stack.pop()); 126 | assert_eq!(Some(&1), stack.min()); 127 | assert_eq!(Some(2), stack.pop()); 128 | assert_eq!(Some(&1), stack.min()); 129 | assert_eq!(Some(1), stack.pop()); 130 | assert_eq!(None, stack.min()); 131 | assert_eq!(None, stack.pop()); 132 | } 133 | 134 | #[test] 135 | fn decending_push() { 136 | let mut stack : MinStack = MinStack::new(); 137 | stack.push(3); 138 | stack.push(2); 139 | stack.push(1); 140 | assert_eq!(Some(&1), stack.min()); 141 | assert_eq!(Some(1), stack.pop()); 142 | assert_eq!(Some(&2), stack.min()); 143 | assert_eq!(Some(2), stack.pop()); 144 | assert_eq!(Some(&3), stack.min()); 145 | assert_eq!(Some(3), stack.pop()); 146 | assert_eq!(None, stack.min()); 147 | assert_eq!(None, stack.pop()); 148 | } 149 | 150 | #[test] 151 | fn duplicate_push() { 152 | let mut stack : MinStack = MinStack::new(); 153 | stack.push(1); 154 | stack.push(2); 155 | stack.push(1); 156 | assert_eq!(Some(&1), stack.min()); 157 | assert_eq!(Some(1), stack.pop()); 158 | assert_eq!(Some(&1), stack.min()); 159 | assert_eq!(Some(2), stack.pop()); 160 | assert_eq!(Some(&1), stack.min()); 161 | assert_eq!(Some(1), stack.pop()); 162 | assert_eq!(None, stack.min()); 163 | assert_eq!(None, stack.pop()); 164 | } 165 | -------------------------------------------------------------------------------- /oddman/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "oddman" 3 | version = "0.1.0" 4 | authors = ["Brian Quinlan "] 5 | -------------------------------------------------------------------------------- /oddman/README.md: -------------------------------------------------------------------------------- 1 | # Odd 
Man 
Out 2 | 3 | ## Problem 4 | 5 | Find the single unique integer in a list of integers where every other integer 6 | occurs exactly twice. The integers can appear in any order. 7 | 8 | For some reason hash tables may not be used in the solution. 9 | 10 | For example: 11 | 12 | 1, -7, 5, -7 -3, 5, 1 => -3 13 | 14 | ## Source: 15 | 16 | http://courses.csail.mit.edu/iap/interview/Hacking_a_Google_Interview_Practice_Questions_Person_B.pdf 17 | -------------------------------------------------------------------------------- /oddman/src/lib.rs: -------------------------------------------------------------------------------- 1 | /// The input slice consists of integers, all but one of which must be 2 | /// duplicated exactly twice. Returns the non-duplicate integer. 3 | /// 4 | /// # Examples 5 | /// 6 | /// ``` 7 | /// use oddman::odd_man_out; 8 | /// 9 | /// assert_eq!(8, odd_man_out(&[1, 8, 5, 6, 5, 1, 6])); 10 | /// ``` 11 | #[inline] 12 | pub fn odd_man_out(l: &[i32]) -> i32 { 13 | let mut mask : i32 = 0; 14 | for x in l { 15 | // Take advantage of the fact that xor-ing an integer with a value 16 | // twice results in the original integer. So every value except for 17 | // the unpaired integer should have cancelled itself out. Then take 18 | // advantage of the property that x^0 == x. 19 | mask ^= *x; 20 | } 21 | return mask; 22 | } 23 | 24 | #[test] 25 | fn positive_only() { 26 | assert_eq!(5, odd_man_out(&[1, 2, 3, 4, 5, 1, 2, 3, 4])); 27 | } 28 | 29 | #[test] 30 | fn negative_numbers() { 31 | assert_eq!(-5, odd_man_out(&[-1, 2, 3, 4, -5, -1, 2, 3, 4])); 32 | } 33 | 34 | #[test] 35 | fn missing_zero() { 36 | assert_eq!(0, odd_man_out(&[1, 2, 3, 0, 1, 2, 3])); 37 | } 38 | -------------------------------------------------------------------------------- /pancake/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pancake" 3 | version = "0.1.0" 4 | authors = ["Brian Quinlan "] 5 | -------------------------------------------------------------------------------- /pancake/README.md: -------------------------------------------------------------------------------- 1 | # Pancake Sort 2 | 3 | ## Problem 4 | 5 | Given a function that reverses a slice of a list from [0..n], 6 | implement a sorting algorithm that only uses that function to 7 | move items. 8 | 9 | ## Source: 10 | 11 | http://www.careercup.com/question?id=5159785815080960 12 | -------------------------------------------------------------------------------- /pancake/src/lib.rs: -------------------------------------------------------------------------------- 1 | /// Reverse the items in the list from [0..index]. 2 | #[inline] 3 | fn reverse_until_index(v: &mut [T], index: usize) { 4 | v[..index+1].reverse(); 5 | } 6 | 7 | /// Return the index of the largest item in the Vec between [0..index]. 8 | #[inline] 9 | fn index_of_max(v: &[T], index: usize) -> usize where T: PartialOrd { 10 | let mut max = &v[0]; 11 | let mut max_index = 0; 12 | for i in 0..(index+1) { 13 | if v[i] > *max { 14 | max = &v[i]; 15 | max_index = i; 16 | } 17 | } 18 | max_index 19 | } 20 | 21 | /// Sort the Vec using only `reverse_until_index` to exchange elements. 22 | /// See: http://en.wikipedia.org/wiki/Pancake_sorting 23 | /// 24 | /// # Examples 25 | /// 26 | /// ``` 27 | /// use pancake::pancake_sort; 28 | /// 29 | /// let mut v = vec![5, 1, 3, 4, 8, 2, 1, 9, 7]; 30 | /// pancake_sort(&mut v); 31 | /// assert_eq!(vec![1, 1, 2, 3, 4, 5, 7, 8, 9], v); 32 | /// ``` 33 | #[inline] 34 | pub fn pancake_sort(v: &mut [T]) where T: PartialOrd { 35 | if v.len() == 0 { 36 | return; 37 | } 38 | let mut index = v.len() - 1; 39 | 40 | while index > 0 { 41 | // Only consider items in the range [0..index], which have yet to be 42 | // sorted. 43 | let max_index = index_of_max(&v, index); 44 | if max_index != index { 45 | // Move the largest item to the 0th position. 46 | reverse_until_index(&mut *v, max_index); 47 | // Reverse the Vec so the 0th item is now at `index`. 48 | reverse_until_index(&mut *v, index); 49 | } 50 | index -= 1; 51 | } 52 | } 53 | 54 | #[test] 55 | fn empty() { 56 | let mut v: Vec = vec![]; 57 | pancake_sort(&mut v); 58 | assert!(v.is_empty()); 59 | } 60 | 61 | #[test] 62 | fn one_element() { 63 | let mut v = vec![1]; 64 | pancake_sort(&mut v); 65 | assert_eq!(vec![1], v); 66 | } 67 | 68 | #[test] 69 | fn reversed() { 70 | let mut v = vec![5, 4, 3, 2, 1]; 71 | pancake_sort(&mut v); 72 | assert_eq!(vec![1, 2, 3, 4, 5], v); 73 | } 74 | 75 | #[test] 76 | fn many_out_of_order() { 77 | let mut v = vec![4, 18, 3, -5, 7, 4, 1, 8, 3, 4, 19, 11, 23, 24, 25, 6, 0]; 78 | pancake_sort(&mut v); 79 | assert_eq!( 80 | vec![-5, 0, 1, 3, 3, 4, 4, 4, 6, 7, 8, 11, 18, 19, 23, 24, 25], 81 | v); 82 | } 83 | 84 | #[test] 85 | fn string() { 86 | let mut v = vec!["Charlie", "Alpha", "Brava", "Echo", "Delta", "Foxtrot"]; 87 | pancake_sort(&mut v); 88 | assert_eq!( 89 | vec!["Alpha", "Brava", "Charlie", "Delta", "Echo", "Foxtrot"], 90 | v); 91 | } 92 | -------------------------------------------------------------------------------- /printmult/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "printmult" 3 | version = "0.1.0" 4 | authors = ["Brian Quinlan "] 5 | -------------------------------------------------------------------------------- /printmult/README.md: -------------------------------------------------------------------------------- 1 | # Print the 12 Times Table 2 | 3 | ## Problem 4 | 5 | Print a table showing multiplication results for all pairs of numbers 6 | from 1 to 12. 7 | 8 | For example: 9 | 10 | 1 2 3 4 5 6 7 8 9 10 11 12 11 | 2 4 6 8 10 12 14 16 18 20 22 24 12 | 3 6 9 12 15 18 21 24 27 30 33 36 13 | 4 8 12 16 20 24 28 32 36 40 44 48 14 | 5 10 15 20 25 30 35 40 45 50 55 60 15 | 6 12 18 24 30 36 42 48 54 60 66 72 16 | 7 14 21 28 35 42 49 56 63 70 77 84 17 | 8 16 24 32 40 48 56 64 72 80 88 96 18 | 9 18 27 36 45 54 63 72 81 90 99 108 19 | 10 20 30 40 50 60 70 80 90 100 110 120 20 | 11 22 33 44 55 66 77 88 99 110 121 132 21 | 12 24 36 48 60 72 84 96 108 120 132 144 22 | 23 | ## Source: 24 | 25 | https://sites.google.com/site/steveyegge2/five-essential-phone-screen-questions 26 | -------------------------------------------------------------------------------- /printmult/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | for i in 1..13 { 3 | for j in 1..13 { 4 | print!("{:4}", i*j); 5 | } 6 | print!("\n"); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /reversewords/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "reversewords" 3 | version = "0.1.0" 4 | authors = ["Brian Quinlan "] 5 | -------------------------------------------------------------------------------- /reversewords/README.md: -------------------------------------------------------------------------------- 1 | # Reverse the Words in a String 2 | 3 | ## Problem 4 | 5 | Reverse the words in a string in-place using only O(1) memory. The 6 | string contains only ASCII characters and all characters other than 7 | the space character (ASCII 0x20) are considered word characters. 8 | 9 | For example: 10 | 11 | "Hello World from Rust!" => "Rust! from World Hello" 12 | 13 | ## Source: 14 | 15 | http://maxnoy.com/interviews.html 16 | -------------------------------------------------------------------------------- /reversewords/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::ascii::AsciiExt; 2 | 3 | const SPACE: u8 = b' '; 4 | 5 | /// Reverse the words in the input string. Words are defined as any characters 6 | /// other than a space. Only ASCII input is acceptable. 7 | /// 8 | /// # Examples 9 | /// 10 | /// ``` 11 | /// use reversewords::ascii_reverse_words; 12 | /// 13 | /// let mut s = "Hello from Rust!".to_string(); 14 | /// ascii_reverse_words(&mut s); 15 | /// assert_eq!("Rust! from Hello", s) 16 | /// ``` 17 | #[inline] 18 | pub fn ascii_reverse_words(s: &mut String) { 19 | let len = s.len(); 20 | if len == 0 { 21 | return; 22 | } 23 | 24 | if !s.is_ascii() { 25 | // Reversing non-ASCII strings is hard because you have to make sure 26 | // not to spit graphemes. 27 | panic!("Unexpected non-ASCII string: \"{}\"", s); 28 | } 29 | 30 | unsafe { 31 | let ref mut bytes = s.as_mut_vec(); // Unsafe. 32 | // Reverse the entire string. So: 33 | // "Hello from Rust!" => "!tsuR morf olleH" 34 | bytes.reverse(); 35 | 36 | // Find each "word" (non-space) in `bytes` and reverse it. So 37 | // "!tsuR" => "Rust1". 38 | let mut left = 0; 39 | while left < len { 40 | if bytes[left] == SPACE { 41 | left += 1; 42 | } else { 43 | let mut right = left; 44 | while right < len && bytes[right] != SPACE { 45 | right += 1; 46 | } 47 | bytes[left..right].reverse(); 48 | left = right; 49 | } 50 | } 51 | } 52 | } 53 | 54 | #[test] 55 | fn ascii_hello_world() { 56 | let mut s = "Hello World".to_string(); 57 | ascii_reverse_words(&mut s); 58 | assert_eq!("World Hello", s) 59 | } 60 | 61 | #[test] 62 | fn ascii_empty() { 63 | let mut s = "".to_string(); 64 | ascii_reverse_words(&mut s); 65 | assert_eq!("", s) 66 | } 67 | 68 | #[test] 69 | fn ascii_single_space_only() { 70 | let mut s = " ".to_string(); 71 | ascii_reverse_words(&mut s); 72 | assert_eq!(" ", s) 73 | } 74 | 75 | #[test] 76 | fn ascii_multiple_spaces_only() { 77 | let mut s = " ".to_string(); 78 | ascii_reverse_words(&mut s); 79 | assert_eq!(" ", s) 80 | } 81 | 82 | #[test] 83 | fn ascii_single_letter_only() { 84 | let mut s = "a".to_string(); 85 | ascii_reverse_words(&mut s); 86 | assert_eq!("a", s) 87 | } 88 | 89 | #[test] 90 | fn ascii_single_word_only() { 91 | let mut s = "Hello".to_string(); 92 | ascii_reverse_words(&mut s); 93 | assert_eq!("Hello", s) 94 | } 95 | 96 | #[test] 97 | fn ascii_space_and_word() { 98 | let mut s = " Hello".to_string(); 99 | ascii_reverse_words(&mut s); 100 | assert_eq!("Hello ", s) 101 | } 102 | 103 | #[test] 104 | fn ascii_space_word_space() { 105 | let mut s = " Hello ".to_string(); 106 | ascii_reverse_words(&mut s); 107 | assert_eq!(" Hello ", s) 108 | } 109 | 110 | #[test] 111 | #[should_panic] 112 | fn non_ascii() { 113 | let mut s = "Я люблю тебя.".to_string(); 114 | ascii_reverse_words(&mut s); 115 | } 116 | -------------------------------------------------------------------------------- /substring/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "substring" 3 | version = "0.1.0" 4 | authors = ["Brian Quinlan "] 5 | -------------------------------------------------------------------------------- /substring/README.md: -------------------------------------------------------------------------------- 1 | # Determine if a String is a Substring of Another String 2 | 3 | ## Problem 4 | 5 | Determine if a string is a substring of another string. The implementation does 6 | not have to be efficient in terms of time or space. 7 | 8 | For example: 9 | 10 | string: "Batman" sub: "at" => true 11 | string: "Dog" sub: "food" => false 12 | 13 | ## Source: 14 | 15 | http://courses.csail.mit.edu/iap/interview/Hacking_a_Google_Interview_Practice_Questions_Person_A.pdf 16 | -------------------------------------------------------------------------------- /substring/src/lib.rs: -------------------------------------------------------------------------------- 1 | /// Returns true if the find string can be found in the search string. 2 | /// 3 | /// # Examples 4 | /// 5 | /// ``` 6 | /// use substring::has_substring; 7 | /// 8 | /// assert!(has_substring("abcdef", "def")); 9 | /// assert!(!has_substring("abcdef", "xyz")); 10 | /// ``` 11 | pub fn has_substring(search: &str, find: &str) -> bool { 12 | // Any non-brute-force algorithm would be too difficult to implement in an 13 | // interview without reference so implement the naive O(nm) solution. 14 | if search.is_empty() { 15 | return find.is_empty(); 16 | } 17 | 18 | let search_bytes = search.as_bytes(); 19 | let find_bytes = find.as_bytes(); 20 | let max_search_index = search_bytes.len() - find_bytes.len(); 21 | 22 | for i in 0..(max_search_index + 1) { 23 | let mut found = true; 24 | for j in 0..find_bytes.len() { 25 | if search_bytes[i + j] != find_bytes[j] { 26 | found = false; 27 | break; 28 | } 29 | } 30 | if found { 31 | return true; 32 | } 33 | } 34 | false 35 | } 36 | 37 | #[test] 38 | fn substring_at_end() { 39 | assert!(has_substring("abcdef", "def")); 40 | } 41 | 42 | #[test] 43 | fn substring_at_beginning() { 44 | assert!(has_substring("abcdef", "abc")); 45 | } 46 | 47 | #[test] 48 | fn substring_in_middle() { 49 | assert!(has_substring("abcdef", "cde")); 50 | } 51 | 52 | #[test] 53 | fn substring_empty() { 54 | assert!(has_substring("abcdef", "")); 55 | } 56 | 57 | #[test] 58 | fn search_string_empty() { 59 | assert!(!has_substring("", "abc")); 60 | } 61 | 62 | #[test] 63 | fn search_string_and_substring_empty() { 64 | assert!(has_substring("", "")); 65 | } 66 | 67 | #[test] 68 | fn search_string_has_repeating_pattern() { 69 | assert!(has_substring("aaaaaaaaaaaaaba", "aaab")); 70 | } 71 | 72 | #[test] 73 | fn substring_not_found() { 74 | assert!(!has_substring("aaaaaaaaaaaaaba", "aaabb")); 75 | } 76 | 77 | #[test] 78 | fn non_ascii() { 79 | assert!(has_substring( 80 | "Οἱ δὲ Φοίνιϰες οὗτοι οἱ σὺν Κάδμῳ ἀπιϰόμενοι..", 81 | "Φοίνιϰες")); 82 | } 83 | -------------------------------------------------------------------------------- /sumfile/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sumfile" 3 | version = "0.1.0" 4 | authors = ["Brian Quinlan "] 5 | -------------------------------------------------------------------------------- /sumfile/README.md: -------------------------------------------------------------------------------- 1 | # Sum the Integers in a File 2 | 3 | ## Problem 4 | 5 | Print the sum of all the integers found in a text file. Each 6 | integer is on it's own line. 7 | 8 | For example: 9 | 10 | 1 11 | 2 12 | 3 13 | 4 14 | 5 15 | => 15 16 | 17 | ## Source: 18 | 19 | https://sites.google.com/site/steveyegge2/five-essential-phone-screen-questions 20 | -------------------------------------------------------------------------------- /sumfile/numbers.txt: -------------------------------------------------------------------------------- 1 | 52638321787 2 | 855779448 3 | 41250908755 4 | 71531002628 5 | 4317243210 6 | 82348396266 7 | 76837242877 8 | 26332391104 9 | 86624438235 10 | 28584150665 11 | 17672783012 12 | 3701338574 13 | 38853363578 14 | 66241307021 15 | 38918889031 16 | 65265937465 17 | 96180348054 18 | 52878422667 19 | 65639714019 20 | 94851373753 21 | 65401582944 22 | 6139223176 23 | 64013326718 24 | 18437915531 25 | 34103703912 26 | 89667534639 27 | 62445488070 28 | 1675981417 29 | 29977052731 30 | 38228939809 31 | 44885047736 32 | 16974716226 33 | 42258633116 34 | 42884401465 35 | 69723769290 36 | 71620563199 37 | 95226989163 38 | 72191804321 39 | 51808431227 40 | 50935481268 41 | 42639179606 42 | 83122286309 43 | 99662815169 44 | 44075217681 45 | 44305291910 46 | 95509998468 47 | 97732765283 48 | 48256675348 49 | 92755937839 50 | 24729724742 51 | 95006790063 52 | 36371363939 53 | 45456968940 54 | 78727135287 55 | 57949469858 56 | 40757480702 57 | 88789520610 58 | 65213997401 59 | 34960395913 60 | 55988925357 61 | 24462900548 62 | 70741116382 63 | 47077612415 64 | 25164775836 65 | 81016394243 66 | 86669767691 67 | 68333339926 68 | 69731663686 69 | 48377103659 70 | 28926218661 71 | 56480887807 72 | 77024329414 73 | 72782240062 74 | 34408102958 75 | 28713129953 76 | 25192370003 77 | 19864401035 78 | 31782911273 79 | 4885125928 80 | 87733099991 81 | 7561380151 82 | 15108741421 83 | 15306068696 84 | 32806464021 85 | 20330162884 86 | 48938890787 87 | 99510750079 88 | 6146017932 89 | 96099789691 90 | 31189638311 91 | 63696092743 92 | 39116269570 93 | 16219520182 94 | 66984592334 95 | 16440876841 96 | 15636861799 97 | 10112742524 98 | 84282508832 99 | 19186513748 100 | 81217178167 -------------------------------------------------------------------------------- /sumfile/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::BufRead; 3 | use std::io::BufReader; 4 | 5 | /// Return the sum of all the integers found in the file at `path`. Each integer 6 | /// must be on its own line. 7 | fn sum_file(path: &str) -> i64 { 8 | let mut sum : i64 = 0; 9 | let file = match File::open(path) { 10 | Ok(f) => f, 11 | Err(e) => panic!("couldn't open {}: {}", path, e), 12 | }; 13 | let reader = BufReader::new(file); 14 | 15 | for readline in reader.lines() { 16 | let line = match readline { 17 | Ok(readline) => readline, 18 | Err(e) => panic!("couldn't read from {}: {}", path, e), 19 | }; 20 | match line.trim().parse::() { 21 | Ok(v) => sum += v, 22 | Err(_) => panic!("invalid integer in {}: {}", path, line), 23 | } 24 | } 25 | sum 26 | } 27 | 28 | fn main() { 29 | println!("Sum: {}", sum_file("numbers.txt")); 30 | } 31 | -------------------------------------------------------------------------------- /targetsum/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "targetsum" 3 | version = "0.1.0" 4 | authors = ["Brian Quinlan "] 5 | -------------------------------------------------------------------------------- /targetsum/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Find a Pair of Integers that Sum to a Specified Value 3 | 4 | ## Problem 5 | 6 | Find a pair of integers in a list of integers whose sum matches a specified 7 | value. The order of the integer list is arbitrary. If there are several 8 | matching pairs then it is acceptable to return any one. 9 | 10 | For some reason hash tables may not be used in the solution. 11 | 12 | For example: 13 | 14 | list: 2, 8, 23, 18, 4, 3, 5, 11 15 | target: 13 16 | => 8, 5 (or 2, 11) 17 | 18 | ## Source: 19 | 20 | http://courses.csail.mit.edu/iap/interview/Hacking_a_Google_Interview_Practice_Questions_Person_A.pdf 21 | -------------------------------------------------------------------------------- /targetsum/src/lib.rs: -------------------------------------------------------------------------------- 1 | /// Return any two numbers found in a `Vec` that can be added up to a target, or 2 | // `None` if no such numbers exist. 3 | /// 4 | /// # Examples 5 | /// 6 | /// ``` 7 | /// use targetsum::target_sum; 8 | /// 9 | /// assert_eq!(Some((3, 7)), target_sum(&vec![1, 2, 3, 5, 7, 11, 13], 10)); 10 | /// assert_eq!(None, target_sum(&vec![1, 2, 3, 5, 7, 11, 13], 11)); 11 | /// ``` 12 | pub fn target_sum(search: &[i32], target: i32) -> Option<(i32, i32)> { 13 | if search.len() < 2 { 14 | return None; 15 | } 16 | // For some reason hash tables are not allowed so sort the vector and 17 | // search from the ends. 18 | let mut sorted_search = search.to_vec(); 19 | sorted_search.sort(); 20 | let mut lowest_index = 0; 21 | let mut highest_index = sorted_search.len() - 1; 22 | 23 | while lowest_index < highest_index { 24 | let low = sorted_search[lowest_index]; 25 | let high = sorted_search[highest_index]; 26 | 27 | if low + high == target { 28 | return Some((low, high)); 29 | } else if low + high > target { 30 | highest_index -= 1; 31 | } else { 32 | lowest_index += 1; 33 | } 34 | } 35 | None 36 | } 37 | 38 | #[test] 39 | fn search_vector_empty() { 40 | assert_eq!(None, target_sum(&vec![], 11)); 41 | } 42 | 43 | #[test] 44 | fn search_vector_size_one() { 45 | assert_eq!(None, target_sum(&vec![5], 9)); 46 | assert_eq!(None, target_sum(&vec![5], 5)); 47 | assert_eq!(None, target_sum(&vec![5], 10)); 48 | } 49 | 50 | #[test] 51 | fn target_at_edges() { 52 | assert_eq!(Some((1, 10)), target_sum(&vec![1, 2, 5, 10], 11)); 53 | } 54 | 55 | #[test] 56 | fn target_same_number() { 57 | assert_eq!(Some((2, 2)), target_sum(&vec![1, 2, 2, 5, 10], 4)); 58 | } 59 | 60 | #[test] 61 | fn target_same_number_appears_once() { 62 | assert_eq!(None, target_sum(&vec![1, 2, 5, 10], 2)); 63 | } 64 | 65 | #[test] 66 | fn target_double_number() { 67 | assert_eq!(None, target_sum(&vec![1, 2, 5, 10], 4)); 68 | } 69 | 70 | #[test] 71 | fn target_not_found() { 72 | assert_eq!(None, target_sum(&vec![1, 2, 3, 5, 7, 13, 17, 19, 23], 11)); 73 | } 74 | 75 | #[test] 76 | fn negative_target() { 77 | assert_eq!( 78 | Some((-23, 23)), 79 | target_sum(&vec![1, 2, 3, 5, 7, 13, 17, 19, 23, 29, 31, 37, -23], 0)); 80 | } 81 | 82 | -------------------------------------------------------------------------------- /validbtree/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "validbtree" 3 | version = "0.1.0" 4 | authors = ["Brian Quinlan "] 5 | -------------------------------------------------------------------------------- /validbtree/README.md: -------------------------------------------------------------------------------- 1 | # Determine if a Binary Search Tree is valid. 2 | 3 | ## Problem 4 | 5 | Determine if a 6 | [binary search tree](http://en.wikipedia.org/wiki/Binary_search_tree) 7 | of distinct integers is valid. Such a binary search tree is valid if every 8 | node is strictly greater than every node in it's left subtree and strictly 9 | less than every node in it's right subtree. 10 | 11 | For example: 12 | 13 | 5 14 | / \ 15 | 3 7 16 | / \ 17 | 1 6 18 | 19 | is invalid because the left subtree contains the value 6, which is greater than the 20 | value of the root node (5). 21 | 22 | ## Source: 23 | 24 | http://courses.csail.mit.edu/iap/interview/Hacking_a_Google_Interview_Practice_Questions_Person_B.pdf 25 | -------------------------------------------------------------------------------- /validbtree/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub struct Node { 2 | pub left: Link, 3 | pub right: Link, 4 | pub value: u32 5 | } 6 | 7 | pub enum Link { 8 | Child(Box), 9 | Nil 10 | } 11 | 12 | fn is_valid_range(node: &Node, min: Option, max: Option) -> bool { 13 | if let Some(m) = min { 14 | if node.value <= m { 15 | return false; 16 | } 17 | } 18 | 19 | if let Some(m) = max { 20 | if node.value >= m { 21 | return false; 22 | } 23 | } 24 | 25 | if let Link::Child(ref left) = node.left { 26 | if !is_valid_range(&left, min, Some(node.value)) { 27 | return false; 28 | } 29 | } 30 | if let Link::Child(ref right) = node.right { 31 | if !is_valid_range(&right, Some(node.value), max) { 32 | return false; 33 | } 34 | } 35 | true 36 | } 37 | 38 | /// Return true if the given binary search tree is valid. 39 | /// 40 | /// # Examples 41 | /// 42 | /// ``` 43 | /// use validbtree::*; 44 | /// 45 | /// let n3 = Node{left: Link::Nil, right: Link::Nil, value: 3}; 46 | /// let n7 = Node{left: Link::Nil, right: Link::Nil, value: 7}; 47 | /// 48 | /// let n5 = Node {left: Link::Child(Box::new(n7)), 49 | /// right: Link::Child(Box::new(n3)), 50 | /// value: 5}; 51 | /// assert!(!is_valid(&n5)); 52 | /// ``` 53 | pub fn is_valid(node: &Node) -> bool { 54 | is_valid_range(node, None, None) 55 | } 56 | 57 | #[test] 58 | fn root_only() { 59 | assert!(is_valid(&Node { left: Link::Nil, right: Link::Nil, value: 25})); 60 | } 61 | 62 | #[test] 63 | fn valid_tree() { 64 | // 9 65 | // / \ 66 | // 5 23 67 | // / \ \ 68 | // 3 7 25 69 | let n3 = Node{left: Link::Nil, right: Link::Nil, value: 3}; 70 | let n7 = Node{left: Link::Nil, right: Link::Nil, value: 7}; 71 | let n25 = Node{left: Link::Nil, right: Link::Nil, value: 25}; 72 | 73 | let n5 = Node{left: Link::Child(Box::new(n3)), 74 | right: Link::Child(Box::new(n7)), 75 | value: 5}; 76 | let n23 = Node{left: Link::Nil, 77 | right: Link::Child(Box::new(n25)), 78 | value: 23}; 79 | 80 | let n9 = Node{left: Link::Child(Box::new(n5)), 81 | right: Link::Child(Box::new(n23)), 82 | value: 9}; 83 | 84 | assert!(is_valid(&n9)); 85 | } 86 | 87 | #[test] 88 | fn leaf_in_left_subtree_greater_than_root() { 89 | // 9 90 | // / \ 91 | // 5 23 92 | // / \ \ 93 | // 3 11 25 94 | let n3 = Node{left: Link::Nil, right: Link::Nil, value: 3}; 95 | let n11 = Node{left: Link::Nil, right: Link::Nil, value: 11}; 96 | let n25 = Node{left: Link::Nil, right: Link::Nil, value: 25}; 97 | 98 | let n5 = Node{left: Link::Child(Box::new(n3)), 99 | right: Link::Child(Box::new(n11)), 100 | value: 5}; 101 | let n23 = Node{left: Link::Nil, 102 | right: Link::Child(Box::new(n25)), 103 | value: 23}; 104 | 105 | let n9 = Node{left: Link::Child(Box::new(n5)), 106 | right: Link::Child(Box::new(n23)), 107 | value: 9}; 108 | 109 | assert!(!is_valid(&n9)); 110 | } 111 | 112 | #[test] 113 | fn duplicate_in_left_sub_tree() { 114 | // 9 115 | // / 116 | // 9 117 | let leaf = Node{left: Link::Nil, right: Link::Nil, value: 9}; 118 | let root = Node{left: Link::Child(Box::new(leaf)), 119 | right: Link::Nil, 120 | value: 9}; 121 | assert!(!is_valid(&root)); 122 | } 123 | 124 | #[test] 125 | fn duplicate_in_right_sub_tree() { 126 | // 9 127 | // \ 128 | // 9 129 | let leaf = Node{left: Link::Nil, right: Link::Nil, value: 9}; 130 | let root = Node{left: Link::Nil, 131 | right: Link::Child(Box::new(leaf)), 132 | value: 9}; 133 | assert!(!is_valid(&root)); 134 | } --------------------------------------------------------------------------------