wynnbuilder-idk/js/c++/utils/bitvector.cpp
2023-04-10 13:08:22 -07:00

202 lines
6.9 KiB
C++

#include "bitvector.h"
#include "base64.h"
#include <algorithm>
#include <stdexcept>
#include <sstream>
BitVector::BitVector() {};
BitVector::BitVector(const BitVector& other) : data(other.data), _length(other.length()) {};
BitVector::BitVector(const std::string b64_data) {
_length = b64_data.length() * 6;
data.reserve(_length/bitvec_data_s + 1);
bitvec_data_t scratch = 0;
size_t bitvec_index = 0;
for (size_t i = 0; i < b64_data.length(); ++i) {
size_t char_num = Base64::digitsMap.find(b64_data[i])->second;
unsigned int pre_pos = bitvec_index % bitvec_data_s;
scratch |= char_num << pre_pos;
bitvec_index += 6; // b64 is 6 bits per character.
unsigned int post_pos = bitvec_index % bitvec_data_s;
if (post_pos < pre_pos) { //we have to have filled up the integer
data.push_back(scratch);
scratch = (char_num >> (6 - post_pos));
}
if (i == b64_data.length()-1 && post_pos != 0) {
data.push_back(scratch);
}
}
}
BitVector::BitVector(bitvec_data_t num, size_t length) {
if (length < 0) {
throw std::range_error("BitVector must have nonnegative length.");
}
if (length > 0) {
data.push_back(num);
}
this->_length = length;
}
/** Return value of bit at index idx.
*
* @param {Number} idx - The index to read
*
* @returns The bit value at position idx
*/
bool BitVector::read_bit(size_t idx) const {
if (idx < 0 || idx >= length()) {
std::stringstream ss;
ss << "Cannot read bit outside the range of the BitVector. (" << idx << " > " << length() << ")";
throw std::range_error(ss.str());
}
return (data[idx / bitvec_data_s] & (1 << (idx % bitvec_data_s))) == 0 ? 0 : 1;
}
/** Returns an integer value (if possible) made from the range of bits [start, end). Undefined behavior if the range to read is too big.
*
* @param {Number} start - The index to start slicing from. Inclusive.
* @param {Number} end - The index to end slicing at. Exclusive.
*
* @returns An integer representation of the sliced bits.
*/
bitvec_data_t BitVector::slice(size_t start, size_t end) const {
if (end < start) {
throw std::range_error("Cannot slice a range where the end is before the start.");
} else if (end == start) {
return 0;
} else if (end - start > bitvec_data_s) {
//requesting a slice of longer than the size of a single data element (safe integer "length")
std::stringstream ss;
ss << "Cannot slice a range of longer than " << bitvec_data_s << " bits (unsafe to store in an integer).";
throw std::range_error(ss.str());
}
bitvec_data_t res = 0;
if ((end-1) / bitvec_data_s == start / bitvec_data_s) {
//the range is within 1 uint32 section - do some relatively fast bit twiddling
//res = (this.bits[Math.floor(start / 32)] & ~((((~0) << ((end - 1))) << 1) | ~((~0) << (start)))) >>> (start % 32);
bitvec_data_t mask = (~(((~0) << ((end - 1) % bitvec_data_s + 1)))) & ((~0) << (start % bitvec_data_s));
res = (data[start / bitvec_data_s] & mask) >> (start % bitvec_data_s);
}
else {
//the number of bits in the uint32s
//let start_pos = (start % 32);
//let int_idx = Math.floor(start/32);
//res = (this.bits[int_idx] & ((~0) << (start))) >>> (start_pos);
//res |= (this.bits[int_idx + 1] & ~((~0) << (end))) << (32 - start_pos);
unsigned int start_pos = start % bitvec_data_s;
unsigned int int_idx = start / bitvec_data_s;
res = (data[int_idx] & ((~0) << start_pos)) >> start_pos;
// IMPORTANT: (end % bitvec_data_s) is never zero.
res |= (data[int_idx + 1] & ~((~0) << (end % bitvec_data_s))) << (bitvec_data_s - start_pos);
}
return res;
// General code - slow
// for (let i = start; i < end; i++) {
// res |= (get_bit(i) << (i - start));
// }
}
/** Assign bit at index idx to 1.
*
* @param {Number} idx - The index to set.
*/
void BitVector::set_bit(size_t idx) {
if (idx < 0 || idx >= length()) {
throw std::range_error("Cannot set bit outside the range of the BitVector.");
}
data[idx / bitvec_data_s] |= (1 << (idx % bitvec_data_s));
}
/** Assign bit at index idx to 0.
*
* @param {Number} idx - The index to clear.
*/
void BitVector::clear_bit(size_t idx) {
if (idx < 0 || idx >= length()) {
throw std::range_error("Cannot clear bit outside the range of the BitVector.");
}
data[idx / bitvec_data_s] &= ~(1 << (idx % bitvec_data_s));
}
/** Creates a string version of the bit vector in B64. Does not keep the order of elements a sensible human readable format.
*
* @returns A b64 string representation of the BitVector.
*/
std::string BitVector::toB64() const {
if (length() == 0) {
return "";
}
std::stringstream b64_str;
size_t i = 0;
while (i < length()) {
b64_str << Base64::fromIntN(this->slice(i, i + 6), 1);
i += 6;
}
return b64_str.str();
}
/** Returns a BitVector in bitstring format. Probably only useful for dev debugging.
*
* @returns A bit string representation of the BitVector. Goes from higher-indexed bits to lower-indexed bits. (n ... 0)
*/
std::string BitVector::toString() const {
std::stringstream ret_str;
for (size_t i = length(); i != 0; --i) {
ret_str << (this->read_bit(i-1) ? "1": "0");
}
return ret_str.str();
}
/** Returns a BitVector in bitstring format. Probably only useful for dev debugging.
*
* @returns A bit string representation of the BitVector. Goes from lower-indexed bits to higher-indexed bits. (0 ... n)
*/
std::string BitVector::toStringR() const {
std::stringstream ret_str;
for (size_t i = 0; i < length(); ++i) {
ret_str << (this->read_bit(i) ? "1": "0");
}
return ret_str.str();
}
#include <iostream>
void BitVector::append(const BitVector& other) {
data.reserve(data.size() + other.data.size());
size_t other_index = 0;
if (this->length() % bitvec_data_s != 0) {
// fill in the last block.
bitvec_data_t scratch = data[data.size() - 1];
size_t bits_remaining = bitvec_data_s - (this->length() % bitvec_data_s);
size_t n = std::min(other.length(), bits_remaining);
scratch |= (other.slice(0, n) << (this->length() % bitvec_data_s));
data[data.size() - 1] = scratch;
other_index += n;
}
while (other_index != other.length()) {
size_t n = std::min(other.length() - other_index, (size_t)bitvec_data_s);
data.push_back(other.slice(other_index, other_index + n));
other_index += n;
}
this->_length += other.length();
}
void BitVector::append(const std::string b64_data) {
BitVector tmp(b64_data);
this->append(tmp);
}
void BitVector::append(bitvec_data_t num, size_t length) {
BitVector tmp(num, length);
this->append(tmp);
}
size_t BitVector::length() const { return this->_length; }