I am trying to understand what the following classes does, in particular function func. I looked up what each line roughly does. It's definitely manipulating bits by shifting them, but I can't understand the big picture.
template<class T>
class classX
{
public:
classX(int _z) : z(_z){}
size_t operator()(T x) const
{
union { T a; size_t b; } u;
u.b = 0;
u.a = x;
unsigned char rng1 = cvRNG(z*(z+1) + u.b);// cvRNG returns the input if number>0, else return (uint64_t)(int64_t)-1
return (size_t)( cvRandReal(&rng1)*(double)(UINT32_MAX) );// cvRandReal returns random floating-point number between 0 and 1
}
private:
int z;
};
template<class T,class H=classX<T>>
class classY
{
public:
classY(int nb, int nh)
: l_(0),c_(0),arr_(0)
{
b_ = nb;
l_ = nb / 8 + 1;
arr_ = new unsigned char[l_];
for(int i=1; i<=nh; i++)
ff.push_back( H(i) );
}
void func(const T& x)
{
for(size_t j=0; j<ff.size(); j++){
size_t key = ff[j](x) % b_;
arr_[ key / 8 ] |= (unsigned char)(1 << (key % 8));
}
c_++;
}
bool func2(const T& x) const
{
size_t z = 0;
for(size_t j=0; j<ff.size(); j++){
size_t key = ff[j](x) % b_;
z += (arr_[ key / 8 ] & (unsigned char)(1 << (key % 8)) ) > 0 ? 1 : 0;
}
return ( z == ff.size() );
}
private:
unsigned char* arr_;
int l_;
int c_;
size_t b_;
std::vector<H> ff;
};
I am trying to understand what the following classes does, in particular function func. I looked up what each line roughly does. It's definitely manipulating bits by shifting them.
This code build a bitmap hash for the hash set.
// Calc Hash for a class
template<class T>
class classX
{
public:
classX(int _z) : z(_z){} // construct hash
// Method returns a hashcode for x based on seed z.
size_t operator()(T x) const
{
// It is a nice try to read first 4 bytes form object x.
// union share the memory for a & b
union { T a; size_t b; } u;
u.b = 0; // just clean the memory
u.a = x; // since u.a share the memory with u.b, this line init u.b with first 4 bytes of x.
// If x is an instance if class with virtual methods, it will be pointer to vtable (same for all instances of the same calss).
// If x is a struct, it will be fist 4 bytes of x data.
// Most likely x is must be a struct.
// rnd1 is a base seed for the cvRandReal function. Note, u.b not a 0!
unsigned char rng1 = cvRNG(z*(z+1) + u.b);// cvRNG returns the input if number>0, else return (uint64_t)(int64_t)-1
// if rng1 a seed, line below just a hash function
return (size_t)( cvRandReal(&rng1)*(double)(UINT32_MAX) );// cvRandReal returns random floating-point number between 0 and 1
}
private:
int z; // base seed
};
// Bitmap Hash for Hash Set with Objects T, H - hash functions
template<class T,class H=classX<T>>
class classY
{
public:
// nb: size of bitmap hash in bits
// nh: number of hash functions.
// Both this number suppose to reduce probability of hash collision
classY(int nb, int nh)
: l_(0),c_(0),arr_(0)
{
b_ = nb; // size of bitmap hash in bits
l_ = nb / 8 + 1; // size of bitmap hash in bytes
arr_ = new unsigned char[l_]; // bitmap array - hash data
// init hash functions. Start from 1, because 0 seeder is not good.
for(int i=1; i<=nh; i++)
ff.push_back( H(i) );
}
// Add x into the hash bitmap (add x to the set)
void func(const T& x)
{
// for all hash fucntions
for(size_t j=0; j<ff.size(); j++)
{
size_t key = ff[j](x) % b_; // calc hash code and normalize it by number if bits in the map
// key - is a bit number in the bitmap
// Just a rise a key bit in the bitmap
// key / 8 - byte number
// key % 8 - bit number
arr_[ key / 8 ] |= (unsigned char)(1 << (key % 8));
}
c_++; // increase number of object that was processed to build a hash
}
// Check if X into the set (Check if X was added with func before)
// It return False if X wasn't added
// It return True of X probably be added (high probability that X was added, but not 100%)
bool func2(const T& x) const
{
size_t z = 0; // number of passed hash tests
for(size_t j=0; j<ff.size(); j++){
size_t key = ff[j](x) % b_; // calc hash code and normalize it by number if bits in the map, like in func()
// Increment z (number of passed hash tests) if key bit is in the bitmask
z += (arr_[ key / 8 ] & (unsigned char)(1 << (key % 8)) ) > 0 ? 1 : 0;
}
return ( z == ff.size() ); // return true if all tests from hash functions was passed.
}
private:
unsigned char* arr_; // hash bitmap
int l_;// size of bitmap in bytes
int c_;// number of object that was processed to build a hash
size_t b_;// size of bitmap in bits
std::vector<H> ff; // hash functions
};
Related
The task (from a Bulgarian judge, click on "Език" to change it to English):
I am given the size of the first (S1 = A) of N corals. The size of every subsequent coral (Si, where i > 1) is calculated using the formula (B*Si-1 + C)%D, where A, B, C and D are some constants. I am told that Nemo is nearby the Kth coral (when the sizes of all corals are sorted in ascending order).
What is the size of the above-mentioned Kth coral ?
I will have T tests and for every one of them I will be given N, K, A, B, C and D and prompted to output the size of the Kth coral.
The requirements:
1 ≤ T ≤ 3
1 ≤ K ≤ N ≤ 107
0 ≤ A < D ≤ 1018
1 ≤ C, B*D ≤ 1018
Memory available is 64 MB
Time limit is 1.9 sec
The problem I have:
For the worst case scenario I will need 107*8B which is 76 MB.
The solution If the memory available was at least 80 MB would be:
#include <iostream>
#include <vector>
#include <iterator>
#include <algorithm>
using biggie = long long;
int main() {
int t;
std::cin >> t;
int i, n, k, j;
biggie a, b, c, d;
std::vector<biggie>::iterator it_ans;
for (i = 0; i != t; ++i) {
std::cin >> n >> k >> a >> b >> c >> d;
std::vector<biggie> lut{ a };
lut.reserve(n);
for (j = 1; j != n; ++j) {
lut.emplace_back((b * lut.back() + c) % d);
}
it_ans = std::next(lut.begin(), k - 1);
std::nth_element(lut.begin(), it_ans, lut.end());
std::cout << *it_ans << '\n';
}
return 0;
}
Question 1: How can I approach this CP task given the requirements listed above ?
Question 2: Is it somehow possible to use std::nth_element to solve it since I am not able to store all N elements ? I mean using std::nth_element in a sliding window technique (If this is possible).
# Christian Sloper
#include <iostream>
#include <queue>
using biggie = long long;
int main() {
int t;
std::cin >> t;
int i, n, k, j, j_lim;
biggie a, b, c, d, prev, curr;
for (i = 0; i != t; ++i) {
std::cin >> n >> k >> a >> b >> c >> d;
if (k < n - k + 1) {
std::priority_queue<biggie, std::vector<biggie>, std::less<biggie>> q;
q.push(a);
prev = a;
for (j = 1; j != k; ++j) {
curr = (b * prev + c) % d;
q.push(curr);
prev = curr;
}
for (; j != n; ++j) {
curr = (b * prev + c) % d;
if (curr < q.top()) {
q.pop();
q.push(curr);
}
prev = curr;
}
std::cout << q.top() << '\n';
}
else {
std::priority_queue<biggie, std::vector<biggie>, std::greater<biggie>> q;
q.push(a);
prev = a;
for (j = 1, j_lim = n - k + 1; j != j_lim; ++j) {
curr = (b * prev + c) % d;
q.push(curr);
prev = curr;
}
for (; j != n; ++j) {
curr = (b * prev + c) % d;
if (curr > q.top()) {
q.pop();
q.push(curr);
}
prev = curr;
}
std::cout << q.top() << '\n';
}
}
return 0;
}
This gets accepted (Succeeds all 40 tests. Largest time 1.4 seconds, for a test with T=3 and D≤10^9. Largest time for a test with larger D (and thus T=1) is 0.7 seconds.).
#include <iostream>
using biggie = long long;
int main() {
int t;
std::cin >> t;
int i, n, k, j;
biggie a, b, c, d;
for (i = 0; i != t; ++i) {
std::cin >> n >> k >> a >> b >> c >> d;
biggie prefix = 0;
for (int shift = d > 1000000000 ? 40 : 20; shift >= 0; shift -= 20) {
biggie prefix_mask = ((biggie(1) << (40 - shift)) - 1) << (shift + 20);
int count[1 << 20] = {0};
biggie s = a;
int rank = 0;
for (j = 0; j != n; ++j) {
biggie s_vs_prefix = s & prefix_mask;
if (s_vs_prefix < prefix)
++rank;
else if (s_vs_prefix == prefix)
++count[(s >> shift) & ((1 << 20) - 1)];
s = (b * s + c) % d;
}
int i = -1;
while (rank < k)
rank += count[++i];
prefix |= biggie(i) << shift;
}
std::cout << prefix << '\n';
}
return 0;
}
The result is a 60 bits number. I first determine the high 20 bits with one pass through the numbers, then the middle 20 bits in another pass, then the low 20 bits in another.
For the high 20 bits, generate all the numbers and count how often each high 20 bits pattern occurrs. After that, add up the counts until you reach K. The pattern where you reach K, that pattern covers the K-th largest number. In other words, that's the result's high 20 bits.
The middle and low 20 bits are computed similarly, except we take the by then known prefix (the high 20 bits or high+middle 40 bits) into account. As a little optimization, when D is small, I skip computing the high 20 bits. That got me from 2.1 seconds down to 1.4 seconds.
This solution is like user3386109 described, except with bucket size 2^20 instead of 10^6 so I can use bit operations instead of divisions and think of bit patterns instead of ranges.
For the memory constraint you hit:
(B*Si-1 + C)%D
requires only the value (Si-2) before itself. So you can compute them in pairs, to use only 1/2 of total you need. This only needs indexing even values and iterating once for odd values. So you can just use half-length LUT and compute the odd value in-flight. Modern CPUs are fast enough to do extra calculations like these.
std::vector<biggie> lut{ a_i,a_i_2,a_i_4,... };
a_i_3=computeOddFromEven(lut[1]);
You can make a longer stride like 4,8 too. If dataset is large, RAM latency is big. So it's like having checkpoints in whole data search space to balance between memory and core usage. 1000-distance checkpoints would put a lot of cpu cycles into re-calculations but then the array would fit CPU's L2/L1 cache which is not bad. When sorting, the maximum re-calc iteration per element would be n=1000 now. O(1000 x size) maybe it's a big constant but maybe somehow optimizable by compiler if some constants really const?
If CPU performance becomes problem again:
write a compiling function that writes your source code with all the "constant" given by user to a string
compile the code using command-line (assuming target computer has some accessible from command line like g++ from main program)
run it and get results
Compiler should enable more speed/memory optimizations when those are really constant in compile-time rather than depending on std::cin.
If you really need to add a hard-limit to the RAM usage, then implement a simple cache with the backing-store as your heavy computations with brute-force O(N^2) (or O(L x N) with checkpoints every L elements as in first method where L=2 or 4, or ...).
Here's a sample direct-mapped cache with 8M long-long value space:
int main()
{
std::vector<long long> checkpoints = {
a_0, a_16, a_32,...
};
auto cacheReadMissFunction = [&](int key){
// your pure computational algorithm here, helper meant to show variable
long long result = checkpoints[key/16];
for(key - key%16 times)
result = iterate(result);
return result;
};
auto cacheWriteMissFunction = [&](int key, long long value){
/* not useful for your algorithm as it doesn't change behavior per element */
// backing_store[key] = value;
};
// due to special optimizations, size has to be 2^k
int cacheSize = 1024*1024*8;
DirectMappedCache<int, long long> cache(cacheSize,cacheReadMissFunction,cacheWriteMissFunction);
std::cout << cache.get(20)<<std::endl;
return 0;
}
If you use a cache-friendly sorting-algorithm, a direct cache access would make a lot of re-use for nearly all the elements in comparisons if you fill the output buffer/terminal with elements one by one by following something like a bitonic-sort-path (that is known in compile-time). If that doesn't work, then you can try accessing files as a "backing-store" of cache for sorting whole array at once. Is file system prohibited for use? Then the online-compiling method above won't work either.
Implementation of a direct mapped cache (don't forget to call flush() after your algorithm finishes, if you use any cache.set() method):
#ifndef DIRECTMAPPEDCACHE_H_
#define DIRECTMAPPEDCACHE_H_
#include<vector>
#include<functional>
#include<mutex>
#include<iostream>
/* Direct-mapped cache implementation
* Only usable for integer type keys in range [0,maxPositive-1]
*
* CacheKey: type of key (only integers: int, char, size_t)
* CacheValue: type of value that is bound to key (same as above)
*/
template< typename CacheKey, typename CacheValue>
class DirectMappedCache
{
public:
// allocates buffers for numElements number of cache slots/lanes
// readMiss: cache-miss for read operations. User needs to give this function
// to let the cache automatically get data from backing-store
// example: [&](MyClass key){ return redis.get(key); }
// takes a CacheKey as key, returns CacheValue as value
// writeMiss: cache-miss for write operations. User needs to give this function
// to let the cache automatically set data to backing-store
// example: [&](MyClass key, MyAnotherClass value){ redis.set(key,value); }
// takes a CacheKey as key and CacheValue as value
// numElements: has to be integer-power of 2 (e.g. 2,4,8,16,...)
DirectMappedCache(CacheKey numElements,
const std::function<CacheValue(CacheKey)> & readMiss,
const std::function<void(CacheKey,CacheValue)> & writeMiss):size(numElements),sizeM1(numElements-1),loadData(readMiss),saveData(writeMiss)
{
// initialize buffers
for(size_t i=0;i<numElements;i++)
{
valueBuffer.push_back(CacheValue());
isEditedBuffer.push_back(0);
keyBuffer.push_back(CacheKey()-1);// mapping of 0+ allowed
}
}
// get element from cache
// if cache doesn't find it in buffers,
// then cache gets data from backing-store
// then returns the result to user
// then cache is available from RAM on next get/set access with same key
inline
const CacheValue get(const CacheKey & key) noexcept
{
return accessDirect(key,nullptr);
}
// only syntactic difference
inline
const std::vector<CacheValue> getMultiple(const std::vector<CacheKey> & key) noexcept
{
const int n = key.size();
std::vector<CacheValue> result(n);
for(int i=0;i<n;i++)
{
result[i]=accessDirect(key[i],nullptr);
}
return result;
}
// thread-safe but slower version of get()
inline
const CacheValue getThreadSafe(const CacheKey & key) noexcept
{
std::lock_guard<std::mutex> lg(mut);
return accessDirect(key,nullptr);
}
// set element to cache
// if cache doesn't find it in buffers,
// then cache sets data on just cache
// writing to backing-store only happens when
// another access evicts the cache slot containing this key/value
// or when cache is flushed by flush() method
// then returns the given value back
// then cache is available from RAM on next get/set access with same key
inline
void set(const CacheKey & key, const CacheValue & val) noexcept
{
accessDirect(key,&val,1);
}
// thread-safe but slower version of set()
inline
void setThreadSafe(const CacheKey & key, const CacheValue & val) noexcept
{
std::lock_guard<std::mutex> lg(mut);
accessDirect(key,&val,1);
}
// use this before closing the backing-store to store the latest bits of data
void flush()
{
try
{
std::lock_guard<std::mutex> lg(mut);
for (size_t i=0;i<size;i++)
{
if (isEditedBuffer[i] == 1)
{
isEditedBuffer[i]=0;
auto oldKey = keyBuffer[i];
auto oldValue = valueBuffer[i];
saveData(oldKey,oldValue);
}
}
}catch(std::exception &ex){ std::cout<<ex.what()<<std::endl; }
}
// direct mapped access
// opType=0: get
// opType=1: set
CacheValue const accessDirect(const CacheKey & key,const CacheValue * value, const bool opType = 0)
{
// find tag mapped to the key
CacheKey tag = key & sizeM1;
// compare keys
if(keyBuffer[tag] == key)
{
// cache-hit
// "set"
if(opType == 1)
{
isEditedBuffer[tag]=1;
valueBuffer[tag]=*value;
}
// cache hit value
return valueBuffer[tag];
}
else // cache-miss
{
CacheValue oldValue = valueBuffer[tag];
CacheKey oldKey = keyBuffer[tag];
// eviction algorithm start
if(isEditedBuffer[tag] == 1)
{
// if it is "get"
if(opType==0)
{
isEditedBuffer[tag]=0;
}
saveData(oldKey,oldValue);
// "get"
if(opType==0)
{
const CacheValue && loadedData = loadData(key);
valueBuffer[tag]=loadedData;
keyBuffer[tag]=key;
return loadedData;
}
else /* "set" */
{
valueBuffer[tag]=*value;
keyBuffer[tag]=key;
return *value;
}
}
else // not edited
{
// "set"
if(opType == 1)
{
isEditedBuffer[tag]=1;
}
// "get"
if(opType == 0)
{
const CacheValue && loadedData = loadData(key);
valueBuffer[tag]=loadedData;
keyBuffer[tag]=key;
return loadedData;
}
else // "set"
{
valueBuffer[tag]=*value;
keyBuffer[tag]=key;
return *value;
}
}
}
}
private:
const CacheKey size;
const CacheKey sizeM1;
std::mutex mut;
std::vector<CacheValue> valueBuffer;
std::vector<unsigned char> isEditedBuffer;
std::vector<CacheKey> keyBuffer;
const std::function<CacheValue(CacheKey)> loadData;
const std::function<void(CacheKey,CacheValue)> saveData;
};
#endif /* DIRECTMAPPEDCACHE_H_ */
You can solve this problem using a Max-heap.
Insert the first k elements into the max-heap. The largest element of these k will now be at the root.
For each remaining element e:
Compare e to the root.
If e is larger than the root, discard it.
If e is smaller than the root, remove the root and insert e into the heap structure.
After all elements have been processed, the k-th smallest element is at the root.
This method uses O(K) space and O(n log n) time.
There’s an algorithm that people often call LazySelect that I think would be perfect here.
With high probability, we make two passes. In the first pass, we save a random sample of size n much less than N. The answer will be around index (K/N)n in the sorted sample, but due to the randomness, we have to be careful. Save the values a and b at (K/N)n ± r instead, where r is the radius of the window. In the second pass, we save all of the values in [a, b], count the number of values less than a (let it be L), and select the value with index K−L if it’s in the window (otherwise, try again).
The theoretical advice on choosing n and r is fine, but I would be pragmatic here. Choose n so that you use most of the available memory; the bigger the sample, the more informative it is. Choose r fairly large as well, but not quite as aggressively due to the randomness.
C++ code below. On the online judge, it’s faster than Kelly’s (max 1.3 seconds on the T=3 tests, 0.5 on the T=1 tests).
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <iostream>
#include <limits>
#include <optional>
#include <random>
#include <vector>
namespace {
class LazySelector {
public:
static constexpr std::int32_t kTargetSampleSize = 1000;
explicit LazySelector() { sample_.reserve(1000000); }
void BeginFirstPass(const std::int32_t n, const std::int32_t k) {
sample_.clear();
mask_ = n / kTargetSampleSize;
mask_ |= mask_ >> 1;
mask_ |= mask_ >> 2;
mask_ |= mask_ >> 4;
mask_ |= mask_ >> 8;
mask_ |= mask_ >> 16;
}
void FirstPass(const std::int64_t value) {
if ((gen_() & mask_) == 0) {
sample_.push_back(value);
}
}
void BeginSecondPass(const std::int32_t n, const std::int32_t k) {
sample_.push_back(std::numeric_limits<std::int64_t>::min());
sample_.push_back(std::numeric_limits<std::int64_t>::max());
const double p = static_cast<double>(sample_.size()) / n;
const double radius = 2 * std::sqrt(sample_.size());
const auto lower =
sample_.begin() + std::clamp<std::int32_t>(std::floor(p * k - radius),
0, sample_.size() - 1);
const auto upper =
sample_.begin() + std::clamp<std::int32_t>(std::ceil(p * k + radius), 0,
sample_.size() - 1);
std::nth_element(sample_.begin(), upper, sample_.end());
std::nth_element(sample_.begin(), lower, upper);
lower_ = *lower;
upper_ = *upper;
sample_.clear();
less_than_lower_ = 0;
equal_to_lower_ = 0;
equal_to_upper_ = 0;
}
void SecondPass(const std::int64_t value) {
if (value < lower_) {
++less_than_lower_;
} else if (upper_ < value) {
} else if (value == lower_) {
++equal_to_lower_;
} else if (value == upper_) {
++equal_to_upper_;
} else {
sample_.push_back(value);
}
}
std::optional<std::int64_t> Select(std::int32_t k) {
if (k < less_than_lower_) {
return std::nullopt;
}
k -= less_than_lower_;
if (k < equal_to_lower_) {
return lower_;
}
k -= equal_to_lower_;
if (k < sample_.size()) {
const auto kth = sample_.begin() + k;
std::nth_element(sample_.begin(), kth, sample_.end());
return *kth;
}
k -= sample_.size();
if (k < equal_to_upper_) {
return upper_;
}
return std::nullopt;
}
private:
std::default_random_engine gen_;
std::vector<std::int64_t> sample_ = {};
std::int32_t mask_ = 0;
std::int64_t lower_ = std::numeric_limits<std::int64_t>::min();
std::int64_t upper_ = std::numeric_limits<std::int64_t>::max();
std::int32_t less_than_lower_ = 0;
std::int32_t equal_to_lower_ = 0;
std::int32_t equal_to_upper_ = 0;
};
} // namespace
int main() {
int t;
std::cin >> t;
for (int i = t; i > 0; --i) {
std::int32_t n;
std::int32_t k;
std::int64_t a;
std::int64_t b;
std::int64_t c;
std::int64_t d;
std::cin >> n >> k >> a >> b >> c >> d;
std::optional<std::int64_t> ans = std::nullopt;
LazySelector selector;
do {
{
selector.BeginFirstPass(n, k);
std::int64_t s = a;
for (std::int32_t j = n; j > 0; --j) {
selector.FirstPass(s);
s = (b * s + c) % d;
}
}
{
selector.BeginSecondPass(n, k);
std::int64_t s = a;
for (std::int32_t j = n; j > 0; --j) {
selector.SecondPass(s);
s = (b * s + c) % d;
}
}
ans = selector.Select(k - 1);
} while (!ans);
std::cout << *ans << '\n';
}
}
While working on one of my class members I ran into a stumbling block...
I'll briefly explain my data structure. I have two one dimensional vectors that are both indexed as two-d array structures. The data in my first table is organized as column major.
My class is a template that takes two integral type arguments. These are not the overall size of either table. The first argument is the number of inputs that are stored in the input table. The input table has a size of [N x 2^N] that is generated by the first template argument. The 2nd table is a [M x 2^N] where both [N] and [M] are the number of columns and [2^N] is the number of rows for both.
The purpose of the first table is to generate all of the possible values for a given N-Input truth table. For example if there are 3 inputs then the first table will have 3 columns and 8 rows. Then if M is 1 there will be 1 column with 8 rows, 2 columns and so on for the output table.
My data vector in memory looks like this:
Inputs: A, B, C; Outputs: X, Y
// input vector
{ a0 ... a7, b0 ... b7, c0 ... c7 }
// output vector
{ X0 ... X7, Y0 ... Y7 }
The first table is automatically generated and this much I have completed. I have also completed the print out to these two tables in a side by side fashion where the inputs are to the left and the outputs to the right.
The set of functions are variadic templates as they can take any number of arguments. The variadic types here are homogeneous as they are all of the same type.
Within my class I'm store the function types as an enum class within a vector and I am using this within a switch statement to apply the appropriate function to the inputs per row and this is where I am a bit stuck...
Now for my question within my class's apply() function which you can see the full class below, I am able to easily enough index into the output table to set the desired output. I can easily enough calculate the initial index into the input table, but where I am stuck is how do I pass each of the N inputs within a given row as the arguments to the functions to be applied? All of the values are known at compile time, I just want to automate the passing of the inputs of a row as individual arguments into the output so for example consider the following truth table:
// Inputs: A, B Outputs: Y, Y = And
0 0 | 0
0 1 | 0
1 0 | 0
1 1 | 1
// Intputs: A, B, C Outputs X, Y X = Or Y = Xor
0 0 0 | 0 0
0 0 1 | 1 1
0 1 0 | 1 1
0 1 1 | 1 0
1 0 0 | 1 1
1 0 1 | 1 0
1 1 0 | 1 0
1 1 1 | 1 (0 or 1) // depending on interpretation of XOr: Single bit high=true or odd parity=true
// Here I'm using C++ operator ^ as the default intepretation!
So as you can see above one can instantiate this class template as seen above: BinaryTTGenerator<2,1> and BinaryTTGenerator<3,2> respectively.
I just need to know how I would be able to apply 2 inputs for the first, and 3 inputs for the second where the amount of inputs to be passed to the respective function is defined by N. I am open to any suggestions and possibilities if it can be done!
Here is my apply() function from my class below:
void apply() {
for (u16 f = 0; f < M; ++f) {
for (u16 y = 0; y < numRows_; ++y) {
for (u16 x = 0; x < N; ++x) {
u16 index = y * M + x - N;
switch (functionTypes_[f]) {
case BFT::AND:
outputTable_[f] = And(inputTable_[index], ... ?); break;
case BFT::OR:
outputTable_[f] = Or(inputTable_[index], ... ?); break;
case BFT::NAND:
outputTable_[f] = Nand(inputTable_[index],... ?); break;
case BFT::NOR:
outputTable_[f] = Nor(inputTable_[index], ... ?); break;
case BFT::XOR:
outputTable_[f] = Xor(inputTable_[index], ... ?); break;
case BFT::XNOR:
outputTable_[f] = XNor(inputTable_[index], ... ?); break;
default:
std::cout << "Invalid Logic function applied to this template\n";
}
}
}
}
}
Also, I'm not sure if the double loop needs to be outside of the switch or performed within each case statement...
Here is my current class so far:
#pragma once
// Binary Truth Table Generator
#include <algorithm>
#include <array>
#include <bitset>
#include <cstdint>
#include <functional>
#include <initializer_list>
#include <iostream>
#include <sstream>
#include <string>
#include <type_traits>
#include <vector>
using u16 = std::uint16_t;
using Bit = std::bitset<1>;
// The Boolean Operational Functions:
enum class BFT {
BUFFER, // Not included within the switch statement of the class!
NOT, // Both Not and And are non variadic but can be applied
// directly to a specific input, or to another function
// as these both take in a `Bit` and return a `Bit` type.
AND, // The following types are all variadic as they can
OR, // have any number of arguments.
NAND,
NOR,
XOR,
XNOR
// Possible Future Implementations:
// Tristate Buffer and Tristate Controlled Buffer.
};
// Helper Templates
template <typename... Bits>
constexpr bool all_bits() {
return (std::is_same_v<Bits, Bit> && ...);
}
template <typename... FuncTypes>
constexpr bool all_same() {
return (std::is_same_v<FuncTypes, BFT> &&...);
}
// Unary Functions
auto Buffer(Bit& b) -> auto {
return b;
}
auto Not(Bit& b) -> auto {
return ~b;
}
// Binary Functions with multiple inputs.
template<typename... Bits>
std::enable_if_t<all_bits<Bits...>(), Bit>
And(Bits... bits) {
return (bits&...);
}
template<typename... Bits>
std::enable_if_t<all_bits<Bits...>(), Bit>
Or(Bits... bits) {
return (bits|...);
}
template<typename... Bits>
std::enable_if_t<all_bits<Bits...>(), Bit>
Nand(Bits... bits) {
return ~(bits&...);
}
template<typename... Bits>
std::enable_if_t<all_bits<Bits...>(), Bit>
Nor(Bits... bits) {
return ~(bits|...);
}
template<typename... Bits>
std::enable_if_t<all_bits<Bits...>(), Bit>
Xor(Bits... bits) {
return (bits^...);
}
template<typename... Bits>
std::enable_if_t<all_bits<Bits...>(), Bit>
XNor(Bits... bits) {
return ~(bits^...);
}
// N is the number of inputs where M is the number of functions performed on the set or row of N
template<u16 N, u16 M>
struct BinaryTTGenerator {
// Calculate the Number of Cols & Rows as well
// as the stride for indexing into the vector
// typically the stride should almost always
// equal that of the number of rows.
const u16 numCols_ = M + N;
const u16 numRows_ = 1U << N;
const u16 stride_ = numCols_;
// Calculate the grid sizes there are 2 different grids
// as well as the overall grid, which are loosely combined.
// The first grid is all of the possible combinations
// of the inputs, the second grid is the respective outputs
// to each applied function to the set of inputs on a specific
// row. The combined grid or table is that concatenation of the two
// with the input grid on the left and the output grid on the right.
const u16 inputGridSize_ = N * numRows_;
const u16 outputGridSize_ = M * numRows_;
std::vector<Bit> inputTable_ = std::vector<Bit>(inputGridSize_, Bit{ 0 });
std::vector<Bit> outputTable_ = std::vector<Bit>(outputGridSize_, Bit{ 0 });
std::vector<BFT> functionTypes_;
BinaryTTGenerator() = default;
explicit BinaryTTGenerator(BFT bft) : functionTypes_{ bft } {}
template<typename... FuncTypes>
BinaryTTGenerator(FuncTypes... funcs) {
/*static_assert((sizeof...(funcs) + 1) == M, "Aguments does not equal the number of functions");
static_assert(std::is_same<
std::integer_sequence<bool, true, std::is_same<BFT, std::remove_reference_t<First>>::value>,
std::integer_sequence<bool, std::is_same<BFT, std::remove_reference_t<First>>::value, true >
> ::value, "!");
static_assert(std::is_same<
std::integer_sequence<bool, true, (std::is_same<BFT, std::remove_reference_t<FuncTypes>>::value)...>,
std::integer_sequence<bool, (std::is_same<BFT, std::remove_reference_t<FuncTypes>>::value)..., true>
>::value, "!");*/
functionTypes_{ funcs... };
}
// initializes all of the input values
void initialize() {
u16 fill = 1U << (N - 1);
for (u16 col = 0; col < N; ++col, fill >>= 1U) {
for (u16 row = fill; row < (1U << N); row += (fill * 2)) {
u16 index = col*numRows_ + row;
std::fill_n(&inputTable_[index], fill, 1);
};
}
}
// apply the set of M functions individually on the N(row) of inputs.
void apply() {
for (u16 f = 0; f < M; ++f) {
for (u16 y = 0; y < numRows_; ++y) {
for (u16 x = 0; x < N; ++x) {
u16 index = y * M + x - N;
switch (functionTypes_[f]) {
case BFT::AND:
outputTable_[f] = And(inputTable_[index]); break;
case BFT::OR:
outputTable_[f] = Or(inputTable_[index]); break;
case BFT::NAND:
outputTable_[f] = Nand(inputTable_[index]); break;
case BFT::NOR:
outputTable_[f] = Nor(inputTable_[index]); break;
case BFT::XOR:
outputTable_[f] = Xor(inputTable_[index]); break;
case BFT::XNOR:
outputTable_[f] = XNor(inputTable_[index]); break;
default:
std::cout << "Invalid Logic function applied to this template\n";
}
}
}
}
}
void show() {
for (u16 y = 0; y < numRows_; ++y) { // y - height
for (u16 x = 0; x < numCols_; ++x) { // x - width
if (x < N) {
// The index variables are not necessary - I don't mind the extra variable.
// I'm using it for readability that pertains to the index value of a container.
// It is also easier to adjust or fix the equation to calculate the appropriate
// index value into the desired container.
std::size_t index = x * numRows_ + y;
std::cout << inputTable_[index].to_string() << " ";
} else {
std::size_t index = y * M + x - N;
std::cout << outputTable_[index].to_string() << " ";
}
}
std::cout << '\n';
}
}
};
Something along these lines (not tested):
template <std::size_t... I>
void apply_impl(std::index_sequence<I...>) {
// ...
case BFT::AND:
outputTable_[f] = And(inputTable_[index + I]...); break;
// ...
}
void apply() {
return apply_impl(std::make_index_sequence<N>());
}
I am curious to know, Is it possible to use array of bit fields? Like:
struct st
{
unsigned int i[5]: 4;
};
No, you can't. Bit field can only be used with integral type variables.
C11-§6.7.2.1/5
A bit-field shall have a type that is a qualified or unqualified version of _Bool, signed int, unsigned int, or some other implementation-defined type.
Alternatively you can do this
struct st
{
unsigned int i: 4;
} arr_st[5];
but its size will be 5 times the size of a struct (as mentioned in comment by #Jonathan Leffler) having 5 members each with bit field 4. So, it doesn't make much sense here.
More closely you can do this
struct st
{
uint8_t i: 4; // Will take only a byte
} arr_st[5];
C does not support arrays of bit-fields, so the short answer is no.
For very large arrays, it might be worthwhile to pack values, 2 per byte, this way:
#define ARRAY_SIZE 1000000
unsigned char arr[(ARRAY_SIZE + 1) / 2];
int get_4bits(const unsigned char *arr, size_t index) {
return arr[index >> 1] >> ((index & 1) << 2);
}
int set_4bits(unsigned char *arr, size_t index, int value) {
arr[index >> 1] &= ~ 0x0F << ((index & 1) << 2);
arr[index >> 1] |= (value & 0x0F) << ((index & 1) << 2);
}
You can write your own class for this case. For example:
template <typename T, size_t ITEM_BIT_SIZE>
class BitArrayView {
private:
static const size_t ARRAY_ENTRY_BITS = sizeof(T) * 8;
static const T ITEM_MASK = (~((T) 0)) >> (ARRAY_ENTRY_BITS - ITEM_BIT_SIZE);
T* arr;
public:
struct ItemMutator {
BitArrayView* owner;
size_t index;
T operator=(T value) {
return owner->set(index, value);
}
operator T() {
return owner->get(index);
}
};
const size_t bitSize;
BitArrayView(T* arr, size_t length) : arr(arr), bitSize((length * ARRAY_ENTRY_BITS) / ITEM_BIT_SIZE) {}
T get(size_t index) const {
size_t bitPos = index * ITEM_BIT_SIZE;
size_t arrIndex = bitPos / ARRAY_ENTRY_BITS;
size_t shiftCount = bitPos % ARRAY_ENTRY_BITS;
return (arr[arrIndex] >> shiftCount) & ITEM_MASK;
}
T set(size_t index, T value) {
size_t bitPos = index * ITEM_BIT_SIZE;
size_t arrIndex = bitPos / ARRAY_ENTRY_BITS;
size_t shiftCount = bitPos % ARRAY_ENTRY_BITS;
value &= ITEM_MASK; // trim
arr[arrIndex] &= ~(ITEM_MASK << shiftCount); // clear target bits
arr[arrIndex] |= value << shiftCount; // insert new bits
return value;
}
ItemMutator operator[](size_t index) {
return { this, index };
}
};
And then you may access it like a "bit field" array:
// create array of some uints
unsigned int arr[5] = { 0, 0, 0, 0, 0 };
// set BitArrayView of 3-bit entries on some part of the array
// (two indexes starting at 1)
BitArrayView<unsigned int, 3> arrView(arr + 1, 2);
// should equal 21 now => (2 * 32) / 3
arrView.bitSize == 21;
for (unsigned int i = 0; i < arrView.bitSize; i++) {
arrView[i] = 7; // eg.: 0b111;
}
// now arr[1] should have all bits set
// and arr[2] should have all bits set but last one unset => (2 * 32) % 3 = 1
// the remaining arr items should stay untouched
This is simple implementation which should work with unsigned backing arrays only.
Notice "the mutator trick" in operator[] ;).
Of course some other operators could be implemented, too.
No, bitfields only support integral types. But for very small arrays, you can store each element as a property individually, for example:
struct st
{
unsigned int i0: 1;
unsigned int i1: 1;
unsigned int i2: 1;
unsigned int i3: 1;
unsigned int i4: 1;
};
The disadvantage of this approach is obviously that you can no longer use array-based operations or methods, such as run-time indexing, but it works well enough for basic applications like mathematical vectors.
I have a vector with digits of number, vector represents big integer in system with base 2^32. For example:
vector <unsigned> vec = {453860625, 469837947, 3503557200, 40}
This vector represent this big integer:
base = 2 ^ 32
3233755723588593872632005090577 = 40 * base ^ 3 + 3503557200 * base ^ 2 + 469837947 * base + 453860625
How to get this decimal representation in string?
Here is an inefficient way to do what you want, get a decimal string from a vector of word values representing an integer of arbitrary size.
I would have preferred to implement this as a class, for better encapsulation and so math operators could be added, but to better comply with the question, this is just a bunch of free functions for manipulating std::vector<unsigned> objects. This does use a typedef BiType as an alias for std::vector<unsigned> however.
Functions for doing the binary division make up most of this code. Much of it duplicates what can be done with std::bitset, but for bitsets of arbitrary size, as vectors of unsigned words. If you want to improve efficiency, plug in a division algorithm which does per-word operations, instead of per-bit. Also, the division code is general-purpose, when it is only ever used to divide by 10, so you could replace it with special-purpose division code.
The code generally assumes a vector of unsigned words and also that the base is the maximum unsigned value, plus one. I left a comment wherever things would go wrong for smaller bases or bases which are not a power of 2 (binary division requires base to be a power of 2).
Also, I only tested for 1 case, the one you gave in the OP -- and this is new, unverified code, so you might want to do some more testing. If you find a problem case, I'll be happy to fix the bug here.
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
namespace bigint {
using BiType = std::vector<unsigned>;
// cmp compares a with b, returning 1:a>b, 0:a==b, -1:a<b
int cmp(const BiType& a, const BiType& b) {
const auto max_size = std::max(a.size(), b.size());
for(auto i=max_size-1; i+1; --i) {
const auto wa = i < a.size() ? a[i] : 0;
const auto wb = i < b.size() ? b[i] : 0;
if(wa != wb) { return wa > wb ? 1 : -1; }
}
return 0;
}
bool is_zero(BiType& bi) {
for(auto w : bi) { if(w) return false; }
return true;
}
// canonize removes leading zero words
void canonize(BiType& bi) {
const auto size = bi.size();
if(!size || bi[size-1]) return;
for(auto i=size-2; i+1; --i) {
if(bi[i]) {
bi.resize(i + 1);
return;
}
}
bi.clear();
}
// subfrom subtracts b from a, modifying a
// a >= b must be guaranteed by caller
void subfrom(BiType& a, const BiType& b) {
unsigned borrow = 0;
for(std::size_t i=0; i<b.size(); ++i) {
if(b[i] || borrow) {
// TODO: handle error if i >= a.size()
const auto w = a[i] - b[i] - borrow;
// this relies on the automatic w = w (mod base),
// assuming unsigned max is base-1
// if this is not the case, w must be set to w % base here
borrow = w >= a[i];
a[i] = w;
}
}
for(auto i=b.size(); borrow; ++i) {
// TODO: handle error if i >= a.size()
borrow = !a[i];
--a[i];
// a[i] must be set modulo base here too
// (this is automatic when base is unsigned max + 1)
}
}
// binary division and its helpers: these require base to be a power of 2
// hi_bit_set is base/2
// the definition assumes CHAR_BIT == 8
const auto hi_bit_set = unsigned(1) << (sizeof(unsigned) * 8 - 1);
// shift_right_1 divides bi by 2, truncating any fraction
void shift_right_1(BiType& bi) {
unsigned carry = 0;
for(auto i=bi.size()-1; i+1; --i) {
const auto next_carry = (bi[i] & 1) ? hi_bit_set : 0;
bi[i] >>= 1;
bi[i] |= carry;
carry = next_carry;
}
// if carry is nonzero here, 1/2 was truncated from the result
canonize(bi);
}
// shift_left_1 multiplies bi by 2
void shift_left_1(BiType& bi) {
unsigned carry = 0;
for(std::size_t i=0; i<bi.size(); ++i) {
const unsigned next_carry = !!(bi[i] & hi_bit_set);
bi[i] <<= 1; // assumes high bit is lost, i.e. base is unsigned max + 1
bi[i] |= carry;
carry = next_carry;
}
if(carry) { bi.push_back(1); }
}
// sets an indexed bit in bi, growing the vector when required
void set_bit_at(BiType& bi, std::size_t index, bool set=true) {
std::size_t widx = index / (sizeof(unsigned) * 8);
std::size_t bidx = index % (sizeof(unsigned) * 8);
if(bi.size() < widx + 1) { bi.resize(widx + 1); }
if(set) { bi[widx] |= unsigned(1) << bidx; }
else { bi[widx] &= ~(unsigned(1) << bidx); }
}
// divide divides n by d, returning the result and leaving the remainder in n
// this is implemented using binary division
BiType divide(BiType& n, BiType d) {
if(is_zero(d)) {
// TODO: handle divide by zero
return {};
}
std::size_t shift = 0;
while(cmp(n, d) == 1) {
shift_left_1(d);
++shift;
}
BiType result;
do {
if(cmp(n, d) >= 0) {
set_bit_at(result, shift);
subfrom(n, d);
}
shift_right_1(d);
} while(shift--);
canonize(result);
canonize(n);
return result;
}
std::string get_decimal(BiType bi) {
std::string dec_string;
// repeat division by 10, using the remainder as a decimal digit
// this will build a string with digits in reverse order, so
// before returning, it will be reversed to correct this.
do {
const auto next_bi = divide(bi, {10});
const char digit_value = static_cast<char>(bi.size() ? bi[0] : 0);
dec_string.push_back('0' + digit_value);
bi = next_bi;
} while(!is_zero(bi));
std::reverse(dec_string.begin(), dec_string.end());
return dec_string;
}
}
int main() {
bigint::BiType my_big_int = {453860625, 469837947, 3503557200, 40};
auto dec_string = bigint::get_decimal(my_big_int);
std::cout << dec_string << '\n';
}
Output:
3233755723588593872632005090577
Here we have a function fire() which accepts two arguments:
A capital letter (char) in the range of 'A' .. 'A'+BS_GRID_ROWS-1 that indicates the row in your grid to attack.
An integer (int) in the range of 1 .. BS_GRID_COLS that indicates the column of your grid to attack.
The return code will be:
0 if there is only open water.
The bit BS_SHIP_HIT will be set, or both BS_SHIP_HIT and BS_SHIP_SANK will be set. In addition, the ship that was hit will be indicated in the lowest four bits of the return code. You may use BS_SHIP_MASK to help extract the number for the ship type.
semi-pseudocode interpretation:
//r is A ... (A + BS_GRID_ROWS - 1)
//c is 1 ... BS_GRID_COLS
fire(char r, int c) {
//some set of commands
if(miss) {
return 0;
else if(sink) {
return hit + sunk + size;
else if(hit) {
return hit;
else {
return miss;
}
}
I am uncertain of exactly how I might go about extracting these individual values (hit, sunk, size) from the return value.
The actual .h file and it's relevant const values are seen here:
#ifndef BATTLESHIP
#define BATTLESHIP
const int BS_SHIP_HIT = 0x10; // Ship is hit, or
const int BS_SHIP_SANK = 0x20; // sank (must also | BS_SHIP_HIT)
const int BS_CARRIER = 1;
const int BS_BATTLESHIP= 2;
const int BS_CRUISER = 3;
const int BS_DESTROYER = 4;
const int BS_SUBMARINE = 5;
const int BS_SHIP_COUNT = 5;
const int BS_SHIP_MASK = 0x0F;
const int BS_CARRIER_SIZE = 5;
const int BS_BATTLESHIP_SIZE= 4;
const int BS_CRUISER_SIZE = 3;
const int BS_DESTROYER_SIZE = 2;
const int BS_SUBMARINE_SIZE = 3;
const int BS_MODE_NEW_GAME = 1;
const int BS_MODE_CONTINUE_GAME = 2;
const int BS_GRID_ROWS = 10; // letters A to J
const int BS_GRID_COLS = 10; // numbers 1 to 10
const int MaxPlayerCount = 65; // Maximum size for following arrays
extern int userIncoming(char, int);
extern int userBattle(int, int);
extern int incomingStub(char, int);
extern int battleStub(int, int);
extern int (*fire[])(char, int);
extern int (*battleship[])(int, int);
extern char const *playerName[];
#endif
Something like this perhaps?
int result = fire(r, c);
if (result & BS_SHIP_HIT)
{
std::cout << "Ship of size " << result & BS_SHIP_MASK << " hit\n";
}
If the BS_SHIP_HIT bit is set in result, the the result of result & BIT_SHIP_HIT will be equal to BS_SHIP_HIT otherwise the result will be zero (which is equivalent to false).
The result of result & BS_SHIP_MASK will be the low four bits in result.
Or lets look at it using the actual bits:
BS_SHIP_HIT is equal to the binary value 00010000 and BS_SHIT_MASK equal 00001111. Lets assume that fire returns 00010101 (BS_SHIP_HIT set and size 5), then the if condition will be
00010000
& 00010101
----------
= 00010000
Then for the printing, the expression will be
00010101
& 00001111
----------
= 00000101