Coverage Report

Created: 2025-08-26 06:27

/src/cppitertools/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
  inline 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
568
      : container_(std::forward<Container>(container)),
39
568
        key_func_(std::move(key_func)) {}
40
41
 public:
42
  GroupProducer(GroupProducer&&) = default;
43
44
  template <typename T>
45
  class Iterator;
46
  template <typename T>
47
  class Group;
48
49
 private:
50
  template <typename T>
51
  using KeyGroupPair = std::pair<key_func_ret<T>, Group<T>>;
52
  template <typename T>
53
  using Holder = DerefHolder<iterator_deref<T>>;
54
55
 public:
56
  template <typename ContainerT>
57
  class Iterator {
58
   private:
59
    template <typename>
60
    friend class Iterator;
61
    IteratorWrapper<ContainerT> sub_iter_;
62
    IteratorWrapper<ContainerT> sub_end_;
63
    Holder<ContainerT> item_;
64
    KeyFunc* key_func_;
65
    std::optional<KeyGroupPair<ContainerT>> current_key_group_pair_;
66
67
   public:
68
    using iterator_category = std::input_iterator_tag;
69
    using value_type = KeyGroupPair<ContainerT>;
70
    using difference_type = std::ptrdiff_t;
71
    using pointer = value_type*;
72
    using reference = value_type&;
73
74
    Iterator(IteratorWrapper<ContainerT>&& sub_iter,
75
        IteratorWrapper<ContainerT>&& sub_end, KeyFunc& key_func)
76
1.13k
        : sub_iter_{std::move(sub_iter)},
77
1.13k
          sub_end_{std::move(sub_end)},
78
1.13k
          key_func_(&key_func) {
79
1.13k
      if (sub_iter_ != sub_end_) {
80
568
        item_.reset(*sub_iter_);
81
568
      }
82
1.13k
    }
83
84
    Iterator(const Iterator& other)
85
        : sub_iter_{other.sub_iter_},
86
          sub_end_{other.sub_end_},
87
          item_{other.item_},
88
          key_func_{other.key_func_} {}
89
90
    Iterator& operator=(const Iterator& other) {
91
      if (this == &other) {
92
        return *this;
93
      }
94
      sub_iter_ = other.sub_iter_;
95
      sub_end_ = other.sub_end_;
96
      item_ = other.item_;
97
      key_func_ = other.key_func_;
98
      current_key_group_pair_.reset();
99
      return *this;
100
    }
101
102
1.13k
    ~Iterator() = default;
103
104
    // NOTE the implicitly generated move constructor would
105
    // be wrong
106
107
3.22k
    KeyGroupPair<ContainerT>& operator*() {
108
3.22k
      set_key_group_pair();
109
3.22k
      return *current_key_group_pair_;
110
3.22k
    }
111
112
    KeyGroupPair<ContainerT>* operator->() {
113
      set_key_group_pair();
114
      return &*current_key_group_pair_;
115
    }
116
117
3.22k
    Iterator& operator++() {
118
3.22k
      if (!current_key_group_pair_) {
119
0
        set_key_group_pair();
120
0
      }
121
3.22k
      current_key_group_pair_.reset();
122
3.22k
      return *this;
123
3.22k
    }
124
125
    Iterator operator++(int) {
126
      auto ret = *this;
127
      ++*this;
128
      return ret;
129
    }
130
131
    template <typename T>
132
3.79k
    bool operator!=(const Iterator<T>& other) const {
133
3.79k
      return sub_iter_ != other.sub_iter_;
134
3.79k
    }
135
136
    template <typename T>
137
    bool operator==(const Iterator<T>& other) const {
138
      return !(*this != other);
139
    }
140
141
14.9k
    void increment_iterator() {
142
14.9k
      if (sub_iter_ != sub_end_) {
143
14.9k
        ++sub_iter_;
144
14.9k
        if (sub_iter_ != sub_end_) {
145
14.4k
          item_.reset(*sub_iter_);
146
14.4k
        }
147
14.9k
      }
148
14.9k
    }
149
150
14.9k
    bool exhausted() const {
151
14.9k
      return !(sub_iter_ != sub_end_);
152
14.9k
    }
153
154
    typename Holder<ContainerT>::reference get() {
155
      return item_.get();
156
    }
157
158
    typename Holder<ContainerT>::pointer get_ptr() {
159
      return item_.get_ptr();
160
    }
161
162
17.6k
    key_func_ret<ContainerT> next_key() {
163
17.6k
      return std::invoke(*key_func_, item_.get());
164
17.6k
    }
165
166
3.22k
    void set_key_group_pair() {
167
3.22k
      if (!current_key_group_pair_) {
168
3.22k
        current_key_group_pair_.emplace(std::invoke(*key_func_, item_.get()),
169
3.22k
            Group<ContainerT>{*this, next_key()});
170
3.22k
      }
171
3.22k
    }
172
  };
173
174
  template <typename ContainerT>
175
  class Group {
176
   private:
177
    template <typename>
178
    friend class Iterator;
179
    friend class GroupIterator;
180
    Iterator<ContainerT>& owner_;
181
    // The key function may return a reference, so we need to call forward, not
182
    // move, when going for efficiency.
183
    key_func_ret<ContainerT> key_;
184
185
    // completed is set if a Group is iterated through
186
    // completely.  It is checked in the destructor, and
187
    // if the Group has not been completed, the destructor
188
    // exhausts it.  This ensures that the next Group starts
189
    // at the correct position when the user short-circuits
190
    // iteration over a Group.
191
    // The move constructor sets the rvalue's completed
192
    // attribute to true, so its destructor doesn't do anything
193
    // when called.
194
    bool completed = false;
195
196
    Group(Iterator<ContainerT>& owner, key_func_ret<ContainerT> key)
197
3.22k
        : owner_(owner), key_(std::forward<key_func_ret<ContainerT>>(key)) {}
198
199
   public:
200
6.45k
    ~Group() {
201
6.45k
      if (!completed) {
202
18.2k
        for (auto iter = begin(), end_it = end(); iter != end_it; ++iter) {
203
14.9k
        }
204
3.22k
      }
205
6.45k
    }
206
207
    // move-constructible, non-copy-constructible, non-assignable
208
    Group(Group&& other) noexcept
209
3.22k
        : owner_(other.owner_),
210
3.22k
          key_{std::forward<key_func_ret<ContainerT>>(other.key_)},
211
3.22k
          completed{other.completed} {
212
3.22k
      other.completed = true;
213
3.22k
    }
214
215
    class GroupIterator {
216
     private:
217
      std::remove_reference_t<key_func_ret<ContainerT>>* key_;
218
      Group* group_p_;
219
220
14.9k
      bool not_at_end() {
221
14.9k
        return !group_p_->owner_.exhausted()
222
14.9k
               && group_p_->owner_.next_key() == *key_;
223
14.9k
      }
224
225
     public:
226
      using iterator_category = std::input_iterator_tag;
227
      using value_type = iterator_traits_deref<ContainerT>;
228
      using difference_type = std::ptrdiff_t;
229
      using pointer = value_type*;
230
      using reference = value_type&;
231
232
      // TODO template this? idk if it's relevant here
233
      GroupIterator(Group* group_p, key_func_ret<ContainerT>& key)
234
6.45k
          : key_{&key}, group_p_{group_p} {}
235
236
18.2k
      bool operator!=(const GroupIterator& other) const {
237
18.2k
        return !(*this == other);
238
18.2k
      }
239
240
18.2k
      bool operator==(const GroupIterator& other) const {
241
18.2k
        return group_p_ == other.group_p_;
242
18.2k
      }
243
244
14.9k
      GroupIterator& operator++() {
245
14.9k
        group_p_->owner_.increment_iterator();
246
14.9k
        if (!not_at_end()) {
247
3.22k
          group_p_->completed = true;
248
3.22k
          group_p_ = nullptr;
249
3.22k
        }
250
14.9k
        return *this;
251
14.9k
      }
252
253
      GroupIterator operator++(int) {
254
        auto ret = *this;
255
        ++*this;
256
        return ret;
257
      }
258
259
      iterator_deref<ContainerT> operator*() {
260
        return group_p_->owner_.get();
261
      }
262
263
      typename Holder<ContainerT>::pointer operator->() {
264
        return group_p_->owner_.get_ptr();
265
      }
266
    };
267
268
3.22k
    GroupIterator begin() {
269
3.22k
      return {this, key_};
270
3.22k
    }
271
272
3.22k
    GroupIterator end() {
273
3.22k
      return {nullptr, key_};
274
3.22k
    }
275
  };
276
277
568
  Iterator<Container> begin() {
278
568
    return {get_begin(container_), get_end(container_), key_func_};
279
568
  }
280
281
568
  Iterator<Container> end() {
282
568
    return {get_end(container_), get_end(container_), key_func_};
283
568
  }
284
285
  Iterator<AsConst<Container>> begin() const {
286
    return {get_begin(std::as_const(container_)),
287
        get_end(std::as_const(container_)), key_func_};
288
  }
289
290
  Iterator<AsConst<Container>> end() const {
291
    return {get_end(std::as_const(container_)),
292
        get_end(std::as_const(container_)), key_func_};
293
  }
294
};
295
296
#endif