/* * 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 */ #pragma once #include "../iterator/unordered_insert.hpp" #include "../iterator/placement_output.hpp" #include "../iterator/tuple_picker.hpp" #include "../cast.hpp" #include #include #include namespace cruft::map { /// A multi-map with a compile-time fixed-size backing store. 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; 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 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&); multi_fixed (multi_fixed &&rhs) noexcept (std::is_nothrow_move_constructible_v) { 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; } } multi_fixed& operator= (multi_fixed &&rhs) noexcept (std::is_nothrow_move_assignable_v) { 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) { 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; } 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 (); ComparatorT cmp {}; while (cmp (cursor->first, kv.first) && cursor != end ()) ++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 void insert (RandomT first, RandomT last) { static_assert ( std::is_same_v< typename std::iterator_traits::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 (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 bool contains (K const&) const; template 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 const_iterator find (K const&) const&; template iterator lower_bound (K const &k)& { ComparatorT cmp {}; return std::find_if ( begin (), end (), [&] (auto const &val) { return !cmp (val.first, k); } ); } template iterator upper_bound (K const &k)& { ComparatorT cmp {}; return std::find_if ( begin (), end (), [&] (auto const &val) { return cmp (k, val.first); } ); } template std::pair equal_range (K const &key)& { return { lower_bound (key), upper_bound (key) }; } 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 ()); std::move (it + 1, end (), it); --m_size; m_store.data[m_size].~value_type (); } /// 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. template std::size_t erase_keys (IteratorT first, IteratorT last) { CHECK (std::is_sorted (first, last)); std::size_t tally = 0; // HACK: This is hopelessly naive, but it works. for (auto cursor = first; cursor != last; ++cursor) { if (auto pos = find (*cursor); pos != end ()) { erase (pos); ++tally; } } return tally; } 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); } std::size_t erase (key_type const&); bool empty (void) const { return m_size == 0; } auto size (void) const { return m_size; } auto capacity (void) const { return elements; } 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); } private: std::size_t m_size = 0; union store { store () { ; } ~store () { ; } char defer; value_type data[elements]; } m_store; }; template < std::size_t SizeV, typename KeyT, typename ValueT, typename ComparatorT > void swap ( multi_fixed &a, multi_fixed &b ) { a.swap (b); } /// 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 multi_fixed operator+ ( multi_fixed const &a, multi_fixed const &b ) { multi_fixed 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 multi_fixed operator- ( multi_fixed const &a, multi_fixed const &b ) { multi_fixed res; std::set_difference ( std::begin (a), std::end (a), std::begin (b), std::end (b), cruft::iterator::unordered_insert (res) ); return res; } template bool operator== ( multi_fixed const &a, multi_fixed const &b ) { return std::equal ( std::begin (a), std::end (a), std::begin (b), std::end (b) ); } }