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

vigna / sux-rs / 24195165729

09 Apr 2026 02:17PM UTC coverage: 70.88% (-0.1%) from 71.015%
24195165729

push

github

vigna
Removed slow tests from testing

7867 of 11099 relevant lines covered (70.88%)

15127499.01 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](PartialArray) implementations.
8

9
use std::marker::PhantomData;
10

11
use mem_dbg::*;
12
use value_traits::slices::SliceByValue;
13

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

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

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

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

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

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

114
        // SAFETY: position < len
115
        unsafe {
116
            self.builder.set_unchecked(position, true);
22✔
117
        }
118
        self.values.push(value);
33✔
119
        self.min_next_pos = position + 1;
11✔
120
    }
121

122
    /// Builds the immutable dense partial array.
123
    pub fn build(self) -> PartialArray<T, Rank9<BitVec<Box<[u64]>>>> {
6✔
124
        let (bit_vec, values) = (self.builder, self.values);
18✔
125
        let rank9 = Rank9::new(bit_vec);
18✔
126
        let values = values.into_boxed_slice();
18✔
127

128
        PartialArray {
129
            index: rank9,
130
            values,
131
            _marker: PhantomData,
132
        }
133
    }
134
}
135

136
/// Creates a new builder for a sparse partial array of the given length.
137
///
138
/// A sparse partial array stores the non-empty positions of the array in an
139
/// [Elias-Fano](crate::dict::EliasFano) structure.
140
///
141
/// If your set of values is really dense, consider using a [dense partial
142
/// array](new_dense).
143
///
144
/// # Examples
145
///
146
/// ```rust
147
/// # use sux::array::partial_array;
148
/// let mut builder = partial_array::new_sparse(10, 3);
149
/// builder.set(1, "foo");
150
/// builder.set(2, "hello");
151
/// builder.set(7, "world");
152
///
153
/// let array = builder.build();
154
/// assert_eq!(array.get(1), Some(&"foo"));
155
/// assert_eq!(array.get(2), Some(&"hello"));
156
/// assert_eq!(array.get(3), None);
157
/// assert_eq!(array.get(7), Some(&"world"));
158
/// ```
159
///
160
/// Note that you must specify the number of values in advance.
161
pub fn new_sparse<T>(
9✔
162
    len: usize,
163
    num_values: usize,
164
) -> PartialArrayBuilder<T, EliasFanoBuilder<u64>> {
165
    PartialArrayBuilder {
166
        builder: EliasFanoBuilder::<u64>::new(num_values, len as u64),
36✔
167
        values: vec![],
9✔
168
        len,
169
        min_next_pos: 0,
170
    }
171
}
172

173
impl<T> PartialArrayBuilder<T, EliasFanoBuilder<u64>> {
174
    /// Sets a value at the given position.
175
    ///
176
    /// The provided position must be greater than the last position
177
    /// set.
178
    pub fn set(&mut self, position: usize, value: T) {
16✔
179
        if position < self.min_next_pos {
16✔
180
            panic!(
1✔
181
                "Positions must be set in increasing order: got {} after {}",
×
182
                position,
×
183
                self.min_next_pos - 1
1✔
184
            );
185
        }
186
        panic_if_out_of_bounds!(position, self.len);
45✔
187
        // SAFETY: conditions have been just checked
188
        unsafe { self.builder.push_unchecked(position as u64) };
42✔
189
        self.values.push(value);
42✔
190
        self.min_next_pos = position + 1;
14✔
191
    }
192

193
    /// Builds the immutable sparse partial array.
194
    pub fn build(self) -> PartialArray<T, SparseIndex<Box<[usize]>>> {
7✔
195
        let (builder, values) = (self.builder, self.values);
21✔
196
        let ef_dict = builder.build_with_dict();
21✔
197
        let values = values.into_boxed_slice();
21✔
198

199
        PartialArray {
200
            index: SparseIndex {
14✔
201
                ef: ef_dict,
202
                first_invalid_pos: self.min_next_pos,
203
            },
204
            values,
205
            _marker: PhantomData,
206
        }
207
    }
208
}
209

210
/// Extends the builder with an iterator of (position, value) pairs.
211
///
212
/// Position must be in strictly increasing order. The first returned
213
/// position must be greater than the last position set.
214
impl<T> Extend<(usize, T)> for PartialArrayBuilder<T, BitVec<Box<[u64]>>> {
215
    fn extend<I: IntoIterator<Item = (usize, T)>>(&mut self, iter: I) {
×
216
        for (pos, val) in iter {
×
217
            self.set(pos, val);
×
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, EliasFanoBuilder<u64>> {
227
    fn extend<I: IntoIterator<Item = (usize, T)>>(&mut self, iter: I) {
×
228
        for (pos, val) in iter {
×
229
            self.set(pos, val);
×
230
        }
231
    }
232
}
233

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

258
impl<T, P, V: AsRef<[T]>> PartialArray<T, P, V> {
259
    /// Returns the number of values stored in the array.
260
    #[inline(always)]
261
    pub fn num_values(&self) -> usize {
14✔
262
        self.values.as_ref().len()
28✔
263
    }
264
}
265

266
impl<T, V: AsRef<[T]>> PartialArray<T, DenseIndex, V> {
267
    /// Returns the total length of the array.
268
    ///
269
    /// This is the length that was specified when creating the builder,
270
    /// not the number of values actually stored.
271
    #[inline(always)]
272
    pub fn len(&self) -> usize {
1,044✔
273
        self.index.len()
2,088✔
274
    }
275

276
    /// Returns `true` if the array has no elements.
277
    #[inline(always)]
278
    pub fn is_empty(&self) -> bool {
1✔
279
        self.len() == 0
1✔
280
    }
281

282
    /// Gets a reference to the value at the given position.
283
    ///
284
    /// Returns `Some(&value)` if a value is present at the position,
285
    /// or `None` if no value was stored there.
286
    ///
287
    /// # Examples
288
    ///
289
    /// ```rust
290
    /// # use sux::array::partial_array;
291
    /// let mut builder = partial_array::new_dense(10);
292
    /// builder.set(5, 42);
293
    ///
294
    /// let array = builder.build();
295
    /// assert_eq!(array.get(5), Some(&42));
296
    /// assert_eq!(array.get(6), None);
297
    /// ```
298
    pub fn get(&self, position: usize) -> Option<&T> {
1,041✔
299
        panic_if_out_of_bounds!(position, self.len());
2,084✔
300

301
        // SAFETY: we just checked
302
        unsafe { self.get_unchecked(position) }
3,120✔
303
    }
304

305
    /// # Safety
306
    ///
307
    /// position < len()
308
    pub unsafe fn get_unchecked(&self, position: usize) -> Option<&T> {
1,040✔
309
        // Check if there's a value at this position
310
        // SAFETY: position < len() guaranteed by caller
311
        if !unsafe { self.index.get_unchecked(position) } {
2,080✔
312
            return None;
1,027✔
313
        }
314

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

319
        // SAFETY: necessarily value_index < num_values().
320
        Some(unsafe { self.values.as_ref().get_unchecked(value_index) })
26✔
321
    }
322
}
323

324
impl<T, D: Backend<Word = usize> + AsRef<[usize]>, L: SliceByValue<Value = u64>, V: AsRef<[T]>>
325
    PartialArray<T, SparseIndex<D, L>, V>
326
where
327
    for<'b> &'b L: crate::traits::IntoUncheckedIterator<Item = u64>,
328
{
329
    /// Returns the total length of the array.
330
    ///
331
    /// This is the length that was specified when creating the builder,
332
    /// not the number of values actually stored.
333
    #[inline(always)]
334
    pub fn len(&self) -> usize {
1,063✔
335
        self.index.ef.upper_bound() as usize
1,063✔
336
    }
337

338
    /// Returns `true` if the array has no elements.
339
    #[inline(always)]
340
    pub fn is_empty(&self) -> bool {
1✔
341
        self.index.ef.upper_bound() == 0
1✔
342
    }
343

344
    /// Gets a reference to the value at the given position.
345
    ///
346
    /// Returns `Some(&value)` if a value is present at the position,
347
    /// or `None` if no value was stored there.
348
    ///
349
    /// # Examples
350
    ///
351
    /// ```rust
352
    /// # use sux::array::partial_array;
353
    /// let mut builder = partial_array::new_sparse(10, 1);
354
    /// builder.set(5, 42);
355
    ///
356
    /// let array = builder.build();
357
    /// assert_eq!(array.get(5), Some(&42));
358
    /// assert_eq!(array.get(6), None);
359
    /// ```
360
    pub fn get(&self, position: usize) -> Option<&T> {
1,061✔
361
        panic_if_out_of_bounds!(position, self.len());
2,124✔
362

363
        // SAFETY: we just checked
364
        unsafe { self.get_unchecked(position) }
3,180✔
365
    }
366

367
    /// # Safety
368
    ///
369
    /// position < len()
370
    pub unsafe fn get_unchecked(&self, position: usize) -> Option<&T> {
1,060✔
371
        if position >= self.index.first_invalid_pos {
1,060✔
372
            return None;
505✔
373
        }
374
        // Check if there's a value at this position
375
        // SAFETY: position <= last set position
376
        let (index, pos) = unsafe { self.index.ef.succ_unchecked::<false>(position as u64) };
2,220✔
377

378
        if pos != position as u64 {
555✔
379
            None
536✔
380
        } else {
381
            // SAFETY: necessarily value_index < num values.
382
            Some(unsafe { self.values.as_ref().get_unchecked(index) })
38✔
383
        }
384
    }
385
}
386

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

392
    fn len(&self) -> usize {
×
393
        self.len()
×
394
    }
395

396
    unsafe fn get_value_unchecked(&self, position: usize) -> Self::Value {
×
397
        // SAFETY: position < len() guaranteed by caller
398
        unsafe { self.get_unchecked(position) }.cloned()
×
399
    }
400
}
401

402
/// Returns an option even when using `get_value_unchecked` because it should be safe to call
403
/// whenever `position < len()`.
404
impl<
405
    T: Clone,
406
    D: Backend<Word = usize> + AsRef<[usize]>,
407
    L: SliceByValue<Value = u64>,
408
    V: AsRef<[T]>,
409
> SliceByValue for PartialArray<T, SparseIndex<D, L>, V>
410
where
411
    for<'b> &'b L: crate::traits::IntoUncheckedIterator<Item = u64>,
412
{
413
    type Value = Option<T>;
414

415
    fn len(&self) -> usize {
×
416
        self.len()
×
417
    }
418

419
    unsafe fn get_value_unchecked(&self, position: usize) -> Self::Value {
×
420
        // SAFETY: position < len() guaranteed by caller
421
        unsafe { self.get_unchecked(position) }.cloned()
×
422
    }
423
}
424

425
// ── Aligned ↔ Unaligned conversion ──────────────────────────────────
426

427
impl<D> TryIntoUnaligned for SparseIndex<D> {
428
    type Unaligned = SparseIndex<D, Unaligned<BitFieldVec<Box<[u64]>>>>;
429
    fn try_into_unaligned(
×
430
        self,
431
    ) -> Result<Self::Unaligned, crate::traits::UnalignedConversionError> {
432
        Ok(SparseIndex {
×
433
            ef: self.ef.try_into_unaligned()?,
×
434
            first_invalid_pos: self.first_invalid_pos,
×
435
        })
436
    }
437
}
438

439
impl<T, P: TryIntoUnaligned, V> TryIntoUnaligned for PartialArray<T, P, V> {
440
    type Unaligned = PartialArray<T, P::Unaligned, V>;
441
    fn try_into_unaligned(
×
442
        self,
443
    ) -> Result<Self::Unaligned, crate::traits::UnalignedConversionError> {
444
        Ok(PartialArray {
×
445
            index: self.index.try_into_unaligned()?,
×
446
            values: self.values,
×
447
            _marker: PhantomData,
×
448
        })
449
    }
450
}
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