From ac38aa759beecbef398852071ec704f954f01adb Mon Sep 17 00:00:00 2001 From: Eric Coissac Date: Thu, 20 Aug 2026 05:31:22 +0200 Subject: [PATCH] refactor: rename batch retrieval methods and update common_traits Renames `get_batch`, `fill_batch`, and `fill_batch_sorted` to `collect_slots_values`, `fill_slots_values`, and `fill_slots_values_sorted` to align with updated `common_traits 0.13` APIs. Introduces optimized batch retrieval that sorts input indices for sequential mmap access before reordering outputs to match the original query order. Updates test suites to reflect the new method signatures without altering validation logic or coverage. --- src/Cargo.lock | 68 ++++++++++- src/obicompactvec/Cargo.toml | 2 +- src/obicompactvec/src/bitmatrix/persistent.rs | 4 +- src/obicompactvec/src/bitvec.rs | 35 ++---- src/obicompactvec/src/intmatrix.rs | 8 +- src/obicompactvec/src/reader.rs | 33 ++---- src/obicompactvec/src/tests/bitvec.rs | 36 +++--- src/obicompactvec/src/tests/intmatrix.rs | 32 +++--- src/obicompactvec/src/views.rs | 107 +++++++++++------- 9 files changed, 180 insertions(+), 145 deletions(-) diff --git a/src/Cargo.lock b/src/Cargo.lock index 02572b44..b90d56a3 100644 --- a/src/Cargo.lock +++ b/src/Cargo.lock @@ -300,7 +300,7 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af737c6c59cb018ecbe6472cbdf86d39c59d78252febfe311953a991b6e4ed85" dependencies = [ - "common_traits", + "common_traits 0.11.4", "epserde", "mem_dbg", ] @@ -475,7 +475,18 @@ checksum = "fda9ae1f26adcae83adb2e92f69cf59421f2a277a942f49f8e59f2fcbd7cf062" dependencies = [ "anyhow", "half", - "impl-tools", + "impl-tools 0.10.3", +] + +[[package]] +name = "common_traits" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8e0d7d0c5977b1e87f759d406740c38227125f374f54d81bc197e877f2c5f26" +dependencies = [ + "anyhow", + "half", + "impl-tools 0.12.0", ] [[package]] @@ -783,7 +794,7 @@ checksum = "c40d342ff20a2ce62d9a85ce406e672dfa137f902ac9670034533184f1533976" dependencies = [ "anyhow", "bitflags 2.11.1", - "common_traits", + "common_traits 0.11.4", "epserde-derive", "maligned", "mem_dbg", @@ -1083,11 +1094,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ae95c9095c2f1126d7db785955c73cdc5fc33e7c3fa911bd4a42931672029a7" dependencies = [ "autocfg", - "impl-tools-lib", + "impl-tools-lib 0.11.4", "proc-macro-error2", "syn 2.0.117", ] +[[package]] +name = "impl-tools" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06c8567c75b167cdda5ecee024da9cc9581d41bd52460e2e0e768967972aeef1" +dependencies = [ + "autocfg", + "impl-tools-lib 0.12.0", + "proc-macro-error3", + "syn 2.0.117", +] + [[package]] name = "impl-tools-lib" version = "0.11.4" @@ -1100,6 +1123,18 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "impl-tools-lib" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c81eb99ba7c6145aeccb8857ddc0c611cd52440091dcfddd605b6a930c9afbf4" +dependencies = [ + "proc-macro-error3", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "indexmap" version = "2.14.0" @@ -1519,7 +1554,7 @@ dependencies = [ name = "obicompactvec" version = "0.1.0" dependencies = [ - "common_traits", + "common_traits 0.13.0", "memmap2", "ndarray", "rayon", @@ -2009,6 +2044,16 @@ dependencies = [ "quote", ] +[[package]] +name = "proc-macro-error-attr3" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0084e6206a967a2dad822180626b2f6b07a3b379325e8f1ec0438e33a469ba7" +dependencies = [ + "proc-macro2", + "quote", +] + [[package]] name = "proc-macro-error2" version = "2.0.1" @@ -2020,6 +2065,17 @@ dependencies = [ "quote", ] +[[package]] +name = "proc-macro-error3" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cf066225f2373bc711684792b69bdeac0356019b007e721090c24d92d5d5a50" +dependencies = [ + "proc-macro-error-attr3", + "proc-macro2", + "quote", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -2093,7 +2149,7 @@ dependencies = [ "cacheline-ef", "clap", "colored", - "common_traits", + "common_traits 0.11.4", "epserde", "epserde-derive", "fastrand", diff --git a/src/obicompactvec/Cargo.toml b/src/obicompactvec/Cargo.toml index 8ccc2195..43d8e42b 100644 --- a/src/obicompactvec/Cargo.toml +++ b/src/obicompactvec/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2024" [dependencies] -common_traits = "0.11" +common_traits = "0.13" memmap2 = "0.9" ndarray = "0.17" rayon = "1" diff --git a/src/obicompactvec/src/bitmatrix/persistent.rs b/src/obicompactvec/src/bitmatrix/persistent.rs index b61fcefa..08956f5d 100644 --- a/src/obicompactvec/src/bitmatrix/persistent.rs +++ b/src/obicompactvec/src/bitmatrix/persistent.rs @@ -200,7 +200,7 @@ impl PersistentBitMatrix { /// buffer, ever — the whole point, see /// `PersistentSparseBitMatrix::nonzero_iter`); `Columnar`/`Packed` /// reuse the same sorted-slot batching `fill_sub_matrix` always used - /// (`BitSliceView::nonzero_among_sorted`), just eagerly collected here + /// (`BitSliceView::enumerate_nonzero_slots`), just eagerly collected here /// rather than filled into a dense buffer — same `get` calls, same /// mmap locality, no per-format duplication of that logic elsewhere. /// `Implicit` is trivial (one column, always present). @@ -228,7 +228,7 @@ impl PersistentBitMatrix { for c in 0..n_cols { let view = self.col_view(c); hits.extend( - view.nonzero_among_sorted(&sorted_slots) + view.enumerate_nonzero_slots(&sorted_slots) .map(|pos| (perm[pos], c, 1u32)), ); } diff --git a/src/obicompactvec/src/bitvec.rs b/src/obicompactvec/src/bitvec.rs index 58297ee9..a9737811 100644 --- a/src/obicompactvec/src/bitvec.rs +++ b/src/obicompactvec/src/bitvec.rs @@ -75,36 +75,15 @@ impl PersistentBitVec { /// The slots are sorted internally before reading so that accesses to /// the underlying mmap are as sequential as possible, then the results /// are reordered to match the input order. - pub fn get_batch(&self, slots: &[usize]) -> Vec { - let mut out = vec![false; slots.len()]; - self.fill_batch(slots, &mut out); - out + #[inline] + pub fn collect_slots_values(&self, slots: &[usize]) -> Vec { + self.view().collect_slots_values(slots) } - /// Like [`get_batch`](Self::get_batch), but fills a caller-provided buffer. - pub fn fill_batch(&self, slots: &[usize], out: &mut [bool]) { - assert_eq!(slots.len(), out.len()); - let n = slots.len(); - if n == 0 { - return; - } - let mut perm: Vec = (0..n).collect(); - perm.sort_by_key(|&i| slots[i]); - let sorted: Vec = perm.iter().map(|&i| slots[i]).collect(); - let mut tmp = vec![false; n]; - self.fill_batch_sorted(&sorted, &mut tmp); - for (i, &orig_idx) in perm.iter().enumerate() { - out[orig_idx] = tmp[i]; - } - } - - /// Fill `out` assuming `sorted_slots` is already in ascending order. - /// Results are written in `sorted_slots` order (no reordering). - pub(crate) fn fill_batch_sorted(&self, sorted_slots: &[usize], out: &mut [bool]) { - assert_eq!(sorted_slots.len(), out.len()); - for (i, &slot) in sorted_slots.iter().enumerate() { - out[i] = self.get(slot); - } + /// Like [`collect_slots_values`](Self::collect_slots_values), but fills a caller-provided buffer. + #[inline] + pub fn fill_slots_values(&self, slots: &[usize], out: &mut [bool]) { + self.view().fill_slots_values(slots, out) } // SAFETY: mmap is page-aligned, HEADER_SIZE=16 divisible by 8 → u64-aligned. diff --git a/src/obicompactvec/src/intmatrix.rs b/src/obicompactvec/src/intmatrix.rs index 5efe5925..8d7cff3c 100644 --- a/src/obicompactvec/src/intmatrix.rs +++ b/src/obicompactvec/src/intmatrix.rs @@ -372,7 +372,7 @@ impl PersistentCompactIntMatrix { let mut out: Vec> = Vec::with_capacity(n_cols); for c in 0..n_cols { let mut col_buf = vec![0u32; slots.len()]; - self.col_view(c).fill_batch(slots, &mut col_buf); + self.col_view(c).fill_slots_values(slots, &mut col_buf); out.push(col_buf); } out @@ -397,7 +397,7 @@ impl PersistentCompactIntMatrix { for (c, col) in out.iter_mut().enumerate() { col.resize(n, 0); let mut tmp = vec![0u32; n]; - self.col_view(c).fill_batch_sorted(&sorted_slots, &mut tmp); + self.col_view(c).fill_slots_values_sorted(&sorted_slots, &mut tmp); for (i, &orig_idx) in perm.iter().enumerate() { col[orig_idx] = tmp[i]; } @@ -411,7 +411,7 @@ impl PersistentCompactIntMatrix { /// design even though no sparse count format exists yet). No native /// low-effort case here the way `PersistentSparseBitMatrix` has one — /// both variants reuse the same sorted-slot batching `fill_sub_matrix` - /// already used, via `IntSliceView::nonzero_among_sorted`. + /// already used, via `IntSliceView::enumerate_nonzero_slots`. pub fn nonzero_iter<'a>(&'a self, slots: &'a [usize]) -> impl Iterator + 'a { let n = slots.len(); let mut perm: Vec = (0..n).collect(); @@ -422,7 +422,7 @@ impl PersistentCompactIntMatrix { for c in 0..n_cols { hits.extend( self.col_view(c) - .nonzero_among_sorted(&sorted_slots) + .enumerate_nonzero_slots(&sorted_slots) .map(|(pos, v)| (perm[pos], c, v)), ); } diff --git a/src/obicompactvec/src/reader.rs b/src/obicompactvec/src/reader.rs index 0cc00c45..78f51e4a 100644 --- a/src/obicompactvec/src/reader.rs +++ b/src/obicompactvec/src/reader.rs @@ -67,34 +67,15 @@ impl PersistentCompactIntVec { /// The slots are sorted internally before reading so that accesses to /// the underlying mmap are as sequential as possible, then the results /// are reordered to match the input order. - pub fn get_batch(&self, slots: &[usize]) -> Vec { - let mut out = vec![0u32; slots.len()]; - self.fill_batch(slots, &mut out); - out + #[inline] + pub fn collect_slots_values(&self, slots: &[usize]) -> Vec { + self.view().collect_slots_values(slots) } - /// Like [`get_batch`](Self::get_batch), but fills a caller-provided buffer. - pub fn fill_batch(&self, slots: &[usize], out: &mut [u32]) { - assert_eq!(slots.len(), out.len()); - let n = slots.len(); - if n == 0 { return; } - let mut perm: Vec = (0..n).collect(); - perm.sort_by_key(|&i| slots[i]); - let sorted: Vec = perm.iter().map(|&i| slots[i]).collect(); - let mut tmp = vec![0u32; n]; - self.fill_batch_sorted(&sorted, &mut tmp); - for (i, &orig_idx) in perm.iter().enumerate() { - out[orig_idx] = tmp[i]; - } - } - - /// Fill `out` assuming `sorted_slots` is already in ascending order. - /// Results are written in `sorted_slots` order (no reordering). - pub(crate) fn fill_batch_sorted(&self, sorted_slots: &[usize], out: &mut [u32]) { - assert_eq!(sorted_slots.len(), out.len()); - for (i, &slot) in sorted_slots.iter().enumerate() { - out[i] = self.get(slot); - } + /// Like [`collect_slots_values`](Self::collect_slots_values), but fills a caller-provided buffer. + #[inline] + pub fn fill_slots_values(&self, slots: &[usize], out: &mut [u32]) { + self.view().fill_slots_values(slots, out) } fn overflow_get(&self, slot: usize) -> u32 { diff --git a/src/obicompactvec/src/tests/bitvec.rs b/src/obicompactvec/src/tests/bitvec.rs index 7214ae69..413c4804 100644 --- a/src/obicompactvec/src/tests/bitvec.rs +++ b/src/obicompactvec/src/tests/bitvec.rs @@ -215,65 +215,65 @@ fn hamming_dist_basic() { assert_eq!(ra.hamming_dist(&rb), 2); } -// ── get_batch tests ──────────────────────────────────────────────────────────── +// ── collect_slots_values tests ──────────────────────────────────────────────────────────── #[test] -fn bitvec_get_batch_in_order() { +fn bitvec_collect_slots_values_in_order() { let bits = vec![true, false, true, false, true]; let (_dir, r) = make_bv(&bits); - let got = r.get_batch(&[0, 1, 2, 3, 4]); + let got = r.collect_slots_values(&[0, 1, 2, 3, 4]); assert_eq!(got, bits); } #[test] -fn bitvec_get_batch_out_of_order() { +fn bitvec_collect_slots_values_out_of_order() { let bits = vec![true, false, true, false, true]; let (_dir, r) = make_bv(&bits); - let got = r.get_batch(&[3, 0, 4, 1]); + let got = r.collect_slots_values(&[3, 0, 4, 1]); assert_eq!(got, vec![false, true, true, false]); } #[test] -fn bitvec_get_batch_with_duplicates() { +fn bitvec_collect_slots_values_with_duplicates() { let bits = vec![true, false, true]; let (_dir, r) = make_bv(&bits); - let got = r.get_batch(&[0, 2, 0, 1]); + let got = r.collect_slots_values(&[0, 2, 0, 1]); assert_eq!(got, vec![true, true, true, false]); } #[test] -fn bitvec_get_batch_empty() { +fn bitvec_collect_slots_values_empty() { let (_dir, r) = make_bv(&[true, false]); - let got = r.get_batch(&[]); + let got = r.collect_slots_values(&[]); assert!(got.is_empty()); } #[test] -fn bitvec_get_batch_out_of_bounds_returns_false() { +fn bitvec_collect_slots_values_out_of_bounds_returns_false() { // PersistentBitVec::get does NOT bounds-check — out-of-range slots read // whatever bit happens to be in the mmap (zero-initialised, so false). - // This is a known gap; get_batch inherits it. + // This is a known gap; collect_slots_values inherits it. let (_dir, r) = make_bv(&[true, false]); - let got = r.get_batch(&[0, 2]); + let got = r.collect_slots_values(&[0, 2]); assert_eq!(got, vec![true, false]); } -// BitSliceView get_batch (same logic, exercised through the view) +// BitSliceView collect_slots_values (same logic, exercised through the view) #[test] -fn bitslice_view_get_batch() { +fn bitslice_view_collect_slots_values() { let bits = vec![true, false, true, false, true]; let (_dir, r) = make_bv(&bits); let view = r.view(); - assert_eq!(view.get_batch(&[0, 1, 2]), vec![true, false, true]); - assert_eq!(view.get_batch(&[4, 3]), vec![true, false]); + assert_eq!(view.collect_slots_values(&[0, 1, 2]), vec![true, false, true]); + assert_eq!(view.collect_slots_values(&[4, 3]), vec![true, false]); } #[test] -fn bitslice_view_get_batch_out_of_bounds_returns_false() { +fn bitslice_view_collect_slots_values_out_of_bounds_returns_false() { // BitSliceView::get does NOT bounds-check either. let bits = vec![true, false]; let (_dir, r) = make_bv(&bits); let view = r.view(); - let got = view.get_batch(&[0, 2]); + let got = view.collect_slots_values(&[0, 2]); assert_eq!(got, vec![true, false]); } diff --git a/src/obicompactvec/src/tests/intmatrix.rs b/src/obicompactvec/src/tests/intmatrix.rs index c9ca9fad..fb05e911 100644 --- a/src/obicompactvec/src/tests/intmatrix.rs +++ b/src/obicompactvec/src/tests/intmatrix.rs @@ -400,7 +400,7 @@ fn partial_relfreq_bray_additive_across_split() { } } -// ── get_batch tests ──────────────────────────────────────────────────────────── +// ── collect_slots_values tests ──────────────────────────────────────────────────────────── fn make_pciv(counts: &[u32]) -> (tempfile::TempDir, PersistentCompactIntVec) { let dir = tempdir().unwrap(); @@ -413,49 +413,49 @@ fn make_pciv(counts: &[u32]) -> (tempfile::TempDir, PersistentCompactIntVec) { } #[test] -fn pciv_get_batch_in_order() { +fn pciv_collect_slots_values_in_order() { let counts = vec![10u32, 255, 300, 1000]; let (_dir, v) = make_pciv(&counts); - let got = v.get_batch(&[0, 1, 2, 3]); + let got = v.collect_slots_values(&[0, 1, 2, 3]); assert_eq!(got, counts); } #[test] -fn pciv_get_batch_out_of_order() { +fn pciv_collect_slots_values_out_of_order() { let counts = vec![10u32, 255, 300, 1000]; let (_dir, v) = make_pciv(&counts); - let got = v.get_batch(&[3, 0, 2, 1]); + let got = v.collect_slots_values(&[3, 0, 2, 1]); assert_eq!(got, vec![1000, 10, 300, 255]); } #[test] -fn pciv_get_batch_with_duplicates() { +fn pciv_collect_slots_values_with_duplicates() { let counts = vec![10u32, 255, 300]; let (_dir, v) = make_pciv(&counts); - let got = v.get_batch(&[0, 2, 0, 1]); + let got = v.collect_slots_values(&[0, 2, 0, 1]); assert_eq!(got, vec![10, 300, 10, 255]); } #[test] -fn pciv_get_batch_empty() { +fn pciv_collect_slots_values_empty() { let (_dir, v) = make_pciv(&[10u32, 20]); - let got: Vec = v.get_batch(&[]); + let got: Vec = v.collect_slots_values(&[]); assert!(got.is_empty()); } #[test] -fn pciv_get_batch_out_of_bounds_panics() { +fn pciv_collect_slots_values_out_of_bounds_panics() { let (_dir, v) = make_pciv(&[10u32, 20]); - let result = std::panic::catch_unwind(|| v.get_batch(&[0, 2])); - assert!(result.is_err(), "get_batch should panic on out-of-bounds slot"); + let result = std::panic::catch_unwind(|| v.collect_slots_values(&[0, 2])); + assert!(result.is_err(), "collect_slots_values should panic on out-of-bounds slot"); } -// IntSliceView get_batch (same logic, exercised through the view) +// IntSliceView collect_slots_values (same logic, exercised through the view) #[test] -fn intslice_view_get_batch() { +fn intslice_view_collect_slots_values() { let counts = vec![10u32, 255, 300, 1000]; let (_dir, v) = make_pciv(&counts); let view = v.view(); - assert_eq!(view.get_batch(&[0, 1, 2]), vec![10, 255, 300]); - assert_eq!(view.get_batch(&[3, 1]), vec![1000, 255]); + assert_eq!(view.collect_slots_values(&[0, 1, 2]), vec![10, 255, 300]); + assert_eq!(view.collect_slots_values(&[3, 1]), vec![1000, 255]); } diff --git a/src/obicompactvec/src/views.rs b/src/obicompactvec/src/views.rs index 6af0aa58..013f74c1 100644 --- a/src/obicompactvec/src/views.rs +++ b/src/obicompactvec/src/views.rs @@ -30,13 +30,13 @@ impl<'a> BitSliceView<'a> { /// /// Slots are sorted internally before reading, then results are reordered /// to match the input order. - pub fn get_batch(&self, slots: &[usize]) -> Vec { + pub fn collect_slots_values(&self, slots: &[usize]) -> Vec { let mut out = vec![false; slots.len()]; - self.fill_batch(slots, &mut out); + self.fill_slots_values(slots, &mut out); out } - /// Like [`get_batch`](Self::get_batch), but fills a caller-provided buffer. - pub fn fill_batch(&self, slots: &[usize], out: &mut [bool]) { + /// Like [`collect_slots_values`](Self::collect_slots_values), but fills a caller-provided buffer. + pub fn fill_slots_values(&self, slots: &[usize], out: &mut [bool]) { assert_eq!(slots.len(), out.len()); let n = slots.len(); if n == 0 { return; } @@ -44,32 +44,43 @@ impl<'a> BitSliceView<'a> { perm.sort_by_key(|&i| slots[i]); let sorted: Vec = perm.iter().map(|&i| slots[i]).collect(); let mut tmp = vec![false; n]; - self.fill_batch_sorted(&sorted, &mut tmp); + self.fill_slots_values_sorted(&sorted, &mut tmp); for (i, &orig_idx) in perm.iter().enumerate() { out[orig_idx] = tmp[i]; } } - /// Fill `out` assuming `sorted_slots` is already in ascending order. - /// Results are written in `sorted_slots` order (no reordering). - pub(crate) fn fill_batch_sorted(&self, sorted_slots: &[usize], out: &mut [bool]) { - assert_eq!(sorted_slots.len(), out.len()); - for (i, &slot) in sorted_slots.iter().enumerate() { - out[i] = self.get(slot); - } - } - - /// Positions in `sorted_slots` (not the slots themselves) whose bit is - /// set — a `filter` over `get`, not a new traversal. `sorted_slots` - /// need not actually be sorted for correctness, but callers batching - /// several columns over the same slot set sort once and reuse it for - /// cache-friendlier `get` access (see `PersistentBitMatrix::nonzero_iter`). - pub(crate) fn nonzero_among_sorted<'b>(self, sorted_slots: &'b [usize]) -> impl Iterator + 'b + /// `(position in slots, value)` for every slot. Plain `enumerate` + + /// `get`: order never affects correctness here, only the caller's cache + /// locality (see `enumerate_nonzero_slots`), so this takes `slots` as + /// given. + #[inline] + pub fn enumerate_slots_values<'b>(self, slots: &'b [usize]) -> impl Iterator + 'b where Self: 'b, { - sorted_slots.iter().enumerate() - .filter_map(move |(pos, &slot)| self.get(slot).then_some(pos)) + slots.iter().enumerate().map(move |(pos, &slot)| (pos, self.get(slot))) + } + + /// Fill `out` assuming `sorted_slots` is already in ascending order. + /// Results are written in `sorted_slots` order (no reordering). + pub(crate) fn fill_slots_values_sorted(&self, sorted_slots: &[usize], out: &mut [bool]) { + assert_eq!(sorted_slots.len(), out.len()); + for (pos, v) in self.enumerate_slots_values(sorted_slots) { + out[pos] = v; + } + } + + /// Positions in `slots` (not the slots themselves) whose bit is set. + /// Order of `slots` never matters for correctness; callers batching + /// several columns over the same slot set sort it once beforehand + /// purely for cache-friendlier `get` access (see + /// `PersistentBitMatrix::nonzero_iter`). + pub fn enumerate_nonzero_slots<'b>(self, slots: &'b [usize]) -> impl Iterator + 'b + where + Self: 'b, + { + self.enumerate_slots_values(slots).filter_map(|(pos, v)| v.then_some(pos)) } pub fn count_ones(&self) -> u64 { @@ -184,14 +195,14 @@ impl<'a> IntSliceView<'a> { /// /// Slots are sorted internally before reading, then results are reordered /// to match the input order. - pub fn get_batch(&self, slots: &[usize]) -> Vec { + pub fn collect_slots_values(&self, slots: &[usize]) -> Vec { let mut out = vec![0u32; slots.len()]; - self.fill_batch(slots, &mut out); + self.fill_slots_values(slots, &mut out); out } - /// Like [`get_batch`](Self::get_batch), but fills a caller-provided buffer. - pub fn fill_batch(&self, slots: &[usize], out: &mut [u32]) { + /// Like [`collect_slots_values`](Self::collect_slots_values), but fills a caller-provided buffer. + pub fn fill_slots_values(&self, slots: &[usize], out: &mut [u32]) { assert_eq!(slots.len(), out.len()); let n = slots.len(); if n == 0 { return; } @@ -199,32 +210,40 @@ impl<'a> IntSliceView<'a> { perm.sort_by_key(|&i| slots[i]); let sorted: Vec = perm.iter().map(|&i| slots[i]).collect(); let mut tmp = vec![0u32; n]; - self.fill_batch_sorted(&sorted, &mut tmp); + self.fill_slots_values_sorted(&sorted, &mut tmp); for (i, &orig_idx) in perm.iter().enumerate() { out[orig_idx] = tmp[i]; } } - /// Fill `out` assuming `sorted_slots` is already in ascending order. - /// Results are written in `sorted_slots` order (no reordering). - pub(crate) fn fill_batch_sorted(&self, sorted_slots: &[usize], out: &mut [u32]) { - assert_eq!(sorted_slots.len(), out.len()); - for (i, &slot) in sorted_slots.iter().enumerate() { - out[i] = self.get(slot); - } - } - - /// Like [`BitSliceView::nonzero_among_sorted`]: `(position in - /// sorted_slots, value)` for every nonzero value — a `filter_map` over - /// `get`, not a new traversal. - pub(crate) fn nonzero_among_sorted<'b>(self, sorted_slots: &'b [usize]) -> impl Iterator + 'b + /// `(position in slots, value)` for every slot. See + /// [`BitSliceView::enumerate_slots_values`]: order never affects + /// correctness here. + #[inline] + pub fn enumerate_slots_values<'b>(self, slots: &'b [usize]) -> impl Iterator + 'b where Self: 'b, { - sorted_slots.iter().enumerate().filter_map(move |(pos, &slot)| { - let v = self.get(slot); - (v != 0).then_some((pos, v)) - }) + slots.iter().enumerate().map(move |(pos, &slot)| (pos, self.get(slot))) + } + + /// Fill `out` assuming `sorted_slots` is already in ascending order. + /// Results are written in `sorted_slots` order (no reordering). + pub(crate) fn fill_slots_values_sorted(&self, sorted_slots: &[usize], out: &mut [u32]) { + assert_eq!(sorted_slots.len(), out.len()); + for (pos, v) in self.enumerate_slots_values(sorted_slots) { + out[pos] = v; + } + } + + /// Like [`BitSliceView::enumerate_nonzero_slots`]: `(position in slots, + /// value)` for every nonzero value — a `filter_map` over `get`, not a + /// new traversal. + pub fn enumerate_nonzero_slots<'b>(self, slots: &'b [usize]) -> impl Iterator + 'b + where + Self: 'b, + { + self.enumerate_slots_values(slots).filter_map(|(pos, v)| (v != 0).then_some((pos, v))) } /// Sequential merge scan: yields all n values in slot order.