Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(795)

Side by Side Diff: base/containers/flat_set.h

Issue 2552343003: First implementation of flat sets (Closed)
Patch Set: Review, round 11. Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « base/BUILD.gn ('k') | base/containers/flat_set_unittest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #ifndef BASE_CONTAINERS_FLAT_SET_H_
6 #define BASE_CONTAINERS_FLAT_SET_H_
7
8 #include <algorithm>
9 #include <functional>
10 #include <utility>
11 #include <vector>
12
13 namespace base {
14
15 // Overview:
16 // This file implements flat_set container. It is an alternative to standard
17 // sorted containers that stores it's elements in contiguous memory (current
18 // version uses sorted std::vector).
19 // Discussion that preceded introduction of this container can be found here:
20 // https://groups.google.com/a/chromium.org/forum/#!searchin/chromium-dev/vector $20based/chromium-dev/4uQMma9vj9w/HaQ-WvMOAwAJ
21 //
22 // Motivation:
23 // Contiguous memory is very beneficial to iteration and copy speed at the cost
24 // of worse algorithmic complexity of insertion/erasure operations. They can
25 // be very fast for set operations and for small number of elements.
26 //
27 // Usage guidance:
28 // Prefer base::flat_set for:
29 // * Very small sets, something that is an easy fit for cache. Consider
30 // "very small" to be under a 100 32bit integers.
31 // * Sets that are built once (using flat_set::flat_set(first, last)). Consider
32 // collecting all data in a vector and then building flat_set out of it.
33 // TODO(dyaroshev): improve the interface to better support this pattern
34 // (crbug.com/682254).
35 // * Sets where mutating happens in big bulks: use erase(std::remove()) idiom
36 // for erasing many elements. Insertion is harder - consider set operations
37 // or building a new vector. Set operations can be slow if one of the sets
38 // is considerably bigger. Also be aware that beating performance of
39 // sort + unique (implementation of flat_set's constructor) is hard, clever
40 // merge of many sets might not win. Generally avoid inserting into flat set
41 // without benchmarks.
42 // * Copying and iterating.
43 // * Set operations (union/intersect etc).
44 //
45 // TODO(dyaroshev): develop standalone benchmarks to find performance boundaries
46 // for different types of sets crbug.com/682215.
47 //
48 // If you do write a benchmark that significantly depends on using sets please
49 // share your results at:
50 // https://groups.google.com/a/chromium.org/forum/#!searchin/chromium-dev/vector $20based/chromium-dev/4uQMma9vj9w/HaQ-WvMOAwAJ
51 //
52 // Important usability aspects:
53 // * flat_set implements std::set interface from C++11 where possible. It
54 // also has reserve(), capacity() and shrink_to_fit() from std::vector.
55 // * iteration invalidation rules differ:
56 // - all cases of std::vector::iterator invalidation also apply.
57 // - we ask (for now) to assume that move operations invalidate iterators.
58 // TODO(dyaroshev): Research the possibility of using a small buffer
59 // optimization crbug.com/682240.
60 // * Constructor sorts elements in a non-stable manner (unlike std::set). So
61 // among equivalent (with respect to provided compare) elements passed to
62 // the constructor it is unspecified with one will end up in the set.
63 // However insert()/emplace() methods are stable with respect to already
64 // inserted elements - an element that is already in the set will not be
65 // replaced.
66 // * allocator support is not implemented.
67 // * insert(first, last) and insert(std::initializer_list) are not
68 // implemented (see Notes section).
69 //
70 // Notes:
71 // Current implementation is based on boost::containers::flat_set,
72 // eastl::vector_set and folly::sorted_vector. All of these implementations do
73 // insert(first, last) as insertion one by one (some implementations with hints
74 // and/or reserve). Boost documentation claims this algorithm to be O(n*log(n))
75 // but it seems to be a quadratic algorithm. For now we do not implement this
76 // method.
77 // TODO(dyaroshev): research an algorithm for range insertion crbug.com/682249.
78
79 template <class Key, class Compare = std::less<Key>>
80 // Meets the requirements of Container, AssociativeContainer,
81 // ReversibleContainer.
82 // Requires: Key is Movable, Compare is a StrictWeakOrdering on Key.
83 class flat_set {
84 private:
85 using underlying_type = std::vector<Key>;
86
87 public:
88 // --------------------------------------------------------------------------
89 // Types.
90 //
91 using key_type = Key;
92 using key_compare = Compare;
93 using value_type = Key;
94 using value_compare = Compare;
95
96 using pointer = typename underlying_type::pointer;
97 using const_pointer = typename underlying_type::const_pointer;
98 using reference = typename underlying_type::reference;
99 using const_reference = typename underlying_type::const_reference;
100 using size_type = typename underlying_type::size_type;
101 using difference_type = typename underlying_type::difference_type;
102 using iterator = typename underlying_type::iterator;
103 using const_iterator = typename underlying_type::const_iterator;
104 using reverse_iterator = typename underlying_type::reverse_iterator;
105 using const_reverse_iterator =
106 typename underlying_type::const_reverse_iterator;
107
108 // --------------------------------------------------------------------------
109 // Lifetime.
110 //
111 // Constructors that take range guarantee O(N * log^2(N)) + O(N) complexity
112 // and take O(N * log(N)) + O(N) if extra memory is available (N is a range
113 // length).
114 //
115 // Assume that move constructors invalidate iterators and references.
116
117 flat_set();
118 explicit flat_set(const Compare& comp);
119
120 template <class InputIterator>
121 flat_set(InputIterator first,
122 InputIterator last,
123 const Compare& comp = Compare());
124
125 flat_set(const flat_set&);
126 flat_set(flat_set&&);
127
128 flat_set(std::initializer_list<value_type> ilist,
129 const Compare& comp = Compare());
130
131 ~flat_set();
132
133 // --------------------------------------------------------------------------
134 // Assignments.
135 //
136 // Assume that move assignment invalidates iterators and references.
137
138 flat_set& operator=(const flat_set&);
139 flat_set& operator=(flat_set&&);
140 flat_set& operator=(std::initializer_list<value_type> ilist);
141
142 // --------------------------------------------------------------------------
143 // Memory management.
144 //
145 // Beware that shrink_to_fit() simply forwards the request to the
146 // underlying_type and its implementation is free to optimize otherwise and
147 // leave capacity() to be greater that its size.
148 //
149 // reserve() and shrink_to_fit() invalidate iterators and references.
150
151 void reserve(size_type new_capacity);
152 size_type capacity() const;
153 void shrink_to_fit();
154
155 // --------------------------------------------------------------------------
156 // Size management.
157 //
158 // clear() leaves the capacity() of the flat_set unchanged.
159
160 void clear();
161
162 size_type size() const;
163 size_type max_size() const;
164 bool empty() const;
165
166 // --------------------------------------------------------------------------
167 // Iterators.
168
169 iterator begin();
170 const_iterator begin() const;
171 const_iterator cbegin() const;
172
173 iterator end();
174 const_iterator end() const;
175 const_iterator cend() const;
176
177 reverse_iterator rbegin();
178 const_reverse_iterator rbegin() const;
179 const_reverse_iterator crbegin() const;
180
181 reverse_iterator rend();
182 const_reverse_iterator rend() const;
183 const_reverse_iterator crend() const;
184
185 // --------------------------------------------------------------------------
186 // Insert operations.
187 //
188 // Assume that every operation invalidates iterators and references.
189 // Insertion of one element can take O(size). See the Notes section in the
190 // class comments on why we do not currently implement range insertion.
191 // Capacity of flat_set grows in an implementation-defined manner.
192
193 std::pair<iterator, bool> insert(const value_type& val);
194 std::pair<iterator, bool> insert(value_type&& val);
195
196 iterator insert(const_iterator position_hint, const value_type& x);
197 iterator insert(const_iterator position_hint, value_type&& x);
198
199 template <class... Args>
200 std::pair<iterator, bool> emplace(Args&&... args);
201
202 template <class... Args>
203 iterator emplace_hint(const_iterator position_hint, Args&&... args);
204
205 // --------------------------------------------------------------------------
206 // Erase operations.
207 //
208 // Assume that every operation invalidates iterators and references.
209 //
210 // erase(position), erase(first, last) can take O(size).
211 // erase(key) may take O(size) + O(log(size)).
212 //
213 // Prefer the erase(std::remove(), end()) idiom for deleting multiple
214 // elements.
215
216 iterator erase(const_iterator position);
217 iterator erase(const_iterator first, const_iterator last);
218 size_type erase(const key_type& key);
219
220 // --------------------------------------------------------------------------
221 // Comparators.
222
223 key_compare key_comp() const;
224 value_compare value_comp() const;
225
226 // --------------------------------------------------------------------------
227 // Search operations.
228 //
229 // Search operations have O(log(size)) complexity.
230
231 size_type count(const key_type& key) const;
232
233 iterator find(const key_type& key);
234 const_iterator find(const key_type& key) const;
235
236 std::pair<iterator, iterator> equal_range(const key_type& ket);
237 std::pair<const_iterator, const_iterator> equal_range(
238 const key_type& key) const;
239
240 iterator lower_bound(const key_type& key);
241 const_iterator lower_bound(const key_type& key) const;
242
243 iterator upper_bound(const key_type& key);
244 const_iterator upper_bound(const key_type& key) const;
245
246 // --------------------------------------------------------------------------
247 // General operations.
248 //
249 // Assume that swap invalidates iterators and references.
250 //
251 // As with std::set, equality and ordering operations for the whole flat_set
252 // are equivalent to using equal() and lexicographical_compare() on the key
253 // types, rather than using element-wise key_comp() as e.g. lower_bound()
254 // does. Implementation note: currently we use operator==() and operator<() on
255 // std::vector, because they have the same contract we need, so we use them
256 // directly for brevity and in case it is more optimal than calling equal()
257 // and lexicograhpical_compare(). If the underlying container type is changed,
258 // this code may need to be modified.
259
260 void swap(flat_set& other);
261
262 friend bool operator==(const flat_set& lhs, const flat_set& rhs) {
263 return lhs.impl_.body_ == rhs.impl_.body_;
264 }
265
266 friend bool operator!=(const flat_set& lhs, const flat_set& rhs) {
267 return !(lhs == rhs);
268 }
269
270 friend bool operator<(const flat_set& lhs, const flat_set& rhs) {
271 return lhs.impl_.body_ < rhs.impl_.body_;
272 }
273
274 friend bool operator>(const flat_set& lhs, const flat_set& rhs) {
275 return rhs < lhs;
276 }
277
278 friend bool operator>=(const flat_set& lhs, const flat_set& rhs) {
279 return !(lhs < rhs);
280 }
281
282 friend bool operator<=(const flat_set& lhs, const flat_set& rhs) {
283 return !(lhs > rhs);
284 }
285
286 friend void swap(flat_set& lhs, flat_set& rhs) { lhs.swap(rhs); }
287
288 private:
289 const flat_set& as_const() { return *this; }
290
291 iterator const_cast_it(const_iterator c_it) {
292 auto distance = std::distance(cbegin(), c_it);
293 return std::next(begin(), distance);
294 }
295
296 void sort_and_unique() {
297 // std::set sorts elements preserving stability because it doesn't have any
298 // performance wins in not doing that. We do, so we use an unstable sort.
299 std::sort(begin(), end(), value_comp());
300 erase(std::unique(begin(), end(),
301 [this](const value_type& lhs, const value_type& rhs) {
302 // lhs is already <= rhs due to sort, therefore
303 // !(lhs < rhs) <=> lhs == rhs.
304 return !value_comp()(lhs, rhs);
305 }),
306 end());
307 }
308
309 // To support comparators that may not be possible to default-construct, we
310 // have to store an instance of Compare. Using this to store all internal
311 // state of flat_set and using private inheritance to store compare lets us
312 // take advantage of an empty base class optimization to avoid extra space in
313 // the common case when Compare has no state.
314 struct Impl : private Compare {
315 Impl() = default;
316
317 template <class Cmp, class... Body>
318 explicit Impl(Cmp&& compare_arg, Body&&... underlying_type_args)
319 : Compare(std::forward<Cmp>(compare_arg)),
320 body_(std::forward<Body>(underlying_type_args)...) {}
321
322 Compare compare() const { return *this; }
323
324 underlying_type body_;
325 } impl_;
326 };
327
328 // ----------------------------------------------------------------------------
329 // Lifetime.
330
331 template <class Key, class Compare>
332 flat_set<Key, Compare>::flat_set() = default;
333
334 template <class Key, class Compare>
335 flat_set<Key, Compare>::flat_set(const Compare& comp) : impl_(comp) {}
336
337 template <class Key, class Compare>
338 template <class InputIterator>
339 flat_set<Key, Compare>::flat_set(InputIterator first,
340 InputIterator last,
341 const Compare& comp)
342 : impl_(comp, first, last) {
343 sort_and_unique();
344 }
345
346 template <class Key, class Compare>
347 flat_set<Key, Compare>::flat_set(const flat_set&) = default;
348
349 template <class Key, class Compare>
350 flat_set<Key, Compare>::flat_set(flat_set&&) = default;
351
352 template <class Key, class Compare>
353 flat_set<Key, Compare>::flat_set(std::initializer_list<value_type> ilist,
354 const Compare& comp)
355 : flat_set(std::begin(ilist), std::end(ilist), comp) {}
356
357 template <class Key, class Compare>
358 flat_set<Key, Compare>::~flat_set() = default;
359
360 // ----------------------------------------------------------------------------
361 // Assignments.
362
363 template <class Key, class Compare>
364 auto flat_set<Key, Compare>::operator=(const flat_set&) -> flat_set& = default;
365
366 template <class Key, class Compare>
367 auto flat_set<Key, Compare>::operator=(flat_set &&) -> flat_set& = default;
368
369 template <class Key, class Compare>
370 auto flat_set<Key, Compare>::operator=(std::initializer_list<value_type> ilist)
371 -> flat_set& {
372 impl_.body_ = ilist;
373 sort_and_unique();
374 return *this;
375 }
376
377 // ----------------------------------------------------------------------------
378 // Memory management.
379
380 template <class Key, class Compare>
381 void flat_set<Key, Compare>::reserve(size_type new_capacity) {
382 impl_.body_.reserve(new_capacity);
383 }
384
385 template <class Key, class Compare>
386 auto flat_set<Key, Compare>::capacity() const -> size_type {
387 return impl_.body_.capacity();
388 }
389
390 template <class Key, class Compare>
391 void flat_set<Key, Compare>::shrink_to_fit() {
392 impl_.body_.shrink_to_fit();
393 }
394
395 // ----------------------------------------------------------------------------
396 // Size management.
397
398 template <class Key, class Compare>
399 void flat_set<Key, Compare>::clear() {
400 impl_.body_.clear();
401 }
402
403 template <class Key, class Compare>
404 auto flat_set<Key, Compare>::size() const -> size_type {
405 return impl_.body_.size();
406 }
407
408 template <class Key, class Compare>
409 auto flat_set<Key, Compare>::max_size() const -> size_type {
410 return impl_.body_.max_size();
411 }
412
413 template <class Key, class Compare>
414 bool flat_set<Key, Compare>::empty() const {
415 return impl_.body_.empty();
416 }
417
418 // ----------------------------------------------------------------------------
419 // Iterators.
420
421 template <class Key, class Compare>
422 auto flat_set<Key, Compare>::begin() -> iterator {
423 return impl_.body_.begin();
424 }
425
426 template <class Key, class Compare>
427 auto flat_set<Key, Compare>::begin() const -> const_iterator {
428 return impl_.body_.begin();
429 }
430
431 template <class Key, class Compare>
432 auto flat_set<Key, Compare>::cbegin() const -> const_iterator {
433 return impl_.body_.cbegin();
434 }
435
436 template <class Key, class Compare>
437 auto flat_set<Key, Compare>::end() -> iterator {
438 return impl_.body_.end();
439 }
440
441 template <class Key, class Compare>
442 auto flat_set<Key, Compare>::end() const -> const_iterator {
443 return impl_.body_.end();
444 }
445
446 template <class Key, class Compare>
447 auto flat_set<Key, Compare>::cend() const -> const_iterator {
448 return impl_.body_.cend();
449 }
450
451 template <class Key, class Compare>
452 auto flat_set<Key, Compare>::rbegin() -> reverse_iterator {
453 return impl_.body_.rbegin();
454 }
455
456 template <class Key, class Compare>
457 auto flat_set<Key, Compare>::rbegin() const -> const_reverse_iterator {
458 return impl_.body_.rbegin();
459 }
460
461 template <class Key, class Compare>
462 auto flat_set<Key, Compare>::crbegin() const -> const_reverse_iterator {
463 return impl_.body_.crbegin();
464 }
465
466 template <class Key, class Compare>
467 auto flat_set<Key, Compare>::rend() -> reverse_iterator {
468 return impl_.body_.rend();
469 }
470
471 template <class Key, class Compare>
472 auto flat_set<Key, Compare>::rend() const -> const_reverse_iterator {
473 return impl_.body_.rend();
474 }
475
476 template <class Key, class Compare>
477 auto flat_set<Key, Compare>::crend() const -> const_reverse_iterator {
478 return impl_.body_.crend();
479 }
480
481 // ----------------------------------------------------------------------------
482 // Insert operations.
483 //
484 // Currently we use position_hint the same way as eastl or boost:
485 // https://github.com/electronicarts/EASTL/blob/master/include/EASTL/vector_set. h#L493
486 //
487 // We duplicate code between copy and move version so that we can avoid
488 // creating a temporary value.
489
490 template <class Key, class Compare>
491 auto flat_set<Key, Compare>::insert(const value_type& val)
492 -> std::pair<iterator, bool> {
493 auto position = lower_bound(val);
494
495 if (position == end() || value_comp()(val, *position))
496 return {impl_.body_.insert(position, val), true};
497
498 return {position, false};
499 }
500
501 template <class Key, class Compare>
502 auto flat_set<Key, Compare>::insert(value_type&& val)
503 -> std::pair<iterator, bool> {
504 auto position = lower_bound(val);
505
506 if (position == end() || value_comp()(val, *position))
507 return {impl_.body_.insert(position, std::move(val)), true};
508
509 return {position, false};
510 }
511
512 template <class Key, class Compare>
513 auto flat_set<Key, Compare>::insert(const_iterator position_hint,
514 const value_type& val) -> iterator {
515 if (position_hint == end() || value_comp()(val, *position_hint)) {
516 if (position_hint == begin() || value_comp()(*(position_hint - 1), val))
517 // We have to cast away const because of crbug.com/677044.
518 return impl_.body_.insert(const_cast_it(position_hint), val);
519 }
520 return insert(val).first;
521 }
522
523 template <class Key, class Compare>
524 auto flat_set<Key, Compare>::insert(const_iterator position_hint,
525 value_type&& val) -> iterator {
526 if (position_hint == end() || value_comp()(val, *position_hint)) {
527 if (position_hint == begin() || value_comp()(*(position_hint - 1), val))
528 // We have to cast away const because of crbug.com/677044.
529 return impl_.body_.insert(const_cast_it(position_hint), std::move(val));
530 }
531 return insert(std::move(val)).first;
532 }
533
534 template <class Key, class Compare>
535 template <class... Args>
536 auto flat_set<Key, Compare>::emplace(Args&&... args)
537 -> std::pair<iterator, bool> {
538 return insert(value_type(std::forward<Args>(args)...));
539 }
540
541 template <class Key, class Compare>
542 template <class... Args>
543 auto flat_set<Key, Compare>::emplace_hint(const_iterator position_hint,
544 Args&&... args) -> iterator {
545 return insert(position_hint, value_type(std::forward<Args>(args)...));
546 }
547
548 // ----------------------------------------------------------------------------
549 // Erase operations.
550
551 template <class Key, class Compare>
552 auto flat_set<Key, Compare>::erase(const_iterator position) -> iterator {
553 // We have to cast away const because of crbug.com/677044.
554 return impl_.body_.erase(const_cast_it(position));
555 }
556
557 template <class Key, class Compare>
558 auto flat_set<Key, Compare>::erase(const key_type& val) -> size_type {
559 auto eq_range = equal_range(val);
560 auto res = std::distance(eq_range.first, eq_range.second);
561 // We have to cast away const because of crbug.com/677044.
562 erase(const_cast_it(eq_range.first), const_cast_it(eq_range.second));
563 return res;
564 }
565
566 template <class Key, class Compare>
567 auto flat_set<Key, Compare>::erase(const_iterator first, const_iterator last)
568 -> iterator {
569 // We have to cast away const because of crbug.com/677044.
570 return impl_.body_.erase(const_cast_it(first), const_cast_it(last));
571 }
572
573 // ----------------------------------------------------------------------------
574 // Comparators.
575
576 template <class Key, class Compare>
577 auto flat_set<Key, Compare>::key_comp() const -> key_compare {
578 return impl_.compare();
579 }
580
581 template <class Key, class Compare>
582 auto flat_set<Key, Compare>::value_comp() const -> value_compare {
583 return impl_.compare();
584 }
585
586 // ----------------------------------------------------------------------------
587 // Search operations.
588
589 template <class Key, class Compare>
590 auto flat_set<Key, Compare>::count(const key_type& key) const -> size_type {
591 auto eq_range = equal_range(key);
592 return std::distance(eq_range.first, eq_range.second);
593 }
594
595 template <class Key, class Compare>
596 auto flat_set<Key, Compare>::find(const key_type& key) -> iterator {
597 return const_cast_it(as_const().find(key));
598 }
599
600 template <class Key, class Compare>
601 auto flat_set<Key, Compare>::find(const key_type& key) const -> const_iterator {
602 auto eq_range = equal_range(key);
603 return (eq_range.first == eq_range.second) ? end() : eq_range.first;
604 }
605
606 template <class Key, class Compare>
607 auto flat_set<Key, Compare>::equal_range(const key_type& key)
608 -> std::pair<iterator, iterator> {
609 auto res = as_const().equal_range(key);
610 return {const_cast_it(res.first), const_cast_it(res.second)};
611 }
612
613 template <class Key, class Compare>
614 auto flat_set<Key, Compare>::equal_range(const key_type& key) const
615 -> std::pair<const_iterator, const_iterator> {
616 auto lower = lower_bound(key);
617
618 if (lower == end() || key_comp()(key, *lower))
619 return {lower, lower};
620
621 return {lower, std::next(lower)};
622 }
623
624 template <class Key, class Compare>
625 auto flat_set<Key, Compare>::lower_bound(const key_type& key) -> iterator {
626 return const_cast_it(as_const().lower_bound(key));
627 }
628
629 template <class Key, class Compare>
630 auto flat_set<Key, Compare>::lower_bound(const key_type& key) const
631 -> const_iterator {
632 return std::lower_bound(begin(), end(), key, key_comp());
633 }
634
635 template <class Key, class Compare>
636 auto flat_set<Key, Compare>::upper_bound(const key_type& key) -> iterator {
637 return const_cast_it(as_const().upper_bound(key));
638 }
639
640 template <class Key, class Compare>
641 auto flat_set<Key, Compare>::upper_bound(const key_type& key) const
642 -> const_iterator {
643 return std::upper_bound(begin(), end(), key, key_comp());
644 }
645
646 // ----------------------------------------------------------------------------
647 // General operations.
648
649 template <class Key, class Compare>
650 void flat_set<Key, Compare>::swap(flat_set& other) {
651 std::swap(impl_, other.impl_);
652 }
653
654 } // namespace base
655
656 #endif // BASE_CONTAINERS_FLAT_SET_H_
OLDNEW
« no previous file with comments | « base/BUILD.gn ('k') | base/containers/flat_set_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698