map/multi_fixed: add multimap with fixed storage
This commit is contained in:
parent
3416442dda
commit
774ccb763d
@ -423,9 +423,11 @@ list (
|
||||
iterator/infix.hpp
|
||||
iterator/iota.hpp
|
||||
iterator/numeric.hpp
|
||||
iterator/placement_output.hpp
|
||||
iterator/referencing.hpp
|
||||
iterator/transform.hpp
|
||||
iterator/unequal.hpp
|
||||
iterator/unordered_insert.hpp
|
||||
iterator/zip.hpp
|
||||
job/fwd.hpp
|
||||
job/dispatch.hpp
|
||||
@ -453,6 +455,8 @@ list (
|
||||
log/sink/null.hpp
|
||||
log/sink/path.cpp
|
||||
log/sink/path.hpp
|
||||
map/multi_fixed.cpp
|
||||
map/multi_fixed.hpp
|
||||
map/fixed.cpp
|
||||
map/fixed.hpp
|
||||
maths.cpp
|
||||
@ -722,6 +726,7 @@ if (TESTS)
|
||||
kmeans
|
||||
list/sort
|
||||
map/fixed
|
||||
map/multi_fixed
|
||||
maths
|
||||
maths/fast
|
||||
matrix
|
||||
|
58
iterator/placement_output.hpp
Normal file
58
iterator/placement_output.hpp
Normal file
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
namespace cruft::iterator {
|
||||
/// An output iterator that calls placement new on a supplied pointer
|
||||
/// during assignment.
|
||||
///
|
||||
/// This simplifies usage of uninitialised arrays with STL algorithms.
|
||||
template <typename PointerT>
|
||||
struct placement_output {
|
||||
public:
|
||||
static_assert (std::is_pointer_v<PointerT>);
|
||||
|
||||
using iterator_category = std::output_iterator_tag;
|
||||
using value_type = void;
|
||||
using difference_type = void;
|
||||
using pointer = void;
|
||||
using reference = void;
|
||||
|
||||
placement_output (PointerT _cursor)
|
||||
: m_cursor (_cursor)
|
||||
{ ; }
|
||||
|
||||
placement_output& operator++ () { ++m_cursor; return *this; }
|
||||
placement_output& operator * () { return *this; }
|
||||
placement_output& operator-> () { return this; }
|
||||
|
||||
template <typename ValueT>
|
||||
placement_output&
|
||||
operator= (ValueT &&val)
|
||||
{
|
||||
new (m_cursor) bare_type (std::forward<ValueT> (val));
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
using bare_type = std::remove_pointer_t<PointerT>;
|
||||
|
||||
PointerT m_cursor;
|
||||
};
|
||||
|
||||
|
||||
template <typename PointerT>
|
||||
placement_output (PointerT) -> placement_output<std::remove_reference_t<PointerT>>;
|
||||
}
|
51
iterator/unordered_insert.hpp
Normal file
51
iterator/unordered_insert.hpp
Normal file
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
namespace cruft::iterator {
|
||||
/// An output iterator that calls insert on a supplied container
|
||||
template <typename ContainerT>
|
||||
struct unordered_insert {
|
||||
public:
|
||||
using iterator_category = std::output_iterator_tag;
|
||||
using value_type = void;
|
||||
using difference_type = void;
|
||||
using pointer = void;
|
||||
using reference = void;
|
||||
|
||||
unordered_insert (ContainerT &_target)
|
||||
: m_target (&_target)
|
||||
{ ; }
|
||||
|
||||
unordered_insert& operator++ () { return *this; }
|
||||
unordered_insert& operator * () { return *this; }
|
||||
unordered_insert& operator-> () { return this; }
|
||||
|
||||
template <typename ValueT>
|
||||
unordered_insert&
|
||||
operator= (ValueT &&val)
|
||||
{
|
||||
m_target->insert (std::forward<ValueT> (val));
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
ContainerT *m_target;
|
||||
};
|
||||
|
||||
|
||||
template <typename ContainerT>
|
||||
unordered_insert (ContainerT &) -> unordered_insert<std::remove_cvref_t<ContainerT>>;
|
||||
}
|
3
map/multi_fixed.cpp
Normal file
3
map/multi_fixed.cpp
Normal file
@ -0,0 +1,3 @@
|
||||
|
||||
|
||||
#include "./multi_fixed.hpp"
|
245
map/multi_fixed.hpp
Normal file
245
map/multi_fixed.hpp
Normal file
@ -0,0 +1,245 @@
|
||||
/*
|
||||
* 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"
|
||||
#include "../cast.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
|
||||
namespace cruft::map {
|
||||
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&);
|
||||
multi_fixed (multi_fixed &&) noexcept;
|
||||
|
||||
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 ();
|
||||
while (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 <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&;
|
||||
|
||||
template <typename K>
|
||||
std::pair<iterator, iterator>
|
||||
equal_range (K const&)&;
|
||||
|
||||
void clear (void);
|
||||
|
||||
auto size (void) const { return m_size; }
|
||||
auto capacity (void) const { return elements; }
|
||||
|
||||
private:
|
||||
std::size_t m_size = 0;
|
||||
|
||||
union store {
|
||||
store () { ; }
|
||||
~store () { ; }
|
||||
|
||||
char defer;
|
||||
value_type data[elements];
|
||||
} m_store;
|
||||
};
|
||||
|
||||
|
||||
/// 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)
|
||||
);
|
||||
}
|
||||
}
|
80
test/map/multi_fixed.cpp
Normal file
80
test/map/multi_fixed.cpp
Normal file
@ -0,0 +1,80 @@
|
||||
#include <cruft/util/map/multi_fixed.hpp>
|
||||
#include <cruft/util/tap.hpp>
|
||||
#include <cruft/util/random.hpp>
|
||||
|
||||
|
||||
static void
|
||||
test_sorted_iterators (cruft::TAP::logger &tap)
|
||||
{
|
||||
std::array<int, 64> keys;
|
||||
|
||||
// Attempt to generate a list of unsorted keys.
|
||||
//
|
||||
// This _might_ fail because shuffle _might_ return the original ordering,
|
||||
// but I feel the number of keys we're using here should sufficiently
|
||||
// mitigate this risk.
|
||||
std::iota (std::begin (keys), std::end (keys), 0);
|
||||
std::shuffle (std::begin (keys), std::end (keys), cruft::random::generator ());
|
||||
if (std::is_sorted (std::begin (keys), std::end (keys)))
|
||||
throw std::logic_error ("Keys should not be sorted before insertion");
|
||||
|
||||
cruft::map::multi_fixed<64,int,int> obj;
|
||||
for (auto const k: keys)
|
||||
obj.insert ({k, k});
|
||||
|
||||
tap.expect (
|
||||
std::is_sorted (
|
||||
obj.begin (),
|
||||
obj.end (),
|
||||
[] (auto const &a, auto const &b) { return a.first < b.first; }
|
||||
),
|
||||
"iterator range is provided in sorted order"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
test_bad_alloc (cruft::TAP::logger &tap)
|
||||
{
|
||||
tap.expect_throw<std::bad_alloc> ([] (void) {
|
||||
cruft::map::multi_fixed<1,int,int> obj;
|
||||
obj.insert ({0,0});
|
||||
obj.insert ({1,1});
|
||||
}, "overcommitting storage results in std::bad_alloc");
|
||||
}
|
||||
|
||||
|
||||
int main ()
|
||||
{
|
||||
cruft::TAP::logger tap;
|
||||
|
||||
{
|
||||
cruft::map::multi_fixed<8,int,int> a, b;
|
||||
tap.expect_eq (a, b, "default construction equality");
|
||||
|
||||
a.insert ({ 0, 1 });
|
||||
tap.expect_eq (a.size (), 1u, "unit size after first insertion");
|
||||
tap.expect_eq (a.find (0)->second, 1, "value retrieval after insertion");
|
||||
|
||||
tap.expect_neq (a, b, "inequality after insertion");
|
||||
}
|
||||
|
||||
{
|
||||
cruft::map::multi_fixed<4,int,int> const a {{ {0,0}, {3,3}, }};
|
||||
cruft::map::multi_fixed<4,int,int> const b {{ {2,2}, {0,0}, }};
|
||||
cruft::map::multi_fixed<4,int,int> const expected {{ {0,0}, {0,0}, {2,2}, {3,3}, }};
|
||||
tap.expect_eq (a + b, expected, "summation of two sets");
|
||||
}
|
||||
|
||||
{
|
||||
cruft::map::multi_fixed<4,int,int> const a {{ {0,0}, {2,2}, {3,3}, }};
|
||||
cruft::map::multi_fixed<4,int,int> const b {{ {0,0}, {1,1}, {2,2}, {4,4}, }};
|
||||
cruft::map::multi_fixed<4,int,int> const expected {{ {3, 3} }};
|
||||
tap.expect_eq (a - b, expected, "difference of two sets");
|
||||
}
|
||||
|
||||
test_sorted_iterators (tap);
|
||||
test_bad_alloc (tap);
|
||||
|
||||
return tap.status ();
|
||||
}
|
Loading…
Reference in New Issue
Block a user