• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

vigna / sux-rs / 29757944694

20 Jul 2026 04:03PM UTC coverage: 72.617% (+1.5%) from 71.124%
29757944694

push

github

vigna
Fifth round of fixes

25 of 58 new or added lines in 7 files covered. (43.1%)

1964 existing lines in 43 files now uncovered.

8669 of 11938 relevant lines covered (72.62%)

16525410.5 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

68.89
/src/array/partial_array.rs
1
/*
2
 * SPDX-FileCopyrightText: 2025 Sebastiano Vigna
3
 *
4
 * SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
5
 */
6

7
//! Immutable [partial array] implementations.
8
//!
9
//! [partial array]: PartialArray
10

11
use std::marker::PhantomData;
12

13
use mem_dbg::*;
14
use value_traits::slices::SliceByValue;
15

16
use crate::bits::{BitFieldVec, BitVec};
17
use crate::dict::EliasFanoBuilder;
18
use crate::dict::elias_fano::EliasFano;
19
use crate::panic_if_out_of_bounds;
20
use crate::rank_sel::{Rank9, SelectZeroAdaptConst};
21
use crate::traits::Backend;
22
use crate::traits::TryIntoUnaligned;
23
use crate::traits::Unaligned;
24
use crate::traits::{BitVecOps, BitVecOpsMut};
25
use crate::traits::{RankUnchecked, SuccUnchecked};
26

27
// Rank9 is inherently u64-based, so the dense index must use u64 backing
28
// regardless of usize.
29
type DenseIndex = Rank9<BitVec<Box<[u64]>>>;
30

31
/// An internal index for sparse partial arrays.
32
///
33
/// We cannot use directly an [Elias–Fano] structure because we need to
34
/// keep track of the first invalid position; and we need to keep track of
35
/// the first invalid position because we want to implement just
36
/// [`SuccUnchecked`] on the Elias–Fano structure, because it requires just
37
/// [`SelectZeroUnchecked`], whereas [`Succ`] would require
38
/// [`SelectUnchecked`] as well.
39
///
40
/// [Elias–Fano]: crate::dict::EliasFano
41
/// [`SuccUnchecked`]: crate::traits::SuccUnchecked
42
/// [`SelectZeroUnchecked`]: crate::traits::SelectZeroUnchecked
43
/// [`Succ`]: crate::traits::Succ
44
/// [`SelectUnchecked`]: crate::traits::SelectUnchecked
45
#[doc(hidden)]
46
#[derive(Debug, Clone, MemSize, MemDbg)]
47
#[cfg_attr(feature = "epserde", derive(epserde::Epserde))]
48
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
49
pub struct SparseIndex<D, L = BitFieldVec<Box<[u64]>>, I = Box<[usize]>> {
50
    ef: EliasFano<u64, SelectZeroAdaptConst<BitVec<D>, I>, L>,
51
    /// self.ef should not be queried for values >= self.first_invalid_position
52
    first_invalid_pos: usize,
53
}
54

55
/// Builder for creating an immutable partial array.
56
///
57
/// The builder allows you to specify the array length and then add
58
/// (position, value) pairs. Positions must be added in strictly
59
/// increasing order.
60
///
61
/// To get a builder you can use either [new_dense] or [new_sparse].
62
#[derive(Debug, Clone, MemSize, MemDbg)]
63
pub struct PartialArrayBuilder<T, B> {
64
    builder: B,
65
    values: Vec<T>,
66
    len: usize,
67
    min_next_pos: usize,
68
}
69

70
/// Creates a new builder for a dense partial array of the given length.
71
///
72
/// A dense partial array stores a bit vector of the given length to mark
73
/// which positions contain values, and uses ranking on this bit vector to
74
/// map positions to indices in a contiguous value array.
75
///
76
/// If your set of values is really sparse, consider using a
77
/// [sparse partial array].
78
///
79
/// # Examples
80
///
81
/// ```rust
82
/// # use sux::array::partial_array;
83
/// let mut builder = partial_array::new_dense(10);
84
/// builder.set(1, "foo");
85
/// builder.set(2, "hello");
86
/// builder.set(7, "world");
87
///
88
/// let array = builder.build();
89
/// assert_eq!(array.get(1), Some(&"foo"));
90
/// assert_eq!(array.get(2), Some(&"hello"));
91
/// assert_eq!(array.get(3), None);
92
/// assert_eq!(array.get(7), Some(&"world"));
93
/// ```
94
///
95
/// [sparse partial array]: new_sparse
96
pub fn new_dense<T>(len: usize) -> PartialArrayBuilder<T, BitVec<Box<[u64]>>> {
8✔
97
    let n_of_words = len.div_ceil(64);
24✔
98
    // SAFETY: the backing has exactly enough words for len bits
99
    let bit_vec = unsafe { BitVec::from_raw_parts(vec![0u64; n_of_words].into_boxed_slice(), len) };
48✔
100
    PartialArrayBuilder {
101
        builder: bit_vec,
102
        values: vec![],
8✔
103
        len,
104
        min_next_pos: 0,
105
    }
106
}
107

108
impl<T> PartialArrayBuilder<T, BitVec<Box<[u64]>>> {
109
    /// Sets a value at the given position.
110
    ///
111
    /// The provided position must be greater than the last position set.
112
    pub fn set(&mut self, position: usize, value: T) {
13✔
113
        if position < self.min_next_pos {
13✔
114
            panic!(
1✔
115
                "Positions must be set in increasing order: got {} after {}",
×
116
                position,
×
117
                self.min_next_pos - 1
1✔
118
            );
119
        }
120
        panic_if_out_of_bounds!(position, self.len);
36✔
121

122
        // SAFETY: position < len
123
        unsafe {
124
            self.builder.set_unchecked(position, true);
22✔
125
        }
126
        self.values.push(value);
33✔
127
        self.min_next_pos = position + 1;
11✔
128
    }
129

130
    /// Builds the immutable dense partial array.
131
    #[must_use]
132
    pub fn build(self) -> PartialArray<T, Rank9<BitVec<Box<[u64]>>>> {
6✔
133
        let (bit_vec, values) = (self.builder, self.values);
18✔
134
        let rank9 = Rank9::new(bit_vec);
18✔
135
        let values = values.into_boxed_slice();
18✔
136

137
        PartialArray {
138
            index: rank9,
139
            values,
140
            _marker: PhantomData,
141
        }
142
    }
143
}
144

145
/// Creates a new builder for a sparse partial array of the given length.
146
///
147
/// A sparse partial array stores the non-empty positions of the array in an
148
/// [Elias-Fano] structure.
149
///
150
/// If your set of values is really dense, consider using a [dense partial array].
151
///
152
/// # Examples
153
///
154
/// ```rust
155
/// # use sux::array::partial_array;
156
/// let mut builder = partial_array::new_sparse(10, 3);
157
/// builder.set(1, "foo");
158
/// builder.set(2, "hello");
159
/// builder.set(7, "world");
160
///
161
/// let array = builder.build();
162
/// assert_eq!(array.get(1), Some(&"foo"));
163
/// assert_eq!(array.get(2), Some(&"hello"));
164
/// assert_eq!(array.get(3), None);
165
/// assert_eq!(array.get(7), Some(&"world"));
166
/// ```
167
///
168
/// You must specify the number of values in advance.
169
///
170
/// # Panics
171
///
172
/// Panics if `num_values > len`, because strictly increasing in-bounds
173
/// positions cannot satisfy that capacity.
174
///
175
/// [Elias-Fano]: crate::dict::EliasFano
176
/// [dense partial array]: new_dense
177
pub fn new_sparse<T>(
12✔
178
    len: usize,
179
    num_values: usize,
180
) -> PartialArrayBuilder<T, EliasFanoBuilder<u64>> {
181
    assert!(
12✔
182
        num_values <= len,
12✔
UNCOV
183
        "number of values ({num_values}) must not exceed array length ({len})"
×
184
    );
185
    PartialArrayBuilder {
186
        builder: EliasFanoBuilder::<u64>::new(num_values, len as u64),
44✔
187
        values: vec![],
11✔
188
        len,
189
        min_next_pos: 0,
190
    }
191
}
192

193
impl<T> PartialArrayBuilder<T, EliasFanoBuilder<u64>> {
194
    /// Sets a value at the given position.
195
    ///
196
    /// The provided position must be greater than the last position
197
    /// set.
198
    pub fn set(&mut self, position: usize, value: T) {
19✔
199
        if position < self.min_next_pos {
19✔
200
            panic!(
1✔
UNCOV
201
                "Positions must be set in increasing order: got {} after {}",
×
UNCOV
202
                position,
×
203
                self.min_next_pos - 1
1✔
204
            );
205
        }
206
        panic_if_out_of_bounds!(position, self.len);
54✔
207
        // usize fits u64 on all supported targets, so this cast is lossless. The
208
        // checked push panics "Too many values" past the declared capacity,
209
        // upholding EliasFano's unchecked "at most n pushes" precondition.
210
        self.builder.push(position as u64);
51✔
211
        self.values.push(value);
51✔
212
        self.min_next_pos = position + 1;
17✔
213
    }
214

215
    /// Builds the immutable sparse partial array.
216
    #[must_use]
217
    pub fn build(self) -> PartialArray<T, SparseIndex<Box<[usize]>>> {
8✔
218
        let (builder, values) = (self.builder, self.values);
24✔
219
        let ef_dict = builder.build_with_dict();
24✔
220
        let values = values.into_boxed_slice();
24✔
221

222
        PartialArray {
223
            index: SparseIndex {
16✔
224
                ef: ef_dict,
225
                first_invalid_pos: self.min_next_pos,
226
            },
227
            values,
228
            _marker: PhantomData,
229
        }
230
    }
231
}
232

233
/// Extends the builder with an iterator of (position, value) pairs.
234
///
235
/// Position must be in strictly increasing order. The first returned
236
/// position must be greater than the last position set.
237
impl<T> Extend<(usize, T)> for PartialArrayBuilder<T, BitVec<Box<[u64]>>> {
UNCOV
238
    fn extend<I: IntoIterator<Item = (usize, T)>>(&mut self, iter: I) {
×
239
        for (pos, val) in iter {
×
240
            self.set(pos, val);
×
241
        }
242
    }
243
}
244

245
/// Extends the builder with an iterator of (position, value) pairs.
246
///
247
/// Position must be in strictly increasing order. The first returned
248
/// position must be greater than the last position set.
249
impl<T> Extend<(usize, T)> for PartialArrayBuilder<T, EliasFanoBuilder<u64>> {
UNCOV
250
    fn extend<I: IntoIterator<Item = (usize, T)>>(&mut self, iter: I) {
×
UNCOV
251
        for (pos, val) in iter {
×
UNCOV
252
            self.set(pos, val);
×
253
        }
254
    }
255
}
256

257
/// An immutable partial array that supports efficient queries
258
/// in compacted storage.
259
///
260
/// This structure stores a *partial array*, an array in which only
261
/// some positions contain values. There is a [dense] and a [sparse]
262
/// implementation with different space/time trade-offs.
263
///
264
/// For convenience, this structure implements [`SliceByValue`].
265
///
266
/// When the index structure `P` supports it, this structure implements the
267
/// [`TryIntoUnaligned`] trait, allowing it
268
/// to be converted into (usually faster) structures using unaligned access.
269
///
270
/// See [`PartialArrayBuilder`] for details on how to create a partial array.
271
///
272
/// [dense]: new_dense
273
/// [sparse]: new_sparse
274
#[derive(Debug, Clone, MemSize, MemDbg)]
275
#[cfg_attr(feature = "epserde", derive(epserde::Epserde))]
276
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
277
pub struct PartialArray<T, P, V = Box<[T]>> {
278
    index: P,
279
    values: V,
280
    _marker: PhantomData<T>,
281
}
282

283
impl<T, P, V: AsRef<[T]>> PartialArray<T, P, V> {
284
    /// Returns the number of values stored in the array.
285
    #[inline(always)]
286
    pub fn num_values(&self) -> usize {
15✔
287
        self.values.as_ref().len()
30✔
288
    }
289
}
290

291
impl<T, V: AsRef<[T]>> PartialArray<T, DenseIndex, V> {
292
    /// Returns the total length of the array.
293
    ///
294
    /// This is the length that was specified when creating the builder,
295
    /// not the number of values actually stored.
296
    #[inline(always)]
297
    pub fn len(&self) -> usize {
9✔
298
        self.index.len()
18✔
299
    }
300

301
    /// Returns `true` if the array has no elements.
302
    #[inline(always)]
303
    pub fn is_empty(&self) -> bool {
1✔
304
        self.len() == 0
1✔
305
    }
306

307
    /// Gets a reference to the value at the given position.
308
    ///
309
    /// Returns `Some(&value)` if a value is present at the position,
310
    /// or `None` if no value was stored there.
311
    ///
312
    /// # Examples
313
    ///
314
    /// ```rust
315
    /// # use sux::array::partial_array;
316
    /// let mut builder = partial_array::new_dense(10);
317
    /// builder.set(5, 42);
318
    ///
319
    /// let array = builder.build();
320
    /// assert_eq!(array.get(5), Some(&42));
321
    /// assert_eq!(array.get(6), None);
322
    /// ```
323
    pub fn get(&self, position: usize) -> Option<&T> {
1,041✔
324
        panic_if_out_of_bounds!(position, self.len());
2,084✔
325

326
        // SAFETY: we just checked
327
        unsafe { self.get_unchecked(position) }
3,120✔
328
    }
329

330
    /// # Safety
331
    ///
332
    /// position < len()
333
    pub unsafe fn get_unchecked(&self, position: usize) -> Option<&T> {
1,040✔
334
        // Check if there's a value at this position
335
        // SAFETY: position < len() guaranteed by caller
336
        if !unsafe { self.index.get_unchecked(position) } {
2,080✔
337
            return None;
1,027✔
338
        }
339

340
        // Use ranking to find the index in the values array
341
        // SAFETY: position < len() guaranteed by caller
342
        let value_index = unsafe { self.index.rank_unchecked(position) };
52✔
343

344
        // SAFETY: necessarily value_index < num_values().
345
        Some(unsafe { self.values.as_ref().get_unchecked(value_index) })
26✔
346
    }
347
}
348

349
impl<
350
    T,
351
    D: Backend<Word = usize> + AsRef<[usize]>,
352
    L: SliceByValue<Value = u64>,
353
    I: AsRef<[usize]>,
354
    V: AsRef<[T]>,
355
> PartialArray<T, SparseIndex<D, L, I>, V>
356
where
357
    for<'b> &'b L: crate::traits::IntoUncheckedIterator<Item = u64>,
358
{
359
    /// Returns the total length of the array.
360
    ///
361
    /// This is the length that was specified when creating the builder,
362
    /// not the number of values actually stored.
363
    #[inline(always)]
364
    pub fn len(&self) -> usize {
11✔
365
        self.index.ef.upper_bound() as usize
11✔
366
    }
367

368
    /// Returns `true` if the array has no elements.
369
    #[inline(always)]
370
    pub fn is_empty(&self) -> bool {
1✔
371
        self.index.ef.upper_bound() == 0
1✔
372
    }
373

374
    /// Gets a reference to the value at the given position.
375
    ///
376
    /// Returns `Some(&value)` if a value is present at the position,
377
    /// or `None` if no value was stored there.
378
    ///
379
    /// # Examples
380
    ///
381
    /// ```rust
382
    /// # use sux::array::partial_array;
383
    /// let mut builder = partial_array::new_sparse(10, 1);
384
    /// builder.set(5, 42);
385
    ///
386
    /// let array = builder.build();
387
    /// assert_eq!(array.get(5), Some(&42));
388
    /// assert_eq!(array.get(6), None);
389
    /// ```
390
    pub fn get(&self, position: usize) -> Option<&T> {
1,063✔
391
        panic_if_out_of_bounds!(position, self.len());
2,128✔
392

393
        // SAFETY: we just checked
394
        unsafe { self.get_unchecked(position) }
3,186✔
395
    }
396

397
    /// # Safety
398
    ///
399
    /// position < len()
400
    pub unsafe fn get_unchecked(&self, position: usize) -> Option<&T> {
1,062✔
401
        if position >= self.index.first_invalid_pos {
1,062✔
402
            return None;
506✔
403
        }
404
        // Check if there's a value at this position
405
        // SAFETY: position <= last set position
406
        let (index, pos) = unsafe { self.index.ef.succ_unchecked::<false>(position as u64) };
2,224✔
407

408
        if pos != position as u64 {
556✔
409
            None
536✔
410
        } else {
411
            // SAFETY: necessarily value_index < num values.
412
            Some(unsafe { self.values.as_ref().get_unchecked(index) })
40✔
413
        }
414
    }
415
}
416

417
/// Returns an option even when using `get_value_unchecked` because it should be safe to call
418
/// whenever `position < len()`.
419
impl<T: Clone, V: AsRef<[T]>> SliceByValue for PartialArray<T, DenseIndex, V> {
420
    type Value = Option<T>;
421

UNCOV
422
    fn len(&self) -> usize {
×
UNCOV
423
        self.len()
×
424
    }
425

UNCOV
426
    unsafe fn get_value_unchecked(&self, position: usize) -> Self::Value {
×
427
        // SAFETY: position < len() guaranteed by caller
UNCOV
428
        unsafe { self.get_unchecked(position) }.cloned()
×
429
    }
430
}
431

432
/// Returns an option even when using `get_value_unchecked` because it should be safe to call
433
/// whenever `position < len()`.
434
impl<
435
    T: Clone,
436
    D: Backend<Word = usize> + AsRef<[usize]>,
437
    L: SliceByValue<Value = u64>,
438
    I: AsRef<[usize]>,
439
    V: AsRef<[T]>,
440
> SliceByValue for PartialArray<T, SparseIndex<D, L, I>, V>
441
where
442
    for<'b> &'b L: crate::traits::IntoUncheckedIterator<Item = u64>,
443
{
444
    type Value = Option<T>;
445

446
    fn len(&self) -> usize {
×
447
        self.len()
×
448
    }
449

UNCOV
450
    unsafe fn get_value_unchecked(&self, position: usize) -> Self::Value {
×
451
        // SAFETY: position < len() guaranteed by caller
UNCOV
452
        unsafe { self.get_unchecked(position) }.cloned()
×
453
    }
454
}
455

456
// ── Aligned ↔ Unaligned conversion ──────────────────────────────────
457

458
impl<D> TryIntoUnaligned for SparseIndex<D> {
459
    type Unaligned = SparseIndex<D, Unaligned<BitFieldVec<Box<[u64]>>>>;
460
    fn try_into_unaligned(
×
461
        self,
462
    ) -> Result<Self::Unaligned, crate::traits::UnalignedConversionError> {
UNCOV
463
        Ok(SparseIndex {
×
UNCOV
464
            ef: self.ef.try_into_unaligned()?,
×
UNCOV
465
            first_invalid_pos: self.first_invalid_pos,
×
466
        })
467
    }
468
}
469

470
impl<T, P: TryIntoUnaligned, V> TryIntoUnaligned for PartialArray<T, P, V> {
471
    type Unaligned = PartialArray<T, P::Unaligned, V>;
UNCOV
472
    fn try_into_unaligned(
×
473
        self,
474
    ) -> Result<Self::Unaligned, crate::traits::UnalignedConversionError> {
UNCOV
475
        Ok(PartialArray {
×
UNCOV
476
            index: self.index.try_into_unaligned()?,
×
UNCOV
477
            values: self.values,
×
UNCOV
478
            _marker: PhantomData,
×
479
        })
480
    }
481
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc