Skip to content

Commit b6239d0

Browse files
Add a class for ranges
These are pairs of begin and end iterators for which we implement filter, map and concat methods. They only manipulate iterators so this avoids having to create intermediate heavy data-structures
1 parent 8a75ce4 commit b6239d0

File tree

1 file changed

+363
-0
lines changed

1 file changed

+363
-0
lines changed

src/util/range.h

Lines changed: 363 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,363 @@
1+
/*******************************************************************\
2+
3+
Module: Range
4+
5+
Author: Romain Brenguier, [email protected]
6+
7+
\*******************************************************************/
8+
9+
/// \file
10+
/// Ranges: pair of begin and end iterators, which can be initialized from
11+
/// containers, provide useful methods such as map, filter and concat which only
12+
/// manipulate iterators, and can be used with ranged-for.
13+
14+
#ifndef CPROVER_UTIL_RANGE_H
15+
#define CPROVER_UTIL_RANGE_H
16+
17+
#include <functional>
18+
19+
#include <util/invariant.h>
20+
#include <util/make_unique.h>
21+
22+
/// Iterator which applies some given function \c f after each increment and
23+
/// returns its result on dereference.
24+
template <typename iteratort, typename outputt>
25+
class map_iteratort
26+
{
27+
public:
28+
using difference_type = void; // Requiered by STL
29+
using value_type = outputt;
30+
using pointer = const outputt *;
31+
using reference = const outputt &;
32+
using iterator_category = std::forward_iterator_tag;
33+
34+
bool operator==(const map_iteratort &other) const
35+
{
36+
return underlying == other.underlying;
37+
}
38+
39+
bool operator!=(const map_iteratort &other) const
40+
{
41+
return !(this->underlying == other.underlying);
42+
}
43+
44+
/// Preincrement operator
45+
map_iteratort &operator++()
46+
{
47+
PRECONDITION(underlying != underlying_end);
48+
++underlying;
49+
if(underlying != underlying_end)
50+
current = util_make_unique<outputt>((*f)(*underlying));
51+
return *this;
52+
}
53+
54+
/// Postincrement operator
55+
const map_iteratort operator++(int)
56+
{
57+
map_iteratort tmp(*this);
58+
this->operator++();
59+
return tmp;
60+
}
61+
62+
const value_type &operator*() const
63+
{
64+
return *current.get();
65+
}
66+
67+
const value_type *operator->() const
68+
{
69+
return &(*current.get());
70+
}
71+
72+
explicit map_iteratort(
73+
iteratort underlying,
74+
iteratort underlying_end,
75+
std::shared_ptr<
76+
std::function<value_type(const typename iteratort::value_type &)>> f)
77+
: underlying(std::move(underlying)),
78+
underlying_end(std::move(underlying_end)),
79+
f(std::move(f))
80+
{
81+
if(this->underlying != this->underlying_end)
82+
current = util_make_unique<outputt>((*this->f)(*this->underlying));
83+
}
84+
85+
map_iteratort(const map_iteratort &other)
86+
: underlying(other.underlying),
87+
underlying_end(other.underlying_end),
88+
f(other.f)
89+
{
90+
if(other.current != nullptr)
91+
current = util_make_unique<outputt>(*other.current.get());
92+
}
93+
94+
private:
95+
iteratort underlying;
96+
iteratort underlying_end;
97+
std::shared_ptr<
98+
std::function<value_type(const typename iteratort::value_type &)>>
99+
f;
100+
101+
/// Stores the result of \c f at the current position of the iterator.
102+
/// Equals nullptr if the iterator reached \c underlying_end.
103+
std::unique_ptr<outputt> current = nullptr;
104+
};
105+
106+
/// Iterator which only stops at elements for which some given function \c f is
107+
/// true.
108+
template <typename iteratort>
109+
class filter_iteratort
110+
{
111+
public:
112+
using difference_type = void; // Required by STL
113+
using value_type = typename iteratort::value_type;
114+
using pointer = const value_type *;
115+
using reference = const value_type &;
116+
using iterator_category = std::forward_iterator_tag;
117+
118+
bool operator==(const filter_iteratort &other) const
119+
{
120+
return underlying == other.underlying;
121+
}
122+
123+
bool operator!=(const filter_iteratort &other) const
124+
{
125+
return !(this->underlying == other.underlying);
126+
}
127+
128+
/// Preincrement operator
129+
filter_iteratort &operator++()
130+
{
131+
++underlying;
132+
point_to_first_to_peek();
133+
return *this;
134+
}
135+
136+
/// Postincrement operator
137+
const filter_iteratort operator++(int)
138+
{
139+
filter_iteratort tmp(*this);
140+
this->operator++();
141+
return tmp;
142+
}
143+
144+
const value_type &operator*() const
145+
{
146+
return *underlying;
147+
}
148+
149+
const value_type *operator->() const
150+
{
151+
return &(*underlying);
152+
}
153+
154+
/// Filter between \p underlying and \p end using \p f.
155+
/// If \c f is not true for any element between \p underlying and \p end, the
156+
/// constructed iterator is equal to the one which would have been constructed
157+
/// using
158+
/// ```
159+
/// filter_iteratort(f, end, end)
160+
/// ```
161+
filter_iteratort(
162+
std::shared_ptr<std::function<bool(const value_type &)>> f,
163+
iteratort underlying,
164+
iteratort end)
165+
: underlying(std::move(underlying)),
166+
underlying_end(std::move(end)),
167+
f(std::move(f))
168+
{
169+
point_to_first_to_peek();
170+
}
171+
172+
private:
173+
iteratort underlying;
174+
const iteratort underlying_end;
175+
std::shared_ptr<std::function<bool(const value_type &)>> f;
176+
177+
/// Ensure that the underlying iterator is always positioned on an element
178+
/// for which `f` is true.
179+
/// This does nothing if \c f is satisfied at the current position.
180+
/// If \c f is not true for any element between underlying and underlying_end
181+
/// underlying will be incremented until underlying_end is reached.
182+
void point_to_first_to_peek()
183+
{
184+
while(underlying != underlying_end && !(*f)(*underlying))
185+
++underlying;
186+
}
187+
};
188+
189+
/// On increment, increments a first iterator and when the corresponding end
190+
/// iterator is reached, starts to increment a second one.
191+
/// Dereference corresponds to dereference on the first iterator if the end is
192+
/// not reached yet, and on the second one otherwise.
193+
template <typename first_iteratort, typename second_iteratort>
194+
struct concat_iteratort
195+
{
196+
public:
197+
using difference_type = void; // Requiered by STL
198+
using value_type = typename first_iteratort::value_type;
199+
using pointer = const value_type *;
200+
using reference = const value_type &;
201+
using iterator_category = std::forward_iterator_tag;
202+
203+
static_assert(
204+
std::is_same<value_type, typename first_iteratort::value_type>::value,
205+
"Concatenated iterators should have the same value type");
206+
207+
bool operator==(const concat_iteratort &other) const
208+
{
209+
return first_begin == other.first_begin && first_end == other.first_end &&
210+
second_begin == other.second_begin;
211+
}
212+
213+
bool operator!=(const concat_iteratort &other) const
214+
{
215+
return !(*this == other);
216+
}
217+
218+
/// Preincrement operator
219+
concat_iteratort &operator++()
220+
{
221+
if(first_begin == first_end)
222+
++second_begin;
223+
else
224+
++first_begin;
225+
return *this;
226+
}
227+
228+
/// Postincrement operator
229+
const concat_iteratort operator++(int)
230+
{
231+
concat_iteratort tmp(first_begin, first_end, second_begin);
232+
this->operator++();
233+
return tmp;
234+
}
235+
236+
const value_type &operator*() const
237+
{
238+
if(first_begin == first_end)
239+
return *second_begin;
240+
return *first_begin;
241+
}
242+
243+
const value_type *operator->() const
244+
{
245+
if(first_begin == first_end)
246+
return &(*second_begin);
247+
return &(*first_begin);
248+
}
249+
250+
concat_iteratort(
251+
first_iteratort first_begin,
252+
first_iteratort first_end,
253+
second_iteratort second_begin)
254+
: first_begin(std::move(first_begin)),
255+
first_end(std::move(first_end)),
256+
second_begin(std::move(second_begin))
257+
{
258+
}
259+
260+
private:
261+
first_iteratort first_begin;
262+
first_iteratort first_end;
263+
second_iteratort second_begin;
264+
};
265+
266+
/// A range is a pair of a begin and an end iterators.
267+
/// The class provides useful methods such as map, filter and concat which only
268+
/// manipulate iterators and thus don't have to create instances of heavy data
269+
/// structures and avoid copies.
270+
/// For instance, to iterate over two vectors, instead of writing
271+
///
272+
/// std::vector new_vector;
273+
/// std::copy(v1.begin(), v1.end(), std::back_inserter(new_vector));
274+
/// std::copy(v2.begin(), v2.end(), std::back_inserter(new_vector));
275+
/// for(const auto &a : new_vector) {...}
276+
///
277+
/// It is possible to write:
278+
///
279+
/// auto range = make_range(v1).concat(make_range(v2));
280+
/// for(const auto &a : range) {...}
281+
///
282+
/// Which is clearer and has the advantage of avoiding the creation of the
283+
/// intermediary vector and the potentially expensive copies.
284+
template <typename iteratort>
285+
struct ranget final
286+
{
287+
public:
288+
using value_typet = typename iteratort::value_type;
289+
290+
ranget(iteratort begin, iteratort end) : begin_value(begin), end_value(end)
291+
{
292+
}
293+
294+
ranget<filter_iteratort<iteratort>>
295+
filter(std::function<bool(const value_typet &)> f)
296+
{
297+
auto shared_f = std::make_shared<decltype(f)>(std::move(f));
298+
filter_iteratort<iteratort> filter_begin(shared_f, begin(), end());
299+
filter_iteratort<iteratort> filter_end(shared_f, end(), end());
300+
return ranget<filter_iteratort<iteratort>>(filter_begin, filter_end);
301+
}
302+
303+
/// Template argument type `outputt` has to be specified when \p f is given as
304+
/// a lambda.
305+
template <typename outputt>
306+
ranget<map_iteratort<iteratort, outputt>>
307+
map(std::function<outputt(const value_typet &)> f)
308+
{
309+
auto shared_f = std::make_shared<decltype(f)>(std::move(f));
310+
auto map_begin =
311+
map_iteratort<iteratort, outputt>(begin(), end(), shared_f);
312+
auto map_end = map_iteratort<iteratort, outputt>(end(), end(), shared_f);
313+
return ranget<map_iteratort<iteratort, outputt>>(
314+
std::move(map_begin), std::move(map_end));
315+
}
316+
317+
template <typename other_iteratort>
318+
ranget<concat_iteratort<iteratort, other_iteratort>>
319+
concat(ranget<other_iteratort> other)
320+
{
321+
auto concat_begin = concat_iteratort<iteratort, other_iteratort>(
322+
begin(), end(), other.begin());
323+
auto concat_end =
324+
concat_iteratort<iteratort, other_iteratort>(end(), end(), other.end());
325+
return ranget<concat_iteratort<iteratort, other_iteratort>>(
326+
concat_begin, concat_end);
327+
}
328+
329+
bool empty() const
330+
{
331+
return begin_value == end_value;
332+
}
333+
334+
iteratort begin()
335+
{
336+
return begin_value;
337+
}
338+
339+
const iteratort &end() const
340+
{
341+
return end_value;
342+
}
343+
344+
private:
345+
iteratort begin_value;
346+
iteratort end_value;
347+
};
348+
349+
template <typename iteratort>
350+
ranget<iteratort> make_range(iteratort begin, iteratort end)
351+
{
352+
return ranget<iteratort>(begin, end);
353+
}
354+
355+
template <
356+
typename containert,
357+
typename iteratort = typename containert::const_iterator>
358+
ranget<iteratort> make_range(const containert &container)
359+
{
360+
return ranget<iteratort>(container.begin(), container.end());
361+
}
362+
363+
#endif // CPROVER_UTIL_RANGE_H

0 commit comments

Comments
 (0)