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

vigna / sux-rs / 24282745314

11 Apr 2026 12:44PM UTC coverage: 71.223% (+0.08%) from 71.146%
24282745314

push

github

vigna
Better docs

8 of 8 new or added lines in 1 file covered. (100.0%)

593 existing lines in 22 files now uncovered.

7227 of 10147 relevant lines covered (71.22%)

17426514.58 hits per line

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

68.97
/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]>>> {
50
    ef: EliasFano<u64, SelectZeroAdaptConst<BitVec<D>, Box<[usize]>, 12, 3>, 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
/// [Elias-Fano]: crate::dict::EliasFano
151
/// [dense partial array]: new_dense
152
///
153
/// If your set of values is really dense, consider using a [dense partial array].
154
///
155
/// # Examples
156
///
157
/// ```rust
158
/// # use sux::array::partial_array;
159
/// let mut builder = partial_array::new_sparse(10, 3);
160
/// builder.set(1, "foo");
161
/// builder.set(2, "hello");
162
/// builder.set(7, "world");
163
///
164
/// let array = builder.build();
165
/// assert_eq!(array.get(1), Some(&"foo"));
166
/// assert_eq!(array.get(2), Some(&"hello"));
167
/// assert_eq!(array.get(3), None);
168
/// assert_eq!(array.get(7), Some(&"world"));
169
/// ```
170
///
171
/// Note that you must specify the number of values in advance.
172
pub fn new_sparse<T>(
9✔
173
    len: usize,
174
    num_values: usize,
175
) -> PartialArrayBuilder<T, EliasFanoBuilder<u64>> {
176
    PartialArrayBuilder {
177
        builder: EliasFanoBuilder::<u64>::new(num_values, len as u64),
36✔
178
        values: vec![],
9✔
179
        len,
180
        min_next_pos: 0,
181
    }
182
}
183

184
impl<T> PartialArrayBuilder<T, EliasFanoBuilder<u64>> {
185
    /// Sets a value at the given position.
186
    ///
187
    /// The provided position must be greater than the last position
188
    /// set.
189
    pub fn set(&mut self, position: usize, value: T) {
16✔
190
        if position < self.min_next_pos {
16✔
191
            panic!(
1✔
192
                "Positions must be set in increasing order: got {} after {}",
×
UNCOV
193
                position,
×
194
                self.min_next_pos - 1
1✔
195
            );
196
        }
197
        panic_if_out_of_bounds!(position, self.len);
45✔
198
        // SAFETY: conditions have been just checked
199
        unsafe { self.builder.push_unchecked(position as u64) };
42✔
200
        self.values.push(value);
42✔
201
        self.min_next_pos = position + 1;
14✔
202
    }
203

204
    /// Builds the immutable sparse partial array.
205
    #[must_use]
206
    pub fn build(self) -> PartialArray<T, SparseIndex<Box<[usize]>>> {
7✔
207
        let (builder, values) = (self.builder, self.values);
21✔
208
        let ef_dict = builder.build_with_dict();
21✔
209
        let values = values.into_boxed_slice();
21✔
210

211
        PartialArray {
212
            index: SparseIndex {
14✔
213
                ef: ef_dict,
214
                first_invalid_pos: self.min_next_pos,
215
            },
216
            values,
217
            _marker: PhantomData,
218
        }
219
    }
220
}
221

222
/// Extends the builder with an iterator of (position, value) pairs.
223
///
224
/// Position must be in strictly increasing order. The first returned
225
/// position must be greater than the last position set.
226
impl<T> Extend<(usize, T)> for PartialArrayBuilder<T, BitVec<Box<[u64]>>> {
227
    fn extend<I: IntoIterator<Item = (usize, T)>>(&mut self, iter: I) {
×
UNCOV
228
        for (pos, val) in iter {
×
UNCOV
229
            self.set(pos, val);
×
230
        }
231
    }
232
}
233

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

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

272
impl<T, P, V: AsRef<[T]>> PartialArray<T, P, V> {
273
    /// Returns the number of values stored in the array.
274
    #[inline(always)]
275
    pub fn num_values(&self) -> usize {
14✔
276
        self.values.as_ref().len()
28✔
277
    }
278
}
279

280
impl<T, V: AsRef<[T]>> PartialArray<T, DenseIndex, V> {
281
    /// Returns the total length of the array.
282
    ///
283
    /// This is the length that was specified when creating the builder,
284
    /// not the number of values actually stored.
285
    #[inline(always)]
286
    pub fn len(&self) -> usize {
9✔
287
        self.index.len()
18✔
288
    }
289

290
    /// Returns `true` if the array has no elements.
291
    #[inline(always)]
292
    pub fn is_empty(&self) -> bool {
1✔
293
        self.len() == 0
1✔
294
    }
295

296
    /// Gets a reference to the value at the given position.
297
    ///
298
    /// Returns `Some(&value)` if a value is present at the position,
299
    /// or `None` if no value was stored there.
300
    ///
301
    /// # Examples
302
    ///
303
    /// ```rust
304
    /// # use sux::array::partial_array;
305
    /// let mut builder = partial_array::new_dense(10);
306
    /// builder.set(5, 42);
307
    ///
308
    /// let array = builder.build();
309
    /// assert_eq!(array.get(5), Some(&42));
310
    /// assert_eq!(array.get(6), None);
311
    /// ```
312
    pub fn get(&self, position: usize) -> Option<&T> {
1,041✔
313
        panic_if_out_of_bounds!(position, self.len());
2,084✔
314

315
        // SAFETY: we just checked
316
        unsafe { self.get_unchecked(position) }
3,120✔
317
    }
318

319
    /// # Safety
320
    ///
321
    /// position < len()
322
    pub unsafe fn get_unchecked(&self, position: usize) -> Option<&T> {
1,040✔
323
        // Check if there's a value at this position
324
        // SAFETY: position < len() guaranteed by caller
325
        if !unsafe { self.index.get_unchecked(position) } {
2,080✔
326
            return None;
1,027✔
327
        }
328

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

333
        // SAFETY: necessarily value_index < num_values().
334
        Some(unsafe { self.values.as_ref().get_unchecked(value_index) })
26✔
335
    }
336
}
337

338
impl<T, D: Backend<Word = usize> + AsRef<[usize]>, L: SliceByValue<Value = u64>, V: AsRef<[T]>>
339
    PartialArray<T, SparseIndex<D, L>, V>
340
where
341
    for<'b> &'b L: crate::traits::IntoUncheckedIterator<Item = u64>,
342
{
343
    /// Returns the total length of the array.
344
    ///
345
    /// This is the length that was specified when creating the builder,
346
    /// not the number of values actually stored.
347
    #[inline(always)]
348
    pub fn len(&self) -> usize {
10✔
349
        self.index.ef.upper_bound() as usize
10✔
350
    }
351

352
    /// Returns `true` if the array has no elements.
353
    #[inline(always)]
354
    pub fn is_empty(&self) -> bool {
1✔
355
        self.index.ef.upper_bound() == 0
1✔
356
    }
357

358
    /// Gets a reference to the value at the given position.
359
    ///
360
    /// Returns `Some(&value)` if a value is present at the position,
361
    /// or `None` if no value was stored there.
362
    ///
363
    /// # Examples
364
    ///
365
    /// ```rust
366
    /// # use sux::array::partial_array;
367
    /// let mut builder = partial_array::new_sparse(10, 1);
368
    /// builder.set(5, 42);
369
    ///
370
    /// let array = builder.build();
371
    /// assert_eq!(array.get(5), Some(&42));
372
    /// assert_eq!(array.get(6), None);
373
    /// ```
374
    pub fn get(&self, position: usize) -> Option<&T> {
1,061✔
375
        panic_if_out_of_bounds!(position, self.len());
2,124✔
376

377
        // SAFETY: we just checked
378
        unsafe { self.get_unchecked(position) }
3,180✔
379
    }
380

381
    /// # Safety
382
    ///
383
    /// position < len()
384
    pub unsafe fn get_unchecked(&self, position: usize) -> Option<&T> {
1,060✔
385
        if position >= self.index.first_invalid_pos {
1,060✔
386
            return None;
505✔
387
        }
388
        // Check if there's a value at this position
389
        // SAFETY: position <= last set position
390
        let (index, pos) = unsafe { self.index.ef.succ_unchecked::<false>(position as u64) };
2,220✔
391

392
        if pos != position as u64 {
555✔
393
            None
536✔
394
        } else {
395
            // SAFETY: necessarily value_index < num values.
396
            Some(unsafe { self.values.as_ref().get_unchecked(index) })
38✔
397
        }
398
    }
399
}
400

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

UNCOV
406
    fn len(&self) -> usize {
×
UNCOV
407
        self.len()
×
408
    }
409

410
    unsafe fn get_value_unchecked(&self, position: usize) -> Self::Value {
×
411
        // SAFETY: position < len() guaranteed by caller
UNCOV
412
        unsafe { self.get_unchecked(position) }.cloned()
×
413
    }
414
}
415

416
/// Returns an option even when using `get_value_unchecked` because it should be safe to call
417
/// whenever `position < len()`.
418
impl<
419
    T: Clone,
420
    D: Backend<Word = usize> + AsRef<[usize]>,
421
    L: SliceByValue<Value = u64>,
422
    V: AsRef<[T]>,
423
> SliceByValue for PartialArray<T, SparseIndex<D, L>, V>
424
where
425
    for<'b> &'b L: crate::traits::IntoUncheckedIterator<Item = u64>,
426
{
427
    type Value = Option<T>;
428

UNCOV
429
    fn len(&self) -> usize {
×
UNCOV
430
        self.len()
×
431
    }
432

433
    unsafe fn get_value_unchecked(&self, position: usize) -> Self::Value {
×
434
        // SAFETY: position < len() guaranteed by caller
UNCOV
435
        unsafe { self.get_unchecked(position) }.cloned()
×
436
    }
437
}
438

439
// ── Aligned ↔ Unaligned conversion ──────────────────────────────────
440

441
impl<D> TryIntoUnaligned for SparseIndex<D> {
442
    type Unaligned = SparseIndex<D, Unaligned<BitFieldVec<Box<[u64]>>>>;
UNCOV
443
    fn try_into_unaligned(
×
444
        self,
445
    ) -> Result<Self::Unaligned, crate::traits::UnalignedConversionError> {
446
        Ok(SparseIndex {
×
UNCOV
447
            ef: self.ef.try_into_unaligned()?,
×
UNCOV
448
            first_invalid_pos: self.first_invalid_pos,
×
449
        })
450
    }
451
}
452

453
impl<T, P: TryIntoUnaligned, V> TryIntoUnaligned for PartialArray<T, P, V> {
454
    type Unaligned = PartialArray<T, P::Unaligned, V>;
UNCOV
455
    fn try_into_unaligned(
×
456
        self,
457
    ) -> Result<Self::Unaligned, crate::traits::UnalignedConversionError> {
458
        Ok(PartialArray {
×
459
            index: self.index.try_into_unaligned()?,
×
UNCOV
460
            values: self.values,
×
UNCOV
461
            _marker: PhantomData,
×
462
        })
463
    }
464
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc