2021-02-03 17:21:42 +11:00
|
|
|
/*
|
|
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
*
|
|
|
|
* Copyright 2021, Danny Robson <danny@nerdcruft.net>
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
#pragma once
|
|
|
|
|
|
|
|
#include "../iterator/unordered_insert.hpp"
|
|
|
|
#include "../iterator/placement_output.hpp"
|
2021-02-05 13:04:20 +11:00
|
|
|
#include "../iterator/tuple_picker.hpp"
|
2021-02-03 17:21:42 +11:00
|
|
|
#include "../cast.hpp"
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
#include <functional>
|
|
|
|
|
|
|
|
#include <cstddef>
|
|
|
|
|
|
|
|
|
|
|
|
namespace cruft::map {
|
2021-02-05 13:04:20 +11:00
|
|
|
/// A multi-map with a compile-time fixed-size backing store.
|
2021-02-03 17:21:42 +11:00
|
|
|
template <
|
|
|
|
std::size_t SizeV,
|
|
|
|
typename KeyT,
|
|
|
|
typename ValueT,
|
|
|
|
typename ComparatorT = std::less<>
|
|
|
|
>
|
|
|
|
class multi_fixed {
|
|
|
|
public:
|
|
|
|
static constexpr auto elements = SizeV;
|
|
|
|
|
|
|
|
using key_type = KeyT;
|
|
|
|
using mapped_type = ValueT;
|
|
|
|
using value_type = std::pair<key_type, mapped_type>;
|
|
|
|
|
|
|
|
using key_compare = ComparatorT;
|
|
|
|
using reference = value_type&;
|
|
|
|
using const_reference = value_type const&;
|
|
|
|
using pointer = value_type*;
|
|
|
|
using const_pointer = value_type const*;
|
|
|
|
|
|
|
|
using iterator = pointer;
|
|
|
|
using const_iterator = const_pointer;
|
|
|
|
|
|
|
|
multi_fixed () = default;
|
|
|
|
~multi_fixed ()
|
|
|
|
{
|
|
|
|
for (std::size_t i = 0; i < m_size; ++i)
|
|
|
|
m_store.data[i].~value_type ();
|
|
|
|
}
|
|
|
|
|
|
|
|
multi_fixed (std::initializer_list<value_type> const &src)
|
|
|
|
{
|
|
|
|
if (src.size () > capacity ())
|
|
|
|
throw std::bad_alloc ();
|
|
|
|
|
|
|
|
std::copy (
|
|
|
|
std::make_move_iterator (std::begin (src)),
|
|
|
|
std::make_move_iterator (std::end (src)),
|
|
|
|
m_store.data + 0
|
|
|
|
);
|
|
|
|
|
|
|
|
m_size = src.size ();
|
|
|
|
|
|
|
|
std::sort (
|
|
|
|
m_store.data + 0,
|
|
|
|
m_store.data + m_size,
|
|
|
|
ComparatorT {}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
multi_fixed (multi_fixed const&);
|
2021-02-05 13:04:20 +11:00
|
|
|
|
|
|
|
multi_fixed (multi_fixed &&rhs) noexcept (std::is_nothrow_move_constructible_v<value_type>)
|
|
|
|
{
|
|
|
|
clear ();
|
|
|
|
|
|
|
|
while (m_size != rhs.m_size) {
|
|
|
|
new (m_store.data + m_size) value_type (std::move (rhs.m_store.data[m_size]));
|
|
|
|
++m_size;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-09 13:14:17 +11:00
|
|
|
multi_fixed&
|
|
|
|
operator= (multi_fixed &&rhs) noexcept (std::is_nothrow_move_assignable_v<value_type>)
|
|
|
|
{
|
|
|
|
for (std::size_t i = 0; i < m_size; ++i)
|
|
|
|
m_store.data[i].~value_type ();
|
|
|
|
m_size = 0;
|
|
|
|
|
|
|
|
for (m_size = 0; m_size != rhs.m_size; ++m_size)
|
|
|
|
m_store.data[m_size] = std::move (rhs.m_store.data[m_size]);
|
|
|
|
|
|
|
|
rhs.m_size = 0;
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
multi_fixed&
|
|
|
|
operator= (multi_fixed const &rhs) noexcept (std::is_nothrow_assignable_v<value_type, value_type>)
|
|
|
|
{
|
|
|
|
for (std::size_t i = 0; i < m_size; ++i)
|
|
|
|
m_store.data[i].~value_type ();
|
|
|
|
|
|
|
|
for (m_size = 0; m_size != rhs.m_size; ++m_size)
|
|
|
|
m_store.data[m_size] = rhs.m_store.data[m_size];
|
|
|
|
|
|
|
|
return *this;
|
|
|
|
}
|
2021-02-03 17:21:42 +11:00
|
|
|
|
|
|
|
iterator begin (void)& { return m_store.data + 0; }
|
|
|
|
iterator end (void)& { return m_store.data + m_size; }
|
|
|
|
|
|
|
|
const_iterator begin (void) const& { return m_store.data + 0; }
|
|
|
|
const_iterator end (void) const& { return m_store.data + m_size; }
|
|
|
|
|
|
|
|
const_iterator cbegin (void) const& { return cbegin (); }
|
|
|
|
const_iterator cend (void) const& { return cend (); }
|
|
|
|
|
|
|
|
iterator
|
|
|
|
insert (value_type const &kv)
|
|
|
|
{
|
|
|
|
// Don't overrun our static buffer
|
|
|
|
if (size () == capacity ())
|
|
|
|
throw std::bad_alloc ();
|
|
|
|
|
|
|
|
// Find the insert position. We prefer lte comparison so that we
|
|
|
|
// avoid moving too many key-value pairs backwards to make room
|
|
|
|
// for the data.
|
|
|
|
auto cursor = begin ();
|
2021-02-05 13:04:20 +11:00
|
|
|
ComparatorT cmp {};
|
|
|
|
while (cmp (cursor->first, kv.first) && cursor != end ())
|
2021-02-03 17:21:42 +11:00
|
|
|
++cursor;
|
|
|
|
|
|
|
|
// Shuffle the items back in the storage.
|
|
|
|
auto offset = std::distance (begin (), cursor);
|
|
|
|
auto remain = size () - offset;
|
|
|
|
std::move_backward (cursor, cursor + remain, cursor + remain + 1);
|
|
|
|
|
|
|
|
// Insert the new data and return
|
|
|
|
new (cursor) value_type (kv);
|
|
|
|
++m_size;
|
|
|
|
return cursor;
|
|
|
|
}
|
|
|
|
|
|
|
|
iterator
|
|
|
|
insert (value_type &&kv)
|
|
|
|
{
|
|
|
|
return insert (kv);
|
|
|
|
}
|
|
|
|
|
|
|
|
template <typename RandomT>
|
|
|
|
void
|
|
|
|
insert (RandomT first, RandomT last)
|
|
|
|
{
|
|
|
|
static_assert (
|
|
|
|
std::is_same_v<
|
|
|
|
typename std::iterator_traits<RandomT>::iterator_category,
|
|
|
|
std::random_access_iterator_tag
|
|
|
|
>,
|
|
|
|
"We assume random iterators here to simplify offset "
|
|
|
|
"calculations and make copying a little more efficient. "
|
|
|
|
"There's no reason it needs to be restricted though."
|
|
|
|
);
|
|
|
|
|
|
|
|
auto const count = std::distance (first, last);
|
|
|
|
if (cruft::cast::sign<size_t> (count) > capacity () - size ())
|
|
|
|
throw std::bad_alloc ();
|
|
|
|
|
|
|
|
std::copy (
|
|
|
|
first,
|
|
|
|
last,
|
|
|
|
cruft::iterator::placement_output {m_store.data + size ()}
|
|
|
|
);
|
|
|
|
|
|
|
|
std::sort (
|
|
|
|
m_store.data + size (),
|
|
|
|
m_store.data + size () + count
|
|
|
|
);
|
|
|
|
|
|
|
|
std::inplace_merge (
|
|
|
|
m_store.data + 0,
|
|
|
|
m_store.data + size (),
|
|
|
|
m_store.data + size () + count
|
|
|
|
);
|
|
|
|
|
|
|
|
m_size += count;
|
|
|
|
}
|
|
|
|
|
|
|
|
template <typename K>
|
|
|
|
bool contains (K const&) const;
|
|
|
|
|
|
|
|
template <typename K>
|
|
|
|
iterator find (K const &k)&
|
|
|
|
{
|
|
|
|
ComparatorT cmp {};
|
|
|
|
|
|
|
|
for (auto cursor = begin (); cursor != end (); ++cursor)
|
|
|
|
if (!cmp (cursor->first, k) && !cmp (k, cursor->first))
|
|
|
|
return cursor;
|
|
|
|
|
|
|
|
return end ();
|
|
|
|
}
|
|
|
|
|
|
|
|
template <typename K>
|
|
|
|
const_iterator find (K const&) const&;
|
|
|
|
|
2021-02-05 13:04:20 +11:00
|
|
|
template <typename K>
|
|
|
|
iterator
|
|
|
|
lower_bound (K const &k)&
|
|
|
|
{
|
|
|
|
ComparatorT cmp {};
|
|
|
|
return std::find_if (
|
|
|
|
begin (),
|
|
|
|
end (),
|
|
|
|
[&] (auto const &val) { return !cmp (val.first, k); }
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
template <typename K>
|
|
|
|
iterator
|
|
|
|
upper_bound (K const &k)&
|
|
|
|
{
|
|
|
|
ComparatorT cmp {};
|
|
|
|
return std::find_if (
|
|
|
|
begin (),
|
|
|
|
end (),
|
|
|
|
[&] (auto const &val) { return cmp (k, val.first); }
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-02-03 17:21:42 +11:00
|
|
|
template <typename K>
|
|
|
|
std::pair<iterator, iterator>
|
2021-02-05 13:04:20 +11:00
|
|
|
equal_range (K const &key)&
|
|
|
|
{
|
|
|
|
return {
|
|
|
|
lower_bound (key),
|
|
|
|
upper_bound (key)
|
|
|
|
};
|
|
|
|
}
|
2021-02-03 17:21:42 +11:00
|
|
|
|
2021-02-05 13:04:20 +11:00
|
|
|
void clear (void)
|
|
|
|
{
|
|
|
|
for (std::size_t i = 0; i < m_size; ++i)
|
|
|
|
m_store.data[i].~value_type ();
|
|
|
|
m_size = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void erase (iterator it)
|
|
|
|
{
|
|
|
|
CHECK_GE (it, begin ());
|
|
|
|
CHECK_LT (it, end ());
|
|
|
|
CHECK (!empty ());
|
2021-02-03 17:21:42 +11:00
|
|
|
|
2021-02-05 13:04:20 +11:00
|
|
|
std::move (it + 1, end (), it);
|
|
|
|
--m_size;
|
|
|
|
m_store.data[m_size].~value_type ();
|
|
|
|
}
|
|
|
|
|
2021-02-08 12:17:09 +11:00
|
|
|
|
|
|
|
/// Erases the keys pointed at by the supplied iterator range.
|
|
|
|
///
|
|
|
|
/// If a key does not exist it is silently ignored.
|
|
|
|
///
|
|
|
|
/// \return The number of keys erased.
|
2021-02-08 10:38:17 +11:00
|
|
|
template <typename IteratorT>
|
2021-02-08 12:17:09 +11:00
|
|
|
std::size_t
|
2021-02-08 10:38:17 +11:00
|
|
|
erase_keys (IteratorT first, IteratorT last)
|
|
|
|
{
|
|
|
|
CHECK (std::is_sorted (first, last));
|
|
|
|
|
2021-02-08 12:17:09 +11:00
|
|
|
std::size_t tally = 0;
|
|
|
|
|
2021-02-08 10:38:17 +11:00
|
|
|
// HACK: This is hopelessly naive, but it works.
|
2021-02-08 12:17:09 +11:00
|
|
|
for (auto cursor = first; cursor != last; ++cursor) {
|
|
|
|
if (auto pos = find (*cursor); pos != end ()) {
|
2021-02-08 10:38:17 +11:00
|
|
|
erase (pos);
|
2021-02-08 12:17:09 +11:00
|
|
|
++tally;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return tally;
|
2021-02-08 10:38:17 +11:00
|
|
|
}
|
|
|
|
|
2021-02-09 10:39:36 +11:00
|
|
|
void
|
|
|
|
erase_item (value_type const &kv)
|
|
|
|
{
|
|
|
|
auto [first, last] = equal_range (kv.first);
|
|
|
|
|
|
|
|
auto const pos = std::find_if (
|
|
|
|
first,
|
|
|
|
last,
|
|
|
|
[&kv] (auto const &candidate) { return candidate.second == kv.second; }
|
|
|
|
);
|
|
|
|
|
|
|
|
if (pos == last)
|
|
|
|
throw std::out_of_range ("unknown key-value");
|
|
|
|
|
|
|
|
erase (pos);
|
|
|
|
}
|
|
|
|
|
2021-02-08 10:38:17 +11:00
|
|
|
|
2021-02-05 13:04:20 +11:00
|
|
|
std::size_t erase (key_type const&);
|
|
|
|
|
|
|
|
bool empty (void) const { return m_size == 0; }
|
2021-02-03 17:21:42 +11:00
|
|
|
auto size (void) const { return m_size; }
|
|
|
|
auto capacity (void) const { return elements; }
|
|
|
|
|
2021-02-05 13:04:20 +11:00
|
|
|
void swap (multi_fixed &rhs)
|
|
|
|
{
|
|
|
|
// To save duplicating the code which moves the trailing excess
|
|
|
|
// items between containers we instead just reverse the direction
|
|
|
|
// here.
|
|
|
|
if (rhs.size () > size ())
|
|
|
|
rhs.swap (*this);
|
|
|
|
|
|
|
|
using std::swap;
|
|
|
|
|
|
|
|
// Swap the commonly held indices
|
|
|
|
std::size_t const common = std::min (size (), rhs.size ());
|
|
|
|
for (std::size_t i = 0; i != common; ++i)
|
|
|
|
swap (m_store.data[i], rhs.m_store.data[i]);
|
|
|
|
|
|
|
|
// Move our indices to the rhs if we're larger. We don't need the
|
|
|
|
// reverse case because we took care of that earlier.
|
|
|
|
CHECK_GE (size (), rhs.size ());
|
|
|
|
if (size () > common) {
|
|
|
|
for (std::size_t i = common; i < size (); ++i)
|
|
|
|
new (rhs.m_store.data + i) value_type (std::move (m_store.data[i]));
|
|
|
|
|
|
|
|
swap (m_size, rhs.m_size);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A helper class that allows iteration over the keys of a
|
|
|
|
/// multi_fixed map.
|
|
|
|
///
|
|
|
|
/// The iterators it provides may be invalidated if any mutating
|
|
|
|
/// operations are performed on the underlying multi_fixed object.
|
|
|
|
class keys_proxy {
|
|
|
|
public:
|
|
|
|
using iterator_category = std::random_access_iterator_tag;
|
|
|
|
using value_type = KeyT;
|
|
|
|
using difference_type = std::ptrdiff_t;
|
|
|
|
using pointer = value_type*;
|
|
|
|
using reference = value_type&;
|
|
|
|
|
|
|
|
keys_proxy (multi_fixed const &_parent)
|
|
|
|
: m_parent (&_parent)
|
|
|
|
{ ; }
|
|
|
|
|
|
|
|
auto begin (void) const { return cruft::iterator::make_tuple_picker<0> (m_parent->begin ()); }
|
|
|
|
auto end (void) const { return cruft::iterator::make_tuple_picker<0> (m_parent->end ()); }
|
|
|
|
|
|
|
|
private:
|
|
|
|
multi_fixed const *m_parent;
|
|
|
|
};
|
|
|
|
|
|
|
|
/// Returns a container that allows traversal of the underlying keys
|
|
|
|
/// of the store.
|
|
|
|
keys_proxy keys (void) const& { return keys_proxy (*this); }
|
|
|
|
|
2021-02-03 17:21:42 +11:00
|
|
|
private:
|
|
|
|
std::size_t m_size = 0;
|
|
|
|
|
|
|
|
union store {
|
|
|
|
store () { ; }
|
|
|
|
~store () { ; }
|
|
|
|
|
|
|
|
char defer;
|
|
|
|
value_type data[elements];
|
|
|
|
} m_store;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2021-02-05 13:04:20 +11:00
|
|
|
template <
|
|
|
|
std::size_t SizeV,
|
|
|
|
typename KeyT,
|
|
|
|
typename ValueT,
|
|
|
|
typename ComparatorT
|
|
|
|
>
|
|
|
|
void swap (
|
|
|
|
multi_fixed<SizeV, KeyT, ValueT, ComparatorT> &a,
|
|
|
|
multi_fixed<SizeV, KeyT, ValueT, ComparatorT> &b
|
|
|
|
) {
|
|
|
|
a.swap (b);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-02-03 17:21:42 +11:00
|
|
|
/// Returns a concatenation of two multi_fixed maps. ie, a copy of
|
|
|
|
/// all entries in the first map, and all entries in the second map.
|
|
|
|
///
|
|
|
|
/// If both maps have the same value then the output will have duplicates.
|
|
|
|
///
|
|
|
|
/// If the combined size of the maps exceeds the static capacity a
|
|
|
|
/// std::bad_alloc will be thrown.
|
|
|
|
template <std::size_t SizeV, typename KeyT, typename ValueT, typename ComparatorT>
|
|
|
|
multi_fixed<SizeV, KeyT, ValueT, ComparatorT>
|
|
|
|
operator+ (
|
|
|
|
multi_fixed<SizeV, KeyT, ValueT, ComparatorT> const &a,
|
|
|
|
multi_fixed<SizeV, KeyT, ValueT, ComparatorT> const &b
|
|
|
|
) {
|
|
|
|
multi_fixed<SizeV, KeyT, ValueT, ComparatorT> res;
|
|
|
|
res.insert (a.begin (), a.end ());
|
|
|
|
res.insert (b.begin (), b.end ());
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Returns the difference between two multi_fixed maps.
|
|
|
|
///
|
|
|
|
/// ie, the entries found in the first but not the second.
|
|
|
|
template <std::size_t SizeV, typename KeyT, typename ValueT, typename ComparatorT>
|
|
|
|
multi_fixed<SizeV, KeyT, ValueT, ComparatorT>
|
|
|
|
operator- (
|
|
|
|
multi_fixed<SizeV, KeyT, ValueT, ComparatorT> const &a,
|
|
|
|
multi_fixed<SizeV, KeyT, ValueT, ComparatorT> const &b
|
|
|
|
) {
|
|
|
|
|
|
|
|
multi_fixed<SizeV, KeyT, ValueT, ComparatorT> res;
|
|
|
|
std::set_difference (
|
|
|
|
std::begin (a), std::end (a),
|
|
|
|
std::begin (b), std::end (b),
|
|
|
|
cruft::iterator::unordered_insert (res)
|
|
|
|
);
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template <std::size_t SizeV, typename KeyT, typename ValueT, typename ComparatorT>
|
|
|
|
bool
|
|
|
|
operator== (
|
|
|
|
multi_fixed<SizeV, KeyT, ValueT, ComparatorT> const &a,
|
|
|
|
multi_fixed<SizeV, KeyT, ValueT, ComparatorT> const &b
|
|
|
|
) {
|
|
|
|
return std::equal (
|
|
|
|
std::begin (a), std::end (a),
|
|
|
|
std::begin (b), std::end (b)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|