halcheck 1.0
Loading...
Searching...
No Matches
dag.hpp
1#ifndef HALCHECK_LIB_DAG_HPP
2#define HALCHECK_LIB_DAG_HPP
3
10#include <halcheck/lib/functional.hpp>
11#include <halcheck/lib/iterator.hpp>
12#include <halcheck/lib/optional.hpp>
13#include <halcheck/lib/scope.hpp>
14#include <halcheck/lib/type_traits.hpp>
15
16#include <algorithm>
17#include <cstddef>
18#include <future>
19#include <initializer_list>
20#include <stdexcept>
21#include <unordered_map>
22#include <utility>
23#include <vector>
24
25namespace halcheck { namespace lib {
26
32template<typename T>
33class dag {
34private:
35 struct edges {
38 };
39
40 std::vector<T> _labels;
41 std::vector<edges> _edges;
43
44public:
48 using value_type = T;
49
54
59
61
62 dag() = default;
63
64 template<
65 typename R,
66 typename F,
73 dag(R &&range, F func) {
76 for (auto &&value : range) {
77 const auto &resources = func(value);
79 for (auto &&resource : resources) {
80 auto it = state.find(resource);
81 if (it != state.end()) {
82 parents.push_back(std::move(it->second));
83 state.erase(it);
84 }
85 }
86
87 auto it = emplace(std::move(parents), value);
88 for (auto &&resource : resources) {
89 auto i = state.find(resource);
90 if (i == state.end())
91 state.emplace(std::move(resource), it);
92 else
93 i->second = it;
94 }
95 }
96 }
97
102 iterator begin() { return iterator(_labels); }
103
108 const_iterator begin() const { return const_iterator(_labels); }
109
114 iterator end() { return iterator(_labels, size()); }
115
120 const_iterator end() const { return const_iterator(_labels, size()); }
121
127 bool empty() const { return _labels.empty(); }
128
133 std::size_t size() const { return _labels.size(); }
134
144 template<
145 typename I,
146 typename... Args,
150 iterator emplace(I first, I last, Args &&...args) {
151 auto index = size();
152 _labels.emplace_back(std::forward<Args>(args)...);
153 _edges.emplace_back();
154 auto &parents = _edges.back().parents;
155
156 try {
157 if (first == last)
158 _roots.push_back(index);
159
160 while (first != last) {
161 const_iterator i = *first++;
162 if (_edges[i.index()].children.empty() || _edges[i.index()].children.back() != index)
163 _edges[i.index()].children.push_back(index);
164 parents.push_back(i.index());
165 }
166 } catch (...) {
167 for (auto &&edge : _edges) {
168 if (edge.children.back() == index)
169 edge.children.pop_back();
170 }
171
172 if (_roots.back() == index)
173 _roots.pop_back();
174
175 _labels.pop_back();
176 _edges.pop_back();
177 throw;
178 }
179
182
183 return iterator(_labels, index);
184 }
185
194 template<
195 typename R,
196 typename... Args,
200 iterator emplace(R &&range, Args &&...args) {
201 return emplace(lib::begin(range), lib::end(range), std::forward<Args>(args)...);
202 }
203
211 template<typename... Args, HALCHECK_REQUIRE(std::is_constructible<T, Args...>())>
213 return emplace(range.begin(), range.end(), std::forward<Args>(args)...);
214 }
215
220 void clear() {
221 _labels.clear();
222 _edges.clear();
223 _roots.clear();
224 }
225
226private:
227 struct to_iterator {
228 iterator operator()(std::size_t index) const { return iterator(self->_labels, index); }
229 dag *self;
230 };
231
232 struct to_const_iterator {
233 to_const_iterator() : self(nullptr) {};
234 explicit to_const_iterator(const dag *self) : self(self) {}
235 to_const_iterator(const to_iterator &other) : self(other.self) {} // NOLINT
236 const_iterator operator()(std::size_t index) const { return const_iterator(self->_labels, index); }
237 const dag *self;
238 };
239
240public:
245
250
257 auto &&data = _edges[it.index()].children;
258 return lib::transform(lib::ref(data), to_const_iterator{this});
259 }
260
267 auto &&data = _edges[it.index()].children;
268 return lib::transform(lib::ref(data), to_iterator{this});
269 }
270
277 auto &&data = _edges[it.index()].parents;
278 return lib::transform(lib::ref(data), to_const_iterator{this});
279 }
280
287 auto &&data = _edges[it.index()].parents;
288 return lib::transform(lib::ref(data), to_iterator{this});
289 }
290
295 const_view roots() const { return lib::transform(lib::ref(_roots), to_const_iterator{this}); }
296
301 view roots() { return lib::transform(lib::ref(_roots), to_iterator{this}); }
302
308 _labels.reserve(size);
309 _edges.reserve(size);
310 }
311};
312
313template<
314 typename R,
315 typename F,
321lib::dag<lib::range_value_t<R>> make_dag(R &&range, F func) {
322 return lib::dag<lib::range_value_t<R>>(std::forward<R>(range), std::move(func));
323}
324
332template<
333 typename F,
334 typename T,
336void async(lib::dag<T> &graph, F func) {
338 futures.reserve(graph.size());
339
340 for (auto i = graph.begin(); i != graph.end(); ++i) {
342 for (auto j : graph.parents(i))
343 parents.push_back(futures[j - graph.begin()]);
344
345 futures.emplace_back(std::async(
346 std::launch::async,
347 [&func, i](const std::vector<std::shared_future<void>> &parents) {
348 for (const auto &parent : parents)
349 parent.wait();
350
351 lib::invoke(func, i);
352 },
353 std::move(parents)));
354 }
355
356 for (auto &&future : futures)
357 future.get();
358}
359
367template<
368 typename F,
369 typename T,
371void async(const lib::dag<T> &graph, F func) {
372 return lib::async(const_cast<lib::dag<T> &>(graph), [&](lib::iterator_t<const lib::dag<T>> it) {
373 return lib::invoke(func, it);
374 });
375}
376
386template<
387 typename F,
388 typename T,
392
393 std::vector<std::promise<result>> results(graph.size());
394 lib::async(graph, [&](lib::iterator_t<lib::dag<T>> it) {
395 results[it - graph.begin()].set_value(lib::invoke(func, it));
396 });
397
398 lib::dag<result> output;
399 output.reserve(graph.size());
400
401 for (std::size_t i = 0; i < results.size(); i++) {
402 output.emplace(
404 graph.parents(graph.begin() + i),
405 [&](lib::iterator_t<lib::dag<T>> it) { return output.begin() + (it - graph.begin()); }),
406 results[i].get_future().get());
407 }
408
409 return output;
410}
411
421template<
422 typename F,
423 typename T,
426 return lib::async(const_cast<lib::dag<T> &>(graph), [&](lib::iterator_t<const lib::dag<T>> it) {
427 return lib::invoke(func, it);
428 });
429}
430
431namespace detail {
432template<typename T, typename S, typename F>
433bool linearize(
434 const lib::dag<T> &dag,
435 S &seed,
436 F func,
437 std::vector<std::size_t> &references,
438 std::vector<typename lib::dag<T>::const_iterator> &queue) {
439 for (std::size_t i = 0; i < queue.size(); ++i) {
440 auto next = seed;
441 auto it = queue[i];
442 if (!lib::invoke(func, *it, next))
443 continue;
444
445 for (auto j : dag.children(it)) {
446 if (--references[j - dag.begin()] == 0)
447 queue.push_back(j);
448 }
449
450 std::swap(queue[i], queue.back());
451 queue.pop_back();
452
453 auto _ = lib::finally([&] {
454 queue.push_back(it);
455 std::swap(queue[i], queue.back());
456
457 for (auto j : dag.children(it)) {
458 if (references[j - dag.begin()]++ == 0)
459 queue.pop_back();
460 }
461 });
462
463 if (linearize(dag, next, func, references, queue)) {
464 seed = std::move(next);
465 return true;
466 }
467 }
468
469 return queue.empty();
470}
471} // namespace detail
472
473template<
474 typename T,
475 typename S,
476 typename F,
477 HALCHECK_REQUIRE(lib::is_copyable<S>()),
478 HALCHECK_REQUIRE(lib::is_invocable_r<bool, F, const T &, S &>())>
479bool linearize(const lib::dag<T> &dag, S &seed, F func) {
480 std::vector<std::size_t> references(dag.size(), 0);
481 for (auto i = dag.begin(); i != dag.end(); ++i) {
482 for (auto j : dag.children(i))
483 ++references[j - dag.begin()];
484 }
485
486 std::vector<typename lib::dag<T>::const_iterator> queue(dag.roots().begin(), dag.roots().end());
487 return detail::linearize(dag, seed, func, references, queue);
488}
489
490template<
491 typename T,
492 typename F,
493 typename G,
494 HALCHECK_REQUIRE(lib::is_movable<lib::invoke_result_t<F>>()),
495 HALCHECK_REQUIRE(lib::is_invocable_r<bool, G, const T &, lib::invoke_result_t<F> &>())>
496lib::optional<lib::invoke_result_t<F>> linearize(const lib::dag<T> &dag, F init, G func) {
497 struct state {
498 private:
502 lib::optional<lib::invoke_result_t<F>> value;
503
504 public:
505 explicit state(F &init, G &func) : init(init), func(func) {}
506
507 state(state &&other) noexcept(lib::is_nothrow_swappable<lib::optional<lib::invoke_result_t<F>>>())
508 : init(other.init), func(other.func), prefix(std::move(other.prefix)) {
509 using std::swap;
510 swap(value, other.value);
511 }
512
513 state &operator=(state &&other) noexcept(lib::is_nothrow_swappable<lib::optional<lib::invoke_result_t<F>>>()) {
514 if (this != &other) {
515 init = other.init;
516 func = other.func;
517 prefix = std::move(other.prefix);
518
519 using std::swap;
520 value.reset();
521 swap(value, other.value);
522 }
523
524 return *this;
525 }
526
527 state(const state &other) : init(other.init), func(other.func), prefix(other.prefix) {}
528
529 state &operator=(const state &other) {
530 if (this != &other) {
531 init = other.init;
532 func = other.func;
533 prefix = other.prefix;
534 }
535
536 return *this;
537 }
538
539 ~state() = default;
540
541 bool apply(const T &label) {
542 if (func(label, get())) {
543 prefix.push_back(label);
544 return true;
545 } else {
546 value.reset();
547 return false;
548 }
549 }
550
551 lib::invoke_result_t<F> &get() {
552 if (!value) {
553 value.emplace(lib::invoke(init));
554 for (auto &&label : prefix) {
555 if (!func(label, *value))
556 throw std::runtime_error("Failed to reproduce move-only value");
557 }
558 }
559
560 return *value;
561 }
562 };
563
564 state seed(init, func);
565 if (lib::linearize(dag, seed, [&](const T &label, state &current) { return current.apply(label); }))
566 return std::move(seed.get());
567 else
568 return lib::nullopt;
569}
570
571}} // namespace halcheck::lib
572
573#endif
T apply(T... args)
T async(T... args)
T back(T... args)
const_view parents(const_iterator it) const
Obtains a node's parents.
Definition dag.hpp:276
void clear()
Removes all nodes from this dag.
Definition dag.hpp:220
view roots()
Obtains the root nodes of this dag.
Definition dag.hpp:301
iterator emplace(const std::initializer_list< const_iterator > &range, Args &&...args)
Adds a node to this dag with its label constructed in place.
Definition dag.hpp:212
const_view roots() const
Obtains the root nodes of this dag.
Definition dag.hpp:295
iterator emplace(R &&range, Args &&...args)
Adds a node to this dag with its label constructed in place.
Definition dag.hpp:200
view parents(const_iterator it)
Obtains a node's parents.
Definition dag.hpp:286
std::size_t size() const
Gets the number of nodes in this dag.
Definition dag.hpp:133
iterator end()
Gets an iterator pointing past the last node in this dag.
Definition dag.hpp:114
const_view children(const_iterator it) const
Obtains a node's children.
Definition dag.hpp:256
const_iterator begin() const
Gets an const_iterator pointing to the first node in this dag.
Definition dag.hpp:108
iterator begin()
Gets an iterator pointing to the first node in this dag.
Definition dag.hpp:102
void reserve(std::size_t size)
Reserves enough memory to contain the specified number of nodes.
Definition dag.hpp:307
iterator emplace(I first, I last, Args &&...args)
Adds a node to this dag with its label constructed in place.
Definition dag.hpp:150
view children(const_iterator it)
Obtains a node's children.
Definition dag.hpp:266
const_iterator end() const
Gets an const_iterator pointing past the last node in this dag.
Definition dag.hpp:120
bool empty() const
Determines whether this dag is empty.
Definition dag.hpp:127
Directed acyclic graphs with labelled nodes.
Definition dag.hpp:33
T value_type
The type of node labels.
Definition dag.hpp:48
lib::index_iterator< std::vector< T > > iterator
A iterator to T. These are also referred to as "nodes".
Definition dag.hpp:53
lib::index_iterator< const std::vector< T > > const_iterator
An iterator to const T. These are also referred to as "nodes".
Definition dag.hpp:58
difference_type index() const
Gets the index of the element this iterator points to.
Definition index.hpp:118
An iterator into a random-access range that is invalidated if and only if the index of the pointed-to...
Definition index.hpp:22
lib::transform_iterator< lib::iterator_t< V >, ref > begin()
Gets an iterator pointing to the beginning of this view.
Definition transform.hpp:311
lib::transform_iterator< lib::iterator_t< V >, ref > end()
Gets an iterator pointing past the end of this view.
Definition transform.hpp:331
A transforming iterator adaptor.
Definition transform.hpp:225
T clear(T... args)
T emplace_back(T... args)
T emplace(T... args)
T empty(T... args)
T end(T... args)
T erase(T... args)
T find(T... args)
T forward(T... args)
HALCHECK_INLINE_CONSTEXPR struct halcheck::lib::@20 invoke
An implementation of std::invoke.
decltype(lib::invoke(std::declval< F >(), std::declval< Args >()...)) invoke_result_t
An implementation of std::invoke_result_t.
Definition invoke.hpp:42
lib::iter_value_t< lib::iterator_t< R > > range_value_t
The type of element contained in a range.
Definition range.hpp:332
decltype(lib::begin(std::declval< T & >())) iterator_t
Obtains the iterator type of a range type.
Definition range.hpp:92
typename std::iterator_traits< I >::reference iter_reference_t
The return type of operator* for an iterator.
Definition base.hpp:24
HALCHECK_INLINE_CONSTEXPR struct halcheck::lib::@31 transform
Constructs a transform_view.
lib::iter_reference_t< lib::iterator_t< R > > range_reference_t
The type returned by operator* for a range type's iterators.
Definition range.hpp:340
static constexpr nullopt_t nullopt
An implementation of std::nullopt.
Definition optional.hpp:41
lib::finally_t< F > finally(F func)
Definition scope.hpp:157
#define HALCHECK_REQUIRE(...)
Expands to a template argument that is only valid if the given argument evaluates to true.
Definition type_traits.hpp:24
void async(lib::dag< T > &graph, F func)
Executes a function on each label in a given graph. Calls for unrelated node labels are executed in p...
Definition dag.hpp:336
STL namespace.
T pop_back(T... args)
T push_back(T... args)
T reserve(T... args)
T size(T... args)
T sort(T... args)
Determines if a type satisfies the EqualityComparable concept.
Definition type_traits.hpp:386
Determines if a type has a valid std::hash specialization.
Definition type_traits.hpp:398
Determines whether a type satisfies the LegacyInputIterator concept.
Definition type_traits.hpp:54
Determines whether the given type is a range.
Definition range.hpp:158
T swap(T... args)
T unique(T... args)