From 5a21b565202d6158ff8b4d5537b5c0008ea6271a Mon Sep 17 00:00:00 2001 From: Enrico Seiler Date: Tue, 25 Jun 2019 15:08:48 +0200 Subject: [PATCH] [FEATURE] dynamic_bitset --- .../seqan3/range/container/dynamic_bitset.hpp | 1477 +++++++++++++++++ test/unit/range/container/CMakeLists.txt | 1 + .../range/container/dynamic_bitset_test.cpp | 735 ++++++++ 3 files changed, 2213 insertions(+) create mode 100644 include/seqan3/range/container/dynamic_bitset.hpp create mode 100644 test/unit/range/container/dynamic_bitset_test.cpp diff --git a/include/seqan3/range/container/dynamic_bitset.hpp b/include/seqan3/range/container/dynamic_bitset.hpp new file mode 100644 index 00000000000..56d2feed37b --- /dev/null +++ b/include/seqan3/range/container/dynamic_bitset.hpp @@ -0,0 +1,1477 @@ +// ----------------------------------------------------------------------------------------------------- +// Copyright (c) 2006-2019, Knut Reinert & Freie Universität Berlin +// Copyright (c) 2016-2019, Knut Reinert & MPI für molekulare Genetik +// This file may be used, modified and/or redistributed under the terms of the 3-clause BSD-License +// shipped with this file and also available at: https://github.com/seqan/seqan3/blob/master/LICENSE.md +// ----------------------------------------------------------------------------------------------------- + +/*!\file + * \author Enrico Seiler + * \brief A constexpr bitset implementation with dynamic size at compile time. + */ + +#pragma once + +#include +#include +#include + +namespace seqan3 +{ + +/*!\brief A constexpr bitset implementation with dynamic size at compile time. + * \implements seqan3::ReservableContainer + * \implements seqan3::Cerealisable + * \ingroup container + * \tparam capacity_ The capacity of the dynamic bitset + * + * This implementation of a bitset can be constructed, accessed and modified at compile time. + * It has a fixed capacity but a dynamic size and provides all functionality of a sequence container. Note + * that it also models a reservable sequence container but all associated member functions are no-op because the + * capacity is fixed. + */ + +template +class dynamic_bitset +{ +private: + //!\brief Stores the actual data. + uint64_t data_{}; + + //!\brief The size in bits of the underlying data structure. 64 for `uint64_t`. + static constexpr uint8_t internal_bits{detail::sizeof_bits}; + //!\brief The number of bits reserved for representing size information in data_. 6 for `uint64_t`. + static constexpr uint8_t size_bits{detail::most_significant_bit_set(detail::next_power_of_two(internal_bits))}; + //!\brief The number of bits reserved for bit information in data_. 58 for `uint64_t`. + static constexpr uint8_t data_bits{internal_bits - size_bits}; + + //!\brief Proxy data type returned by seqan3::dynamic_bitset as reference to the bit. + class reference_proxy_type + { + public: + constexpr reference_proxy_type() noexcept = default; //!< Defaulted. + constexpr reference_proxy_type(reference_proxy_type const &) noexcept = default; //!< Defaulted. + constexpr reference_proxy_type(reference_proxy_type &&) noexcept = default; //!< Defaulted. + + constexpr reference_proxy_type & operator=(reference_proxy_type rhs) noexcept //!< Assign value. + { + rhs ? set() : reset(); + return *this; + } + + ~reference_proxy_type() noexcept = default; //!< Defaulted. + + //!\brief Initialise from seqan3::dynamic_bitset's underlying data and a bit position. + constexpr reference_proxy_type(uint64_t & internal_, size_t pos) noexcept : + internal{internal_}, mask{1ULL<(internal & mask); + } + + //!\brief Returns the inverted value of the referenced bit. + constexpr bool operator~() const noexcept + { + return !static_cast(internal & mask); + } + + //!\brief Sets the referenced bit to value. + constexpr reference_proxy_type & operator=(bool const value) + { + value ? set() : reset(); + return *this; + } + + //!\brief Sets the referenced bit to the result of a bitwise or with value. + constexpr reference_proxy_type & operator|=(bool const value) + { + if (value) + set(); + return *this; + } + + //!\brief Sets the referenced bit to the result of a bitwise and with value. + constexpr reference_proxy_type & operator&=(bool const value) + { + if (!value) + reset(); + return *this; + } + + //!\brief Sets the referenced bit to the result of a bitwise xor with value. + constexpr reference_proxy_type & operator^=(bool const value) + { + operator bool() && value ? reset() : set(); + return *this; + } + + private: + //!\brief The proxy of the underlying data type; wrapped in semiregular_t, because it isn't semiregular itself. + ranges::semiregular_t internal; + //!\brief Bitmask to access one specific bit. + uint64_t mask; + + //!\brief Sets the referenced bit to 1. + constexpr void set() noexcept + { + internal |= mask; + } + + //!\brief Sets the referenced bit to 0. + constexpr void reset() noexcept + { + uint64_t size_chunk = internal >> data_bits << data_bits; + internal &= ~mask << size_bits >> size_bits | size_chunk; + } + }; + +public: + static_assert(capacity_ <= data_bits, "The capacity of the dynamic_bitset exceeds the limit of 58."); //58=data_bits + + /*!\name Associated types + * \{ + */ + //!\brief Equals bool. + using value_type = bool; + //!\brief A proxy type that enables assignment. + using reference = reference_proxy_type; + //!\brief Equals the value_type. + using const_reference = bool; + //!\brief The iterator type of this container (a random access iterator). + using iterator = detail::random_access_iterator; + //!\brief The const_iterator type of this container (a random access iterator). + using const_iterator = detail::random_access_iterator; + //!\brief A std::ptrdiff_t. + using difference_type = ptrdiff_t; + //!\brief An unsigned integer type (usually std::size_t) + using size_type = detail::min_viable_uint_t; + //!\} + + //!\cond + // this signals to range-v3 that something is a container :| + using allocator_type = void; + //!\endcond + + /*!\name Constructors, destructor and assignment + * \{ + */ + constexpr dynamic_bitset() noexcept = default; //!< Defaulted. + constexpr dynamic_bitset(dynamic_bitset const &) noexcept = default; //!< Defaulted. + constexpr dynamic_bitset(dynamic_bitset &&) noexcept = default; //!< Defaulted. + constexpr dynamic_bitset & operator=(dynamic_bitset const &) noexcept = default; //!< Defaulted. + constexpr dynamic_bitset & operator=(dynamic_bitset &&) noexcept = default; //!< Defaulted. + ~dynamic_bitset() noexcept = default; //!< Defaulted. + + /*!\brief Construct from an integral type. + * \tparam t The type of range to construct from; must satisfy std::Integral and must be at most 64 bits long. + * \param[in] value The integral to construct/assign from. + * \throws std::invalid_argument if uint64_t representation of value has bits set past the 58'th. + * + * Delegates to the seqan3::dynamic_bitset(uint64_t value) constructor. + * + * ### Complexity + * + * Constant. + * + * ### Exceptions + * + * Throws std::invalid_argument if uint64_t representation of value has a set bit past the 58 one, + * i.e. only bits in [0,58) may be set. + */ + template + //!\cond + requires detail::sizeof_bits <= internal_bits + //!\endcond + constexpr dynamic_bitset(t value) : dynamic_bitset(static_cast(value)) + {} + + /*!\brief Construct from an uint64_t. + * \param[in] value The uint64_t to construct/assign from. + * \throws std::invalid_argument if value has bits set past the 58'th. + * + * ### Complexity + * + * Constant. + * + * ### Exceptions + * + * Throws std::invalid_argument value has a set bit past the 58 one, i.e. only bits in [0,58) may be set. + */ + constexpr dynamic_bitset(uint64_t value) + { + if (detail::popcount(value >> data_bits) != 0) + throw std::invalid_argument{"The dynamic_bitset can be at most 58 long."}; + data_ |= value; + data_ |= data_ ? static_cast(detail::most_significant_bit_set(data_) + 1) << data_bits : 0u; + } + + /*!\brief Construct from two iterators. + * \tparam begin_it_type Must model std::ForwardIterator and `value_type` must be constructible from + * the reference type of begin_it_type. + * \tparam end_it_type Must model std::Sentinel. + * \param[in] begin_it Begin of range to construct/assign from. + * \param[in] end_it End of range to construct/assign from. + * + * ### Complexity + * + * Linear in the distance between `begin_it` and `end_it`. + * + * ### Exceptions + * + * No-throw guarantee. + */ + template end_it_type> + //!\cond + requires std::Constructible> + //!\endcond + constexpr dynamic_bitset(begin_it_type begin_it, end_it_type end_it) noexcept: + dynamic_bitset{} + { + assign(begin_it, end_it); + } + + /*!\brief Construct from a different range. + * \tparam other_range_t The type of range to be inserted; must satisfy std::ranges::InputRange and `value_type` + * must be constructible from reference_t. + * \param[in] range The sequence to construct/assign from. + * + * ### Complexity + * + * Linear in the size of `range`. + * + * ### Exceptions + * + * No-throw guarantee. + */ + template + //!\cond + requires !std::is_same_v, dynamic_bitset> + /*ICE: && std::Constructible>*/ + //!\endcond + explicit constexpr dynamic_bitset(other_range_t && range) noexcept : + dynamic_bitset{std::ranges::begin(range), std::ranges::end(range)} + {} + + /*!\brief Construct with `n` times `value`. + * \param[in] n Number of elements. + * \param[in] value The initial value to be assigned. + * + * ### Complexity + * + * Linear in `n`. + * + * ### Exceptions + * + * No-throw guarantee. + */ + constexpr dynamic_bitset(size_type n, value_type value) noexcept : + dynamic_bitset{} + { + assign(n, value); + } + + /*!\brief Assign from `std::initializer_list`. + * \param[in] ilist A `std::initializer_list` of value_type. + * + * ### Complexity + * + * Linear in the size of `ilist`. + * + * ### Exceptions + * + * No-throw guarantee. + */ + constexpr dynamic_bitset & operator=(std::initializer_list ilist) noexcept + { + assign(std::ranges::begin(ilist), std::ranges::end(ilist)); + return *this; + } + + /*!\brief Construction from literal. + * \param _lit The literal to construct the string for. May only contain '0' and '1'. + * \throws std::invalid_argument if any character is not '0' or '1'. + * + * The `char` literal is expected to be null-terminated (asserted in debug-mode). If it is not, the last character + * will be lost when copying to the instance of `dynamic_bitset`. + * + * ### Exceptions + * + * Throws std::invalid_argument if any character is not '0' or '1'. + */ + template + constexpr dynamic_bitset(char const (&_lit)[N]) : dynamic_bitset{} + { + static_assert(N <= capacity_ + 1, "Length of string literal exceeds capacity of dynamic_bitset."); + assign(_lit); + } + + /*!\brief Assign from literal. + * \param _lit The literal to assign the string from. May only contain '0' and '1'. + * \throws std::invalid_argument if any character is not '0' or '1'. + * + * The `char` literal is expected to be null-terminated (asserted in debug-mode). If it is not, the last character + * will be lost when copying to the instance of `dynamic_bitset`. + * + * ### Complexity + * + * Linear in the size of _lit. + * + * ### Exceptions + * + * Throws std::invalid_argument if any character is not '0' or '1'. + */ + template + constexpr dynamic_bitset & operator=(char const (&_lit)[N]) + { + static_assert(N <= capacity_ + 1, "Length of string literal exceeds capacity of dynamic_bitset."); + assign(_lit); + return *this; + } + + /*!\brief Assign from literal. + * \param _lit The literal to assign the string from. May only contain '0' and '1'. + * \throws std::invalid_argument if any character is not '0' or '1'. + * + * The `char` literal is expected to be null-terminated (asserted in debug-mode). If it is not, the last character + * will be lost when copying to the instance of `dynamic_bitset`. + * + * ### Complexity + * + * Linear in the size of _lit. + * + * ### Exceptions + * + * Throws std::invalid_argument if any character is not '0' or '1'. + */ + template + constexpr void assign(char const (&_lit)[N]) + { + static_assert(N <= capacity_ + 1, "Length of string literal exceeds capacity of dynamic_bitset."); + assert(_lit[N - 1] == '\0'); + uint64_t value{}; + for (size_t i = 0; i != N - 1; ++i) + { + if (_lit[i] == '0') + { + value <<= 1; + } + else if (_lit[i] == '1') + { + if (i != 0) + { + value <<= 1; + } + value |= 1u; + } + else + { + throw std::invalid_argument{"The string to construct a dynamic_bitset from May only contain 0 and 1."}; + } + } + + (*this) = value; + resize(N - 1); + } + + /*!\brief Assign from `std::initializer_list`. + * \param[in] ilist A `std::initializer_list` of value_type. + * + * ### Complexity + * + * Linear in the size of `ilist`. + * + * ### Exceptions + * + * No-throw guarantee. + */ + constexpr void assign(std::initializer_list ilist) noexcept + { + assign(std::ranges::begin(ilist), std::ranges::end(ilist)); + } + + /*!\brief Assign with `count` times `value`. + * \param[in] count Number of elements. + * \param[in] value The initial value to be assigned. + * + * ### Complexity + * + * In \f$O(count)\f$. + * + * ### Exceptions + * + * No-throw guarantee. + */ + constexpr void assign(size_type const count, value_type const value) noexcept + { + clear(); + auto tmp = view::repeat_n(value, count); + assign(std::ranges::begin(tmp), std::ranges::end(tmp)); + } + + /*!\brief Assign from a different range. + * \tparam other_range_t The type of range to be inserted; must satisfy std::ranges::InputRange and `value_type` + * must be constructible from reference_t. + * \param[in] range The sequences to construct/assign from. + * + * ### Complexity + * + * Linear in the size of `range`. + * + * ### Exceptions + * + * No-throw guarantee. + */ + template + //!\cond + requires std::Constructible> + //!\endcond + constexpr void assign(other_range_t && range) noexcept + { + assign(std::ranges::begin(range), std::ranges::end(range)); + } + + /*!\brief Assign from pair of iterators. + * \tparam begin_it_type Must satisfy std::ForwardIterator and the `value_type` must be constructible from + * the reference type of begin_it_type. + * \tparam end_it_type Must satisfy std::Sentinel. + * \param[in] begin_it Begin of range to construct/assign from. + * \param[in] end_it End of range to construct/assign from. + * + * ### Complexity + * + * Linear in the distance between `begin_it` and `end_it`. + * + * ### Exceptions + * + * No-throw guarantee. + */ + template end_it_type> + constexpr void assign(begin_it_type begin_it, end_it_type end_it) noexcept + //!\cond + requires std::Constructible> + //!\endcond + { + clear(); + insert(cbegin(), begin_it, end_it); + } + //!\} + + /*!\name Iterators + * \{ + */ + //!\brief Returns the begin to the dynamic_bitset. + constexpr iterator begin() noexcept + { + return iterator{*this}; + } + + //!\copydoc begin() + constexpr const_iterator begin() const noexcept + { + return const_iterator{*this}; + } + + //!\copydoc begin() + constexpr const_iterator cbegin() const noexcept + { + return const_iterator{*this}; + } + + //!\brief Returns iterator past the end of the dynamic_bitset. + constexpr iterator end() noexcept + { + return iterator{*this, size()}; + } + + //!\copydoc end() + constexpr const_iterator end() const noexcept + { + return const_iterator{*this, size()}; + } + + //!\copydoc end() + constexpr const_iterator cend() const noexcept + { + return const_iterator{*this, size()}; + } + //!\} + + /*!\name Bit manipulation + * \{ + */ + //!\brief Sets the bits to the result of binary AND on corresponding pairs of bits of *this and rhs. + constexpr dynamic_bitset & operator&=(dynamic_bitset const & rhs) noexcept + { + assert(size() == rhs.size()); + data_ &= rhs.data_; + return *this; + } + + //!\brief Sets the bits to the result of binary OR on corresponding pairs of bits of *this and rhs. + constexpr dynamic_bitset & operator|=(dynamic_bitset const & rhs) noexcept + { + assert(size() == rhs.size()); + data_ |= rhs.data_; + return *this; + } + + //!\brief Sets the bits to the result of binary XOR on corresponding pairs of bits of *this and rhs. + constexpr dynamic_bitset & operator^=(dynamic_bitset const & rhs) noexcept + { + assert(size() == rhs.size()); + uint64_t tmp_size = size_chunk(); + data_ ^= rhs.data_; + data_ |= tmp_size; + return *this; + } + + //!\brief Returns a temporary copy of *this with all bits flipped (binary NOT). + constexpr dynamic_bitset operator~() const noexcept + { + // Shift the part containing the bit information all the way to the left, flip bits, and shift back. + dynamic_bitset tmp{~(data_ << offset()) >> offset()}; + tmp.resize(size()); + return tmp; + } + + //!\brief Performs binary shift left on the current object. + constexpr dynamic_bitset & operator<<=(size_t const count) noexcept + { + assert(count > 0); + assert(count < size()); + uint64_t new_data = data_chunk(); + new_data <<= offset() + count; + new_data >>= offset(); + new_data |= size_chunk(); + data_ = std::move(new_data); + return *this; + } + + //!\brief Performs binary shift right on the current object. + constexpr dynamic_bitset & operator>>=(size_t const count) noexcept + { + assert(count > 0); + assert(count < size()); + uint64_t new_data = data_chunk(); + new_data >>= count; + new_data |= size_chunk(); + data_ = std::move(new_data); + return *this; + } + + //!\brief Performs binary shift right. + constexpr dynamic_bitset operator>>(size_t const count) const noexcept + { + assert(count > 0); + assert(count < size()); + dynamic_bitset tmp{*this}; + tmp >>= count; + return tmp; + } + + //!\brief Performs binary shift left. + constexpr dynamic_bitset operator<<(size_t const count) const noexcept + { + assert(count > 0); + assert(count < size()); + dynamic_bitset tmp{*this}; + tmp <<= count; + return tmp; + } + + //!\brief Sets all bits to 1. + constexpr dynamic_bitset & set() noexcept + { + data_ |= ~0ULL >> offset(); + return *this; + } + + /*!\brief Sets the i'th bit to value. + * \param[in] i Index of the bit to value. + * \param[in] value Value to set. Default true. + * \throws std::out_of_range if you access an element behind the last. + * + * ### Complexity + * + * Constant. + * + * ### Exceptions + * + * Throws std::out_of_range if `i >= size()`. + */ + constexpr dynamic_bitset & set(size_t const i, bool const value = true) + { + at(i) = value; + return *this; + } + + //!\brief Sets all bits to 0. + constexpr dynamic_bitset & reset() noexcept + { + data_ = size_chunk(); + return *this; + } + + /*!\brief Sets the i'th bit to false. + * \param[in] i Index of the bit to value. + * \throws std::out_of_range if you access an element behind the last. + * + * ### Complexity + * + * Constant. + * + * ### Exceptions + * + * Throws std::out_of_range if `i >= size()`. + */ + constexpr dynamic_bitset & reset(size_t const i) + { + set(i, false); + return *this; + } + + //!\brief Flips all bits. (binary NOT) + constexpr dynamic_bitset & flip() noexcept + { + data_ = ~(data_chunk() << offset()) >> offset() | size_chunk(); + return *this; + } + + /*!\brief Flips the i'th bit. (binary NOT) + * \param[in] i Index of the bit to flip. + * \throws std::out_of_range if you access an element behind the last. + * + * ### Complexity + * + * Constant. + * + * ### Exceptions + * + * Throws std::out_of_range if `i >= size()`. + */ + constexpr dynamic_bitset & flip(size_t const i) + { + at(i) ? reset(i) : set(i); + return *this; + } + //!\} + + /*!\name Element Access + * \{ + */ + /*!\brief Checks if all bit are set. + * + * \returns `true` if all bits are set or the bitset is empty, `false` otherwise. + */ + constexpr bool all() const noexcept + { + return count() == size(); + } + + /*!\brief Checks if any bit is set. + * + * \returns `true` if any bit is set, `false` otherwise. + */ + constexpr bool any() const noexcept + { + return count() != 0; + } + + /*!\brief Checks if no bit is set. + * + * \returns `true` if no bit is set, `false` otherwise. + */ + constexpr bool none() const noexcept + { + return count() == 0; + } + + //!\brief Returns the number of set bits. + constexpr size_type count() const noexcept + { + return detail::popcount(data_chunk()); + } + + /*!\brief Return the i-th element. + * \param[in] i Index of the element to retrieve. + * \throws std::out_of_range If you access an element behind the last. + * \returns A reference to the value at position `i`. + * + * ### Complexity + * + * Constant. + * + * ### Exceptions + * + * Throws std::out_of_range if `i >= size()`. + */ + constexpr reference at(size_t i) + { + if (i >= size()) // [[unlikely]] + throw std::out_of_range{"Trying to access position " + std::to_string(i) + + " in a seqan3::dynamic_bitset of size " + std::to_string(size()) + "."}; + return (*this)[i]; + } + + //!\copydoc at() + constexpr const_reference at(size_t i) const + { + if (i >= size()) // [[unlikely]] + throw std::out_of_range{"Trying to access position " + std::to_string(i) + + " in a seqan3::dynamic_bitset of size " + std::to_string(size()) + "."}; + return (*this)[i]; + } + + //!\copydoc at() + constexpr const_reference test(size_t i) const + { + return at(i); + } + + /*!\brief Return the i-th element. + * \param i The element to retrieve. + * \returns A reference to the value at position `i`. + * + * Accessing an element behind the last causes undefined behaviour. In debug mode an assertion checks the size of + * the container. + * + * ### Complexity + * + * Constant. + * + * ### Exceptions + * + * No-throw guarantee. + */ + constexpr reference operator[](size_t i) + { + assert(i <= size()); + return {data_, i}; + } + + //!\copydoc operator[]() + constexpr const_reference operator[](size_t i) const + { + assert(i <= size()); + return data_ << (internal_bits - i - 1) >> (internal_bits - 1); + } + + /*!\brief Return the first element. Calling front on an empty container is undefined. + * \returns A reference to the value at the first position. + * + * Calling front on an empty container is undefined. In debug mode an assertion checks the size of the container. + * + * ### Complexity + * + * Constant. + * + * ### Exceptions + * + * No-throw guarantee. + */ + constexpr reference front() noexcept + { + assert(size() > 0); + return (*this)[0]; + } + + //!\copydoc front() + constexpr const_reference front() const noexcept + { + assert(size() > 0); + return (*this)[0]; + } + + /*!\brief Return the last element. + * \returns A reference to the value at the last position. + * + * Calling back on an empty container is undefined. In debug mode an assertion checks the size of the container. + * + * ### Complexity + * + * Constant. + * + * ### Exceptions + * + * No-throw guarantee. + */ + constexpr reference back() noexcept + { + assert(size() > 0); + return (*this)[size()-1]; + } + + //!\copydoc back() + constexpr const_reference back() const noexcept + { + assert(size() > 0); + return (*this)[size()-1]; + } + //!\} + + /*!\name Capacity + * \{ + */ + /*!\brief Checks whether the container is empty. + * \returns `true` if the container is empty, `false` otherwise. + * + * ### Complexity + * + * Constant. + * + * ### Exceptions + * + * No-throw guarantee. + */ + constexpr bool empty() const noexcept + { + return size() == 0; + } + + /*!\brief Returns the number of elements in the container, i.e. std::distance(begin(), end()). + * \returns The number of elements in the container. + * + * ### Complexity + * + * Constant. + * + * ### Exceptions + * + * No-throw guarantee. + */ + constexpr size_type size() const noexcept + { + return static_cast(data_ >> data_bits); + } + + /*!\brief Returns the maximum number of elements the container is able to hold and resolves to `capacity_`. + * \returns The number of elements in the container. + * + * This value typically reflects the theoretical limit on the size of the container. At runtime, the size + * of the container may be limited to a value smaller than max_size() by the amount of RAM available. + * + * ### Complexity + * + * Constant. + * + * ### Exceptions + * + * No-throw guarantee. + */ + constexpr size_type max_size() const noexcept + { + return capacity(); + } + + /*!\brief Returns the number of elements that the container is able to hold and resolves to `capacity_`. + * \returns The capacity of the currently allocated storage. + * + * ### Complexity + * + * Constant. + * + * ### Exceptions + * + * No-throw guarantee. + */ + constexpr size_type capacity() const noexcept + { + return capacity_; + } + + //!\brief Since the capacity is fixed on compile time, this is a no-op. + constexpr void reserve(size_t) const noexcept + { + // no-op + } + + //!\brief Since the capacity is fixed on compile time, this is a no-op. + constexpr void shrink_to_fit() const noexcept + { + // no-op + } + //!\} + + /*!\name Modifiers + * \{ + */ + /*!\brief Removes all elements from the container. + * + * ### Complexity + * + * Constant. + * + * ### Exceptions + * + * No-throw guarantee. + */ + constexpr void clear() noexcept + { + data_ &= 0ULL; + } + + /*!\brief Inserts value before position in the container. + * \param pos Iterator before which the content will be inserted. `pos` may be the end() iterator. + * \param value Element value to insert. + * \returns Iterator pointing to the inserted value. + * + * Inserting a value although the maximum capacity is reached is undefined behaviour. + * + * ### Complexity + * + * Worst-case linear in size(). + * + * ### Exceptions + * + * No-throw guarantee. + */ + constexpr iterator insert(const_iterator pos, value_type const value) noexcept + { + return insert(pos, 1, value); + } + + /*!\brief Inserts count copies of value before position in the container. + * \param pos Iterator before which the content will be inserted. `pos` may be the end() iterator. + * \param count Number of copies. + * \param value Element value to insert. + * \returns Iterator pointing to the first element inserted, or `pos` if `count==0`. + * + * If `size()` + `count` > `capacity()` this function results in undefined behaviour. + * + * ### Complexity + * + * Worst-case linear in size(). + * + * ### Exceptions + * + * No-throw guarantee. + */ + constexpr iterator insert(const_iterator pos, size_type const count, value_type const value) noexcept + { + auto tmp = view::repeat_n(value, count); + return insert(pos, std::ranges::begin(tmp), std::ranges::end(tmp)); + } + + /*!\brief Inserts elements from range `[begin_it, end_it)` before position in the container. + * \tparam begin_it_type Must satisfy std::ForwardIterator and the `value_type` must be constructible from + * the reference type of begin_it_type. + * \tparam end_it_type Must satisfy std::Sentinel. + * \param[in] pos Iterator before which the content will be inserted. `pos` may be the end() iterator. + * \param[in] begin_it Begin of range to construct/assign from. + * \param[in] end_it End of range to construct/assign from. + * \returns Iterator pointing to the first element inserted, or `pos` if `begin_it==end_it`. + * + * The behaviour is undefined if begin_it and end_it are iterators into `*this` or if, given the size `n` of the + * range represented by [begin_t, end_it), `size()` + `n` > `capacity()`. + * + * ### Complexity + * + * Worst-case linear in size(). + * + * ### Exceptions + * + * No-throw guarantee. + */ + template end_it_type> + //!\cond + requires std::Constructible> + //!\endcond + constexpr iterator insert(const_iterator pos, begin_it_type begin_it, end_it_type end_it) noexcept + { + auto const pos_as_num = std::ranges::distance(cbegin(), pos); + auto const length = std::ranges::distance(begin_it, end_it); + + if (length == 0) + return begin(); // nothing to insert + + size_type const tmp_size{size()}; + resize(tmp_size + length); + + for (size_type i = tmp_size + length - 1; i > pos_as_num + length - 1; --i) + (*this)[i] = (*this)[i - length]; + + // std::ranges::copy(begin_it, end_it, (*this)[pos_as_num]); + for (auto i = pos_as_num; begin_it != end_it; ++i, ++begin_it) + (*this)[i] = *begin_it; + + return begin() + pos_as_num; + } + + /*!\brief Inserts elements from initializer list before position in the container. + * \param pos Iterator before which the content will be inserted. `pos` may be the end() iterator. + * \param ilist Initializer list with values to insert. + * \returns Iterator pointing to the first element inserted, or `pos` if `ilist` is empty. + * + * Given the size `n` of `ilist`, this function results in undefined behaviour if `size()` + `n` > `capacity()`. + * + * ### Complexity + * + * Worst-case linear in size(). + * + * ### Exceptions + * + * No-throw guarantee. + */ + constexpr iterator insert(const_iterator pos, std::initializer_list const & ilist) noexcept + { + return insert(pos, ilist.begin(), ilist.end()); + } + + /*!\brief Removes specified elements from the container. + * \param begin_it Begin of range to erase. + * \param end_it Behind the end of range to erase. + * \returns Iterator following the last element removed. If the iterator `pos` refers to the last element, + * the end() iterator is returned. + * + * Invalidates iterators and references at or after the point of the erase, including the end() iterator. + * + * The iterator begin_it does not need to be dereferenceable if begin_it==end_it: erasing an empty range is a no-op. + * + * ### Complexity + * + * Linear in size(). + * + * ### Exceptions + * + * No-throw guarantee. + */ + constexpr iterator erase(const_iterator begin_it, const_iterator end_it) noexcept + { + if (begin_it >= end_it) // [[unlikely]] + return begin() + std::ranges::distance(cbegin(), end_it); + + auto const length = std::ranges::distance(begin_it, end_it); + auto out_it = begin() + std::ranges::distance(cbegin(), begin_it); + + while (end_it != cend()) + *(out_it++) = *(end_it++); + + resize(size() - length); + return begin() + std::ranges::distance(cbegin(), begin_it); + } + + /*!\brief Removes specified elements from the container. + * \param pos Remove the element at pos. + * \returns Iterator following the last element removed. If the iterator `pos` refers to the last element, + * the end() iterator is returned. + * + * Invalidates iterators and references at or after the point of the erase, including the end() iterator. + * + * The iterator `pos` must be valid and dereferenceable. Thus the end() iterator (which is valid, but is not + * dereferencable) cannot be used as a value for pos. + * + * ### Complexity + * + * Linear in size(). + * + * ### Exceptions + * + * No-throw guarantee. + */ + constexpr iterator erase(const_iterator pos) noexcept + { + return erase(pos, pos + 1); + } + + /*!\brief Appends the given element value to the end of the container. + * \param value The value to append. + * + * If the new size() is greater than capacity() this is undefined behaviour. + * + * ### Complexity + * + * Constant. + * + * ### Exceptions + * + * No-throw guarantee. + */ + constexpr void push_back(value_type const value) noexcept + { + assert(size() < capacity_); + (*this)[size()] = value; + resize(size() + 1); + } + + /*!\brief Removes the last element of the container. + * + * Calling pop_back() on an empty container is undefined. In debug mode an assertion will be thrown. + * + * No iterators or references except for back() and end() are invalidated. + * + * ### Complexity + * + * Constant. + * + * ### Exceptions + * + * No-throw guarantee. + */ + constexpr void pop_back() noexcept + { + assert(size() > 0); + resize(size() - 1); + } + + /*!\brief Resizes the container to contain count elements. + * \param[in] count The new size. + * \param[in] value Append copies of value when resizing, default = false. + * + * If count is greater than capacity this is undefined behaviour. + * If the dynamic_bitset is enlarged, bits in [0, size()) stay the same and bits in [size(), count) + * are set to value. + * If the dynamic_bitset is shrunk, bits in [0, count) stay the same and bits in [count, size()) are set to 0. + * + * ### Complexity + * + * Constant. + * + * ### Exceptions + * + * No-throw guarantee. + */ + constexpr void resize(size_type const count, value_type const value = false) noexcept + { + assert(count <= capacity_); + // In case of enlarging, we need the original size after setting the size bits. + size_t tmp_size{size()}; + // Shrinking. + for (size_t i = tmp_size; i > count; --i) + (*this)[i - 1] = false; + // Set size bits. + data_ &= data_ << size_bits >> size_bits; + data_ |= static_cast(count) << data_bits; + // Enlarging, only needed when we have to set bits. + for (size_t i = tmp_size; value && i < count; ++i) + (*this)[i] = value; + } + + /*!\brief Swap contents with another instance. + * \param rhs The other instance to swap with. + * + * ### Complexity + * + * Linear in the size of both containers. + * + * ### Exceptions + * + * No-throw guarantee if value_type is std::is_nothrow_copy_constructible. + */ + constexpr void swap(dynamic_bitset & rhs) noexcept + { + uint64_t tmp = std::move(data_); + data_ = std::move(rhs.data_); + rhs.data_ = std::move(tmp); + } + + //!\overload + constexpr void swap(dynamic_bitset && rhs) noexcept + { + data_ = std::move(rhs.data_); + } + + //!\} + + /*!\brief Swap contents with another instance. + * \param lhs The first instance. + * \param rhs The other instance to swap with. + * + * ### Complexity + * + * Linear in the size of both containers. + * + * ### Exceptions + * + * No-throw guarantee if value_type is std::is_nothrow_copy_constructible. + */ + friend constexpr void swap(dynamic_bitset & lhs, dynamic_bitset & rhs) noexcept + { + lhs.swap(rhs); + } + + //!\overload + friend constexpr void swap(dynamic_bitset && lhs, dynamic_bitset && rhs) noexcept + { + lhs.swap(rhs); + } + + /*!\name Bitwise operators + * \{ + */ + //!\brief Returns dynamic_bitset containing the result of binary AND on corresponding pairs of bits of lhs and rhs. + template + //!\cond + requires cap2 <= capacity_ + //!\endcond + friend constexpr dynamic_bitset operator&(dynamic_bitset const & lhs, dynamic_bitset const & rhs) noexcept + { + assert(lhs.size() == rhs.size()); + dynamic_bitset tmp{lhs}; + tmp &= rhs; + return tmp; + } + + //!\brief Returns dynamic_bitset containing the result of binary XOR on corresponding pairs of bits of lhs and rhs. + template + //!\cond + requires cap2 <= capacity_ + //!\endcond + friend constexpr dynamic_bitset operator^(dynamic_bitset const & lhs, dynamic_bitset const & rhs) noexcept + { + assert(lhs.size() == rhs.size()); + dynamic_bitset tmp{lhs}; + tmp ^= rhs; + return tmp; + } + + //!\brief Returns dynamic_bitset containing the result of binary OR on corresponding pairs of bits of lhs and rhs. + template + //!\cond + requires cap2 <= capacity_ + //!\endcond + friend constexpr dynamic_bitset operator|(dynamic_bitset const & lhs, dynamic_bitset const & rhs) noexcept + { + assert(lhs.size() == rhs.size()); + dynamic_bitset tmp{lhs}; + tmp |= rhs; + return tmp; + } + //!\} + + //!\name Comparison operators + //!\{ + //!\brief Performs element-wise comparison. + template + //!\cond + requires cap2 <= capacity_ /* resolves ambiguousness when comparing two dynamic_bitsets of unequal capacity */ + //!\endcond + friend constexpr bool operator==(dynamic_bitset const & lhs, dynamic_bitset const & rhs) noexcept + { + return lhs.data_ == rhs.data_; + } + + //!\brief Performs element-wise comparison. + template + //!\cond + requires cap2 <= capacity_ + //!\endcond + friend constexpr bool operator!=(dynamic_bitset const & lhs, dynamic_bitset const & rhs) noexcept + { + return !(lhs == rhs); + } + + //!\brief Performs element-wise comparison. + template + //!\cond + requires cap2 <= capacity_ + //!\endcond + friend constexpr bool operator<(dynamic_bitset const & lhs, dynamic_bitset const & rhs) noexcept + { + return lhs.data_chunk() < rhs.data_chunk(); + } + + //!\brief Performs element-wise comparison. + template + //!\cond + requires cap2 <= capacity_ + //!\endcond + friend constexpr bool operator>(dynamic_bitset const & lhs, dynamic_bitset const & rhs) noexcept + { + return lhs.data_chunk() > rhs.data_chunk(); + } + + //!\brief Performs element-wise comparison. + template + //!\cond + requires cap2 <= capacity_ + //!\endcond + friend constexpr bool operator<=(dynamic_bitset const & lhs, dynamic_bitset const & rhs) noexcept + { + return !(lhs > rhs); + } + + //!\brief Performs element-wise comparison. + template + //!\cond + requires cap2 <= capacity_ + //!\endcond + friend constexpr bool operator>=(dynamic_bitset const & lhs, dynamic_bitset const & rhs) noexcept + { + return !(lhs < rhs); + } + //!\} + + /*!\name Conversions + * \{ + */ + /*!\brief Converts the dynamic_bitset to a std::string. + * \tparam char_t Character type; must model seqan3::Char. + * \param[in] zero Character of type char_t representing `false`. Default '0'. + * \param[in] one Character of type char_t representing `true`. Default '1'. + * \throws std::bad_alloc from the the std::string constructor. + * \returns A std::string representing the dynamic_bitset. + * + * \attention This is the only function of this class that is **not** constexpr because std::string is not + * constexpr. + * + * ### Complexity + * + * Linear in size(). + * + * ### Exceptions + * + * Throws std::bad_alloc from the std::string constructor. + */ + template + std::string to_string(char_t zero = char_t{'0'}, char_t one = char_t{'1'}) const + { + std::string str{}; + str.reserve(size()); + for (bool const bit : std::view::reverse(*this)) + bit ? str.push_back(one) : str.push_back(zero); + return str; + } + + /*!\brief Converts the dynamic_bitset to an `unsigned long` integer. + * \throws std::overflow_error if the value cannot be represented in `unsigned long`. + * \returns A `unsigned long` representing the dynamic_bitset. + * + * ### Complexity + * + * Constant. + * + * ### Exceptions + * + * Throws std::overflow_error if the value cannot be represented in `unsigned long`. + */ + inline constexpr unsigned long to_ulong() const + { + if constexpr (std::numeric_limits::max() < std::numeric_limits::max()) + { + if (data_chunk() > std::numeric_limits::max()) + throw std::overflow_error{"seqan3::dynamic_bitset cannot be represented as unsigned long."}; + } + + return static_cast(data_chunk()); + } + + /*!\brief Converts the dynamic_bitset to an `unsigned long long` integer. + * \throws std::overflow_error if the value cannot be represented in `unsigned long long`. + * \returns A `unsigned long long` representing the dynamic_bitset. + * + * ### Complexity + * + * Constant. + * + * ### Exceptions + * + * Throws std::overflow_error if the value cannot be represented in `unsigned long long`. + */ + inline constexpr unsigned long long to_ullong() const + { + if constexpr (std::numeric_limits::max() < std::numeric_limits::max()) + { + if (data_chunk() >= std::numeric_limits::max()) + throw std::overflow_error{"seqan3::dynamic_bitset cannot be represented as unsigned long long."}; + } + + return static_cast(data_chunk()); + } + //!\} + + /*!\name Input/output + * \{ + */ + /*!\brief Formatted output for the seqan3::dynamic_bitset. + * \param[in,out] os The std::basic_ostream to write to. + * \param[in] arg The seqan3::dynamic_bitset to read from. + * \returns `os`. + * + * \details + * + * Internally calls `os << arg.to_string()`. + */ + friend std::ostream & operator<<(std::ostream & os, dynamic_bitset const & arg) + { + os << arg.to_string(); + return os; + } + + /*!\brief Formatted input for the seqan3::dynamic_bitset. + * \param[in,out] is The std::basic_istream to read from. + * \param[out] arg The seqan3::dynamic_bitset to write to. + * \returns `is`. + * + * \details + * + * Reads at most seqan3::dynamic_bitset::max_size characters from the stream. + * If a stream error occurred or no characters could be extracted the std::ios_base::failbit is set. + * This may throw an exception. + */ + friend std::istream & operator>>(std::istream & is, dynamic_bitset & arg) + { + // Check if stream is ok and skip leading whitespaces. + std::istream::sentry s(is); + if (s) + { + arg.clear(); // clear the bitset + std::streamsize num_char = (is.width() > 0) + ? std::min(is.width(), arg.max_size()) + : arg.max_size(); + assert(num_char > 0); + for (std::streamsize n = num_char; n > 0 && (is.peek() == is.widen('0') || is.peek() == is.widen('1')); --n) + { + char c = is.get(); + if (is.eof()) + break; + c == is.widen('0') ? arg.insert(arg.cbegin(), false) : arg.insert(arg.cbegin(), true); + } + + if (arg.size() == 0) // nothing extracted so we set the fail bit. + is.setstate(std::ios_base::failbit); + + is.width(0); // cancel the effects of std::setw, if any. + } + return is; + } + //!\} + +private: + //!\brief Returns the part of data_ containing the size information. + constexpr uint64_t size_chunk() const noexcept + { + return data_ >> data_bits << data_bits; + } + + //!\brief Returns the part of data_ containing the bits. + constexpr uint64_t data_chunk() const noexcept + { + return data_ << size_bits >> size_bits; + } + + //!\brief Distance between MSB of data_ and MSB of data_chunk, i.e. `data_chunk` << offset() will shift the MSB + //! of data_chunk to the MSB position of data_. + constexpr size_type offset() const noexcept + { + return internal_bits - size(); + } + + //!\cond DEV + /*!\brief Serialisation support function. + * \tparam archive_t Type of `archive`; must satisfy seqan3::CerealArchive. + * \param archive The archive being serialised from/to. + * + * \attention These functions are never called directly, see \ref serialisation for more details. + */ + template + void CEREAL_SERIALIZE_FUNCTION_NAME(archive_t & archive) + { + archive(data_); + } + //!\endcond +}; + +} // namespace seqan3 diff --git a/test/unit/range/container/CMakeLists.txt b/test/unit/range/container/CMakeLists.txt index d8662945688..d686152f54f 100644 --- a/test/unit/range/container/CMakeLists.txt +++ b/test/unit/range/container/CMakeLists.txt @@ -2,5 +2,6 @@ seqan3_test(aligned_allocator_test.cpp) seqan3_test(container_concept_test.cpp) seqan3_test(container_of_container_test.cpp) seqan3_test(container_test.cpp) +seqan3_test(dynamic_bitset_test.cpp) seqan3_test(small_string_test.cpp) seqan3_test(small_vector_test.cpp) diff --git a/test/unit/range/container/dynamic_bitset_test.cpp b/test/unit/range/container/dynamic_bitset_test.cpp new file mode 100644 index 00000000000..d95272c02e7 --- /dev/null +++ b/test/unit/range/container/dynamic_bitset_test.cpp @@ -0,0 +1,735 @@ +// ----------------------------------------------------------------------------------------------------- +// Copyright (c) 2006-2019, Knut Reinert & Freie Universität Berlin +// Copyright (c) 2016-2019, Knut Reinert & MPI für molekulare Genetik +// This file may be used, modified and/or redistributed under the terms of the 3-clause BSD-License +// shipped with this file and also available at: https://github.com/seqan/seqan3/blob/master/LICENSE.md +// ----------------------------------------------------------------------------------------------------- + +#include + +#include +#include +#include + +using namespace seqan3; + +// Standard construction. +TEST(dynamic_bitset, standard_construction) +{ + EXPECT_TRUE((std::is_default_constructible_v>)); + EXPECT_TRUE((std::is_nothrow_default_constructible_v>)); + EXPECT_TRUE((std::is_copy_constructible_v>)); + EXPECT_TRUE((std::is_trivially_copy_constructible_v>)); + EXPECT_TRUE((std::is_nothrow_copy_constructible_v>)); + EXPECT_TRUE((std::is_move_constructible_v>)); + EXPECT_TRUE((std::is_trivially_move_constructible_v>)); + EXPECT_TRUE((std::is_nothrow_move_constructible_v>)); + EXPECT_TRUE((std::is_copy_assignable_v>)); + EXPECT_TRUE((std::is_trivially_copy_assignable_v>)); + EXPECT_TRUE((std::is_nothrow_copy_assignable_v>)); + EXPECT_TRUE((std::is_move_assignable_v>)); + EXPECT_TRUE((std::is_trivially_move_assignable_v>)); + EXPECT_TRUE((std::is_nothrow_move_assignable_v>)); +} + +TEST(dynamic_bitset, concepts) +{ + EXPECT_TRUE((ReservableContainer>)); + EXPECT_TRUE((std::ranges::RandomAccessRange>)); +} + +constexpr bool comparison_test() +{ + constexpr dynamic_bitset t1{0b11'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111}; + constexpr dynamic_bitset t2{0b11'1111'1111'1111'1111'1111'1100'1111'1111'1111'1111'1111'1111'1111'1100}; + constexpr dynamic_bitset t3{0b00'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111}; + constexpr dynamic_bitset t4{72'057'594'037'927'935}; + constexpr dynamic_bitset t5{"1111111111111111111111111111111111111111111111111111111111"}; + constexpr dynamic_bitset t6{"1111111111111111111111110011111111111111111111111111111100"}; + constexpr dynamic_bitset t7{"11111111111111111111111111111111111111111111111111111111"}; + + bool res = t3 == t4; + res &= t1 == t5; + res &= t2 == t6; + res &= t3 == t7; + + res &= t1 > t2; + res &= t2 > t3; + res &= t1 > t3; + + res &= t1 >= t2; + res &= t2 >= t3; + res &= t1 >= t3; + + res &= t2 <= t1; + res &= t3 <= t2; + res &= t3 <= t1; + + res &= t2 < t1; + res &= t3 < t2; + res &= t3 < t1; + + res &= t1 != t2; + res &= t2 != t3; + res &= t1 != t3; + + return res; +} + +TEST(dynamic_bitset, comparison) +{ + constexpr bool b = comparison_test(); + EXPECT_TRUE(b); +} + +constexpr bool size_test() +{ + constexpr dynamic_bitset t1{0b11'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111}; + bool res = t1.size() == 58u; + + constexpr dynamic_bitset t2{0b11'1111'1111'1111'1111'1111'1100'1111'1111'1111'1111'1111'1111'1111'1111}; + res &= t2.size() == 58u; + + constexpr dynamic_bitset t3{0b00'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111}; + res &= t3.size() == 56u; + + constexpr dynamic_bitset t4{0b11'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1100}; + res &= t4.size() == 58u; + + constexpr dynamic_bitset t5; + res &= t5.size() == 0u; + + return res; +} + +TEST(dynamic_bitset, size) +{ + constexpr bool b = size_test(); + EXPECT_TRUE(b); +} + +constexpr bool count_test() +{ + constexpr dynamic_bitset t1{0b11'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111}; + bool res = t1.count() == 58u; + + constexpr dynamic_bitset t2{0b11'1111'1111'1111'1111'1111'1100'1111'1111'1111'1111'1111'1111'1111'1111}; + res &= t2.count() == 56u; + + constexpr dynamic_bitset t3{0b00'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111}; + res &= t3.count() == 56u; + + constexpr dynamic_bitset t4{0b11'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1100}; + res &= t4.count() == 56u; + + constexpr dynamic_bitset t5; + res &= t5.count() == 0u; + + return res; +} + +TEST(dynamic_bitset, count) +{ + constexpr bool b = count_test(); + EXPECT_TRUE(b); +} + +constexpr bool all_test() +{ + constexpr dynamic_bitset t1{0b11'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111}; + bool res = t1.all(); + + constexpr dynamic_bitset t2{0b11'1111'1111'1111'1111'1111'1100'1111'1111'1111'1111'1111'1111'1111'1111}; + res &= !t2.all(); + + constexpr dynamic_bitset t3{0b00'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111}; + res &= t3.all(); + + constexpr dynamic_bitset t4{0b11'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1100}; + res &= !t4.all(); + + constexpr dynamic_bitset t5; + res &= t5.all(); + + return res; +} + +TEST(dynamic_bitset, all) +{ + constexpr bool b = all_test(); + EXPECT_TRUE(b); +} + +constexpr bool any_test() +{ + constexpr dynamic_bitset t1{0b11'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111}; + bool res = t1.any(); + + constexpr dynamic_bitset t2{0b11'1111'1111'1111'1111'1111'1100'1111'1111'1111'1111'1111'1111'1111'1111}; + res &= t2.any(); + + constexpr dynamic_bitset t3{0b00'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111}; + res &= t3.any(); + + constexpr dynamic_bitset t4{0b11'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1100}; + res &= t4.any(); + + constexpr dynamic_bitset t5; + res &= !t5.any(); + + return res; +} + +TEST(dynamic_bitset, any) +{ + constexpr bool b = any_test(); + EXPECT_TRUE(b); +} + +constexpr bool none_test() +{ + constexpr dynamic_bitset t1{0b11'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111}; + bool res = !t1.none(); + + constexpr dynamic_bitset t2{0b11'1111'1111'1111'1111'1111'1100'1111'1111'1111'1111'1111'1111'1111'1111}; + res &= !t2.none(); + + constexpr dynamic_bitset t3{0b00'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111}; + res &= !t3.none(); + + constexpr dynamic_bitset t4{0b11'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1111'1100}; + res &= !t4.none(); + + constexpr dynamic_bitset t5; + res &= t5.none(); + + return res; +} + +TEST(dynamic_bitset, none) +{ + constexpr bool b = none_test(); + EXPECT_TRUE(b); +} + +constexpr bool set_test() +{ + dynamic_bitset t1{0b11'1110'1111'1111'1111'1111'1100'1111'1111'1111'1111'1111'1111'1111'1110}; + bool res = !t1.all(); + t1.set(); + res &= t1.all(); + + dynamic_bitset t2{0b11'1111'1111'1111'1111'1111'1101'1111'1111'1111'1111'1111'1111'1111'1110}; + res &= !t2.all(); + t2.set(0, true); + t2.set(33, true); + res &= t2.all(); + + return res; +} + +TEST(dynamic_bitset, set) +{ + constexpr bool b = set_test(); + EXPECT_TRUE(b); +} + +constexpr bool reset_test() +{ + dynamic_bitset t1{0b11'1110'1111'1111'1111'1111'1100'1111'1111'1111'1111'1111'1111'1111'1110}; + bool res = !t1.none(); + t1.reset(); + res &= t1.none(); + + dynamic_bitset t2{0b10'0000'0000'0000'0000'0000'0000'0000'0000'0000'0000'0000'0000'0000'0001}; + res &= !t2.none(); + t2.reset(0); + t2.reset(57); + res &= t2.none(); + + return res; +} + +TEST(dynamic_bitset, reset) +{ + constexpr bool b = reset_test(); + EXPECT_TRUE(b); +} + +constexpr bool flip_test() +{ + dynamic_bitset t1{0b1111111111111111111111111111111111111111111111111111111111}; + bool res = t1.all(); + t1.flip(); + res &= t1.none(); + + dynamic_bitset t2{0b1111111111111111111111111111111111111111111111111111111111}; + res &= t2.all(); + t2.flip(0); + res &= !t2.all(); + res &= t2.any(); + + return res; +} + +TEST(dynamic_bitset, flip) +{ + constexpr bool b = flip_test(); + EXPECT_TRUE(b); +} + +constexpr bool access_test() +{ + bool res = true; + + dynamic_bitset t1{0b1111'0000'0000'0000}; + dynamic_bitset const t2{0b1111'0000'0000'0000}; + dynamic_bitset expected{0b0111'0000'0000'0011}; + expected.resize(t1.size()); + + for (size_t i = 0u; i < t1.size() - 4u; ++i) + { + res &= t1.at(i) == 0 && t1.test(i) == 0 && t1[i] == 0; + res &= t2.at(i) == 0 && t2.test(i) == 0 && t2[i] == 0; + } + + for (size_t i = t1.size() - 4u; i < t1.size(); ++i) + { + res &= t1.at(i) == 1 && t1.test(i) == 1 && t1[i] == 1; + res &= t2.at(i) == 1 && t2.test(i) == 1 && t2[i] == 1; + } + + res &= !t1.front(); + res &= t1.back(); + res &= !t2.front(); + res &= t2.back(); + + t1[1] = true; + res &= t1 == dynamic_bitset{0b1111'0000'0000'0010}; + + t1.front() = true; + res &= t1 == dynamic_bitset{0b1111'0000'0000'0011}; + + t1.back() = false; + res &= t1 == expected; + + return res; +} + +TEST(dynamic_bitset, access) +{ + constexpr bool b = access_test(); + EXPECT_TRUE(b); + + constexpr dynamic_bitset t1{0b1111'0000'0000'0000}; + EXPECT_THROW(t1.at(16), std::out_of_range); + EXPECT_THROW(t1.test(16), std::out_of_range); +} + +constexpr bool bitwise_and_test() +{ + dynamic_bitset t1{0b1111'0001'0000'1100}; + dynamic_bitset t2{0b1010'0001'0000'0011}; + dynamic_bitset expected{0b1010'0001'0000'0000}; + + bool res = (t1 & t2) == expected; + t1 &= t2; + res = t1 == expected; + + return res; +} + +TEST(dynamic_bitset, bitwise_and) +{ + constexpr bool b = bitwise_and_test(); + EXPECT_TRUE(b); +} + +constexpr bool bitwise_or_test() +{ + dynamic_bitset t1{0b1111'0001'0000'1100}; + dynamic_bitset t2{0b1010'0001'0000'0011}; + dynamic_bitset expected{0b1111'0001'0000'1111}; + + bool res = (t1 | t2) == expected; + t1 |= t2; + res = t1 == expected; + + return res; +} + +TEST(dynamic_bitset, bitwise_or) +{ + constexpr bool b = bitwise_or_test(); + EXPECT_TRUE(b); +} + +constexpr bool bitwise_xor_test() +{ + dynamic_bitset t1{0b1111'0001'0000'1100}; + dynamic_bitset t2{0b1010'0001'0000'0011}; + dynamic_bitset expected{0b0101'0000'0000'1111}; + expected.resize(t1.size()); + + bool res = (t1 ^ t2) == expected; + t1 ^= t2; + res = t1 == expected; + + return res; +} + +TEST(dynamic_bitset, bitwise_xor) +{ + constexpr bool b = bitwise_xor_test(); + EXPECT_TRUE(b); +} + +constexpr bool bitwise_not_test() +{ + dynamic_bitset t1{0b1111'0001'0000'1100}; + dynamic_bitset expected{0b0000'1110'1111'0011}; + expected.resize(t1.size()); + + return ~t1 == expected; +} + +TEST(dynamic_bitset, bitwise_not) +{ + constexpr bool b = bitwise_not_test(); + EXPECT_TRUE(b); +} + +constexpr bool swap_test() +{ + dynamic_bitset t1{0b1111'0001'0000'1100}; + dynamic_bitset t2; + dynamic_bitset expected{t1}; + + t1.swap(t2); + bool res = t2 == expected; + + swap(t1, t2); + res &= t1 == expected; + + t2.swap(std::move(t1)); + res &= t2 == expected; + + swap(std::move(t1), std::move(t2)); + res &= t1 == expected; + + return res; +} + +TEST(dynamic_bitset, swap) +{ + constexpr bool b = swap_test(); + EXPECT_TRUE(b); +} + +constexpr bool assign_test() +{ + dynamic_bitset t1{0b1111}; + dynamic_bitset t2{0b1001}; + dynamic_bitset t3, t4, t5, t6; + + t3.assign(4, true); + bool res = t3 == t1; + + t4.assign(t2.cbegin(), t2.cend()); + res &= t4 == t2; + + t5.assign({true, true, true, true}); + res &= t5 == t1; + + t6 = {1, 0, 0, 1}; + res &= t6 == t2; + + return res; +} + +TEST(dynamic_bitset, assign) +{ + constexpr bool b = assign_test(); + EXPECT_TRUE(b); +} + +constexpr bool iterator_test() +{ + dynamic_bitset t1{0b1111'0001'0000'1100}; + dynamic_bitset const t2{0b1010'0001'0000'0011}; + + bool res = !*t1.begin(); + res &= !*t1.cbegin(); + res &= *t2.begin(); + res &= *t2.cbegin(); + + res &= *(t1.end() - 1); + res &= *(t1.cend() - 1); + res &= *(t2.end() - 1); + res &= *(t2.cend() - 1); + + res &= t1.end() == t1.cend(); + + *t1.begin() = true; + res &= t1 == dynamic_bitset{0b1111'0001'0000'1101}; + + return res; +} + +TEST(dynamic_bitset, iterators) +{ + constexpr bool b = iterator_test(); + EXPECT_TRUE(b); +} + +constexpr bool capacity_test() +{ + dynamic_bitset t0{}; + dynamic_bitset t1{0b1111'0001'0000'1100}; + dynamic_bitset const t2{0b1010'0001'0000'0011}; + + bool res = t0.empty(); + res &= !t1.empty(); + res &= !t2.empty(); + + res &= t0.size() == 0u; + res &= t1.size() == 16u; + res &= t2.size() == 16u; + + res &= t0.capacity() >= t0.size(); + res &= t1.capacity() >= t1.size(); + res &= t2.capacity() >= t2.size(); + + res &= t0.max_size() == t0.capacity(); + res &= t1.max_size() == t1.capacity(); + res &= t2.max_size() == t2.capacity(); + + size_t cap = t0.capacity(); + t0.reserve(1000); + res &= t0.capacity() == cap; + t0.shrink_to_fit(); + res &= t0.capacity() == cap; + + res &= t1.capacity() == 58u; + dynamic_bitset<30> t3; + res &= t3.capacity() == 30u; + + return res; +} + +TEST(dynamic_bitset, capacity) +{ + constexpr bool b = capacity_test(); + EXPECT_TRUE(b); +} + +constexpr bool clear_test() +{ + dynamic_bitset t1{0b1111'0001'0000'1100}; + dynamic_bitset expected{}; + + t1.clear(); + + return t1 == expected; +} + +TEST(dynamic_bitset, clear) +{ + constexpr bool b = clear_test(); + EXPECT_TRUE(b); +} + +constexpr bool insert_test() +{ + dynamic_bitset t0{}; + dynamic_bitset t1{0b100101}; + + t0.insert(t0.cend(), 1); + t0.insert(t0.cend(), 0); + t0.insert(t0.cend(), 1); + t0.insert(t0.cend(), 0); + t0.insert(t0.cend(), 1); + t0.insert(t0.cbegin() + 3, 0); + bool res = t0 == t1; + + t0.clear(); + t0.insert(t0.cend(), 3, true); + t0.insert(t0.cbegin() + 1, false); + t0.insert(t0.cbegin() + 3, 2, false); + res &= t0 == t1; + + return res; +} + +TEST(dynamic_bitset, insert) +{ + constexpr bool b = insert_test(); + EXPECT_TRUE(b); +} + +constexpr bool erase_test() +{ + dynamic_bitset t1{0b100101}; + + t1.erase(t1.begin()); + bool res = t1 == dynamic_bitset{0b10010}; + + t1.erase(t1.begin() + 1, t1.begin() + 3); + res &= t1 == dynamic_bitset{0b100}; + + t1.erase(t1.begin(), t1.begin()); + res &= t1 == dynamic_bitset{0b100}; + + return res; +} + +TEST(dynamic_bitset, erase) +{ + constexpr bool b = erase_test(); + EXPECT_TRUE(b); +} + +constexpr bool push_pop_test() +{ + dynamic_bitset t1{}; + dynamic_bitset expected{0b01}; + expected.resize(2); + + t1.push_back(true); + bool res = t1 == dynamic_bitset{0b1}; + + t1.push_back(false); + res &= t1 == expected; + + t1.pop_back(); + res &= t1 == dynamic_bitset{0b1}; + + t1.pop_back(); + res &= t1 == dynamic_bitset{}; + + return res; +} + +TEST(dynamic_bitset, push_pop) +{ + constexpr bool b = push_pop_test(); + EXPECT_TRUE(b); +} + +constexpr bool resize_test() +{ + dynamic_bitset t1{}; + + t1.resize(2); + bool res = !t1.at(0) && !t1.at(1); + + t1.resize(5, true); + res &= t1 == dynamic_bitset{0b11100}; + + t1.resize(4, true); + res &= t1 == dynamic_bitset{0b1100}; + + t1.resize(3); + res &= t1 == dynamic_bitset{0b100}; + + return res; +} + +TEST(dynamic_bitset, resize) +{ + constexpr bool b = resize_test(); + EXPECT_TRUE(b); +} + +TEST(dynamic_bitset, to_string) +{ + dynamic_bitset t1{"0011000"}; + EXPECT_EQ(t1.to_string(), std::string{"0011000"}); + + dynamic_bitset t2{0b001100}; + EXPECT_EQ(t2.to_string(), std::string{"1100"}); + t2.resize(6); + EXPECT_EQ(t2.to_string(), std::string{"001100"}); + EXPECT_EQ(t2.to_string('#'), std::string{"##11##"}); + EXPECT_EQ(t2.to_string('#', '*'), std::string{"##**##"}); +} + +constexpr bool to_ulong_test() +{ + dynamic_bitset t1{"0011000"}; + dynamic_bitset t2{0b001100}; + + return t1.to_ulong() == 24UL && t2.to_ulong() == 12UL; +} + +TEST(dynamic_bitset, to_ulong) +{ + constexpr bool b = to_ulong_test(); + EXPECT_TRUE(b); + if constexpr (std::numeric_limits::max() < std::numeric_limits::max()) + { + dynamic_bitset t1{std::numeric_limits::max() + 1}; + EXPECT_THROW(t1.to_ulong(), std::overflow_error); + } +} + +constexpr bool to_ullong_test() +{ + dynamic_bitset t1{"0011000"}; + dynamic_bitset t2{0b001100}; + + return t1.to_ullong() == 24ULL && t2.to_ullong() == 12ULL; +} + +TEST(dynamic_bitset, to_ullong) +{ + constexpr bool b = to_ullong_test(); + EXPECT_TRUE(b); + if constexpr (std::numeric_limits::max() < std::numeric_limits::max()) + { + dynamic_bitset t1{std::numeric_limits::max() + 1}; + EXPECT_THROW(t1.to_ulong(), std::overflow_error); + } +} + +TEST(dynamic_bitset, output) +{ + dynamic_bitset t1{"0011000"}; + std::ostringstream os; + os << t1; + EXPECT_EQ(os.str(), std::string{"0011000"}); +} + +TEST(dynamic_bitset, input) +{ + { // Until whitespace + dynamic_bitset t1{""}; + std::istringstream is{"0011 0001"}; + is >> t1; + EXPECT_EQ(t1, dynamic_bitset{"0011"}); + } + + { // Exceed capacity + dynamic_bitset<5> t1{"11111"}; + std::istringstream is{"00110001"}; + is >> t1; + EXPECT_EQ(t1, dynamic_bitset{"00110"}); + + std::string remaining{}; + is >> remaining; + EXPECT_EQ(remaining, std::string{"001"}); + } + + { // eof before capacity reached + dynamic_bitset t1{""}; + std::istringstream is{"00110001"}; + is >> t1; + EXPECT_EQ(t1, dynamic_bitset{"00110001"}); + } +} + +TEST(dynamic_bitset, serialisation) +{ + dynamic_bitset t1{0b100101}; + test::do_serialisation(t1); +}