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

vigna / sux-rs / 23354375031

20 Mar 2026 05:19PM UTC coverage: 76.49% (-1.4%) from 77.85%
23354375031

push

github

vigna
Downgraded Criterion to 0.7.0

5453 of 7129 relevant lines covered (76.49%)

109991459.88 hits per line

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

76.92
/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::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::{BitVecOps, BitVecOpsMut};
21
use crate::traits::{RankUnchecked, SuccUnchecked};
22

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

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

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

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

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

112
        // SAFETY: position < len
113
        unsafe {
114
            self.builder.set_unchecked(position, true);
10✔
115
        }
116
        self.values.push(value);
15✔
117
        self.min_next_pos = position + 1;
5✔
118
    }
119

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

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

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

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

188
    /// Builds the immutable sparse partial array.
189
    pub fn build(self) -> PartialArray<T, SparseIndex<Box<[usize]>>> {
5✔
190
        let (builder, values) = (self.builder, self.values);
15✔
191
        let ef_dict = builder.build_with_dict();
15✔
192
        let values = values.into_boxed_slice();
15✔
193

194
        PartialArray {
195
            index: SparseIndex {
10✔
196
                ef: ef_dict,
197
                first_invalid_pos: self.min_next_pos,
198
            },
199
            values,
200
            _marker: PhantomData,
201
        }
202
    }
203
}
204

205
/// Extends the builder with an iterator of (position, value) pairs.
206
///
207
/// Position must be in strictly increasing order. The first returned
208
/// position must be greater than the last position set.
209
impl<T> Extend<(usize, T)> for PartialArrayBuilder<T, BitVec<Box<[u64]>>> {
210
    fn extend<I: IntoIterator<Item = (usize, T)>>(&mut self, iter: I) {
×
211
        for (pos, val) in iter {
×
212
            self.set(pos, val);
×
213
        }
214
    }
215
}
216

217
/// Extends the builder with an iterator of (position, value) pairs.
218
///
219
/// Position must be in strictly increasing order. The first returned
220
/// position must be greater than the last position set.
221
impl<T> Extend<(usize, T)> for PartialArrayBuilder<T, EliasFanoBuilder> {
222
    fn extend<I: IntoIterator<Item = (usize, T)>>(&mut self, iter: I) {
×
223
        for (pos, val) in iter {
×
224
            self.set(pos, val);
×
225
        }
226
    }
227
}
228

229
/// An immutable partial array that supports efficient queries
230
/// in compacted storage.
231
///
232
/// This structure stores a *partial array*—an array in which only
233
/// some positions contain values. There is a [dense](new_dense)
234
/// and a [sparse](new_sparse) implementation with different
235
/// space/time trade-offs.
236
///
237
/// For convenience, this structure implements [`SliceByValue`].
238
///
239
/// See [`PartialArrayBuilder`] for details on how to create a partial array.
240
#[derive(Debug, Clone, MemDbg, MemSize)]
241
#[cfg_attr(feature = "epserde", derive(epserde::Epserde))]
242
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
243
pub struct PartialArray<T, P, V: AsRef<[T]> = Box<[T]>> {
244
    index: P,
245
    values: V,
246
    _marker: PhantomData<T>,
247
}
248

249
impl<T, P, V: AsRef<[T]>> PartialArray<T, P, V> {
250
    /// Returns the number of values stored in the array.
251
    #[inline(always)]
252
    pub fn num_values(&self) -> usize {
8✔
253
        self.values.as_ref().len()
16✔
254
    }
255
}
256

257
impl<T, V: AsRef<[T]>> PartialArray<T, DenseIndex, V> {
258
    /// Returns the total length of the array.
259
    ///
260
    /// This is the length that was specified when creating the builder,
261
    /// not the number of values actually stored.
262
    #[inline(always)]
263
    pub fn len(&self) -> usize {
1,014✔
264
        self.index.len()
2,028✔
265
    }
266

267
    /// Returns true if the array length is 0.
268
    #[inline(always)]
269
    pub fn is_empty(&self) -> bool {
1✔
270
        self.len() == 0
1✔
271
    }
272

273
    /// Gets a reference to the value at the given position.
274
    ///
275
    /// Returns `Some(&value)` if a value is present at the position,
276
    /// or `None` if no value was stored there.
277
    ///
278
    /// # Examples
279
    ///
280
    /// ```rust
281
    /// # use sux::array::partial_array;
282
    /// let mut builder = partial_array::new_dense(10);
283
    /// builder.set(5, 42);
284
    ///
285
    /// let array = builder.build();
286
    /// assert_eq!(array.get(5), Some(&42));
287
    /// assert_eq!(array.get(6), None);
288
    /// ```
289
    pub fn get(&self, position: usize) -> Option<&T> {
1,011✔
290
        panic_if_out_of_bounds!(position, self.len());
2,024✔
291

292
        // SAFETY: we just checked
293
        unsafe { self.get_unchecked(position) }
3,030✔
294
    }
295

296
    /// # Safety
297
    ///
298
    /// position < len()
299
    pub unsafe fn get_unchecked(&self, position: usize) -> Option<&T> {
1,010✔
300
        // Check if there's a value at this position
301
        // SAFETY: position < len() guaranteed by caller
302
        if !unsafe { self.index.get_unchecked(position) } {
2,020✔
303
            return None;
1,006✔
304
        }
305

306
        // Use ranking to find the index in the values array
307
        // SAFETY: position < len() guaranteed by caller
308
        let value_index = unsafe { self.index.rank_unchecked(position) };
16✔
309

310
        // SAFETY: necessarily value_index < num_values().
311
        Some(unsafe { self.values.as_ref().get_unchecked(value_index) })
8✔
312
    }
313
}
314

315
impl<T, D: Backend<Word = usize> + AsRef<[usize]>, V: AsRef<[T]>>
316
    PartialArray<T, SparseIndex<D>, V>
317
{
318
    /// Returns the total length of the array.
319
    ///
320
    /// This is the length that was specified when creating the builder,
321
    /// not the number of values actually stored.
322
    #[inline(always)]
323
    pub fn len(&self) -> usize {
1,033✔
324
        self.index.ef.upper_bound() as usize
1,033✔
325
    }
326

327
    /// Returns true if the array len is 0.
328
    #[inline(always)]
329
    pub fn is_empty(&self) -> bool {
1✔
330
        self.index.ef.upper_bound() == 0
1✔
331
    }
332

333
    /// Gets a reference to the value at the given position.
334
    ///
335
    /// Returns `Some(&value)` if a value is present at the position,
336
    /// or `None` if no value was stored there.
337
    ///
338
    /// # Examples
339
    ///
340
    /// ```rust
341
    /// # use sux::array::partial_array;
342
    /// let mut builder = partial_array::new_sparse(10, 1);
343
    /// builder.set(5, 42);
344
    ///
345
    /// let array = builder.build();
346
    /// assert_eq!(array.get(5), Some(&42));
347
    /// assert_eq!(array.get(6), None);
348
    /// ```
349
    pub fn get(&self, position: usize) -> Option<&T> {
1,031✔
350
        panic_if_out_of_bounds!(position, self.len());
2,064✔
351

352
        // SAFETY: we just checked
353
        unsafe { self.get_unchecked(position) }
3,090✔
354
    }
355

356
    /// # Safety
357
    ///
358
    /// position < len()
359
    pub unsafe fn get_unchecked(&self, position: usize) -> Option<&T> {
1,030✔
360
        if position >= self.index.first_invalid_pos {
1,030✔
361
            return None;
505✔
362
        }
363
        // Check if there's a value at this position
364
        // SAFETY: position <= last set position
365
        let (index, pos) = unsafe { self.index.ef.succ_unchecked::<false>(position as u64) };
2,100✔
366

367
        if pos != position as u64 {
525✔
368
            None
515✔
369
        } else {
370
            // SAFETY: necessarily value_index < num values.
371
            Some(unsafe { self.values.as_ref().get_unchecked(index) })
20✔
372
        }
373
    }
374
}
375

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

381
    fn len(&self) -> usize {
×
382
        self.len()
×
383
    }
384

385
    unsafe fn get_value_unchecked(&self, position: usize) -> Self::Value {
×
386
        // SAFETY: position < len() guaranteed by caller
387
        unsafe { self.get_unchecked(position) }.cloned()
×
388
    }
389
}
390

391
/// Returns an option even when using `get_value_unchecked` because it should be safe to call
392
/// whenever `position < len()`.
393
impl<T: Clone, D: Backend<Word = usize> + AsRef<[usize]>, V: AsRef<[T]>> SliceByValue
394
    for PartialArray<T, SparseIndex<D>, V>
395
{
396
    type Value = Option<T>;
397

398
    fn len(&self) -> usize {
×
399
        self.len()
×
400
    }
401

402
    unsafe fn get_value_unchecked(&self, position: usize) -> Self::Value {
×
403
        // SAFETY: position < len() guaranteed by caller
404
        unsafe { self.get_unchecked(position) }.cloned()
×
405
    }
406
}
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