1 | use std::collections::HashMap; |
2 | |
3 | use fnv::FnvHashMap; |
4 | |
5 | use crate::config::{Entity, Position}; |
6 | |
7 | /// A structure to keep information for [`Entity`] as a key. |
8 | #[derive (Debug, Default, Clone, PartialEq, Eq)] |
9 | pub struct EntityMap<T> { |
10 | // we have a global type to allocate in on stack. |
11 | // because most of the time no changes are made to the [`EntityMap`]. |
12 | global: T, |
13 | columns: FnvHashMap<usize, T>, |
14 | rows: FnvHashMap<usize, T>, |
15 | cells: FnvHashMap<Position, T>, |
16 | } |
17 | |
18 | impl<T> EntityMap<T> { |
19 | /// Creates an empty [`EntityMap`]. |
20 | pub fn new(global: T) -> Self { |
21 | Self { |
22 | global, |
23 | rows: FnvHashMap::default(), |
24 | columns: FnvHashMap::default(), |
25 | cells: FnvHashMap::default(), |
26 | } |
27 | } |
28 | |
29 | /// Verifies whether anything was set beside a global entry. |
30 | pub fn is_empty(&self) -> bool { |
31 | self.columns.is_empty() && self.rows.is_empty() && self.cells.is_empty() |
32 | } |
33 | |
34 | /// Get a value for an [`Entity`]. |
35 | pub fn get(&self, entity: Entity) -> &T { |
36 | if self.rows.is_empty() && self.columns.is_empty() && self.cells.is_empty() { |
37 | return &self.global; |
38 | } |
39 | |
40 | match entity { |
41 | Entity::Column(col) => self.columns.get(&col).unwrap_or(&self.global), |
42 | Entity::Row(row) => self.rows.get(&row).unwrap_or(&self.global), |
43 | Entity::Cell(row, col) => { |
44 | // todo: optimize; |
45 | // |
46 | // Cause we can change rows/columns/cells separately we need to check them separately. |
47 | // But we often doing this checks in `Grid::fmt` and I believe if we could optimize it it could be beneficial. |
48 | // |
49 | // Haven't found a solution for that yet. |
50 | // |
51 | // I was wondering if there is a hash function like. |
52 | // Apparently it doesn't make sense cause we will reset columns/rows on cell insert which is not what we want. |
53 | // |
54 | // ``` |
55 | // hash(column, row) == hash(column) == hash(row) |
56 | // ``` |
57 | // |
58 | // ref: https://opendsa-server.cs.vt.edu/ODSA/Books/Everything/html/Sparse.html |
59 | // ref: https://users.rust-lang.org/t/make-hash-return-same-value-whather-the-order-of-element-of-a-tuple/69932/13 |
60 | |
61 | self.cells |
62 | .get(&(row, col)) |
63 | .or_else(|| self.columns.get(&col)) |
64 | .or_else(|| self.rows.get(&row)) |
65 | .unwrap_or(&self.global) |
66 | } |
67 | Entity::Global => &self.global, |
68 | } |
69 | } |
70 | |
71 | /// Removes a value for an [`Entity`]. |
72 | pub fn remove(&mut self, entity: Entity) { |
73 | match entity { |
74 | Entity::Global => { |
75 | self.cells.clear(); |
76 | self.rows.clear(); |
77 | self.columns.clear(); |
78 | } |
79 | Entity::Column(col) => self.cells.retain(|&(_, c), _| c != col), |
80 | Entity::Row(row) => self.cells.retain(|&(r, _), _| r != row), |
81 | Entity::Cell(row, col) => { |
82 | self.cells.remove(&(row, col)); |
83 | } |
84 | } |
85 | } |
86 | } |
87 | |
88 | impl<T: Clone> EntityMap<T> { |
89 | /// Set a value for an [`Entity`]. |
90 | pub fn insert(&mut self, entity: Entity, value: T) { |
91 | match entity { |
92 | Entity::Column(col) => { |
93 | for &row in self.rows.keys() { |
94 | self.cells.insert((row, col), value.clone()); |
95 | } |
96 | |
97 | self.columns.insert(col, value); |
98 | } |
99 | Entity::Row(row) => { |
100 | for &col in self.columns.keys() { |
101 | self.cells.insert((row, col), value.clone()); |
102 | } |
103 | |
104 | self.rows.insert(row, value); |
105 | } |
106 | Entity::Cell(row, col) => { |
107 | self.cells.insert((row, col), value); |
108 | } |
109 | Entity::Global => { |
110 | self.remove(Entity::Global); |
111 | self.global = value |
112 | } |
113 | } |
114 | } |
115 | } |
116 | |
117 | impl<T> From<EntityMap<T>> for HashMap<Entity, T> { |
118 | fn from(value: EntityMap<T>) -> Self { |
119 | let mut m: HashMap = HashMap::new(); |
120 | m.insert(k:Entity::Global, v:value.global); |
121 | |
122 | for ((row: usize, col: usize), value: T) in value.cells { |
123 | m.insert(k:Entity::Cell(row, col), v:value); |
124 | } |
125 | |
126 | for (row: usize, value: T) in value.rows { |
127 | m.insert(k:Entity::Row(row), v:value); |
128 | } |
129 | |
130 | for (col: usize, value: T) in value.columns { |
131 | m.insert(k:Entity::Column(col), v:value); |
132 | } |
133 | |
134 | m |
135 | } |
136 | } |
137 | |