/src/immer/immer/algorithm.hpp
Line | Count | Source |
1 | | // |
2 | | // immer: immutable data structures for C++ |
3 | | // Copyright (C) 2016, 2017, 2018 Juan Pedro Bolivar Puente |
4 | | // |
5 | | // This software is distributed under the Boost Software License, Version 1.0. |
6 | | // See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt |
7 | | // |
8 | | |
9 | | #pragma once |
10 | | |
11 | | #include <algorithm> |
12 | | #include <cassert> |
13 | | #include <functional> |
14 | | #include <numeric> |
15 | | #include <type_traits> |
16 | | |
17 | | namespace immer { |
18 | | |
19 | | /** |
20 | | * @defgroup algorithm |
21 | | * @{ |
22 | | */ |
23 | | |
24 | | /*@{*/ |
25 | | // Right now these algorithms dispatch directly to the vector |
26 | | // implementations unconditionally. This will be changed in the |
27 | | // future to support other kinds of containers. |
28 | | |
29 | | /*! |
30 | | * Apply operation `fn` for every contiguous *chunk* of data in the |
31 | | * range sequentially. Each time, `Fn` is passed two `value_type` |
32 | | * pointers describing a range over a part of the vector. This allows |
33 | | * iterating over the elements in the most efficient way. |
34 | | * |
35 | | * @rst |
36 | | * |
37 | | * .. tip:: This is a low level method. Most of the time, :doc:`other |
38 | | * wrapper algorithms <algorithms>` should be used instead. |
39 | | * |
40 | | * @endrst |
41 | | */ |
42 | | template <typename Range, typename Fn> |
43 | | void for_each_chunk(const Range& r, Fn&& fn) |
44 | | { |
45 | | r.impl().for_each_chunk(std::forward<Fn>(fn)); |
46 | | } |
47 | | |
48 | | template <typename Iterator, typename Fn> |
49 | | void for_each_chunk(const Iterator& first, const Iterator& last, Fn&& fn) |
50 | | { |
51 | | assert(&first.impl() == &last.impl()); |
52 | | first.impl().for_each_chunk( |
53 | | first.index(), last.index(), std::forward<Fn>(fn)); |
54 | | } |
55 | | |
56 | | template <typename T, typename Fn> |
57 | | void for_each_chunk(const T* first, const T* last, Fn&& fn) |
58 | | { |
59 | | std::forward<Fn>(fn)(first, last); |
60 | | } |
61 | | |
62 | | /*! |
63 | | * Apply operation `fn` for every contiguous *chunk* of data in the |
64 | | * range sequentially, until `fn` returns `false`. Each time, `Fn` is |
65 | | * passed two `value_type` pointers describing a range over a part of |
66 | | * the vector. This allows iterating over the elements in the most |
67 | | * efficient way. |
68 | | * |
69 | | * @rst |
70 | | * |
71 | | * .. tip:: This is a low level method. Most of the time, :doc:`other |
72 | | * wrapper algorithms <algorithms>` should be used instead. |
73 | | * |
74 | | * @endrst |
75 | | */ |
76 | | template <typename Range, typename Fn> |
77 | | bool for_each_chunk_p(const Range& r, Fn&& fn) |
78 | | { |
79 | | return r.impl().for_each_chunk_p(std::forward<Fn>(fn)); |
80 | | } |
81 | | |
82 | | template <typename Iterator, typename Fn> |
83 | | bool for_each_chunk_p(const Iterator& first, const Iterator& last, Fn&& fn) |
84 | | { |
85 | | assert(&first.impl() == &last.impl()); |
86 | | return first.impl().for_each_chunk_p( |
87 | | first.index(), last.index(), std::forward<Fn>(fn)); |
88 | | } |
89 | | |
90 | | template <typename T, typename Fn> |
91 | | bool for_each_chunk_p(const T* first, const T* last, Fn&& fn) |
92 | | { |
93 | | return std::forward<Fn>(fn)(first, last); |
94 | | } |
95 | | |
96 | | namespace detail { |
97 | | |
98 | | template <class Iter, class T> |
99 | | T accumulate_move(Iter first, Iter last, T init) |
100 | | { |
101 | | for (; first != last; ++first) |
102 | | init = std::move(init) + *first; |
103 | | return init; |
104 | | } |
105 | | |
106 | | template <class Iter, class T, class Fn> |
107 | | T accumulate_move(Iter first, Iter last, T init, Fn op) |
108 | | { |
109 | | for (; first != last; ++first) |
110 | | init = op(std::move(init), *first); |
111 | | return init; |
112 | | } |
113 | | |
114 | | } // namespace detail |
115 | | |
116 | | /*! |
117 | | * Equivalent of `std::accumulate` applied to the range `r`. |
118 | | */ |
119 | | template <typename Range, typename T> |
120 | | T accumulate(Range&& r, T init) |
121 | | { |
122 | | for_each_chunk(r, [&](auto first, auto last) { |
123 | | init = detail::accumulate_move(first, last, init); |
124 | | }); |
125 | | return init; |
126 | | } |
127 | | |
128 | | template <typename Range, typename T, typename Fn> |
129 | | T accumulate(Range&& r, T init, Fn fn) |
130 | | { |
131 | | for_each_chunk(r, [&](auto first, auto last) { |
132 | | init = detail::accumulate_move(first, last, init, fn); |
133 | | }); |
134 | | return init; |
135 | | } |
136 | | |
137 | | /*! |
138 | | * Equivalent of `std::accumulate` applied to the range @f$ [first, |
139 | | * last) @f$. |
140 | | */ |
141 | | template <typename Iterator, typename T> |
142 | | T accumulate(Iterator first, Iterator last, T init) |
143 | | { |
144 | | for_each_chunk(first, last, [&](auto first, auto last) { |
145 | | init = detail::accumulate_move(first, last, init); |
146 | | }); |
147 | | return init; |
148 | | } |
149 | | |
150 | | template <typename Iterator, typename T, typename Fn> |
151 | | T accumulate(Iterator first, Iterator last, T init, Fn fn) |
152 | | { |
153 | | for_each_chunk(first, last, [&](auto first, auto last) { |
154 | | init = detail::accumulate_move(first, last, init, fn); |
155 | | }); |
156 | | return init; |
157 | | } |
158 | | |
159 | | /*! |
160 | | * Equivalent of `std::for_each` applied to the range `r`. |
161 | | */ |
162 | | template <typename Range, typename Fn> |
163 | | Fn&& for_each(Range&& r, Fn&& fn) |
164 | | { |
165 | | for_each_chunk(r, [&](auto first, auto last) { |
166 | | for (; first != last; ++first) |
167 | | fn(*first); |
168 | | }); |
169 | | return std::forward<Fn>(fn); |
170 | | } |
171 | | |
172 | | /*! |
173 | | * Equivalent of `std::for_each` applied to the range @f$ [first, |
174 | | * last) @f$. |
175 | | */ |
176 | | template <typename Iterator, typename Fn> |
177 | | Fn&& for_each(Iterator first, Iterator last, Fn&& fn) |
178 | | { |
179 | | for_each_chunk(first, last, [&](auto first, auto last) { |
180 | | for (; first != last; ++first) |
181 | | fn(*first); |
182 | | }); |
183 | | return std::forward<Fn>(fn); |
184 | | } |
185 | | |
186 | | /*! |
187 | | * Equivalent of `std::copy` applied to the range `r`. |
188 | | */ |
189 | | template <typename Range, typename OutIter> |
190 | | OutIter copy(Range&& r, OutIter out) |
191 | | { |
192 | | for_each_chunk( |
193 | | r, [&](auto first, auto last) { out = std::copy(first, last, out); }); |
194 | | return out; |
195 | | } |
196 | | |
197 | | /*! |
198 | | * Equivalent of `std::copy` applied to the range @f$ [first, |
199 | | * last) @f$. |
200 | | */ |
201 | | template <typename InIter, typename OutIter> |
202 | | OutIter copy(InIter first, InIter last, OutIter out) |
203 | | { |
204 | | for_each_chunk(first, last, [&](auto first, auto last) { |
205 | | out = std::copy(first, last, out); |
206 | | }); |
207 | | return out; |
208 | | } |
209 | | |
210 | | /*! |
211 | | * Equivalent of `std::all_of` applied to the range `r`. |
212 | | */ |
213 | | template <typename Range, typename Pred> |
214 | | bool all_of(Range&& r, Pred p) |
215 | | { |
216 | | return for_each_chunk_p( |
217 | | r, [&](auto first, auto last) { return std::all_of(first, last, p); }); |
218 | | } |
219 | | |
220 | | /*! |
221 | | * Equivalent of `std::all_of` applied to the range @f$ [first, last) |
222 | | * @f$. |
223 | | */ |
224 | | template <typename Iter, typename Pred> |
225 | | bool all_of(Iter first, Iter last, Pred p) |
226 | | { |
227 | | return for_each_chunk_p(first, last, [&](auto first, auto last) { |
228 | | return std::all_of(first, last, p); |
229 | | }); |
230 | | } |
231 | | |
232 | | /*! |
233 | | * Object that can be used to process changes as computed by the @a diff |
234 | | * algorithm. |
235 | | * |
236 | | * @tparam AddedFn Unary function that is be called whenever an added element is |
237 | | * found. It is called with the added element as argument. |
238 | | * |
239 | | * @tparam RemovedFn Unary function that is called whenever a removed element is |
240 | | * found. It is called with the removed element as argument. |
241 | | * |
242 | | * @tparam ChangedFn Unary function that is called whenever a changed element is |
243 | | * found. It is called with the changed element as argument. |
244 | | */ |
245 | | template <class AddedFn, class RemovedFn, class ChangedFn> |
246 | | struct differ |
247 | | { |
248 | | AddedFn added; |
249 | | RemovedFn removed; |
250 | | ChangedFn changed; |
251 | | }; |
252 | | |
253 | | /*! |
254 | | * Produces a @a differ object with `added`, `removed` and `changed` functions. |
255 | | */ |
256 | | template <class AddedFn, class RemovedFn, class ChangedFn> |
257 | | auto make_differ(AddedFn&& added, RemovedFn&& removed, ChangedFn&& changed) |
258 | | -> differ<std::decay_t<AddedFn>, |
259 | | std::decay_t<RemovedFn>, |
260 | | std::decay_t<ChangedFn>> |
261 | 12.1k | { |
262 | 12.1k | return {std::forward<AddedFn>(added), |
263 | 12.1k | std::forward<RemovedFn>(removed), |
264 | 12.1k | std::forward<ChangedFn>(changed)}; |
265 | 12.1k | } |
266 | | |
267 | | /*! |
268 | | * Produces a @a differ object with `added` and `removed` functions and no |
269 | | * `changed` function. |
270 | | */ |
271 | | template <class AddedFn, class RemovedFn> |
272 | | auto make_differ(AddedFn&& added, RemovedFn&& removed) |
273 | | { |
274 | | return make_differ(std::forward<AddedFn>(added), |
275 | | std::forward<RemovedFn>(removed), |
276 | | [](auto&&...) {}); |
277 | | } |
278 | | |
279 | | /*! |
280 | | * Compute the differences between `a` and `b`. |
281 | | * |
282 | | * Changes detected are notified via the differ object, which should support the |
283 | | * following expressions: |
284 | | * |
285 | | * - `differ.added(x)`, invoked when element `x` is found in `b` but not in |
286 | | * `a`. |
287 | | * |
288 | | * - `differ.removed(x)`, invoked when element `x` is found in `a` but not in |
289 | | * `b`. |
290 | | * |
291 | | * - `differ.changed(x, y)`, invoked when element `x` and `y` from `a` and `b` |
292 | | * share the same key but map to a different value. |
293 | | * |
294 | | * This method leverages structural sharing to offer a complexity @f$ O(|diff|) |
295 | | * @f$ when `b` is derived from `a` by performing @f$ |diff| @f$ updates. This |
296 | | * is, this function can detect changes in effectively constant time per update, |
297 | | * as oposed to the @f$ O(|a|+|b|) @f$ complexity of a trivial implementation. |
298 | | * |
299 | | * @rst |
300 | | * |
301 | | * .. note:: This method is only implemented for ``map`` and ``set``. When sets |
302 | | * are diffed, the ``changed`` function is never called. |
303 | | * |
304 | | * @endrst |
305 | | */ |
306 | | template <typename T, typename Differ> |
307 | | void diff(const T& a, const T& b, Differ&& differ) |
308 | 12.1k | { |
309 | 12.1k | a.impl().template diff<std::equal_to<typename T::value_type>>( |
310 | 12.1k | b.impl(), std::forward<Differ>(differ)); |
311 | 12.1k | } |
312 | | |
313 | | /*! |
314 | | * Compute the differences between `a` and `b` using the callbacks in `fns` as |
315 | | * differ. Equivalent to `diff(a, b, make_differ(fns)...)`. |
316 | | */ |
317 | | template <typename T, typename... Fns> |
318 | | void diff(const T& a, const T& b, Fns&&... fns) |
319 | 12.1k | { |
320 | 12.1k | diff(a, b, make_differ(std::forward<Fns>(fns)...)); |
321 | 12.1k | } |
322 | | |
323 | | /** @} */ // group: algorithm |
324 | | |
325 | | } // namespace immer |