Skip to content

Commit 0474926

Browse files
committed
flat_map simplified, more tests
1 parent 0771583 commit 0474926

3 files changed

Lines changed: 369 additions & 157 deletions

File tree

container/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ Defines `flat_map` and `flat_set`, sorted associative containers backed by vecto
1212
values in separate vectors and exposes pair-like proxy iterators with `first`/`second` and `key()`/`value()` access.
1313
Both containers support ordinary insertion, merging from a sorted range, and batched unsorted appends followed
1414
by tail sorting and merging. Existing entries and the first newly inserted entry win duplicate keys. Key equality uses
15-
`operator==` when the compared types provide it, otherwise comparator equivalence; equal keys must also be equivalent
16-
under the comparator. Batch entries are added with `append_unsorted()`; ordered operations and iteration must not be
17-
used between `begin_batch()` and `end_batch()`.
15+
`operator==` when the compared types provide it, otherwise comparator equivalence. When both operations are available,
16+
they must define the same equivalence. Batch entries are added with `append_unsorted()`; ordered operations and
17+
iteration must not be used between `begin_batch()` and `end_batch()`.
1818
Map dereference returns its proxy by value, so `auto entry` and `const auto& entry` work in range loops but `auto& entry`
1919
does not. Read-only standard algorithms and construction of ordinary pair containers are supported; algorithms that
2020
reorder entries are intentionally ill-formed because keys are immutable.

container/flat_map.hpp

Lines changed: 79 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,21 @@ namespace FlatContainerInternal {
2121
};
2222

2323
template <typename Left, typename Right, typename Compare>
24-
[[nodiscard]] bool keys_equal(const Left& left, const Right& right, const Compare& compare)
24+
[[nodiscard]] bool sorted_keys_equal(const Left& left, const Right& right, [[maybe_unused]] const Compare& compare)
2525
{
2626
if constexpr (EqualityComparable<Left, Right>)
2727
return left == right;
2828
else
29-
return !compare(left, right) && !compare(right, left);
29+
return !compare(left, right); // Sortedness already established !(right < left)
3030
}
3131

3232
template <typename Left, typename Right, typename Compare>
33-
[[nodiscard]] bool ordering_equivalent(const Left& left, const Right& right, const Compare& compare)
33+
[[nodiscard]] bool lower_bound_matches(const Left& stored_key, const Right& query, [[maybe_unused]] const Compare& compare)
3434
{
35-
return !compare(left, right) && !compare(right, left);
35+
if constexpr (EqualityComparable<Left, Right>)
36+
return stored_key == query;
37+
else
38+
return !compare(query, stored_key); // lower_bound already established !(stored_key < query)
3639
}
3740

3841
template <typename Key, typename Mapped, typename MappedReference>
@@ -323,6 +326,8 @@ class flat_map
323326
return { position, inserted };
324327
}
325328

329+
iterator erase(iterator position) { return erase(const_iterator(position)); }
330+
326331
iterator erase(const_iterator position)
327332
{
328333
assert_not_batching();
@@ -437,25 +442,19 @@ class flat_map
437442
template <typename Query>
438443
[[nodiscard]] size_type find_index(const Query& key) const
439444
{
440-
const auto first = lower_bound_index(key);
441-
const auto last = upper_bound_index(key);
442-
for (auto index = first; index < last; ++index) {
443-
if (FlatContainerInternal::keys_equal(_keys[index], key, _compare))
444-
return index;
445-
}
445+
const auto index = lower_bound_index(key);
446+
if (index < size() && FlatContainerInternal::lower_bound_matches(_keys[index], key, _compare))
447+
return index;
446448
return size();
447449
}
448450

449451
template <typename Query>
450452
[[nodiscard]] std::pair<size_type, size_type> find_or_insertion_index(const Query& key) const
451453
{
452-
const auto first = lower_bound_index(key);
453-
const auto last = upper_bound_index(key);
454-
for (auto index = first; index < last; ++index) {
455-
if (FlatContainerInternal::keys_equal(_keys[index], key, _compare))
456-
return { index, last };
457-
}
458-
return { size(), last };
454+
const auto index = lower_bound_index(key);
455+
if (index < size() && FlatContainerInternal::lower_bound_matches(_keys[index], key, _compare))
456+
return { index, index };
457+
return { size(), index };
459458
}
460459

461460
void merge_sorted(std::vector<Key>&& incoming_keys, std::vector<Mapped>&& incoming_values)
@@ -469,73 +468,50 @@ class flat_map
469468
merged_keys.reserve(size() + incoming_keys.size());
470469
merged_values.reserve(size() + incoming_values.size());
471470

472-
auto append_existing_group = [&](size_type first, size_type last) {
473-
for (auto index = first; index < last; ++index) {
474-
merged_keys.emplace_back(std::move(_keys[index]));
475-
merged_values.emplace_back(std::move(_values[index]));
476-
}
471+
auto append_existing = [&](size_type index) {
472+
merged_keys.emplace_back(std::move(_keys[index]));
473+
merged_values.emplace_back(std::move(_values[index]));
477474
};
478-
auto append_incoming_group = [&](size_type first, size_type last, size_type merged_group_start) {
479-
for (auto index = first; index < last; ++index) {
480-
const auto duplicate = std::any_of(merged_keys.begin() + static_cast<difference_type>(merged_group_start), merged_keys.end(),
481-
[&](const Key& key) { return FlatContainerInternal::keys_equal(key, incoming_keys[index], _compare); });
482-
if (!duplicate) {
483-
merged_keys.emplace_back(std::move(incoming_keys[index]));
484-
merged_values.emplace_back(std::move(incoming_values[index]));
485-
}
475+
auto append_incoming = [&](size_type index) {
476+
if (merged_keys.empty() || !FlatContainerInternal::sorted_keys_equal(merged_keys.back(), incoming_keys[index], _compare)) {
477+
merged_keys.emplace_back(std::move(incoming_keys[index]));
478+
merged_values.emplace_back(std::move(incoming_values[index]));
486479
}
487480
};
488481

489482
auto existing_index = size_type{ 0 };
490483
auto incoming_index = size_type{ 0 };
491-
while (existing_index < size() || incoming_index < incoming_keys.size()) {
492-
if (existing_index == size()) {
493-
const auto incoming_group_end = ordering_group_end(incoming_keys, incoming_index);
494-
const auto merged_group_start = merged_keys.size();
495-
append_incoming_group(incoming_index, incoming_group_end, merged_group_start);
496-
incoming_index = incoming_group_end;
497-
continue;
498-
}
499-
if (incoming_index == incoming_keys.size()) {
500-
const auto existing_group_end = ordering_group_end(_keys, existing_index);
501-
append_existing_group(existing_index, existing_group_end);
502-
existing_index = existing_group_end;
503-
continue;
504-
}
505-
506-
if (_compare(_keys[existing_index], incoming_keys[incoming_index])) {
507-
const auto existing_group_end = ordering_group_end(_keys, existing_index);
508-
append_existing_group(existing_index, existing_group_end);
509-
existing_index = existing_group_end;
510-
} else if (_compare(incoming_keys[incoming_index], _keys[existing_index])) {
511-
const auto incoming_group_end = ordering_group_end(incoming_keys, incoming_index);
512-
const auto merged_group_start = merged_keys.size();
513-
append_incoming_group(incoming_index, incoming_group_end, merged_group_start);
514-
incoming_index = incoming_group_end;
484+
while (existing_index < size() && incoming_index < incoming_keys.size()) {
485+
if constexpr (FlatContainerInternal::EqualityComparable<Key, Key>) {
486+
if (_keys[existing_index] == incoming_keys[incoming_index]) {
487+
append_existing(existing_index++);
488+
++incoming_index;
489+
} else if (_compare(_keys[existing_index], incoming_keys[incoming_index])) {
490+
append_existing(existing_index++);
491+
} else {
492+
assert(_compare(incoming_keys[incoming_index], _keys[existing_index]));
493+
append_incoming(incoming_index++);
494+
}
515495
} else {
516-
const auto existing_group_end = ordering_group_end(_keys, existing_index);
517-
const auto incoming_group_end = ordering_group_end(incoming_keys, incoming_index);
518-
const auto merged_group_start = merged_keys.size();
519-
append_existing_group(existing_index, existing_group_end);
520-
append_incoming_group(incoming_index, incoming_group_end, merged_group_start);
521-
existing_index = existing_group_end;
522-
incoming_index = incoming_group_end;
496+
if (_compare(_keys[existing_index], incoming_keys[incoming_index])) {
497+
append_existing(existing_index++);
498+
} else if (_compare(incoming_keys[incoming_index], _keys[existing_index])) {
499+
append_incoming(incoming_index++);
500+
} else {
501+
append_existing(existing_index++);
502+
++incoming_index;
503+
}
523504
}
524505
}
506+
while (existing_index < size())
507+
append_existing(existing_index++);
508+
while (incoming_index < incoming_keys.size())
509+
append_incoming(incoming_index++);
525510

526511
_keys = std::move(merged_keys);
527512
_values = std::move(merged_values);
528513
}
529514

530-
template <typename Keys>
531-
[[nodiscard]] size_type ordering_group_end(const Keys& keys, size_type first) const
532-
{
533-
auto last = first + 1;
534-
while (last < keys.size() && FlatContainerInternal::ordering_equivalent(keys[first], keys[last], _compare))
535-
++last;
536-
return last;
537-
}
538-
539515
std::vector<Key> _keys;
540516
std::vector<Mapped> _values;
541517
[[no_unique_address]] Compare _compare{};
@@ -694,25 +670,19 @@ class flat_set
694670
template <typename Query>
695671
[[nodiscard]] size_type find_index(const Query& key) const
696672
{
697-
const auto first = lower_bound_index(key);
698-
const auto last = upper_bound_index(key);
699-
for (auto index = first; index < last; ++index) {
700-
if (FlatContainerInternal::keys_equal(_keys[index], key, _compare))
701-
return index;
702-
}
673+
const auto index = lower_bound_index(key);
674+
if (index < size() && FlatContainerInternal::lower_bound_matches(_keys[index], key, _compare))
675+
return index;
703676
return size();
704677
}
705678

706679
template <typename Query>
707680
[[nodiscard]] std::pair<size_type, size_type> find_or_insertion_index(const Query& key) const
708681
{
709-
const auto first = lower_bound_index(key);
710-
const auto last = upper_bound_index(key);
711-
for (auto index = first; index < last; ++index) {
712-
if (FlatContainerInternal::keys_equal(_keys[index], key, _compare))
713-
return { index, last };
714-
}
715-
return { size(), last };
682+
const auto index = lower_bound_index(key);
683+
if (index < size() && FlatContainerInternal::lower_bound_matches(_keys[index], key, _compare))
684+
return { index, index };
685+
return { size(), index };
716686
}
717687

718688
void merge_sorted(std::vector<Key>&& incoming_keys)
@@ -723,64 +693,43 @@ class flat_set
723693
std::vector<Key> merged_keys;
724694
merged_keys.reserve(size() + incoming_keys.size());
725695

726-
auto append_incoming_group = [&](size_type first, size_type last, size_type merged_group_start) {
727-
for (auto index = first; index < last; ++index) {
728-
const auto duplicate = std::any_of(merged_keys.begin() + static_cast<difference_type>(merged_group_start), merged_keys.end(),
729-
[&](const Key& key) { return FlatContainerInternal::keys_equal(key, incoming_keys[index], _compare); });
730-
if (!duplicate)
731-
merged_keys.emplace_back(std::move(incoming_keys[index]));
732-
}
696+
auto append_incoming = [&](size_type index) {
697+
if (merged_keys.empty() || !FlatContainerInternal::sorted_keys_equal(merged_keys.back(), incoming_keys[index], _compare))
698+
merged_keys.emplace_back(std::move(incoming_keys[index]));
733699
};
734700

735701
auto existing_index = size_type{ 0 };
736702
auto incoming_index = size_type{ 0 };
737-
while (existing_index < size() || incoming_index < incoming_keys.size()) {
738-
if (existing_index == size()) {
739-
const auto incoming_group_end = ordering_group_end(incoming_keys, incoming_index);
740-
const auto merged_group_start = merged_keys.size();
741-
append_incoming_group(incoming_index, incoming_group_end, merged_group_start);
742-
incoming_index = incoming_group_end;
743-
continue;
744-
}
745-
if (incoming_index == incoming_keys.size()) {
746-
const auto existing_group_end = ordering_group_end(_keys, existing_index);
747-
for (; existing_index < existing_group_end; ++existing_index)
748-
merged_keys.emplace_back(std::move(_keys[existing_index]));
749-
continue;
750-
}
751-
752-
if (_compare(_keys[existing_index], incoming_keys[incoming_index])) {
753-
const auto existing_group_end = ordering_group_end(_keys, existing_index);
754-
for (; existing_index < existing_group_end; ++existing_index)
755-
merged_keys.emplace_back(std::move(_keys[existing_index]));
756-
} else if (_compare(incoming_keys[incoming_index], _keys[existing_index])) {
757-
const auto incoming_group_end = ordering_group_end(incoming_keys, incoming_index);
758-
const auto merged_group_start = merged_keys.size();
759-
append_incoming_group(incoming_index, incoming_group_end, merged_group_start);
760-
incoming_index = incoming_group_end;
703+
while (existing_index < size() && incoming_index < incoming_keys.size()) {
704+
if constexpr (FlatContainerInternal::EqualityComparable<Key, Key>) {
705+
if (_keys[existing_index] == incoming_keys[incoming_index]) {
706+
merged_keys.emplace_back(std::move(_keys[existing_index++]));
707+
++incoming_index;
708+
} else if (_compare(_keys[existing_index], incoming_keys[incoming_index])) {
709+
merged_keys.emplace_back(std::move(_keys[existing_index++]));
710+
} else {
711+
assert(_compare(incoming_keys[incoming_index], _keys[existing_index]));
712+
append_incoming(incoming_index++);
713+
}
761714
} else {
762-
const auto existing_group_end = ordering_group_end(_keys, existing_index);
763-
const auto incoming_group_end = ordering_group_end(incoming_keys, incoming_index);
764-
const auto merged_group_start = merged_keys.size();
765-
for (; existing_index < existing_group_end; ++existing_index)
766-
merged_keys.emplace_back(std::move(_keys[existing_index]));
767-
append_incoming_group(incoming_index, incoming_group_end, merged_group_start);
768-
incoming_index = incoming_group_end;
715+
if (_compare(_keys[existing_index], incoming_keys[incoming_index])) {
716+
merged_keys.emplace_back(std::move(_keys[existing_index++]));
717+
} else if (_compare(incoming_keys[incoming_index], _keys[existing_index])) {
718+
append_incoming(incoming_index++);
719+
} else {
720+
merged_keys.emplace_back(std::move(_keys[existing_index++]));
721+
++incoming_index;
722+
}
769723
}
770724
}
725+
while (existing_index < size())
726+
merged_keys.emplace_back(std::move(_keys[existing_index++]));
727+
while (incoming_index < incoming_keys.size())
728+
append_incoming(incoming_index++);
771729

772730
_keys = std::move(merged_keys);
773731
}
774732

775-
template <typename Keys>
776-
[[nodiscard]] size_type ordering_group_end(const Keys& keys, size_type first) const
777-
{
778-
auto last = first + 1;
779-
while (last < keys.size() && FlatContainerInternal::ordering_equivalent(keys[first], keys[last], _compare))
780-
++last;
781-
return last;
782-
}
783-
784733
std::vector<Key> _keys;
785734
[[no_unique_address]] Compare _compare{};
786735
// While a batch is open, only the prefix before _batch_start is ordered and searchable.

0 commit comments

Comments
 (0)