Skip to content
This repository has been archived by the owner on Nov 6, 2020. It is now read-only.

Sparse Table Implementation (Row, Col) -> Val #545

Merged
merged 5 commits into from
Mar 1, 2016
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions util/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ pub mod network;
pub mod log;
pub mod panics;
pub mod keys;
pub mod table;

pub use common::*;
pub use misc::*;
Expand Down
260 changes: 260 additions & 0 deletions util/src/table.rs
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())
Copy link
Collaborator

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 of iter() in such cases

}

/// Check if there is any element in this Table
pub fn is_empty(&self) -> bool {
self.len() == 0
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is incorrect if there are empty rows

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's ok (note it's not self.map.len())

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, true

Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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>> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Our convention is not to use get_ prefix in function names. Should be just row_mut

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>> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get_ prefix

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) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Idiomatic way would be map.entry(row).or_insert_with(|| Hasmap::new()).insert(col, val)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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);
}
}