BioC++ core-0.7.0
The Modern C++ libraries for Bioinformatics.
 
Loading...
Searching...
No Matches
dictionary.hpp
Go to the documentation of this file.
1// -----------------------------------------------------------------------------------------------------
2// Copyright (c) 2022 deCODE Genetics
3// Copyright (c) 2006-2020, Knut Reinert & Freie Universität Berlin
4// Copyright (c) 2016-2020, Knut Reinert & MPI für molekulare Genetik
5// This file may be used, modified and/or redistributed under the terms of the 3-clause BSD-License
6// shipped with this file and also available at: https://github.com/biocpp/biocpp-core/blob/main/LICENSE.md
7// -----------------------------------------------------------------------------------------------------
8
14#pragma once
15
16#include <array>
17#include <concepts>
18#include <initializer_list>
19#include <ranges>
20#include <type_traits>
21#include <unordered_map>
22
23#if __has_include(<cereal/types/vector.hpp>)
24# include <cereal/types/vector.hpp>
25#endif
26#if __has_include(<cereal/types/unordered_map.hpp>)
27# include <cereal/types/unordered_map.hpp>
28#endif
29
32#include <bio/meta/tag/vtag.hpp>
34
35namespace bio::ranges
36{
37
98template <typename key_t, typename mapped_t, bool mapped_t_is_context_aware = false>
100{
101public:
102 static_assert(std::is_object_v<key_t>, "The key type may not be a reference or have const.");
103 static_assert(std::convertible_to<key_t, std::string_view>,
104 "The key type must be std::convertible_to<std::string_view>.");
105 static_assert(std::is_object_v<mapped_t>, "The mapped type may not be a reference or have const.");
106
115 using difference_type = ptrdiff_t;
116 using size_type = size_t;
117 using const_iterator = detail::random_access_iterator<dictionary const>;
118 using iterator = std::conditional_t<mapped_t_is_context_aware,
120 detail::random_access_iterator<dictionary>>;
122
124 // this signals to range-v3 that something is a container :|
125 using allocator_type = void;
127
131 dictionary() = default;
132 dictionary(dictionary const &) = default;
133 dictionary(dictionary &&) noexcept = default;
134 dictionary & operator=(dictionary const &) = default;
135 dictionary & operator=(dictionary &&) noexcept = default;
136 ~dictionary() = default;
137
150 template <typename... value_type_>
151 requires((std::convertible_to<value_type_, value_type> && ...) && sizeof...(value_type_) > 0)
152 explicit dictionary(value_type_ &&... args)
153 {
154 assign(std::forward<value_type_>(args)...);
155 }
156
172 template <meta::different_from<iterator> begin_it_type, typename end_it_type>
173 requires(meta::different_from<const_iterator, begin_it_type> && std::forward_iterator<begin_it_type> &&
174 std::sentinel_for<end_it_type, begin_it_type> &&
175 std::convertible_to<std::iter_reference_t<begin_it_type>, value_type>)
176 dictionary(begin_it_type const begin_it, end_it_type const end_it) : dictionary{}
177 {
178 assign(begin_it, end_it);
179 }
180
182 dictionary(iterator const begin_it, iterator const end_it) : dictionary{} { assign(begin_it, end_it); }
183
185 dictionary(const_iterator const begin_it, const_iterator const end_it)
186 requires(!std::same_as<iterator, const_iterator>)
187 : dictionary{}
188 {
189 assign(begin_it, end_it);
190 }
191
205 template <meta::different_from<dictionary> other_range_t>
206 requires(std::ranges::input_range<other_range_t>)
207 explicit dictionary(other_range_t && range) : dictionary{std::ranges::begin(range), std::ranges::end(range)}
208 {}
209
223 template <typename... value_type_>
227 (std::convertible_to<value_type_, value_type> && ...) && (sizeof...(value_type_) > 0))
228 void assign(value_type_ &&... args)
229 {
230 storage.clear();
231 storage.reserve(sizeof...(args));
232 (storage.push_back(std::forward<value_type_>(args)), ...);
233 recompute_hashes(); // this validates uniqueness of keys
234 }
235
249 template <std::ranges::input_range other_range_t>
250 requires std::convertible_to<std::ranges::range_reference_t<other_range_t>, value_type>
251 void assign(other_range_t && range)
252 {
253 //TODO we could create move-iterators for
254 assign(std::ranges::begin(range), std::ranges::end(range));
255 }
256
272 template <meta::different_from<iterator> begin_it_type, typename end_it_type>
273 requires(meta::different_from<const_iterator, begin_it_type> && std::forward_iterator<begin_it_type> &&
274 std::sentinel_for<end_it_type, begin_it_type> &&
275 std::convertible_to<std::iter_reference_t<begin_it_type>, value_type>)
276 void assign(begin_it_type begin_it, end_it_type end_it)
277 {
278 assign_impl(begin_it, end_it);
279 }
280
282 void assign(iterator begin_it, iterator end_it) { assign_impl(begin_it, end_it); }
283
285 void assign(const_iterator begin_it, const_iterator end_it)
286 requires(!std::same_as<iterator, const_iterator>)
287 {
288 assign_impl(begin_it, end_it);
289 }
291
296 iterator begin() noexcept { return iterator{*this}; }
297
299 const_iterator begin() const noexcept { return const_iterator{*this}; }
300
302 const_iterator cbegin() const noexcept { return const_iterator{*this}; }
303
305 iterator end() noexcept { return iterator{*this, size()}; }
306
308 const_iterator end() const noexcept { return const_iterator{*this, size()}; }
309
311 const_iterator cend() const noexcept { return const_iterator{*this, size()}; }
313
333 {
334 if (i >= size())
335 {
336 throw std::out_of_range{"Trying to access element behind the last in dictionary."};
337 }
338 return (*this)[i];
339 }
340
343 {
344 if (i >= size())
345 {
346 throw std::out_of_range{"Trying to access element behind the last in dictionary."};
347 }
348 return (*this)[i];
349 }
350
369 {
370 assert(i < size());
371 return reference{get<0>(storage[i]), get<1>(storage[i])};
372 }
373
375 const_reference operator[](size_type const i) const noexcept
376 {
377 assert(i < size());
378 return storage[i];
379 }
380
396 reference front() noexcept
397 {
398 assert(size() > 0);
399 return (*this)[0];
400 }
401
403 const_reference front() const noexcept
404 {
405 assert(size() > 0);
406 return (*this)[0];
407 }
408
424 reference back() noexcept
425 {
426 assert(size() > 0);
427 return (*this)[size() - 1];
428 }
429
431 const_reference back() const noexcept
432 {
433 assert(size() > 0);
434 return (*this)[size() - 1];
435 }
437
441//TODO(GCC11): always use string_view after deprecating GCC10
442#ifdef __cpp_lib_generic_unordered_lookup
443 using het_key_t = std::string_view const;
444#else
445 using het_key_t = key_t const &;
446#endif
447
460 bool contains(het_key_t key) const { return key_to_index.contains(key); }
461
476 size_t count(het_key_t key) const { return key_to_index.count(key); }
477
491 {
492 if (auto it = key_to_index.find(key); it == key_to_index.end())
493 return end();
494 else
495 return begin() + get<1>(*it);
496 }
497
500 {
501 if (auto it = key_to_index.find(key); it == key_to_index.end())
502 return end();
503 else
504 return begin() + get<1>(*it);
505 }
506
523 {
524 auto it = key_to_index.find(key);
525 if (it == key_to_index.end())
526 throw std::out_of_range{"Key not found in dictionary."};
527 return get<1>(storage[it->second]);
528 }
529
531 mapped_t const & at(het_key_t key) const
532 {
533 auto it = key_to_index.find(key);
534 if (it == key_to_index.end())
535 throw std::out_of_range{"Key not found in dictionary."};
536 return get<1>(storage[it->second]);
537 }
538
556 mapped_ref_t operator[](het_key_t key) { return at(key); }
557
559 mapped_t const & operator[](het_key_t key) const { return at(key); }
561
585 template <small_string key>
586 friend decltype(auto) get(meta::decays_to<dictionary> auto && dict)
587 requires(mapped_t_is_context_aware && requires { get<key>(get<1>(dict.storage[0])); })
588 {
589#ifdef __cpp_lib_generic_unordered_lookup
590 auto it = dict.key_to_index.find(key);
591#else //TODO(GCC11): remove special case
592 auto it = dict.key_to_index.find(static_cast<key_t>(key));
593#endif
594 if (it == dict.key_to_index.end())
595 throw std::out_of_range{"Key not found in dictionary."};
596
597 if constexpr (std::is_rvalue_reference_v<decltype(dict)>)
598 return std::move(get<key>(get<1>(dict.storage[it->second])));
599 else
600 return get<key>(get<1>(dict.storage[it->second]));
601 }
602
623 template <small_string key>
624 decltype(auto) at(meta::vtag_t<key> BIOCPP_DOXYGEN_ONLY(key_tag))
625 requires(requires(dictionary d) { get<key>(d); })
626 {
627 return get<key>(*this);
628 }
629
631 template <small_string key>
632 decltype(auto) at(meta::vtag_t<key> BIOCPP_DOXYGEN_ONLY(key_tag)) const
633 requires(requires(dictionary const d) { get<key>(d); })
634 {
635 return get<key>(*this);
636 }
637
639 template <small_string key>
640 decltype(auto) operator[](meta::vtag_t<key> BIOCPP_DOXYGEN_ONLY(key_tag))
641 requires(requires(dictionary d) { get<key>(d); })
642 {
643 return get<key>(*this);
644 }
645
647 template <small_string key>
648 decltype(auto) operator[](meta::vtag_t<key> BIOCPP_DOXYGEN_ONLY(key_tag)) const
649 requires(requires(dictionary const d) { get<key>(d); })
650 {
651 return get<key>(*this);
652 }
654
669 bool empty() const noexcept { return size() == 0; }
670
682 size_type size() const noexcept { return storage.size(); }
683
698 size_type max_size() const noexcept { return storage.max_size(); }
699
716 size_type capacity() const noexcept { return storage.capacity(); }
717
733 void reserve(size_type const new_cap)
734 {
735 storage.reserve(new_cap);
736 key_to_index.reserve(new_cap);
737 }
738
751 void shrink_to_fit() { storage.shrink_to_fit(); }
753
767 void clear() noexcept
768 {
769 storage.clear();
770 key_to_index.clear();
771 }
772
785 template <std::convertible_to<value_type> value_type_ = value_type>
786 void push_back(value_type_ && value)
787 {
788 if (key_to_index.contains(get<0>(value)))
789 throw std::runtime_error{"An element with this key already exists."};
790 storage.push_back(std::forward<value_type_>(value));
791 key_to_index[get<0>(storage.back())] = storage.size() - 1;
792 }
793
806 void emplace_back(auto &&... args)
807 requires(std::constructible_from<value_type, decltype(args)...>)
808 {
809 storage.emplace_back(std::forward<decltype(args)>(args)...);
810 if (key_to_index.contains(get<0>(storage.back())))
811 throw std::runtime_error{"An element with this key already exists."};
812 key_to_index[get<0>(storage.back())] = storage.size() - 1;
813 }
814
829 void pop_back()
830 {
831 assert(!empty());
832 key_to_index.erase(get<0>(storage.back()));
833 storage.pop_back();
834 }
835
851 {
852 assert(!empty());
853 value_type tmp = std::move(storage.back());
854 pop_back();
855 return tmp;
856 }
858
861
863 friend bool operator==(dictionary const & lhs, dictionary const & rhs) noexcept
864 {
865 return lhs.storage == rhs.storage;
866 }
867
869 friend bool operator<=>(dictionary const & lhs, dictionary const & rhs) noexcept
870 {
871 return lhs.storage <=> rhs.storage;
872 }
874
875private:
877
879 struct hash_string
880 {
882 using is_transparent = void;
883
885 std::size_t operator()(std::string_view const v) const { return std::hash<std::string_view>{}(v); }
886 };
887
889 struct eq_string
890 {
892 using is_transparent = void;
893
895 bool operator()(std::string_view const lhs, std::string_view const rhs) const
896 {
897 return std::ranges::equal(lhs, rhs);
898 }
899 };
900
905
907 void recompute_hashes()
908 {
909 key_to_index.clear();
910 key_to_index.reserve(storage.size());
911 size_t i = 0;
912 for (auto && [key, value] : storage)
913 key_to_index[key] = i++;
914 if (key_to_index.size() != storage.size())
915 throw std::runtime_error{"When creating/assigning bio::ranges::dictionary, keys where not unique."};
916 }
917
919 void assign_impl(auto begin_it, auto end_it)
920 {
921 if constexpr (requires { storage.assign(begin_it, end_it); })
922 {
923 storage.assign(begin_it, end_it);
924 }
925 else
926 {
927 storage.clear();
928
929 if constexpr (std::sized_sentinel_for<decltype(end_it), decltype(begin_it)>)
930 storage.reserve(end_it - begin_it);
931
932 for (; begin_it != end_it; ++begin_it)
933 storage.push_back(*begin_it);
934 }
935 }
936
937public:
939
945 template <typename archive_t>
946 void serialize(archive_t & archive)
947 {
948 archive(storage);
949 archive(key_to_index);
950 }
952};
953
954} // namespace bio::ranges
T begin(T... args)
An associative container with contiguous, predictable storage.
Definition: dictionary.hpp:100
void clear() noexcept
Removes all elements from the container.
Definition: dictionary.hpp:767
dictionary(other_range_t &&range)
Construct from a different range.
Definition: dictionary.hpp:207
void shrink_to_fit()
Reduce capacity to current size() to free unused memory.
Definition: dictionary.hpp:751
iterator end() noexcept
Returns iterator past the end.
Definition: dictionary.hpp:305
iterator begin() noexcept
Returns the begin iterator.
Definition: dictionary.hpp:296
const_iterator end() const noexcept
Returns iterator past the end.
Definition: dictionary.hpp:308
reference at(size_type const i)
Return the i-th element.
Definition: dictionary.hpp:332
const_iterator begin() const noexcept
Returns the begin iterator.
Definition: dictionary.hpp:299
const_reference front() const noexcept
Return the first element. Calling front on an empty container is undefined.
Definition: dictionary.hpp:403
size_type max_size() const noexcept
Returns the maximum number of elements the container is able to hold.
Definition: dictionary.hpp:698
detail::random_access_iterator< dictionary const > const_iterator
The const_iterator type.
Definition: dictionary.hpp:117
mapped_ref_t at(het_key_t key)
Access element by key.
Definition: dictionary.hpp:522
void assign(begin_it_type begin_it, end_it_type end_it)
Assign from pair of iterators.
Definition: dictionary.hpp:276
key_t const & het_key_t
Type used for key-based access.
Definition: dictionary.hpp:445
decltype(auto) at(meta::vtag_t< key > key_tag) const
Access element by compile-time string and implicitly call get() on it.
Definition: dictionary.hpp:632
friend decltype(auto) get(meta::decays_to< dictionary > auto &&dict)
Access element by compile-time string and implicitly call get() on it.
Definition: dictionary.hpp:586
const_reference operator[](size_type const i) const noexcept
Return the i-th element.
Definition: dictionary.hpp:375
size_t count(het_key_t key) const
The number of elements in the container with the specified key.
Definition: dictionary.hpp:476
void assign(iterator begin_it, iterator end_it)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition: dictionary.hpp:282
const_reference back() const noexcept
Return the last element.
Definition: dictionary.hpp:431
bool empty() const noexcept
Checks whether the container is empty.
Definition: dictionary.hpp:669
dictionary(iterator const begin_it, iterator const end_it)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition: dictionary.hpp:182
reference front() noexcept
Return the first element. Calling front on an empty container is undefined.
Definition: dictionary.hpp:396
dictionary(dictionary &&) noexcept=default
Defaulted.
const_reference at(size_type const i) const
Return the i-th element.
Definition: dictionary.hpp:342
mapped_ref_t operator[](het_key_t key)
Access element by key.
Definition: dictionary.hpp:556
std::tuple< key_t, mapped_t > value_type
The value_type type.
Definition: dictionary.hpp:112
size_type size() const noexcept
Returns the number of elements in the container.
Definition: dictionary.hpp:682
value_type extract_back()
Removes and returns the last element of the container.
Definition: dictionary.hpp:850
void reserve(size_type const new_cap)
Reserve storage in the underlying data types to accomodate for future inserts.
Definition: dictionary.hpp:733
mapped_t const & operator[](het_key_t key) const
Access element by key.
Definition: dictionary.hpp:559
dictionary()=default
Defaulted.
decltype(auto) at(meta::vtag_t< key > key_tag)
Access element by compile-time string and implicitly call get() on it.
Definition: dictionary.hpp:624
reference operator[](size_type const i) noexcept
Return the i-th element.
Definition: dictionary.hpp:368
void assign(other_range_t &&range)
Assign from a different range.
Definition: dictionary.hpp:251
dictionary(dictionary const &)=default
Defaulted.
ptrdiff_t difference_type
The difference_type type.
Definition: dictionary.hpp:115
dictionary(begin_it_type const begin_it, end_it_type const end_it)
Construct from two iterators.
Definition: dictionary.hpp:176
mapped_t const & at(het_key_t key) const
Access element by key.
Definition: dictionary.hpp:531
dictionary(const_iterator const begin_it, const_iterator const end_it)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition: dictionary.hpp:185
friend bool operator<=>(dictionary const &lhs, dictionary const &rhs) noexcept
Performs element-wise comparison.
Definition: dictionary.hpp:869
friend bool operator==(dictionary const &lhs, dictionary const &rhs) noexcept
Performs element-wise comparison.
Definition: dictionary.hpp:863
iterator find(het_key_t key)
Find an element with the given key.
Definition: dictionary.hpp:490
const_iterator cend() const noexcept
Returns iterator past the end.
Definition: dictionary.hpp:311
size_t size_type
The size_type type.
Definition: dictionary.hpp:116
const_iterator cbegin() const noexcept
Returns the begin iterator.
Definition: dictionary.hpp:302
size_type capacity() const noexcept
Returns the number of elements that the container is able to hold without reallocating (see below).
Definition: dictionary.hpp:716
void push_back(value_type_ &&value)
Appends the given element value to the end of the container.
Definition: dictionary.hpp:786
void emplace_back(auto &&... args)
Constructs an element in-place at the end of the container.
Definition: dictionary.hpp:806
void pop_back()
Removes the last element of the container.
Definition: dictionary.hpp:829
void assign(const_iterator begin_it, const_iterator end_it)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition: dictionary.hpp:285
reference back() noexcept
Return the last element.
Definition: dictionary.hpp:424
void assign(value_type_ &&... args)
Assign from multiple elements.
Definition: dictionary.hpp:228
bool contains(het_key_t key) const
Check whether the container has an element with the given key.
Definition: dictionary.hpp:460
const_iterator find(het_key_t key) const
Find an element with the given key.
Definition: dictionary.hpp:499
T clear(T... args)
Resolves to std::same_as<std::decay_t<t>, std::decay_t<u>>.
Definition: core_language.hpp:133
The negation of bio::meta::decays_to.
Definition: core_language.hpp:138
T contains(T... args)
Provides concepts for core language types and relations that don't have concepts in C++20 (yet).
T count(T... args)
T end(T... args)
T equal(T... args)
T erase(T... args)
T find(T... args)
T forward(T... args)
Provides metaprogramming utilities for integer types.
T is_rvalue_reference_v
The ranges module's namespace.
Provides the bio::ranges::detail::random_access_iterator class.
T reserve(T... args)
T size(T... args)
The type of bio::meta::vtag. [Declaration].
Definition: vtag.hpp:63
Provides bio::meta::vtag.