//
// libsemigroups - C++ library for semigroups and monoids
// Copyright (C) 2020 James D. Mitchell
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// TODO(later)
// 1) doc
#ifndef LIBSEMIGROUPS_BITSET_HPP_
#define LIBSEMIGROUPS_BITSET_HPP_
#include <array>
// for array
#include <bitset>
// for bitset
#include <climits>
// for CHAR_BIT
#include <cstddef>
// for size_t
#include <iosfwd>
// for operator<<, ostringstream
#include <type_traits>
// for false_type
#include <utility>
// for hash
#include "config.hpp" // for LIBSEMIGROUPS_SIZEOF_VOID_P
#include "debug.hpp" // for LIBSEMIGROUPS_ASSERT
#include "exception.hpp" // for LIBSEMIGROUPS_EXCEPTION
#include "string.hpp" // for detail::to_string
namespace libsemigroups {
// The code below for popcnt is borrowed/adapted from GAP.
#if LIBSEMIGROUPS_USE_POPCNT &&
defined(LIBSEMIGROUPS_HAVE___BUILTIN_POPCOUNTL)
template <
typename T>
static inline size_t COUNT_TRUES_BLOCK(T block) {
return __builtin_popcountl(block);
}
#else
#if LIBSEMIGROUPS_SIZEOF_VOID_P == 8
template <
typename T>
static inline auto COUNT_TRUES_BLOCK(T block)
-> std::enable_if_t<
sizeof(T) == 8, size_t> {
block
= (block & 0x5555555555555555L) + ((block >> 1) & 0x5555555555555555L);
block
= (block & 0x3333333333333333L) + ((block >> 2) & 0x3333333333333333L);
block = (block + (block >> 4)) & 0x0f0f0f0f0f0f0f0fL;
block = (block + (block >> 8));
block = (block + (block >> 16));
block = (block + (block >> 32)) & 0x00000000000000ffL;
return block;
}
#endif
template <
typename T>
static inline auto COUNT_TRUES_BLOCK(T block)
-> std::enable_if_t<
sizeof(T) == 4, size_t> {
block = (block & 0x55555555) + ((block >> 1) & 0x55555555);
block = (block & 0x33333333) + ((block >> 2) & 0x33333333);
block = (block + (block >> 4)) & 0x0f0f0f0f;
block = (block + (block >> 8));
block = (block + (block >> 16)) & 0x000000ff;
return block;
}
template <
typename T>
static inline auto COUNT_TRUES_BLOCK(T block)
-> std::enable_if_t<
sizeof(T) == 2, size_t> {
block = (block & 0x5555) + ((block >> 1) & 0x5555);
block = (block & 0x3333) + ((block >> 2) & 0x3333);
block = (block + (block >> 4)) & 0x0f0f;
block = (block + (block >> 8)) & 0x00ff;
return block;
}
template <
typename T>
static inline auto COUNT_TRUES_BLOCK(T block)
-> std::enable_if_t<
sizeof(T) == 1, size_t> {
static constexpr std::array<size_t, 256>
const lookup = {
0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3, 2, 3, 3, 4,
2, 3, 3, 4, 3, 4, 4, 5, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 1, 2, 2, 3, 2, 3, 3, 4,
2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6,
4, 5, 5, 6, 5, 6, 6, 7, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5,
3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6,
4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8};
return lookup[block];
}
#endif
template <size_t N>
class BitSet {
static_assert(N > 0,
"BitSet does not support 0 entries");
#if LIBSEMIGROUPS_SIZEOF_VOID_P == 8
static_assert(N <= 64,
"BitSet does not support more than 64 entries");
#else
static_assert(N <= 32,
"BitSet does not support more than 32 entries");
#endif
public:
using block_type = std::conditional_t<
N <= 8,
uint_fast8_t,
std::conditional_t<
N <= 16,
uint_fast16_t,
std::conditional_t<N <= 32, uint_fast32_t, uint64_t>>>;
explicit constexpr BitSet(block_type block) noexcept : _block(block) {}
constexpr BitSet() noexcept : BitSet(0) {}
constexpr BitSet(BitSet
const&) noexcept =
default;
constexpr BitSet(BitSet&&) noexcept =
default;
BitSet&
operator=(BitSet
const&) noexcept =
default;
BitSet&
operator=(BitSet&&) noexcept =
default;
template <
typename T>
BitSet(T first, T last) : BitSet() {
LIBSEMIGROUPS_ASSERT(first <= last);
size_t
const K = std::distance(first, last);
if (K > size()) {
LIBSEMIGROUPS_EXCEPTION(
"the size of the container is %llu, trying to initialize with %llu "
"items",
static_cast<uint64_t>(size()),
static_cast<uint64_t>(K))
}
auto it = first;
for (size_t i = 0; i < K; ++i, ++it) {
set(i, *it);
}
}
~BitSet() =
default;
// Could be static
constexpr size_t size()
const noexcept {
return N;
}
static constexpr size_t max_size() noexcept {
#if LIBSEMIGROUPS_SIZEOF_VOID_P == 8
return 64;
#else
return 32;
#endif
}
bool operator<(BitSet
const& that)
const noexcept {
clear_hi_bits();
that.clear_hi_bits();
return _block < that._block;
}
bool operator==(BitSet
const& that)
const noexcept {
clear_hi_bits();
that.clear_hi_bits();
return _block == that._block;
}
bool operator!=(BitSet
const& that)
const noexcept {
return !(*
this == that);
}
void operator&=(BitSet
const& that)
const noexcept {
_block &= that._block;
}
BitSet<N>
operator&(BitSet
const& that)
const noexcept {
return BitSet(_block & that._block);
}
void operator|=(BitSet
const& that)
const noexcept {
_block |= that._block;
}
bool test(size_t pos)
const noexcept {
LIBSEMIGROUPS_ASSERT(pos < N);
return _block & mask(pos);
}
bool operator[](size_t pos)
const noexcept {
return test(pos);
}
BitSet& set() noexcept {
_block = ~0;
return *
this;
}
BitSet& set(size_t pos,
bool value =
true) noexcept {
LIBSEMIGROUPS_ASSERT(pos < N);
if (value) {
_block |= mask(pos);
}
else {
_block &= ~mask(pos);
}
return *
this;
}
BitSet& set(size_t first, size_t last,
bool value) noexcept {
LIBSEMIGROUPS_ASSERT(first < N);
LIBSEMIGROUPS_ASSERT(last <= N);
LIBSEMIGROUPS_ASSERT(first < last);
block_type m = ~0;
m = (m >> first);
m = (m << (first + (block_count() - last)));
m = (m >> (block_count() - last));
if (value) {
_block |= m;
}
else {
_block &= ~m;
}
return *
this;
}
BitSet& reset() noexcept {
_block = 0;
return *
this;
}
BitSet& reset(size_t pos) noexcept {
LIBSEMIGROUPS_ASSERT(pos < N);
_block &= ~mask(pos);
return *
this;
}
BitSet& reset(size_t first, size_t last) {
LIBSEMIGROUPS_ASSERT(first < N);
LIBSEMIGROUPS_ASSERT(last <= N);
LIBSEMIGROUPS_ASSERT(first < last);
return set(first, last,
false);
}
size_t count()
const noexcept {
clear_hi_bits();
return COUNT_TRUES_BLOCK(_block);
}
template <
typename S>
void apply(S&& func)
const {
#if LIBSEMIGROUPS_USE_CLZLL &&
defined(LIBSEMIGROUPS_HAVE___BUILTIN_CLZLL)
block_type block = _block;
while (block != 0) {
block_type t = block & -block;
size_t i =
static_cast<size_t>(__builtin_ctzll(block));
if (i >= size()) {
break;
}
func(i);
block ^= t;
}
#else
for (size_t i = 0; i < size(); ++i) {
if (test(i)) {
func(i);
}
}
#endif
}
block_type to_int()
const noexcept {
clear_hi_bits();
return _block;
}
friend std::ostringstream&
operator<<(std::ostringstream& os,
BitSet<N>
const& bs) {
for (size_t i = 0; i < N; ++i) {
os << bs.test(i);
}
return os;
}
friend std::ostream&
operator<<(std::ostream& os, BitSet<N>
const& bs) {
os << detail::to_string(bs);
return os;
}
private:
void clear_hi_bits()
const noexcept {
size_t s = block_count() - N;
_block = _block << s;
_block = _block >> s;
}
constexpr size_t block_count()
const noexcept {
return sizeof(block_type) * CHAR_BIT;
}
constexpr block_type mask(size_t i)
const noexcept {
// LIBSEMIGROUPS_ASSERT(i < size());
return static_cast<block_type>(MASK[i]);
}
static constexpr uint64_t MASK[64] = {0x1,
0x2,
0x4,
0x8,
0x10,
0x20,
0x40,
0x80,
0x100,
0x200,
0x400,
0x800,
0x1000,
0x2000,
0x4000,
0x8000,
0x10000,
0x20000,
0x40000,
0x80000,
0x100000,
0x200000,
0x400000,
0x800000,
0x1000000,
0x2000000,
0x4000000,
0x8000000,
0x10000000,
0x20000000,
0x40000000,
0x80000000,
0x100000000,
0x200000000,
0x400000000,
0x800000000,
0x1000000000,
0x2000000000,
0x4000000000,
0x8000000000,
0x10000000000,
0x20000000000,
0x40000000000,
0x80000000000,
0x100000000000,
0x200000000000,
0x400000000000,
0x800000000000,
0x1000000000000,
0x2000000000000,
0x4000000000000,
0x8000000000000,
0x10000000000000,
0x20000000000000,
0x40000000000000,
0x80000000000000,
0x100000000000000,
0x200000000000000,
0x400000000000000,
0x800000000000000,
0x1000000000000000,
0x2000000000000000,
0x4000000000000000,
0x8000000000000000};
mutable block_type _block;
};
template <size_t N>
constexpr uint64_t BitSet<N>::MASK[64];
namespace detail {
template <
typename T>
struct IsBitSetHelper : std::false_type {};
template <size_t N>
struct IsBitSetHelper<BitSet<N>> : std::true_type {};
}
// namespace detail
template <
typename T>
static constexpr
bool IsBitSet = detail::IsBitSetHelper<T>::value;
namespace detail {
struct LessBitSet {
// not noexcept because std::bitset<N>::to_ullong throws
// std::overflow_error if N exceeds the capacity of a unsigned long
// long.
template <size_t N>
bool operator()(std::bitset<N>
const& x, std::bitset<N>
const& y)
const {
return x.to_ullong() < y.to_ullong();
}
template <size_t N>
bool operator()(BitSet<N>
const& x, BitSet<N>
const& y)
const noexcept {
return x < y;
}
};
}
// namespace detail
}
// namespace libsemigroups
namespace std {
template <size_t N>
struct hash<libsemigroups::BitSet<N>> {
using block_type =
typename libsemigroups::BitSet<N>::block_type;
size_t
operator()(libsemigroups::BitSet<N>
const& bs)
const noexcept(
std::is_nothrow_default_constructible<hash<block_type>>::value) {
return hash<block_type>()(bs.to_int());
}
};
}
// namespace std
#endif // LIBSEMIGROUPS_BITSET_HPP_