Group rows by one dim's values and reduce `attr_idx` per group. Returns groups in first-seen order.
(
tile: &SparseTile,
dim_idx: usize,
attr_idx: usize,
reducer: Reducer,
)
| 173 | /// Group rows by one dim's values and reduce `attr_idx` per group. |
| 174 | /// Returns groups in first-seen order. |
| 175 | pub fn group_by_dim( |
| 176 | tile: &SparseTile, |
| 177 | dim_idx: usize, |
| 178 | attr_idx: usize, |
| 179 | reducer: Reducer, |
| 180 | ) -> Vec<GroupAggregate> { |
| 181 | let Some(dict) = tile.dim_dicts.get(dim_idx) else { |
| 182 | return Vec::new(); |
| 183 | }; |
| 184 | let Some(col) = tile.attr_cols.get(attr_idx) else { |
| 185 | return Vec::new(); |
| 186 | }; |
| 187 | let mut order: Vec<CoordValue> = Vec::new(); |
| 188 | let mut by_key: HashMap<CoordValue, AggregateResult> = HashMap::new(); |
| 189 | // Iterate physical rows; track live_idx separately because attr_cols only |
| 190 | // has entries for Live rows — sentinel rows carry no attr payload. |
| 191 | let mut live_idx = 0usize; |
| 192 | for row in 0..tile.row_count() { |
| 193 | let kind = match tile.row_kind(row) { |
| 194 | Ok(k) => k, |
| 195 | Err(_) => break, |
| 196 | }; |
| 197 | if kind != RowKind::Live { |
| 198 | continue; |
| 199 | } |
| 200 | let cell = match col.get(live_idx) { |
| 201 | Some(c) => c, |
| 202 | None => break, |
| 203 | }; |
| 204 | live_idx += 1; |
| 205 | let key = dict.values[dict.indices[row] as usize].clone(); |
| 206 | let one = single_cell(cell, reducer); |
| 207 | match by_key.get_mut(&key) { |
| 208 | Some(slot) => *slot = slot.merge(one), |
| 209 | None => { |
| 210 | order.push(key.clone()); |
| 211 | by_key.insert(key, empty(reducer).merge(one)); |
| 212 | } |
| 213 | } |
| 214 | } |
| 215 | order |
| 216 | .into_iter() |
| 217 | .map(|k| GroupAggregate { |
| 218 | result: by_key.remove(&k).unwrap_or(empty(reducer)), |
| 219 | key: k, |
| 220 | }) |
| 221 | .collect() |
| 222 | } |
| 223 | |
| 224 | #[cfg(test)] |
| 225 | mod tests { |