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

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

Issue 2552343003: First implementation of flat sets (Closed)
Patch Set: Rewiew, round 7. 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 // Unless you have good benchmarks prefer base::flat_set over std::set for sets
29 // that are built once and then practically never mutate.
30 // TODO(dyaroshev): develop standalone benchmarks to find performance boundaries
31 // for different types performance of different sets crbug.com/682215.
dyaroshev 2017/01/30 14:10:58 for different types of sets.
32 //
33 // Important usability aspects:
34 // * flat_set implements std::set interface from C++11 where possible. It
35 // also has reserve(), capacity() and shrink_to_fit() from std::vector.
36 // * iteration invalidation rules differ:
37 // - all cases of std::vector::iterator invalidation also apply
38 // - we ask (for now) to assume that move operations invalidate iterators.
39 // TODO(dyaroshev): Research the possibility of using a small buffer
40 // optimization crbug.com/682240.
41 // * allocator support is not implemented.
42 // * insert(first, last) and insert(std::initializer_list) are not
43 // implemented (see Notes section).
44 //
45 // Notes:
46 // Current implementation is based on boost::containers::flat_set,
47 // eastl::vector_set and folly::sorted_vector. All of these implementations do
48 // insert(first, last) as insertion one by one (some implementations with hints
49 // and/or reserve). Boost documentation claims this algorithm to be O(n*log(n))
50 // but it seems to be a quadratic algorithm. For now we do not implement this
51 // method.
52 // TODO(dyaroshev): research an algorithm for range insertion crbug.com/682249.
53
54 template <class Key, class Compare = std::less<Key>>
55 // Meets the requirements of Container, AssociativeContainer,
56 // ReversibleContainer.
danakj 2017/01/30 20:24:31 just wrap lines normally
dyaroshev 2017/01/31 23:08:25 Done.
57 // Requires:
58 // Key is Movable
59 // Compare is a StrictWeakOrdering on Key
60 class flat_set {
61 private:
62 using underlying_type = std::vector<Key>;
63
64 public:
65 // --------------------------------------------------------------------------
66 // Types.
67 //
68 using key_type = Key;
69 using key_compare = Compare;
70 using value_type = Key;
71 using value_compare = Compare;
72
73 using pointer = typename underlying_type::pointer;
74 using const_pointer = typename underlying_type::const_pointer;
75 using reference = typename underlying_type::reference;
76 using const_reference = typename underlying_type::const_reference;
77 using size_type = typename underlying_type::size_type;
78 using difference_type = typename underlying_type::difference_type;
79 using iterator = typename underlying_type::iterator;
80 using const_iterator = typename underlying_type::const_iterator;
81 using reverse_iterator = typename underlying_type::reverse_iterator;
82 using const_reverse_iterator =
83 typename underlying_type::const_reverse_iterator;
84
85 // --------------------------------------------------------------------------
86 // Lifetime.
87 //
88 // Constructors that take range guarantee O(N * log^2(N)) + O(N) complexity
89 // and take O(N * log(N)) + O(N) if extra memory is available (N is a range
90 // length).
91 //
92 // Assume that move constructors invalidate iterators and references.
93
94 flat_set();
95 explicit flat_set(const Compare& comp);
96
97 template <class InputIterator>
98 flat_set(InputIterator first,
99 InputIterator last,
100 const Compare& comp = Compare());
101
102 flat_set(const flat_set&);
103 flat_set(flat_set&&);
104
105 flat_set(std::initializer_list<value_type> ilist,
106 const Compare& comp = Compare());
107
108 ~flat_set();
109
110 // --------------------------------------------------------------------------
111 // Assignments.
112 //
113 // Assume that move assignment invalidates iterators and references.
114
115 flat_set& operator=(const flat_set&);
116 flat_set& operator=(flat_set&&);
117 flat_set& operator=(std::initializer_list<value_type> ilist);
118
119 // --------------------------------------------------------------------------
120 // Memory management.
121 //
122 // Beware that shrink_to_fit() simply forwards the request to the
123 // underlying_type and its implementation is free to optimize otherwise and
124 // leave capacity() to be greater that its size.
125 //
126 // reserve() and shrink_to_fit() invalidate iterators and references.
127
128 void reserve(size_type new_capacity);
129 size_type capacity() const;
130 void shrink_to_fit();
131
132 // --------------------------------------------------------------------------
133 // Size management.
134 //
135 // clear() leaves the capacity() of the flat_set unchanged.
136
137 void clear();
138
139 size_type size() const;
140 size_type max_size() const;
141 bool empty() const;
142
143 // --------------------------------------------------------------------------
144 // Iterators.
145
146 iterator begin();
147 const_iterator begin() const;
148 const_iterator cbegin() const;
149
150 iterator end();
151 const_iterator end() const;
152 const_iterator cend() const;
153
154 reverse_iterator rbegin();
155 const_reverse_iterator rbegin() const;
156 const_reverse_iterator crbegin() const;
157
158 reverse_iterator rend();
159 const_reverse_iterator rend() const;
160 const_reverse_iterator crend() const;
161
162 // --------------------------------------------------------------------------
163 // Insert operations.
164 //
165 // Assume that every operation invalidates iterators and references.
166 // Insertion of one element can take O(size). See the Notes section in the
167 // class comments on why we do not currently implement range insertion.
168 // Capacity of flat_set grows in an implementation-defined manner.
169
170 std::pair<iterator, bool> insert(const value_type& val);
171 std::pair<iterator, bool> insert(value_type&& val);
172
173 iterator insert(const_iterator position_hint, const value_type& x);
174 iterator insert(const_iterator position_hint, value_type&& x);
175
176 template <class... Args>
177 std::pair<iterator, bool> emplace(Args&&... args);
178
179 template <class... Args>
180 iterator emplace_hint(const_iterator position_hint, Args&&... args);
181
182 // --------------------------------------------------------------------------
183 // Erase operations.
184 //
185 // Assume that every operation invalidates iterators and references.
186 //
187 // erase(position), erase(first, last) can take O(size).
188 // erase(key) may take O(size) + O(log(size)).
189 //
190 // Prefer the erase(std::remove(), end()) idiom for deleting multiple
191 // elements.
192
193 iterator erase(const_iterator position);
194 iterator erase(const_iterator first, const_iterator last);
195 size_type erase(const key_type& key);
196
197 // --------------------------------------------------------------------------
198 // Comparators.
199
200 key_compare key_comp() const;
201 value_compare value_comp() const;
202
203 // --------------------------------------------------------------------------
204 // Search operations.
205 //
206 // Search operations have O(log(size)) complexity.
207
208 size_type count(const key_type& key) const;
209
210 iterator find(const key_type& key);
211 const_iterator find(const key_type& key) const;
212
213 std::pair<iterator, iterator> equal_range(const key_type& ket);
214 std::pair<const_iterator, const_iterator> equal_range(
215 const key_type& key) const;
216
217 iterator lower_bound(const key_type& key);
218 const_iterator lower_bound(const key_type& key) const;
219
220 iterator upper_bound(const key_type& key);
221 const_iterator upper_bound(const key_type& key) const;
222
223 // --------------------------------------------------------------------------
224 // General operations.
225 //
226 // Assume that swap invalidates iterators and references.
227 //
228 // As with std::set, equality and ordering operations for the whole flat_set
229 // are equivalent to using equal() and lexicographical_compare() on the key
230 // types, rather than using element-wise key_comp() as e.g. lower_bound()
231 // does. Implementation note: currently we use operator==() and operator<() on
232 // std::vector, because they have the same contract we need, so we use them
233 // directly for brevity and in case it is more optimal than calling equal()
234 // and lexicograhpical_compare(). If the underlying container type is changed,
235 // this code may need to be modified.
236
237 void swap(flat_set& other);
238
239 friend bool operator==(const flat_set& lhs, const flat_set& rhs) {
240 return lhs.impl_.body_ == rhs.impl_.body_;
241 }
242
243 friend bool operator!=(const flat_set& lhs, const flat_set& rhs) {
244 return !operator==(lhs, rhs);
245 }
246
247 friend bool operator<(const flat_set& lhs, const flat_set& rhs) {
248 return lhs.impl_.body_ < rhs.impl_.body_;
249 }
250
251 friend bool operator>(const flat_set& lhs, const flat_set& rhs) {
252 return rhs < lhs;
253 }
254
255 friend bool operator>=(const flat_set& lhs, const flat_set& rhs) {
256 return !(lhs < rhs);
257 }
258
259 friend bool operator<=(const flat_set& lhs, const flat_set& rhs) {
260 return !(lhs > rhs);
261 }
262
263 friend void swap(flat_set& lhs, flat_set& rhs) { lhs.swap(rhs); }
264
265 private:
266 const flat_set& as_const() { return *this; }
267
268 iterator const_cast_it(const_iterator c_it) {
269 auto distance = std::distance(cbegin(), c_it);
270 return std::next(begin(), distance);
271 }
272
273 void sort_and_unique() {
274 // std::set sorts elements preserving stability. If we were to use unstable
275 // sort this could introduce hard to diagnose bugs.
276 std::stable_sort(begin(), end(), value_comp());
dyaroshev 2017/01/30 00:07:13 Stability bugs me, I would like your opinion on th
danakj 2017/01/30 20:28:38 I don't think we need to preserve ordering. I woul
dyaroshev 2017/01/31 23:08:25 Done.
277 erase(std::unique(begin(), end(),
278 [this](const value_type& lhs, const value_type& rhs) {
279 // lhs is already <= rhs due to sort, therefore !(lhs <
280 // rhs) <=>
danakj 2017/01/30 20:24:31 dont line wrap here?
dyaroshev 2017/01/31 23:08:25 Done.
281 // lhs == rhs.
282 return !value_comp()(lhs, rhs);
283 }),
284 end());
285 }
286
287 // To support comparators that may not be possible to default-construct, we
288 // have to store an instance of Compare. Putting all internal state of
danakj 2017/01/30 20:24:31 "Putting all internal state" => "Using this to sto
dyaroshev 2017/01/31 23:08:25 Done.
289 // flat_set and using private inheritance to store compare lets us take
290 // advantage of an empty base class optimization to avoid extra space in the
291 // common case when Compare has no state.
292 struct Impl : Compare {
danakj 2017/01/30 20:24:31 make private explicit
dyaroshev 2017/01/31 23:08:25 Done. Had to do a few modifications - it wasn't p
293 Impl() = default;
294
295 template <class Cmp, class... Body>
296 explicit Impl(Cmp&& compare_arg, Body&&... underlying_type_args)
297 : Compare(std::forward<Cmp>(compare_arg)),
298 body_(std::forward<Body>(underlying_type_args)...) {}
299
300 underlying_type body_;
301 } impl_;
302 };
303
304 // ----------------------------------------------------------------------------
305 // Lifetime.
306
307 template <class Key, class Compare>
308 flat_set<Key, Compare>::flat_set() = default;
309
310 template <class Key, class Compare>
311 flat_set<Key, Compare>::flat_set(const Compare& comp) : impl_(comp) {}
312
313 template <class Key, class Compare>
314 template <class InputIterator>
315 flat_set<Key, Compare>::flat_set(InputIterator first,
316 InputIterator last,
317 const Compare& comp)
318 : impl_(comp, first, last) {
319 sort_and_unique();
320 }
321
322 template <class Key, class Compare>
323 flat_set<Key, Compare>::flat_set(const flat_set&) = default;
324
325 template <class Key, class Compare>
326 flat_set<Key, Compare>::flat_set(flat_set&&) = default;
327
328 template <class Key, class Compare>
329 flat_set<Key, Compare>::flat_set(std::initializer_list<value_type> ilist,
330 const Compare& comp)
331 : flat_set(std::begin(ilist), std::end(ilist), comp) {}
332
333 template <class Key, class Compare>
334 flat_set<Key, Compare>::~flat_set() = default;
335
336 // ----------------------------------------------------------------------------
337 // Assignments.
338
339 template <class Key, class Compare>
340 auto flat_set<Key, Compare>::operator=(const flat_set&) -> flat_set& = default;
341
342 template <class Key, class Compare>
343 auto flat_set<Key, Compare>::operator=(flat_set &&) -> flat_set& = default;
danakj 2017/01/30 20:24:31 no space before &&
dyaroshev 2017/01/31 23:08:25 Done.
danakj 2017/02/03 20:30:49 Looks to be missed. Is clang-format doing this?
dyaroshev 2017/02/04 00:59:15 Yeah, sorry. Want me to disable it?
Peter Kasting 2017/02/04 01:03:35 That sounds like a clang-fmt bug (it sounds like i
dcheng 2017/02/04 05:57:18 I filed https://llvm.org/bugs/show_bug.cgi?id=3186
344
345 template <class Key, class Compare>
346 auto flat_set<Key, Compare>::operator=(std::initializer_list<value_type> ilist)
347 -> flat_set& {
348 impl_.body_ = ilist;
349 sort_and_unique();
350 return *this;
351 }
352
353 // ----------------------------------------------------------------------------
354 // Memory management.
355
356 template <class Key, class Compare>
357 void flat_set<Key, Compare>::reserve(size_type new_capacity) {
358 impl_.body_.reserve(new_capacity);
359 }
360
361 template <class Key, class Compare>
362 auto flat_set<Key, Compare>::capacity() const -> size_type {
363 return impl_.body_.capacity();
364 }
365
366 template <class Key, class Compare>
367 void flat_set<Key, Compare>::shrink_to_fit() {
368 impl_.body_.shrink_to_fit();
369 }
370
371 // ----------------------------------------------------------------------------
372 // Size management.
373
374 template <class Key, class Compare>
375 void flat_set<Key, Compare>::clear() {
376 impl_.body_.clear();
377 }
378
379 template <class Key, class Compare>
380 auto flat_set<Key, Compare>::size() const -> size_type {
381 return impl_.body_.size();
382 }
383
384 template <class Key, class Compare>
385 auto flat_set<Key, Compare>::max_size() const -> size_type {
386 return impl_.body_.max_size();
387 }
388
389 template <class Key, class Compare>
390 bool flat_set<Key, Compare>::empty() const {
391 return impl_.body_.empty();
392 }
393
394 // ----------------------------------------------------------------------------
395 // Iterators.
396
397 template <class Key, class Compare>
398 auto flat_set<Key, Compare>::begin() -> iterator {
399 return impl_.body_.begin();
400 }
401
402 template <class Key, class Compare>
403 auto flat_set<Key, Compare>::begin() const -> const_iterator {
404 return impl_.body_.begin();
405 }
406
407 template <class Key, class Compare>
408 auto flat_set<Key, Compare>::cbegin() const -> const_iterator {
409 return impl_.body_.cbegin();
410 }
411
412 template <class Key, class Compare>
413 auto flat_set<Key, Compare>::end() -> iterator {
414 return impl_.body_.end();
415 }
416
417 template <class Key, class Compare>
418 auto flat_set<Key, Compare>::end() const -> const_iterator {
419 return impl_.body_.end();
420 }
421
422 template <class Key, class Compare>
423 auto flat_set<Key, Compare>::cend() const -> const_iterator {
424 return impl_.body_.cend();
425 }
426
427 template <class Key, class Compare>
428 auto flat_set<Key, Compare>::rbegin() -> reverse_iterator {
429 return impl_.body_.rbegin();
430 }
431
432 template <class Key, class Compare>
433 auto flat_set<Key, Compare>::rbegin() const -> const_reverse_iterator {
434 return impl_.body_.rbegin();
435 }
436
437 template <class Key, class Compare>
438 auto flat_set<Key, Compare>::crbegin() const -> const_reverse_iterator {
439 return impl_.body_.crbegin();
440 }
441
442 template <class Key, class Compare>
443 auto flat_set<Key, Compare>::rend() -> reverse_iterator {
444 return impl_.body_.rend();
445 }
446
447 template <class Key, class Compare>
448 auto flat_set<Key, Compare>::rend() const -> const_reverse_iterator {
449 return impl_.body_.rend();
450 }
451
452 template <class Key, class Compare>
453 auto flat_set<Key, Compare>::crend() const -> const_reverse_iterator {
454 return impl_.body_.crend();
455 }
456
457 // ----------------------------------------------------------------------------
458 // Insert operations.
459 //
460 // Currently we use hint the same way as eastl or boost:
danakj 2017/01/30 20:24:31 we use the position hint
dyaroshev 2017/01/31 23:08:25 Done.
461 // https://github.com/electronicarts/EASTL/blob/master/include/EASTL/vector_set. h#L493
462 //
463 // We duplicate code between copy and move version so that we can avoid
464 // creating a temporary value.
465
466 template <class Key, class Compare>
467 auto flat_set<Key, Compare>::insert(const value_type& val)
468 -> std::pair<iterator, bool> {
469 auto position = lower_bound(val);
470
471 if (position == end() || value_comp()(val, *position))
472 return {impl_.body_.insert(position, val), true};
473
474 return {position, false};
475 }
476
477 template <class Key, class Compare>
478 auto flat_set<Key, Compare>::insert(value_type&& val)
479 -> std::pair<iterator, bool> {
480 auto position = lower_bound(val);
481
482 if (position == end() || value_comp()(val, *position))
483 return {impl_.body_.insert(position, std::move(val)), true};
484
485 return {position, false};
486 }
487
488 template <class Key, class Compare>
489 auto flat_set<Key, Compare>::insert(const_iterator position_hint,
490 const value_type& val) -> iterator {
491 if (position_hint == end() || value_comp()(val, *position_hint)) {
492 if (position_hint == begin() || value_comp()(*(position_hint - 1), val))
493 // We have to cast away const because of crbug.com/677044.
494 return impl_.body_.insert(const_cast_it(position_hint), val);
495 }
496 return insert(val).first;
497 }
498
499 template <class Key, class Compare>
500 auto flat_set<Key, Compare>::insert(const_iterator position_hint,
501 value_type&& val) -> iterator {
502 if (position_hint == end() || value_comp()(val, *position_hint)) {
503 if (position_hint == begin() || value_comp()(*(position_hint - 1), val))
504 // We have to cast away const because of crbug.com/677044.
505 return impl_.body_.insert(const_cast_it(position_hint), std::move(val));
506 }
507 return insert(std::move(val)).first;
508 }
509
510 template <class Key, class Compare>
511 template <class... Args>
512 auto flat_set<Key, Compare>::emplace(Args&&... args)
513 -> std::pair<iterator, bool> {
514 return insert(value_type(std::forward<Args>(args)...));
515 }
516
517 template <class Key, class Compare>
518 template <class... Args>
519 auto flat_set<Key, Compare>::emplace_hint(const_iterator position_hint,
520 Args&&... args) -> iterator {
521 return insert(position_hint, value_type(std::forward<Args>(args)...));
522 }
523
524 // ----------------------------------------------------------------------------
525 // Erase operations.
526
527 template <class Key, class Compare>
528 auto flat_set<Key, Compare>::erase(const_iterator position) -> iterator {
529 // We have to cast away const because of crbug.com/677044.
530 return impl_.body_.erase(const_cast_it(position));
531 }
532
533 template <class Key, class Compare>
534 auto flat_set<Key, Compare>::erase(const key_type& val) -> size_type {
535 auto eq_range = equal_range(val);
536 auto res = std::distance(eq_range.first, eq_range.second);
537 // We have to cast away const because of crbug.com/677044.
538 erase(const_cast_it(eq_range.first), const_cast_it(eq_range.second));
539 return res;
540 }
541
542 template <class Key, class Compare>
543 auto flat_set<Key, Compare>::erase(const_iterator first, const_iterator last)
544 -> iterator {
545 // We have to cast away const because of crbug.com/677044.
546 return impl_.body_.erase(const_cast_it(first), const_cast_it(last));
547 }
548
549 // ----------------------------------------------------------------------------
550 // Comparators.
551
552 template <class Key, class Compare>
553 auto flat_set<Key, Compare>::key_comp() const -> key_compare {
554 return key_compare(impl_);
555 }
556
557 template <class Key, class Compare>
558 auto flat_set<Key, Compare>::value_comp() const -> value_compare {
559 return value_compare(impl_);
560 }
561
562 // ----------------------------------------------------------------------------
563 // Search operations.
564
565 template <class Key, class Compare>
566 auto flat_set<Key, Compare>::count(const key_type& key) const -> size_type {
567 auto eq_range = equal_range(key);
568 return std::distance(eq_range.first, eq_range.second);
569 }
570
571 template <class Key, class Compare>
572 auto flat_set<Key, Compare>::find(const key_type& key) -> iterator {
573 return const_cast_it(as_const().find(key));
574 }
575
576 template <class Key, class Compare>
577 auto flat_set<Key, Compare>::find(const key_type& key) const -> const_iterator {
578 auto eq_range = equal_range(key);
579 return (eq_range.first == eq_range.second) ? end() : eq_range.first;
580 }
581
582 template <class Key, class Compare>
583 auto flat_set<Key, Compare>::equal_range(const key_type& key)
584 -> std::pair<iterator, iterator> {
585 auto res = as_const().equal_range(key);
586 return {const_cast_it(res.first), const_cast_it(res.second)};
587 }
588
589 template <class Key, class Compare>
590 auto flat_set<Key, Compare>::equal_range(const key_type& key) const
591 -> std::pair<const_iterator, const_iterator> {
592 auto lower = lower_bound(key);
593
594 if (lower == end() || key_comp()(key, *lower))
595 return {lower, lower};
596
597 return {lower, std::next(lower)};
598 }
599
600 template <class Key, class Compare>
601 auto flat_set<Key, Compare>::lower_bound(const key_type& key) -> iterator {
602 return const_cast_it(as_const().lower_bound(key));
603 }
604
605 template <class Key, class Compare>
606 auto flat_set<Key, Compare>::lower_bound(const key_type& key) const
607 -> const_iterator {
608 return std::lower_bound(begin(), end(), key, key_comp());
609 }
610
611 template <class Key, class Compare>
612 auto flat_set<Key, Compare>::upper_bound(const key_type& key) -> iterator {
613 return const_cast_it(as_const().upper_bound(key));
614 }
615
616 template <class Key, class Compare>
617 auto flat_set<Key, Compare>::upper_bound(const key_type& key) const
618 -> const_iterator {
619 return std::upper_bound(begin(), end(), key, key_comp());
620 }
621
622 // ----------------------------------------------------------------------------
623 // General operations.
624
625 template <class Key, class Compare>
626 void flat_set<Key, Compare>::swap(flat_set& other) {
627 std::swap(impl_, other.impl_);
628 }
629
630 } // namespace base
631
632 #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