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

vigna / webgraph-rs / 24296210049

12 Apr 2026 01:49AM UTC coverage: 66.827% (-0.05%) from 66.877%
24296210049

push

github

vigna
Clippy hapy

6674 of 9987 relevant lines covered (66.83%)

47792805.58 hits per line

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

78.43
/webgraph/src/traits/split.rs
1
/*
2
 * SPDX-FileCopyrightText: 2023 Tommaso Fontana
3
 * SPDX-FileCopyrightText: 2023 Sebastiano Vigna
4
 *
5
 * SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
6
 */
7

8
//! Traits and basic implementations to support parallel completion by splitting
9
//! the [iterator] of a labeling into multiple iterators.
10
//!
11
//! [iterator]: SequentialLabeling::Lender
12

13
use std::rc::Rc;
14

15
use impl_tools::autoimpl;
16

17
use super::{labels::SequentialLabeling, lenders::NodeLabelsLender};
18

19
/// A trait providing methods to split the labeling [iterator] into multiple
20
/// thread-safe parts.
21
///
22
/// The main method is [`split_iter_at`], which takes a sequence of cutpoints
23
/// and splits the iteration at those points. Each cutpoint is a node id; the
24
/// sequence must be non-decreasing and contain at least two elements.
25
///
26
/// The convenience method [`split_iter`] provides a default implementation
27
/// that splits the iteration into `n` approximately equal parts. It is
28
/// implemented in terms of [`split_iter_at`], so implementors only need to
29
/// provide the latter.
30
///
31
/// Note that the parts are required to be [`Send`] and [`Sync`], so that they
32
/// can be safely shared among threads.
33
///
34
/// Due to some limitations of the current Rust type system, we cannot provide
35
/// blanket implementations for this trait. However, we provide ready-made
36
/// implementations for the [sequential] and [random-access] cases. To use
37
/// them, you must implement the trait by specifying the associated types
38
/// `SplitLender` and `IntoIterator`, and then just return a [`seq::Iter`] or
39
/// [`ra::Iter`] structure from [`split_iter_at`].
40
///
41
/// [iterator]: SequentialLabeling::Lender
42
/// [`split_iter_at`]: SplitLabeling::split_iter_at
43
/// [`split_iter`]: SplitLabeling::split_iter
44
/// [sequential]: seq
45
/// [random-access]: ra
46
#[autoimpl(for<S: trait + ?Sized> &S, &mut S, Rc<S>)]
47
pub trait SplitLabeling: SequentialLabeling {
48
    type SplitLender<'a>: for<'next> NodeLabelsLender<'next, Label = <Self as SequentialLabeling>::Label>
49
        + Send
50
        + Sync
51
    where
52
        Self: 'a;
53

54
    type IntoIterator<'a>: IntoIterator<Item = Self::SplitLender<'a>>
55
    where
56
        Self: 'a;
57

58
    /// Splits the labeling iterator at the given cutpoints.
59
    ///
60
    /// The cutpoints are a non-decreasing sequence of node ids with at least
61
    /// two elements. They define `n` − 1 segments, where `n` is the number of
62
    /// cutpoints, and the `i`-th segment covers nodes in [`cutpoints[i]` . . `cutpoints[i + 1]`).
63
    fn split_iter_at(&self, cutpoints: impl IntoIterator<Item = usize>) -> Self::IntoIterator<'_>;
64

65
    /// Splits the labeling iterator into `n` approximately equal parts.
66
    ///
67
    /// This is a convenience method implemented in terms of
68
    /// [`split_iter_at`].
69
    ///
70
    /// [`split_iter_at`]: SplitLabeling::split_iter_at
71
    fn split_iter(&self, n: usize) -> Self::IntoIterator<'_> {
622,130✔
72
        let step = self.num_nodes().div_ceil(n);
3,110,650✔
73
        let num_nodes = self.num_nodes();
1,866,390✔
74
        self.split_iter_at((0..n + 1).map(move |i| (i * step).min(num_nodes)))
11,820,668✔
75
    }
76
}
77

78
/// Ready-made implementation for the sequential case.
79
///
80
/// This implementation walks through the iterator of a labeling and clones it
81
/// at the cutpoints, using [`advance_by`] to skip nodes between cutpoints. It
82
/// is designed for labelings whose [`iter_from`] is not more efficient than
83
/// sequential iteration (e.g., compressed graphs without an index); if
84
/// `iter_from` can seek efficiently, use [`ra::Iter`] instead.
85
///
86
/// To use it, you have to implement the trait by specifying the associated
87
/// types `SplitLender` and `IntoIterator` using the [`seq::Lender`] and
88
/// [`seq::IntoIterator`] type aliases, and then return a [`seq::Iter`]
89
/// structure.
90
///
91
/// # Examples
92
///
93
/// The code for [`BvGraphSeq`] is:
94
/// ```ignore
95
/// impl<F: SequentialDecoderFactory> SplitLabeling for BvGraphSeq<F>
96
/// where
97
///     for<'a> <F as SequentialDecoderFactory>::Decoder<'a>: Clone + Send + Sync,
98
/// {
99
///     type SplitLender<'a> = split::seq::Lender<'a, BvGraphSeq<F>> where Self: 'a;
100
///     type IntoIterator<'a> = split::seq::IntoIterator<'a, BvGraphSeq<F>> where Self: 'a;
101
///
102
///     fn split_iter_at(&self, cutpoints: impl IntoIterator<Item = usize>) -> Self::IntoIterator<'_> {
103
///         split::seq::Iter::new(self.iter(), cutpoints)
104
///     }
105
/// }
106
/// ```
107
///
108
/// [`advance_by`]: lender::Lender::advance_by
109
/// [`iter_from`]: SequentialLabeling::iter_from
110
/// [`BvGraphSeq`]: crate::graphs::bvgraph::sequential::BvGraphSeq
111
pub mod seq {
112
    use crate::prelude::SequentialLabeling;
113

114
    /// An iterator over segments of a sequential labeling defined by cutpoints.
115
    pub struct Iter<L> {
116
        lender: L,
117
        cutpoints: Vec<usize>,
118
        i: usize,
119
    }
120

121
    impl<L: lender::Lender> Iter<L> {
122
        /// Creates a new iterator from a lender and a sequence of cutpoints.
123
        ///
124
        /// The cutpoints must be a non-decreasing sequence with at least 2
125
        /// elements, and the last cutpoint must be at most the number of nodes
126
        /// in the labeling.
127
        pub fn new(lender: L, cutpoints: impl core::iter::IntoIterator<Item = usize>) -> Self {
38✔
128
            let cutpoints: Vec<usize> = cutpoints.into_iter().collect();
190✔
129
            assert!(
38✔
130
                cutpoints.len() >= 2,
38✔
131
                "cutpoints must have at least 2 elements"
×
132
            );
133
            assert!(
38✔
134
                cutpoints.windows(2).all(|w| w[0] <= w[1]),
456✔
135
                "cutpoints must be non-decreasing"
×
136
            );
137
            Self {
138
                lender,
139
                cutpoints,
140
                i: 0,
141
            }
142
        }
143
    }
144

145
    impl<L: lender::Lender + Clone> Iterator for Iter<L> {
146
        type Item = lender::Take<L>;
147

148
        fn next(&mut self) -> Option<Self::Item> {
228✔
149
            if self.i + 1 >= self.cutpoints.len() {
456✔
150
                return None;
38✔
151
            }
152
            if self.i > 0 {
190✔
153
                let advance = self.cutpoints[self.i] - self.cutpoints[self.i - 1];
456✔
154
                self.lender.advance_by(advance).ok()?;
608✔
155
            }
156
            let len = self.cutpoints[self.i + 1] - self.cutpoints[self.i];
570✔
157
            self.i += 1;
190✔
158
            Some(self.lender.clone().take(len))
570✔
159
        }
160

161
        fn size_hint(&self) -> (usize, Option<usize>) {
20✔
162
            let remaining = self.cutpoints.len() - 1 - self.i;
40✔
163
            (remaining, Some(remaining))
20✔
164
        }
165

166
        fn count(self) -> usize {
×
167
            self.cutpoints.len() - 1 - self.i
×
168
        }
169
    }
170

171
    impl<L: lender::Lender + Clone> ExactSizeIterator for Iter<L> {}
172
    impl<L: lender::Lender + Clone> core::iter::FusedIterator for Iter<L> {}
173

174
    pub type Lender<'a, S> = lender::Take<<S as SequentialLabeling>::Lender<'a>>;
175
    pub type IntoIterator<'a, S> = Iter<<S as SequentialLabeling>::Lender<'a>>;
176
}
177

178
/// Ready-made implementation for the random-access case.
179
///
180
/// This implementation calls [`iter_from`] at each cutpoint, seeking directly
181
/// to the desired position. It is designed for labelings with an efficient
182
/// `iter_from` (e.g., compressed graphs with an index such as an `.ef` file).
183
/// If `iter_from` is no faster than sequential iteration (e.g.,
184
/// [`ArcListGraph`]), use [`seq::Iter`] instead.
185
///
186
/// The bound is [`RandomAccessLabeling`] rather than [`SequentialLabeling`]
187
/// even though only `iter_from` is used: the stronger bound ensures that
188
/// `iter_from` is efficient, preventing silent quadratic slowdowns.
189
///
190
/// To use it, you have to implement the trait by specifying the associated
191
/// types `SplitLender` and `IntoIterator` using the [`ra::Lender`] and
192
/// [`ra::IntoIterator`] type aliases, and then return a [`ra::Iter`]
193
/// structure.
194
///
195
/// # Examples
196
///
197
/// The code for [`BvGraph`] is
198
/// ```ignore
199
/// impl<F: RandomAccessDecoderFactory> SplitLabeling for BvGraph<F>
200
/// where
201
///     for<'a> <F as RandomAccessDecoderFactory>::Decoder<'a>: Send + Sync,
202
/// {
203
///     type SplitLender<'a> = split::ra::Lender<'a, BvGraph<F>> where Self: 'a;
204
///     type IntoIterator<'a> = split::ra::IntoIterator<'a, BvGraph<F>> where Self: 'a;
205
///
206
///     fn split_iter_at(&self, cutpoints: impl IntoIterator<Item = usize>) -> Self::IntoIterator<'_> {
207
///         split::ra::Iter::new(self, cutpoints)
208
///     }
209
/// }
210
/// ```
211
///
212
/// [`iter_from`]: SequentialLabeling::iter_from
213
/// [`ArcListGraph`]: crate::graphs::arc_list_graph::ArcListGraph
214
/// [`RandomAccessLabeling`]: crate::traits::RandomAccessLabeling
215
/// [`BvGraph`]: crate::graphs::bvgraph::random_access::BvGraph
216
pub mod ra {
217
    use crate::prelude::{RandomAccessLabeling, SequentialLabeling};
218

219
    /// An iterator over segments of a random-access labeling defined by
220
    /// cutpoints.
221
    pub struct Iter<'a, R: RandomAccessLabeling> {
222
        labeling: &'a R,
223
        cutpoints: Vec<usize>,
224
        i: usize,
225
    }
226

227
    impl<'a, R: RandomAccessLabeling> Iter<'a, R> {
228
        /// Creates a new iterator from a labeling and a sequence of cutpoints.
229
        ///
230
        /// The cutpoints must be a non-decreasing sequence with at least 2
231
        /// elements, and the last cutpoint must be at most [`num_nodes()`].
232
        ///
233
        /// [`num_nodes()`]: SequentialLabeling::num_nodes
234
        pub fn new(
622,112✔
235
            labeling: &'a R,
236
            cutpoints: impl core::iter::IntoIterator<Item = usize>,
237
        ) -> Self {
238
            let cutpoints: Vec<usize> = cutpoints.into_iter().collect();
3,110,560✔
239
            assert!(
622,112✔
240
                cutpoints.len() >= 2,
622,112✔
241
                "cutpoints must have at least 2 elements"
×
242
            );
243
            assert!(
622,112✔
244
                cutpoints.windows(2).all(|w| w[0] <= w[1]),
6,221,162✔
245
                "cutpoints must be non-decreasing"
×
246
            );
247
            assert!(
622,112✔
248
                *cutpoints.last().unwrap() <= labeling.num_nodes(),
1,244,224✔
249
                "last cutpoint ({}) must be <= num_nodes ({})",
×
250
                cutpoints.last().unwrap(),
×
251
                labeling.num_nodes()
×
252
            );
253
            Self {
254
                labeling,
255
                cutpoints,
256
                i: 0,
257
            }
258
        }
259
    }
260

261
    impl<'a, R: RandomAccessLabeling> Iterator for Iter<'a, R> {
262
        type Item = Lender<'a, R>;
263

264
        fn next(&mut self) -> Option<Self::Item> {
3,110,581✔
265
            use lender::Lender;
266

267
            if self.i + 1 >= self.cutpoints.len() {
6,221,162✔
268
                return None;
622,112✔
269
            }
270
            let start = self.cutpoints[self.i];
4,976,938✔
271
            let end = self.cutpoints[self.i + 1];
4,976,938✔
272
            self.i += 1;
2,488,469✔
273
            Some(self.labeling.iter_from(start).take(end - start))
12,442,345✔
274
        }
275

276
        fn size_hint(&self) -> (usize, Option<usize>) {
24✔
277
            let remaining = self.cutpoints.len() - 1 - self.i;
48✔
278
            (remaining, Some(remaining))
24✔
279
        }
280

281
        fn count(self) -> usize {
×
282
            self.cutpoints.len() - 1 - self.i
×
283
        }
284
    }
285

286
    impl<R: RandomAccessLabeling> ExactSizeIterator for Iter<'_, R> {}
287
    impl<R: RandomAccessLabeling> core::iter::FusedIterator for Iter<'_, R> {}
288

289
    pub type Lender<'a, R> = lender::Take<<R as SequentialLabeling>::Lender<'a>>;
290
    pub type IntoIterator<'a, R> = Iter<'a, R>;
291
}
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