-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
Cleanup Saddleback Search #824
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 11 commits
b8fc4fe
4c31581
326bded
b54a02b
acc74d6
95aa66e
f5f6179
78bd242
1fb9023
4436d2c
9b15298
928c630
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,85 +1,255 @@ | ||||||
| // Saddleback search is a technique used to find an element in a sorted 2D matrix in O(m + n) time, | ||||||
| // where m is the number of rows, and n is the number of columns. It works by starting from the | ||||||
| // top-right corner of the matrix and moving left or down based on the comparison of the current | ||||||
| // element with the target element. | ||||||
| use std::cmp::Ordering; | ||||||
|
|
||||||
| pub fn saddleback_search(matrix: &[Vec<i32>], element: i32) -> (usize, usize) { | ||||||
| // Initialize left and right indices | ||||||
| /// Custom error type to represent errors related to matrix validation. | ||||||
| #[derive(Debug, PartialEq, Eq)] | ||||||
| pub enum MatrixError { | ||||||
| NonRectangularInput, | ||||||
| NotSorted, | ||||||
| } | ||||||
|
|
||||||
sozelfist marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
| /// Checks if the given matrix (vector of vectors) is sorted row-wise and column-wise. | ||||||
| /// | ||||||
| /// # Arguments | ||||||
| /// | ||||||
| /// * `matrix` - A vector of vectors representing the matrix to check. | ||||||
| /// | ||||||
| /// # Returns | ||||||
| /// | ||||||
| /// Returns `true` if the matrix is sorted both row-wise and column-wise. Otherwise, returns `false`. | ||||||
| fn is_sorted(matrix: &[Vec<isize>]) -> bool { | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Imagine that you have some matrix. You know that you will query the position of many elements and you want to check if the matrix is soared only once.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I understand this point. By the way, do you know how can we express the tests here? Suppose, we have two functions, one for validate matrix, one for saddleback search like this: fn validate_matrix(matrix: &[Vec<isize>]) -> Result<(), MatrixError> {
// Check if all rows have the same length (rectangular matrix).
if matrix.iter().any(|row| row.len() != matrix[0].len()) {
return Err(MatrixError::NonRectangularInput);
}
// Check if matrix is sorted.
if !is_sorted(matrix) {
return Err(MatrixError::NotSorted);
}
Ok(())
}and fn saddleback_search(matrix: &[Vec<isize>], element: isize) -> Option<(usize, usize)> {
if matrix.is_empty() || matrix.iter().all(|row| row.is_empty()) {
return None;
}
let mut left_index = 0;
let mut right_index = matrix[0].len() - 1;
while left_index < matrix.len() {
match element.cmp(&matrix[left_index][right_index]) {
Ordering::Equal => return Some((left_index, right_index)),
Ordering::Greater => {
left_index += 1;
}
Ordering::Less => {
if right_index == 0 {
break;
} else {
right_index -= 1;
}
}
}
}
None
}Can you propose how we can write tests, @vil02?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would create a function checking if the input is rectangular and simply use it in both Regarding testing strategy: it should be enough to check
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you suggest the detailed changes? |
||||||
| let rows = matrix.len(); | ||||||
| let cols = matrix[0].len(); | ||||||
|
Comment on lines
+20
to
+21
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we need to check if the input is rectangular.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can move the logic that checks the matrix form and
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As above: I would introduce a new function checking if the input is rectangular and use it in both public functions. |
||||||
|
|
||||||
| // Check if rows are sorted. | ||||||
| for row in matrix { | ||||||
| if row.windows(2).any(|w| w[0] > w[1]) { | ||||||
| return false; | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| // Check if columns are sorted. | ||||||
| for col in 0..cols { | ||||||
| for row in 1..rows { | ||||||
| if matrix[row - 1][col] > matrix[row][col] { | ||||||
| return false; | ||||||
| } | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| true | ||||||
| } | ||||||
|
|
||||||
| /// Performs Saddleback search on a sorted matrix represented as a vector of vectors. | ||||||
| /// | ||||||
| /// The Saddleback search algorithm finds the position of a target element in a matrix where | ||||||
| /// each row and each column is sorted in ascending order. The search starts from the top-right | ||||||
| /// corner of the matrix and moves left or down based on comparisons with the target element. | ||||||
| /// | ||||||
| /// Optionally, the matrix can be validated for being sorted before the search starts. | ||||||
| /// | ||||||
| /// # Arguments | ||||||
| /// | ||||||
| /// * `matrix` - A vector of vectors representing the sorted matrix. | ||||||
| /// * `element` - The target element to search for. | ||||||
| /// * `check_sorted` - If true, verifies that the matrix is sorted before performing the search. | ||||||
| /// | ||||||
| /// # Returns | ||||||
| /// | ||||||
| /// Returns `Ok(Some((row, column)))` where both indices are 0-based if the element is found. | ||||||
| /// Returns `Ok(None)` if the element is not found. | ||||||
| /// Returns `Err(MatrixError)` if the matrix is not rectangular or not sorted. | ||||||
| pub fn saddleback_search( | ||||||
| matrix: &[Vec<isize>], | ||||||
| element: isize, | ||||||
| check_sorted: bool, | ||||||
| ) -> Result<Option<(usize, usize)>, MatrixError> { | ||||||
| if matrix.is_empty() || matrix.iter().all(|row| row.is_empty()) { | ||||||
| return Ok(None); | ||||||
| } | ||||||
|
|
||||||
| if matrix.iter().any(|row| row.len() != matrix[0].len()) { | ||||||
| return Err(MatrixError::NonRectangularInput); | ||||||
| } | ||||||
|
|
||||||
| if check_sorted && !is_sorted(matrix) { | ||||||
| return Err(MatrixError::NotSorted); | ||||||
| } | ||||||
|
|
||||||
sozelfist marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
| let mut left_index = 0; | ||||||
| let mut right_index = matrix[0].len() - 1; | ||||||
|
|
||||||
| // Start searching | ||||||
| while left_index < matrix.len() { | ||||||
| match element.cmp(&matrix[left_index][right_index]) { | ||||||
| // If the current element matches the target element, return its position (indices are 1-based) | ||||||
| Ordering::Equal => return (left_index + 1, right_index + 1), | ||||||
| Ordering::Equal => return Ok(Some((left_index, right_index))), | ||||||
| Ordering::Greater => { | ||||||
| // If the target element is greater, move to the next row (downwards) | ||||||
| left_index += 1; | ||||||
| } | ||||||
| Ordering::Less => { | ||||||
| // If the target element is smaller, move to the previous column (leftwards) | ||||||
| if right_index == 0 { | ||||||
| break; // If we reach the left-most column, exit the loop | ||||||
| break; | ||||||
| } else { | ||||||
| right_index -= 1; | ||||||
| } | ||||||
| } | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| // If the element is not found, return (0, 0) | ||||||
| (0, 0) | ||||||
| Ok(None) | ||||||
| } | ||||||
|
|
||||||
| #[cfg(test)] | ||||||
| mod tests { | ||||||
| use super::*; | ||||||
|
|
||||||
| // Test when the element is not present in the matrix | ||||||
| #[test] | ||||||
| fn test_element_not_found() { | ||||||
| let matrix = vec![vec![1, 10, 100], vec![2, 20, 200], vec![3, 30, 300]]; | ||||||
| assert_eq!(saddleback_search(&matrix, 123), (0, 0)); | ||||||
| } | ||||||
|
|
||||||
| // Test when the element is at the top-left corner of the matrix | ||||||
| #[test] | ||||||
| fn test_element_at_top_left() { | ||||||
| let matrix = vec![vec![1, 10, 100], vec![2, 20, 200], vec![3, 30, 300]]; | ||||||
| assert_eq!(saddleback_search(&matrix, 1), (1, 1)); | ||||||
| } | ||||||
|
|
||||||
| // Test when the element is at the bottom-right corner of the matrix | ||||||
| #[test] | ||||||
| fn test_element_at_bottom_right() { | ||||||
| let matrix = vec![vec![1, 10, 100], vec![2, 20, 200], vec![3, 30, 300]]; | ||||||
| assert_eq!(saddleback_search(&matrix, 300), (3, 3)); | ||||||
| } | ||||||
|
|
||||||
| // Test when the element is at the top-right corner of the matrix | ||||||
| #[test] | ||||||
| fn test_element_at_top_right() { | ||||||
| let matrix = vec![vec![1, 10, 100], vec![2, 20, 200], vec![3, 30, 300]]; | ||||||
| assert_eq!(saddleback_search(&matrix, 100), (1, 3)); | ||||||
| } | ||||||
|
|
||||||
| // Test when the element is at the bottom-left corner of the matrix | ||||||
| #[test] | ||||||
| fn test_element_at_bottom_left() { | ||||||
| let matrix = vec![vec![1, 10, 100], vec![2, 20, 200], vec![3, 30, 300]]; | ||||||
| assert_eq!(saddleback_search(&matrix, 3), (3, 1)); | ||||||
| macro_rules! saddleback_tests { | ||||||
| ($($name:ident: $tc:expr,)*) => { | ||||||
| $( | ||||||
| #[test] | ||||||
| fn $name() { | ||||||
| let (matrix, element, expected) = $tc; | ||||||
| assert_eq!(saddleback_search(&matrix, element, true), expected); | ||||||
| } | ||||||
| )* | ||||||
| }; | ||||||
| } | ||||||
|
|
||||||
| // Additional test case: Element in the middle of the matrix | ||||||
| #[test] | ||||||
| fn test_element_in_middle() { | ||||||
| let matrix = vec![ | ||||||
| vec![1, 10, 100, 1000], | ||||||
| vec![2, 20, 200, 2000], | ||||||
| vec![3, 30, 300, 3000], | ||||||
| ]; | ||||||
| assert_eq!(saddleback_search(&matrix, 200), (2, 3)); | ||||||
| saddleback_tests! { | ||||||
| test_element_not_found: ( | ||||||
| vec![ | ||||||
| vec![1, 10, 100], | ||||||
| vec![2, 20, 200], | ||||||
| vec![3, 30, 300] | ||||||
| ], | ||||||
| 123, | ||||||
| Ok(None::<(usize, usize)>), | ||||||
| ), | ||||||
| test_element_at_top_left: ( | ||||||
| vec![ | ||||||
| vec![1, 10, 100], | ||||||
| vec![2, 20, 200], | ||||||
| vec![3, 30, 300] | ||||||
| ], | ||||||
| 1, | ||||||
| Ok(Some((0, 0))), | ||||||
| ), | ||||||
| test_element_at_bottom_right: ( | ||||||
| vec![ | ||||||
| vec![1, 10, 100], | ||||||
| vec![2, 20, 200], | ||||||
| vec![3, 30, 300] | ||||||
| ], | ||||||
| 300, | ||||||
| Ok(Some((2, 2))), | ||||||
| ), | ||||||
| test_element_at_top_right: ( | ||||||
| vec![ | ||||||
| vec![1, 10, 100], | ||||||
| vec![2, 20, 200], | ||||||
| vec![3, 30, 300] | ||||||
| ], | ||||||
| 100, | ||||||
| Ok(Some((0, 2))), | ||||||
| ), | ||||||
| test_element_at_bottom_left: ( | ||||||
| vec![ | ||||||
| vec![1, 10, 100], | ||||||
| vec![2, 20, 200], | ||||||
| vec![3, 30, 300] | ||||||
| ], | ||||||
| 3, | ||||||
| Ok(Some((2, 0))), | ||||||
| ), | ||||||
| test_element_in_middle: ( | ||||||
| vec![ | ||||||
| vec![1, 10, 100, 1000], | ||||||
| vec![2, 20, 200, 2000], | ||||||
| vec![3, 30, 300, 3000], | ||||||
| ], | ||||||
| 200, | ||||||
| Ok(Some((1, 2))), | ||||||
| ), | ||||||
| test_element_smaller_than_min: ( | ||||||
| vec![ | ||||||
| vec![1, 10, 100], | ||||||
| vec![2, 20, 200], | ||||||
| vec![3, 30, 300], | ||||||
| ], | ||||||
| 0, | ||||||
| Ok(None::<(usize, usize)>), | ||||||
| ), | ||||||
| test_horizontal: ( | ||||||
| vec![ | ||||||
| vec![1, 10, 100], | ||||||
| ], | ||||||
| 100, | ||||||
| Ok(Some((0, 2))), | ||||||
| ), | ||||||
| test_vertical: ( | ||||||
| vec![ | ||||||
| vec![1], | ||||||
| vec![2], | ||||||
| vec![3], | ||||||
| ], | ||||||
| 2, | ||||||
| Ok(Some((1, 0))), | ||||||
| ), | ||||||
| test_single_element: ( | ||||||
| vec![ | ||||||
| vec![1], | ||||||
| ], | ||||||
| 1, | ||||||
| Ok(Some((0, 0))), | ||||||
| ), | ||||||
| test_empty_matrix: ( | ||||||
| vec![], | ||||||
| 1, | ||||||
| Ok(None::<(usize, usize)>), | ||||||
| ), | ||||||
| test_non_rectangular_matrix: ( | ||||||
| vec![ | ||||||
| vec![1, 10, 100], | ||||||
| vec![2, 20], | ||||||
| vec![3, 30, 300], | ||||||
| ], | ||||||
| 20, | ||||||
| Err::<Option<(usize, usize)>, MatrixError>(MatrixError::NonRectangularInput), | ||||||
| ), | ||||||
| test_empty_row: ( | ||||||
| vec![ | ||||||
| vec![1, 2, 3], | ||||||
| vec![], | ||||||
| vec![4, 5, 6], | ||||||
| ], | ||||||
| 3, | ||||||
| Err::<Option<(usize, usize)>, MatrixError>(MatrixError::NonRectangularInput), | ||||||
| ), | ||||||
| test_full_empty_rows: ( | ||||||
| vec![ | ||||||
| vec![], | ||||||
| vec![], | ||||||
| vec![], | ||||||
| vec![], | ||||||
| ], | ||||||
| 1, | ||||||
| Ok(None::<(usize, usize)>), | ||||||
| ), | ||||||
sozelfist marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
| test_unsorted_matrix_row_wise: ( | ||||||
| vec![ | ||||||
| vec![1, 10, 100], | ||||||
| vec![20, 200, 2], | ||||||
| vec![3, 30, 300], | ||||||
| ], | ||||||
| 200, | ||||||
| Err::<Option<(usize, usize)>, MatrixError>(MatrixError::NotSorted), | ||||||
| ), | ||||||
| test_unsorted_matrix_column_wise: ( | ||||||
| vec![ | ||||||
| vec![1, 10, 100], | ||||||
| vec![2, 20, 30], | ||||||
| vec![3, 15, 300], | ||||||
| ], | ||||||
| 200, | ||||||
| Err::<Option<(usize, usize)>, MatrixError>(MatrixError::NotSorted), | ||||||
| ), | ||||||
sozelfist marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
| } | ||||||
| } | ||||||
Uh oh!
There was an error while loading. Please reload this page.