Some (skippable) context:
I'm writing program that minimizes an arbitrary logical function. Such function takes an unsigned integer as an argument and returns either true
or false
.
The input to my program is a list of all values for which the minimized function returns true
. Those values are called "minterms".
Because I want to handle functions that take arguments up to 32 bits, the number of minterms may reach billions and I need a way to effectively store them.
Description of the code:
This is a collection that stores set of unique 32-bit unsigned integers from range 0..n where n is decided at runtime and can be as high as 2^32 - 1. In a typical case, the number of stored integers is close to the half of the size of the range.
The primary concern while writing this, was RAM usage.
Using std::vector<std::uint32_t>
would result in 16 GB of memory in the worst case.
Instead, I've opted for using a std::vector<bool>
as a bitset. Each bool
in it represents an integer equal to its the index in the vector, where true
means that the number is present and false
that it is not.
This reduces the memory usage to "merely" 512 MB.
Another, also very important concern, is the speed. This is the reason for which almost everything is inlined and contained in the header file. Fortunately, this way of storing the data allows for checking and storing arbitrary values in O(1) time and this is the may mode of interaction with the data.
The iterator is not invalidated when elements are added / removed, which is useful in a few algorithms.
I tried to make this code work in all the popular compilers. I also avoided using any external libraries to make it compile out of the box for every user.
Purpose of the review:
I think the algorithm I'm using is sound and decently optimized but of course I welcome any suggestions on how to make this code faster.
The main purpose of the review, however, is too gauge the quality and readability of the code.
I have quite a lot of experience with C++ but I'm mostly self-learned. I've never worked professionally with other people knowing this language so I might be not aware of some more obscure conventions in the language. Please tell me if I'm doing anything that an average professional C++ programmer would consider weird. One thing that I am aware of is that my naming convention does not match the one from the standard library. I just like the Java naming convention.
Do you think my code is readable? Are any names unclear? Should I add an explanatory comment somewhere?
Are there any aspects of the C++ language that would be in some way helpful but I seem unaware of?
E.g. things like the attribute [[nodiscard]]
.
Roast my code:
This code uses 2 headers from my project that are not included in the review:
- "Minterm.hh" defines the type
Minterm
which is just an alias tostd::uint_fast32_t
. - "global.hh" defines a value
maxMinterm
which depends on the program's input but it doesn't change after its initialization at the beginning. It's value is in range from 0 to 2^32 - 1.
#pragma once
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <iterator>
#include <vector>
#include "global.hh"
#include "Minterm.hh"
class Minterms
{
std::vector<bool> bitset;
std::size_t size = 0;
public:
using overlapping_t = std::vector<Minterm>;
class ConstIterator
{
const Minterms &minterms;
std::uint_fast64_t i;
ConstIterator(const Minterms &minterms, const std::uint_fast64_t i) : minterms(minterms), i(i) {}
friend class Minterms;
public:
[[nodiscard]] bool operator==(const ConstIterator &other) const { return this->i == other.i; }
[[nodiscard]] bool operator!=(const ConstIterator &other) const { return this->i != other.i; }
inline ConstIterator& operator++();
inline ConstIterator& operator--();
[[nodiscard]] Minterm operator*() const { return static_cast<Minterm>(i); }
};
Minterms() : bitset(static_cast<std::uint_fast64_t>(::maxMinterm) + 1, false) {}
[[nodiscard]] bool operator==(const Minterms &other) const { return this->bitset == other.bitset; }
[[nodiscard]] bool operator!=(const Minterms &other) const { return this->bitset != other.bitset; }
[[nodiscard]] bool isEmpty() const { return size == 0; }
[[nodiscard]] bool isFull() const { return size - 1 == ::maxMinterm; }
[[nodiscard]] std::size_t getSize() const { return size; }
[[nodiscard]] std::size_t getCapacity() const { return bitset.size(); }
[[nodiscard]] bool check(const Minterm minterm) const { return bitset[minterm]; }
[[nodiscard]] inline overlapping_t findOverlapping(const Minterms &other) const; // Optimized for when there is little to none of them.
inline bool add(const Minterm minterm);
inline void add(const Minterms &other, const std::size_t overlappingCount);
inline bool remove(const Minterm minterm);
[[nodiscard]] inline ConstIterator begin() const;
[[nodiscard]] ConstIterator end() const { return {*this, bitset.size()}; }
[[nodiscard]] ConstIterator cbegin() const { return begin(); }
[[nodiscard]] ConstIterator cend() const { return end(); }
#ifndef NDEBUG
void validate() const;
#endif
};
Minterms::ConstIterator& Minterms::ConstIterator::operator++()
{
for (++i; i != minterms.bitset.size() && !minterms.bitset[i]; ++i) { }
return *this;
}
Minterms::ConstIterator& Minterms::ConstIterator::operator--()
{
for (--i; i != 0 && !minterms.bitset[i]; --i) { }
return *this;
}
Minterms::overlapping_t Minterms::findOverlapping(const Minterms &other) const
{
overlapping_t overlapping;
std::set_intersection(
this->cbegin(), this->cend(),
other.cbegin(), other.cend(),
std::back_inserter(overlapping));
return overlapping;
}
bool Minterms::add(const Minterm minterm)
{
const bool previous = check(minterm);
if (!previous)
{
bitset[minterm] = true;
++size;
}
return !previous;
}
void Minterms::add(const Minterms &other, const std::size_t overlappingCount)
{
std::transform(
other.bitset.begin(), other.bitset.end(),
this->bitset.begin(), this->bitset.begin(),
std::logical_or<bool>());
this->size += other.size - overlappingCount;
}
bool Minterms::remove(const Minterm minterm)
{
const bool previous = check(minterm);
if (previous)
{
bitset[minterm] = false;
--size;
}
return previous;
}
Minterms::ConstIterator Minterms::begin() const
{
ConstIterator iter{*this, 0};
if (!bitset.empty() && !bitset[0])
++iter;
return iter;
}
#include "./Minterms.hh"
#include <cassert>
#ifndef NDEBUG
void Minterms::validate() const
{
std::size_t actualCount = 0;
for ([[maybe_unused]] const Minterm minterm : *this)
++actualCount;
assert(size == actualCount);
}
#endif