Coverage Report

Created: 2023-12-08 06:28

/src/cppitertools/groupby.hpp
Line
Count
Source (jump to first uncovered line)
1
#ifndef ITER_GROUP_BY_HPP_
2
#define ITER_GROUP_BY_HPP_
3
4
// this is easily the most functionally complex itertool
5
6
#include "internal/iterator_wrapper.hpp"
7
#include "internal/iterbase.hpp"
8
9
#include <functional>
10
#include <iterator>
11
#include <memory>
12
#include <optional>
13
#include <type_traits>
14
#include <utility>
15
16
namespace iter {
17
  namespace impl {
18
    template <typename Container, typename KeyFunc>
19
    class GroupProducer;
20
21
    using GroupByFn = IterToolFnOptionalBindSecond<GroupProducer, Identity>;
22
  }
23
  constexpr impl::GroupByFn groupby{};
24
}
25
26
template <typename Container, typename KeyFunc>
27
class iter::impl::GroupProducer {
28
 private:
29
  Container container_;
30
  mutable KeyFunc key_func_;
31
32
  friend GroupByFn;
33
34
  template <typename T>
35
  using key_func_ret = std::invoke_result_t<KeyFunc, iterator_deref<T>>;
36
37
  GroupProducer(Container&& container, KeyFunc key_func)
38
503
      : container_(std::forward<Container>(container)), key_func_(key_func) {}
39
40
 public:
41
  GroupProducer(GroupProducer&&) = default;
42
43
  template <typename T>
44
  class Iterator;
45
  template <typename T>
46
  class Group;
47
48
 private:
49
  template <typename T>
50
  using KeyGroupPair = std::pair<key_func_ret<T>, Group<T>>;
51
  template <typename T>
52
  using Holder = DerefHolder<iterator_deref<T>>;
53
54
 public:
55
  template <typename ContainerT>
56
  class Iterator {
57
   private:
58
    template <typename>
59
    friend class Iterator;
60
    IteratorWrapper<ContainerT> sub_iter_;
61
    IteratorWrapper<ContainerT> sub_end_;
62
    Holder<ContainerT> item_;
63
    KeyFunc* key_func_;
64
    std::optional<KeyGroupPair<ContainerT>> current_key_group_pair_;
65
66
   public:
67
    using iterator_category = std::input_iterator_tag;
68
    using value_type = KeyGroupPair<ContainerT>;
69
    using difference_type = std::ptrdiff_t;
70
    using pointer = value_type*;
71
    using reference = value_type&;
72
73
    Iterator(IteratorWrapper<ContainerT>&& sub_iter,
74
        IteratorWrapper<ContainerT>&& sub_end, KeyFunc& key_func)
75
        : sub_iter_{std::move(sub_iter)},
76
          sub_end_{std::move(sub_end)},
77
1.00k
          key_func_(&key_func) {
78
1.00k
      if (sub_iter_ != sub_end_) {
79
503
        item_.reset(*sub_iter_);
80
503
      }
81
1.00k
    }
82
83
    Iterator(const Iterator& other)
84
        : sub_iter_{other.sub_iter_},
85
          sub_end_{other.sub_end_},
86
          item_{other.item_},
87
          key_func_{other.key_func_} {}
88
89
    Iterator& operator=(const Iterator& other) {
90
      if (this == &other) {
91
        return *this;
92
      }
93
      sub_iter_ = other.sub_iter_;
94
      sub_end_ = other.sub_end_;
95
      item_ = other.item_;
96
      key_func_ = other.key_func_;
97
      current_key_group_pair_.reset();
98
      return *this;
99
    }
100
101
1.00k
    ~Iterator() = default;
102
103
    // NOTE the implicitly generated move constructor would
104
    // be wrong
105
106
1.93k
    KeyGroupPair<ContainerT>& operator*() {
107
1.93k
      set_key_group_pair();
108
1.93k
      return *current_key_group_pair_;
109
1.93k
    }
110
111
    KeyGroupPair<ContainerT>* operator->() {
112
      set_key_group_pair();
113
      return &*current_key_group_pair_;
114
    }
115
116
1.93k
    Iterator& operator++() {
117
1.93k
      if (!current_key_group_pair_) {
118
0
        set_key_group_pair();
119
0
      }
120
1.93k
      current_key_group_pair_.reset();
121
1.93k
      return *this;
122
1.93k
    }
123
124
    Iterator operator++(int) {
125
      auto ret = *this;
126
      ++*this;
127
      return ret;
128
    }
129
130
    template <typename T>
131
2.43k
    bool operator!=(const Iterator<T>& other) const {
132
2.43k
      return sub_iter_ != other.sub_iter_;
133
2.43k
    }
134
135
    template <typename T>
136
    bool operator==(const Iterator<T>& other) const {
137
      return !(*this != other);
138
    }
139
140
11.3k
    void increment_iterator() {
141
11.3k
      if (sub_iter_ != sub_end_) {
142
11.3k
        ++sub_iter_;
143
11.3k
        if (sub_iter_ != sub_end_) {
144
10.8k
          item_.reset(*sub_iter_);
145
10.8k
        }
146
11.3k
      }
147
11.3k
    }
148
149
11.3k
    bool exhausted() const {
150
11.3k
      return !(sub_iter_ != sub_end_);
151
11.3k
    }
152
153
    typename Holder<ContainerT>::reference get() {
154
      return item_.get();
155
    }
156
157
    typename Holder<ContainerT>::pointer get_ptr() {
158
      return item_.get_ptr();
159
    }
160
161
12.7k
    key_func_ret<ContainerT> next_key() {
162
12.7k
      return std::invoke(*key_func_, item_.get());
163
12.7k
    }
164
165
1.93k
    void set_key_group_pair() {
166
1.93k
      if (!current_key_group_pair_) {
167
1.93k
        current_key_group_pair_.emplace(std::invoke(*key_func_, item_.get()),
168
1.93k
            Group<ContainerT>{*this, next_key()});
169
1.93k
      }
170
1.93k
    }
171
  };
172
173
  template <typename ContainerT>
174
  class Group {
175
   private:
176
    template <typename>
177
    friend class Iterator;
178
    friend class GroupIterator;
179
    Iterator<ContainerT>& owner_;
180
    key_func_ret<ContainerT> key_;
181
182
    // completed is set if a Group is iterated through
183
    // completely.  It is checked in the destructor, and
184
    // if the Group has not been completed, the destructor
185
    // exhausts it.  This ensures that the next Group starts
186
    // at the correct position when the user short-circuits
187
    // iteration over a Group.
188
    // The move constructor sets the rvalue's completed
189
    // attribute to true, so its destructor doesn't do anything
190
    // when called.
191
    bool completed = false;
192
193
    Group(Iterator<ContainerT>& owner, key_func_ret<ContainerT> key)
194
1.93k
        : owner_(owner), key_(key) {}
195
196
   public:
197
3.86k
    ~Group() {
198
3.86k
      if (!completed) {
199
13.2k
        for (auto iter = begin(), end_it = end(); iter != end_it; ++iter) {
200
11.3k
        }
201
1.93k
      }
202
3.86k
    }
203
204
    // move-constructible, non-copy-constructible, non-assignable
205
    Group(Group&& other) noexcept
206
1.93k
        : owner_(other.owner_), key_{other.key_}, completed{other.completed} {
207
1.93k
      other.completed = true;
208
1.93k
    }
209
210
    class GroupIterator {
211
     private:
212
      std::remove_reference_t<key_func_ret<ContainerT>>* key_;
213
      Group* group_p_;
214
215
11.3k
      bool not_at_end() {
216
11.3k
        return !group_p_->owner_.exhausted()
217
11.3k
               && group_p_->owner_.next_key() == *key_;
218
11.3k
      }
219
220
     public:
221
      using iterator_category = std::input_iterator_tag;
222
      using value_type = iterator_traits_deref<ContainerT>;
223
      using difference_type = std::ptrdiff_t;
224
      using pointer = value_type*;
225
      using reference = value_type&;
226
227
      // TODO template this? idk if it's relevant here
228
      GroupIterator(Group* group_p, key_func_ret<ContainerT>& key)
229
3.86k
          : key_{&key}, group_p_{group_p} {}
230
231
13.2k
      bool operator!=(const GroupIterator& other) const {
232
13.2k
        return !(*this == other);
233
13.2k
      }
234
235
13.2k
      bool operator==(const GroupIterator& other) const {
236
13.2k
        return group_p_ == other.group_p_;
237
13.2k
      }
238
239
11.3k
      GroupIterator& operator++() {
240
11.3k
        group_p_->owner_.increment_iterator();
241
11.3k
        if (!not_at_end()) {
242
1.93k
          group_p_->completed = true;
243
1.93k
          group_p_ = nullptr;
244
1.93k
        }
245
11.3k
        return *this;
246
11.3k
      }
247
248
      GroupIterator operator++(int) {
249
        auto ret = *this;
250
        ++*this;
251
        return ret;
252
      }
253
254
      iterator_deref<ContainerT> operator*() {
255
        return group_p_->owner_.get();
256
      }
257
258
      typename Holder<ContainerT>::pointer operator->() {
259
        return group_p_->owner_.get_ptr();
260
      }
261
    };
262
263
1.93k
    GroupIterator begin() {
264
1.93k
      return {this, key_};
265
1.93k
    }
266
267
1.93k
    GroupIterator end() {
268
1.93k
      return {nullptr, key_};
269
1.93k
    }
270
  };
271
272
503
  Iterator<Container> begin() {
273
503
    return {get_begin(container_), get_end(container_), key_func_};
274
503
  }
275
276
503
  Iterator<Container> end() {
277
503
    return {get_end(container_), get_end(container_), key_func_};
278
503
  }
279
280
  Iterator<AsConst<Container>> begin() const {
281
    return {get_begin(std::as_const(container_)),
282
        get_end(std::as_const(container_)), key_func_};
283
  }
284
285
  Iterator<AsConst<Container>> end() const {
286
    return {get_end(std::as_const(container_)),
287
        get_end(std::as_const(container_)), key_func_};
288
  }
289
};
290
291
#endif