Repository: becheran/grid (language: rust)

Issue:
Columns insert/remove operations can be more efficiently implemented
Operations `push_col`, `pop_col`, `insert_col` are asymptotically less efficient, than they could be. Currently they each run in `O(rows * rows * cols)` but they could be implemented in `O(rows * cols)` time.



Discussion / hints:
@demin-dmitriy thanks for reporting! I will fix this as soon as I find the time.

Fix patch:
diff --git a/Cargo.toml b/Cargo.toml
index f8a221b..4fab6e4 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -19,7 +19,7 @@ branch = "master"
 status = "actively-developed"
 
 [dev-dependencies]
-criterion = "0.3.1"
+criterion = "0.3.6"
 rand="0.8.5"
 
 [dependencies]
diff --git a/benches/benches.rs b/benches/benches.rs
index 723f541..15dbd50 100644
--- a/benches/benches.rs
+++ b/benches/benches.rs
@@ -14,7 +14,11 @@ fn init_vec_flat() -> Vec<u32> {
 }
 
 fn init_grid() -> Grid<u32> {
-    Grid::init(SIZE, SIZE, 0)
+    let mut grid = Grid::init(SIZE, SIZE, 0);
+    for (idx, val) in grid.iter_mut().enumerate() {
+        *val += idx as u32;
+    }
+    grid
 }
 
 fn criterion_benchmark(c: &mut Criterion) {
@@ -25,7 +29,7 @@ fn criterion_benchmark(c: &mut Criterion) {
     let mut rand_u32 = || rng_u32.gen::<u32>();
 
     // Init macro
-    c.bench_function("Macro init vec vec", |b| {
+    c.bench_function("vecvec_init_macro", |b| {
         b.iter(|| {
             vec![
                 vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
@@ -41,7 +45,7 @@ fn criterion_benchmark(c: &mut Criterion) {
             ]
         })
     });
-    c.bench_function("Macro init vec flat", |b| {
+    c.bench_function("vec_init_macro", |b| {
         b.iter(|| {
             vec![
                 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7,
@@ -51,7 +55,7 @@ fn criterion_benchmark(c: &mut Criterion) {
             ]
         })
     });
-    c.bench_function("Init grid from_vec", |b| {
+    c.bench_function("grid_from_vec", |b| {
         b.iter(|| {
             let vec = vec![
                 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7,
@@ -62,7 +66,7 @@ fn criterion_benchmark(c: &mut Criterion) {
             Grid::from_vec(vec, 10)
         })
     });
-    c.bench_function("Macro init grid", |b| {
+    c.bench_function("grid_init_macro", |b| {
         b.iter(|| {
             grid![[0,1,2,3,4,5,6,7,8,9]
             [0,1,2,3,4,5,6,7,8,9]
@@ -78,46 +82,72 @@ fn criterion_benchmark(c: &mut Criterion) {
     });
 
     // New
-    c.bench_function("Init vec vec", |b| b.iter(|| init_vec_vec()));
-    c.bench_function("Init vec flat", |b| b.iter(|| init_vec_flat()));
-    c.bench_function("Init grid", |b| b.iter(|| init_grid()));
+    c.bench_function("vecvec_init", |b| b.iter(|| vec![vec![0; SIZE]; SIZE]));
+    c.bench_function("flatvec_init", |b| b.iter(init_vec_flat));
+    c.bench_function("grid_init", |b| b.iter(|| Grid::init(SIZE, SIZE, 0)));
 
     // Get
-    c.bench_function("Idx vec vec", |b| {
+    c.bench_function("vecvec_idx", |b| {
         let vec_vec = init_vec_vec();
         b.iter(|| vec_vec[rand()][rand()])
     });
-    c.bench_function("Idx grid", |b| {
+    c.bench_function("grid_idx", |b| {
         let grid = init_grid();
         b.iter(|| grid[rand()][rand()])
     });
-    c.bench_function("Get_fn vec vec", |b| {
+    c.bench_function("vecvec_get_fn", |b| {
         let vec_vec = init_vec_vec();
         b.iter(|| vec_vec.get(rand()).unwrap().get(rand()))
     });
-    c.bench_function("Get_fn grid", |b| {
+    c.bench_function("grid_get_fn", |b| {
         let grid = init_grid();
         b.iter(|| grid.get(rand(), rand()))
     });
 
     //Set
-    c.bench_function("Set vec vec", |b| {
+    c.bench_function("vecvec_set", |b| {
         let mut vec_vec = init_vec_vec();
         b.iter(|| vec_vec[rand()][rand()] = rand_u32())
     });
-    c.bench_function("Set gird", |b| {
+    c.bench_function("gird_set", |b| {
         let mut gird = init_grid();
         b.iter(|| gird[rand()][rand()] = rand_u32())
     });
 
     // Push
-    c.bench_function("Push row grid", |b| {
-        let mut grid = init_grid();
-        b.iter(|| grid.push_row(vec![10; SIZE]))
+    c.bench_function("grid_push_row", |b| {
+        let grid = init_grid();
+        b.iter_batched(
+            || grid.clone(),
+            |mut g| g.push_row(vec![0; SIZE]),
+            criterion::BatchSize::SmallInput,
+        )
     });
-    c.bench_function("Push col grid", |b| {
-        let mut gird = init_grid();
-        b.iter(|| gird.push_col(vec![10; SIZE]))
+    c.bench_function("grid_push_col", |b| {
+        let grid = init_grid();
+        b.iter_batched(
+            || grid.clone(),
+            |mut g| g.push_col(vec![0; SIZE]),
+            criterion::BatchSize::SmallInput,
+        )
+    });
+
+    // Pop
+    c.bench_function("grid_pop_row", |b| {
+        let grid = init_grid();
+        b.iter_batched(
+            || grid.clone(),
+            |mut g| g.pop_row(),
+            criterion::BatchSize::SmallInput,
+        )
+    });
+    c.bench_function("grid_pop_col", |b| {
+        let grid = init_grid();
+        b.iter_batched(
+            || grid.clone(),
+            |mut g| g.pop_col(),
+            criterion::BatchSize::SmallInput,
+        )
     });
 }
 
diff --git a/src/lib.rs b/src/lib.rs
index df23150..89bbb8a 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -451,24 +451,24 @@ impl<T> Grid<T> {
     ///
     /// Panics if the grid is not empty and `row.len() != grid.cols()`.
     pub fn push_row(&mut self, row: Vec<T>) {
-        let input_row_len = row.len();
-        if self.rows > 0 && input_row_len != self.cols {
+        if self.rows > 0 && row.len() != self.cols {
             panic!(
                 "pushed row does not match. Length must be {:?}, but was {:?}.",
-                self.cols, input_row_len
+                self.cols, row.len()
             )
         }
         self.data.extend(row);
         self.rows += 1;
-        self.cols = input_row_len;
+        if self.cols == 0{
+            self.cols = self.data.len();
+        }
     }
 
     /// Add a new column to the grid.
     ///
     /// *Important:*
     /// Please note that `Grid` uses a Row-Major memory layout. Therefore, the `push_col()`
-    /// operation requires quite a lot of memory shifting and will be significantly slower compared
-    /// to a `push_row()` operation.
+    /// operation will be significantly slower compared to a `push_row()` operation.
     ///
     /// # Examples
     ///
@@ -496,20 +496,21 @@ impl<T> Grid<T> {
     ///
     /// Panics if the grid is not empty and `col.len() != grid.rows()`.
     pub fn push_col(&mut self, col: Vec<T>) {
-        let input_col_len = col.len();
-        if self.cols > 0 && input_col_len != self.rows {
+        if self.cols > 0 && col.len() != self.rows {
             panic!(
                 "pushed column does not match. Length must be {:?}, but was {:?}.",
-                self.rows, input_col_len
+                self.rows, col.len()
             )
         }
-        self.data.reserve(col.len());
-        for (idx, d) in col.into_iter().enumerate() {
-            let vec_idx = (idx + 1) * self.cols + idx;
-            self.data.insert(vec_idx, d);
+        self.data.extend(col);
+        for i in (1..self.rows).rev() {
+            let row_idx = i * self.cols; 
+            self.data[row_idx..row_idx+self.cols+i].rotate_right(i);
         }
         self.cols += 1;
-        self.rows = input_col_len;
+        if self.rows == 0{
+            self.rows = self.data.len();
+        }
     }
 
     /// Removes the last row from a grid and returns it, or None if it is empty.
@@ -523,16 +524,15 @@ impl<T> Grid<T> {
     /// assert_eq![grid.pop_row(), None];
     /// ```
     pub fn pop_row(&mut self) -> Option<Vec<T>> {
-        if self.rows > 0 {
-            let row = self.data.split_off((self.rows - 1) * self.cols);
-            self.rows -= 1;
-            if self.rows == 0 {
-                self.cols = 0;
-            }
-            Some(row)
-        } else {
-            None
+        if self.rows == 0 {
+            return None
+        }
+        let row = self.data.split_off((self.rows - 1) * self.cols);
+        self.rows -= 1;
+        if self.rows == 0 {
+            self.cols = 0;
         }
+        Some(row)
     }
 
     /// Removes the last column from a grid and returns it, or None if it is empty.
@@ -550,20 +550,19 @@ impl<T> Grid<T> {
     /// assert_eq![grid.pop_col(), None];
     /// ```
     pub fn pop_col(&mut self) -> Option<Vec<T>> {
-        if self.cols > 0 {
-            let mut col = Vec::with_capacity(self.rows);
-            for i in 0..self.rows {
-                let idx = i * self.cols + self.cols - 1 - i;
-                col.push(self.data.remove(idx));
-            }
-            self.cols -= 1;
-            if self.cols == 0 {
-                self.rows = 0;
-            }
-            Some(col)
-        } else {
-            None
+        if self.cols == 0 {
+            return None
         }
+        for i in 1..self.rows {
+            let row_idx = i * (self.cols - 1); 
+            self.data[row_idx..row_idx+self.cols+i-1].rotate_left(i);
+        }
+        let col = self.data.split_off(self.data.len() - self.rows);
+        self.cols -= 1;
+        if self.cols == 0 {
+            self.rows = 0;
+        }
+        Some(col)
     }
 
     /// Insert a new row at the index and shifts all rows after down.
@@ -805,7 +804,27 @@ mod test {
     }
 
     #[test]
-    fn pop_col() {
+    fn pop_col_1x3() {
+        let mut grid: Grid<u8> = Grid::from_vec(vec![1, 2, 3], 3);
+        assert_eq!(grid.pop_col(), Some(vec![3]));
+        assert_eq!(grid.size(), (1, 2));
+        assert_eq!(grid.pop_col(), Some(vec![2]));
+        assert_eq!(grid.size(), (1, 1));
+        assert_eq!(grid.pop_col(), Some(vec![1]));
+        assert!(grid.is_empty());
+        assert_eq!(grid.pop_col(), None);
+    }
+
+    #[test]
+    fn pop_col_3x1() {
+        let mut grid: Grid<u8> = Grid::from_vec(vec![1, 2, 3], 1);
+        assert_eq!(grid.pop_col(), Some(vec![1,2,3]));
+        assert!(grid.is_empty());
+        assert_eq!(grid.pop_col(), None);
+    }
+
+    #[test]
+    fn pop_col_2x2() {
         let mut grid: Grid<u8> = Grid::from_vec(vec![1, 2, 3, 4], 2);
         assert_eq!(grid.pop_col(), Some(vec![2, 4]));
         assert_eq!(grid.size(), (2, 1));
@@ -814,6 +833,20 @@ mod test {
         assert_eq!(grid.pop_col(), None);
     }
 
+    #[test]
+    fn pop_col_3x4() {
+        let mut grid: Grid<u16> = Grid::from_vec(vec![1, 2, 3, 4, 11, 22, 33, 44, 111, 222, 333, 444], 4);
+        assert_eq!(grid.pop_col(), Some(vec![4, 44, 444]));
+        assert_eq!(grid.size(), (3, 3));
+        assert_eq!(grid.pop_col(), Some(vec![3, 33, 333]));
+        assert_eq!(grid.size(), (3, 2));
+        assert_eq!(grid.pop_col(), Some(vec![2, 22, 222]));
+        assert_eq!(grid.size(), (3, 1));
+        assert_eq!(grid.pop_col(), Some(vec![1, 11, 111]));
+        assert_eq!(grid.size(), (0, 0));
+        assert_eq!(grid.pop_col(), None);
+    }
+
     #[test]
     fn pop_col_empty() {
         let mut grid: Grid<u8> = Grid::from_vec(vec![], 0);
@@ -821,7 +854,7 @@ mod test {
     }
 
     #[test]
-    fn pop_row() {
+    fn pop_row_2x2() {
         let mut grid: Grid<u8> = Grid::from_vec(vec![1, 2, 3, 4], 2);
         assert_eq!(grid.pop_row(), Some(vec![3, 4]));
         assert_ne!(grid.size(), (1, 4));
@@ -878,7 +911,7 @@ mod test {
     }
 
     #[test]
-    fn push_col_small() {
+    fn push_col_2x3() {
         let mut grid: Grid<u8> = grid![  
                     [0, 1, 2]
                     [10, 11, 12]];
@@ -895,7 +928,7 @@ mod test {
     }
 
     #[test]
-    fn push_col() {
+    fn push_col_3x4() {
         let mut grid: Grid<char> = grid![  
                     ['a', 'b', 'c', 'd']
                     ['a', 'b', 'c', 'd']
@@ -917,7 +950,7 @@ mod test {
     }
 
     #[test]
-    fn push_col_single() {
+    fn push_col_1x3() {
         let mut grid: Grid<char> = grid![['a', 'b', 'c']];
         grid.push_col(vec!['d']);
         assert_eq!(grid.size(), (1, 4));
@@ -1008,6 +1041,7 @@ mod test {
     #[test]
     fn clear() {
         let mut grid: Grid<u8> = grid![[1, 2, 3]];
+        assert!(!grid.is_empty());
         grid.clear();
         assert!(grid.is_empty());
     }
@@ -1019,9 +1053,13 @@ mod test {
     }
 
     #[test]
-    fn is_empty_true() {
-        let grid: Grid<u8> = grid![];
-        assert!(grid.is_empty());
+    fn is_empty() {
+        let mut g : Grid<u8> =grid![[]];
+        assert!(g.is_empty());
+        g = grid![];
+        assert!(g.is_empty());
+        g = Grid::from_vec(vec![], 0);
+        assert!(g.is_empty());
     }
 
     #[test]
@@ -1088,18 +1126,6 @@ mod test {
         assert_eq!(grid[0][2], 3);
     }
 
-    #[test]
-    fn macro_init_empty() {
-        let grid: Grid<usize> = grid![];
-        assert_eq!(grid.size(), (0, 0));
-    }
-
-    #[test]
-    fn from_vec_zero() {
-        let grid: Grid<u8> = Grid::from_vec(vec![], 0);
-        assert_eq!(grid.size(), (0, 0));
-    }
-
     #[test]
     #[should_panic]
     fn from_vec_panics_1() {
@@ -1179,14 +1205,14 @@ mod test {
     #[should_panic]
     fn idx_panic_1() {
         let grid = Grid::init(1, 2, 3);
-        grid[20][0];
+        let _ = grid[20][0];
     }
 
     #[test]
     #[should_panic]
     fn idx_panic_2() {
         let grid = Grid::init(1, 2, 3);
-        grid[0][20];
+        let _ = grid[0][20];
     }
 
     #[test]


Test patch: