-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Sparse Table Implementation (Row, Col) -> Val #545
Changes from 2 commits
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 |
---|---|---|
@@ -0,0 +1,260 @@ | ||
// Copyright 2015, 2016 Ethcore (UK) Ltd. | ||
// This file is part of Parity. | ||
|
||
// Parity is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU General Public License as published by | ||
// the Free Software Foundation, either version 3 of the License, or | ||
// (at your option) any later version. | ||
|
||
// Parity is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU General Public License for more details. | ||
|
||
// You should have received a copy of the GNU General Public License | ||
// along with Parity. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
//! A collection associating pair of keys (row and column) with a single value. | ||
|
||
use std::hash::Hash; | ||
use std::collections::HashMap; | ||
|
||
/// Structure to hold double-indexed values | ||
/// | ||
/// You can obviously use `HashMap<(Row,Col), Val>`, but this structure gives | ||
/// you better access to all `Columns` in Specific `Row`. Namely you can get sub-hashmap | ||
/// `HashMap<Col, Val>` for specific `Row` | ||
pub struct Table<Row, Col, Val> | ||
where Row: Eq + Hash + Clone, | ||
Col: Eq + Hash { | ||
map: HashMap<Row, HashMap<Col, Val>>, | ||
} | ||
|
||
impl<Row, Col, Val> Table<Row, Col, Val> | ||
where Row: Eq + Hash + Clone, | ||
Col: Eq + Hash { | ||
/// Creates new Table | ||
pub fn new() -> Table<Row, Col, Val> { | ||
Table { | ||
map: HashMap::new(), | ||
} | ||
} | ||
|
||
/// Removes all elements from this Table | ||
pub fn clear(&mut self) { | ||
self.map.clear(); | ||
} | ||
|
||
/// Returns length of the Table (number of (row, col, val) tuples) | ||
pub fn len(&self) -> usize { | ||
self.map.iter().fold(0, |acc, (_k, v)| acc + v.len()) | ||
} | ||
|
||
/// Check if there is any element in this Table | ||
pub fn is_empty(&self) -> bool { | ||
self.len() == 0 | ||
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. This is incorrect if there are empty rows 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 it's ok (note it's not 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. Ah, true 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. Changed the implementation to be more efficient (and more explicit) |
||
} | ||
|
||
/// Get mutable reference for single Table row. | ||
pub fn get_row_mut(&mut self, row: &Row) -> Option<&mut HashMap<Col, Val>> { | ||
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. Our convention is not to use |
||
self.map.get_mut(row) | ||
} | ||
|
||
/// Checks if row is defined for that table (note that even if defined it might be empty) | ||
pub fn has_row(&self, row: &Row) -> bool { | ||
self.map.contains_key(row) | ||
} | ||
|
||
/// Get immutable reference for single row in this Table | ||
pub fn get_row(&self, row: &Row) -> Option<&HashMap<Col, Val>> { | ||
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.
|
||
self.map.get(row) | ||
} | ||
|
||
/// Get element in cell described by `(row, col)` | ||
pub fn get(&self, row: &Row, col: &Col) -> Option<&Val> { | ||
self.map.get(row).and_then(|r| r.get(col)) | ||
} | ||
|
||
/// Remove value from specific cell | ||
/// | ||
/// It will remove the row if it's the last value in it | ||
pub fn remove(&mut self, row: &Row, col: &Col) -> Option<Val> { | ||
let (val, is_empty) = { | ||
let row_map = self.map.get_mut(row); | ||
if let None = row_map { | ||
return None; | ||
} | ||
let mut row_map = row_map.unwrap(); | ||
let val = row_map.remove(col); | ||
(val, row_map.is_empty()) | ||
}; | ||
// Clean row | ||
if is_empty { | ||
self.map.remove(row); | ||
} | ||
val | ||
} | ||
|
||
/// Remove given row from Table if there are no values defined in it | ||
/// | ||
/// When using `#get_row_mut` it may happen that all values from some row are drained. | ||
/// Table however will not be aware that row is empty. | ||
/// You can use this method to explicitly remove row entry from the Table. | ||
pub fn clear_if_empty(&mut self, row: &Row) { | ||
let is_empty = self.map.get(row).map_or(false, |m| m.is_empty()); | ||
if is_empty { | ||
self.map.remove(row); | ||
} | ||
} | ||
|
||
/// Inserts new value to specified cell | ||
/// | ||
/// Returns previous value (if any) | ||
pub fn insert(&mut self, row: Row, col: Col, val: Val) -> Option<Val> { | ||
if !self.map.contains_key(&row) { | ||
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. Idiomatic way would be 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. Wow! Awesome! Thanks! |
||
let m = HashMap::new(); | ||
self.map.insert(row.clone(), m); | ||
} | ||
|
||
let mut columns = self.map.get_mut(&row).unwrap(); | ||
columns.insert(col, val) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
use super::*; | ||
|
||
#[test] | ||
fn should_create_empty_table() { | ||
// when | ||
let table : Table<usize, usize, bool> = Table::new(); | ||
|
||
// then | ||
assert!(table.is_empty()); | ||
assert_eq!(table.len(), 0); | ||
} | ||
|
||
#[test] | ||
fn should_insert_elements_and_return_previous_if_any() { | ||
// given | ||
let mut table = Table::new(); | ||
|
||
// when | ||
let r1 = table.insert(5, 4, true); | ||
let r2 = table.insert(10, 4, true); | ||
let r3 = table.insert(10, 10, true); | ||
let r4 = table.insert(10, 10, false); | ||
|
||
// then | ||
assert!(r1.is_none()); | ||
assert!(r2.is_none()); | ||
assert!(r3.is_none()); | ||
assert!(r4.is_some()); | ||
assert!(!table.is_empty()); | ||
assert_eq!(r4.unwrap(), true); | ||
assert_eq!(table.len(), 3); | ||
} | ||
|
||
#[test] | ||
fn should_remove_element() { | ||
// given | ||
let mut table = Table::new(); | ||
table.insert(5, 4, true); | ||
assert!(!table.is_empty()); | ||
assert_eq!(table.len(), 1); | ||
|
||
// when | ||
let r = table.remove(&5, &4); | ||
|
||
// then | ||
assert!(table.is_empty()); | ||
assert_eq!(table.len() ,0); | ||
assert_eq!(r.unwrap(), true); | ||
} | ||
|
||
#[test] | ||
fn should_return_none_if_trying_to_remove_non_existing_element() { | ||
// given | ||
let mut table : Table<usize, usize, usize> = Table::new(); | ||
assert!(table.is_empty()); | ||
|
||
// when | ||
let r = table.remove(&5, &4); | ||
|
||
// then | ||
assert!(r.is_none()); | ||
} | ||
|
||
#[test] | ||
fn should_clear_row_if_removing_last_element() { | ||
// given | ||
let mut table = Table::new(); | ||
table.insert(5, 4, true); | ||
assert!(table.has_row(&5)); | ||
|
||
// when | ||
let r = table.remove(&5, &4); | ||
|
||
// then | ||
assert!(r.is_some()); | ||
assert!(!table.has_row(&5)); | ||
} | ||
|
||
#[test] | ||
fn should_return_element_given_row_and_col() { | ||
// given | ||
let mut table = Table::new(); | ||
table.insert(1551, 1234, 123); | ||
|
||
// when | ||
let r1 = table.get(&1551, &1234); | ||
let r2 = table.get(&5, &4); | ||
|
||
// then | ||
assert!(r1.is_some()); | ||
assert!(r2.is_none()); | ||
assert_eq!(r1.unwrap(), &123); | ||
} | ||
|
||
#[test] | ||
fn should_clear_table() { | ||
// given | ||
let mut table = Table::new(); | ||
table.insert(1, 1, true); | ||
table.insert(1, 2, false); | ||
table.insert(2, 2, false); | ||
assert_eq!(table.len(), 3); | ||
|
||
// when | ||
table.clear(); | ||
|
||
// then | ||
assert!(table.is_empty()); | ||
assert_eq!(table.len(), 0); | ||
assert_eq!(table.has_row(&1), false); | ||
assert_eq!(table.has_row(&2), false); | ||
} | ||
|
||
#[test] | ||
fn should_return_mutable_row() { | ||
// given | ||
let mut table = Table::new(); | ||
table.insert(1, 1, true); | ||
table.insert(1, 2, false); | ||
table.insert(2, 2, false); | ||
|
||
// when | ||
{ | ||
let mut row = table.get_row_mut(&1).unwrap(); | ||
row.remove(&1); | ||
row.remove(&2); | ||
} | ||
assert!(table.has_row(&1)); | ||
table.clear_if_empty(&1); | ||
|
||
// then | ||
assert!(!table.has_row(&1)); | ||
assert_eq!(table.len(), 1); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clippy usually gives a warning to use
values()
instead ofiter()
in such cases