Related
#include <iostream>
#include <set>
using namespace std;
struct A
{
int n1;
int n2;
A(int n1, int n2)
{
this->n1 = n1;
this->n2 = n2;
}
};
struct Cop {
bool operator() (const A&a, const A&b)
{
if (a.n1 > b.n1)
return true;
return false;
}
};
int main(void)
{
set<A, Cop> a;
a.insert(A(4, 5));
a.insert(A(4, 6));
for (set<A>::iterator it = a.begin(); it != a.end(); ++it)
{
cout << it->n1 << " " << it->n2 << endl;
}
}
result:
4 5
As you can see, even though the n2 elements of the A structure are not the same, the (4, 6) element is deleted. Can I solve this through operator overloading?
First of all I've tried to search for similar questions but I didn't find any response explaining what could my problem be.
The problem is the following: Given a set of N nodes with coordinates (x,y,z) sort them using a 4th value F as fast as possible.
I want to use a std::set with a custom comparator for this purpose because it has O(log(N)) complexity. I know I could also try a std::vector and a call to std::sort on std::vector but in theory is a slower operation.
Why this? Because I'm constantly inserting elements in the set, changing the F value (it means I change the value and to reorder the element in the container I erase and re-insert it) and I want to take the element with the less F value (that's the element at the front of the container).
But let's go with the std::set problem.
The coordinates define the uniqueness property, following the strict weak ordering rules,it means that a and b are the considered the same object if
!comp(a,b) && !comp(b,a)
The problem is related to define a uniqueness criteria based on the coordinates and a sorting criteria based on the F value. I don't want the set to store two elements with the same coordiantes, but I want it to be allow to store two elements with different coordinates but same F value
The comparator should also satisfais the following three properties:
Irreflexivity x < x false
Assymetry x < y true implies y < x false
Transitivy x < y && y < z implies x < z true
So knowing all these properties I've been working around with the following example implementation:
Some definitions
class Node;
struct NodeComparator;
using NodePair = std::pair<Node *, int>;
using NodeSet = std::set<NodePair, NodeComparator>;
Here I'm using pointers for convenience
Class Node
class Node
{
public:
Node()
{
}
Node(int _x, int _y, int _z, int _val) : x(_x), y(_y), z(_z), value(_val)
{
}
int x, y, z;
int value;
friend inline std::ostream &operator<<(std::ostream &os, const Node &dt)
{
os << "[" << dt.x << ", " << dt.y << ", " << dt.z << "], [" << dt.value << "]";
return os;
}
friend bool operator==(const Node &_lhs, const Node &_rhs){
if( _lhs.x == _rhs.x &&
_lhs.y == _rhs.y &&
_lhs.z == _rhs.z ){
return true;
}
return false;
}
};
Here the operator << is overloaded only for debugging purposes
The comparator
struct NodeComparator
{
bool operator()(const NodePair &_lhs, const NodePair &_rhs) const
{
if( _lhs.first == nullptr || _rhs.first == nullptr )
return false;
/*
This first check implements uniqueness.
If _lhs == _rhs --> comp(_lhs,_rhs) == false && comp(_rhs, _lhs) == false
So ( !comp(l,r) && !comp(r,l) ) == true
*/
if( *_lhs.first == *_rhs.first)
return false;
int ret = _lhs.second - _rhs.second;
return ret < 0;
}
};
I guess one problem could be the case of two nodes with different coordinates but same F value
Full example with concrete cases
Ìn this example I use the above classes to insert/find/erase some elements, but has it is show on the output, it's not behaving as expected:
#include <iostream>
#include <set>
#include <vector>
#include <algorithm>
#include <tuple>
class Node;
struct NodeComparator;
using NodePair = std::pair<Node *, int>;
using NodeSet = std::set<NodePair, NodeComparator>;
class Node
{
public:
Node()
{
}
Node(int _x, int _y, int _z, int _val) : x(_x), y(_y), z(_z), value(_val)
{
}
int x, y, z;
int value;
friend inline std::ostream &operator<<(std::ostream &os, const Node &dt)
{
os << "[" << dt.x << ", " << dt.y << ", " << dt.z << "], [" << dt.value << "]";
return os;
}
};
struct NodeComparator
{
bool operator()(const NodePair &_lhs, const NodePair &_rhs) const
{
/*
This first check implements uniqueness.
If _lhs == _rhs --> comp(_lhs,_rhs) == false && comp(_rhs, _lhs) == false
So ( !comp(l,r) && !comp(r,l) ) == true
*/
if(_lhs == _rhs)
return false;
int ret = _lhs.second - _rhs.second;
return ret < 0;
}
};
int main(int argc, char **argv)
{
Node n1(0, 2, 4, 12),
n2(2, 4, 5, 25),
n3(0, 1, 4, 34),
n4(0, 1, 4, 20),
n5(0, 1, 5, 20),
n6(0, 2, 4, 112);
NodeSet set;
set.insert({&n1, n1.value});
set.insert({&n2, n2.value});
set.insert({&n3, n3.value});
set.insert({&n4, n4.value}); //Should not be inserted because it already exists n3 with same coords
set.insert({&n5, n5.value});
//Try to insert multiple times a previously inserted node (n1 coords is == n6 coords)
//It should not be inserted because it already exists one node with the same coords (n1)
set.insert({&n6, n6.value});
set.insert({&n6, n6.value});
set.insert({&n6, n6.value});
set.insert({&n6, n6.value});
set.insert({&n6, 0});
set.insert({&n6, 1});
if (set.find({&n4, n4.value}) != set.end())
std::cout << "Found n4" << std::endl;
auto it = set.erase({&n4, 20});
std::cout << "It value (elements erased): " << it << std::endl;
if (set.find({&n4, n4.value}) != set.end())
std::cout << "Found n4 after removal" << std::endl;
std::cout << "Final Set content: " << std::endl;
for (auto &it : set)
std::cout << *it.first << std::endl;
return 0;
}
To compile it with C++11 or above: g++ -o main main.cpp
Output:
Found n4
It value (elements erased): 1
Final Set content:
[0, 2, 4], [12]
[2, 4, 5], [25]
[0, 1, 4], [34]
[0, 2, 4], [112]
**Expected Output: ** Correspond to elements n1, n5, n2, n3 ordered from the one with less F (n1) to the one with the higher F (n3).
Final Set content:
[0, 2, 4], [12]
[0, 1, 5], [20]
[2, 4, 5], [25]
[0, 1, 4], [34]
I would appreciate a lot any help or ideas and alternatives of implementation. Thanks
Unfortunately, your requirements cannot be fulfilled by one std::set alone. The std::set uses the same comparator for sorting and uniqueness. The comparator has no state, meaning, you cannot compare one time with the first and the next time with a second condition. That will not work.
So, you need to use 2 containers, like first a std::unordered_set using a comparator for equal coordinates and the a second container for the sorting, like a std::multiset..
You could also use a std::unordered_map in conjunction with a std::multiset.
Or you create your own container as a class and try to optimize performance.
Let me show you an example using the combination of std::unordered_set and std::multiset. It will be fast, because the std::unordered_set uses hashes.
#include <iostream>
#include <unordered_set>
#include <set>
#include <array>
#include <vector>
using Coordinate = std::array<int, 3>;
struct Node {
Coordinate coordinate{};
int value{};
bool operator == (const Node& other) const { return coordinate == other.coordinate; }
friend std::ostream& operator << (std::ostream& os, const Node& n) {
return os << "[" << n.coordinate[0] << ", " << n.coordinate[1] << ", " << n.coordinate[2] << "], [" << n.value << "]"; }
};
struct CompareOnSecond { bool operator ()(const Node& n1, const Node& n2)const { return n1.value < n2.value; } };
struct Hash {size_t operator()(const Node& n) const {return n.coordinate[0] ^ n.coordinate[1] ^ n.coordinate[2];} };
using UniqueNodes = std::unordered_set<Node, Hash>;
using Sorter = std::multiset<Node, CompareOnSecond>;
int main() {
// a vector with some test nodes
std::vector<Node> testNodes{
{ {{0, 2, 4}}, 12 },
{ {{2, 4, 5}}, 25 },
{ {{0, 1, 4}}, 34 },
{ {{0, 1, 4}}, 20 },
{ {{0, 1, 5}}, 20 },
{ {{0, 2, 4}}, 112 } };
// Here we will store the unique nodes
UniqueNodes uniqueNodes{};
for (const Node& n : testNodes) uniqueNodes.insert(n);
// And now, do the sorting
Sorter sortedNodes(uniqueNodes.begin(), uniqueNodes.end());
// Some test functions
std::cout << "\nSorted unique nodes:\n";
for (const Node& n : sortedNodes) std::cout << n << '\n';
// find a node
if (sortedNodes.find({ {{0, 1, 4}}, 20 }) != sortedNodes.end())
std::cout << "\nFound n4\n";
// Erase a node
auto it = sortedNodes.erase({ {{0, 1, 4}}, 20 });
std::cout << "It value (elements erased): " << it << '\n';
// Was it really erased?
if (sortedNodes.find({ {{0, 1, 4}}, 20 }) != sortedNodes.end())
std::cout << "\nFound n4 after removal\n";
// Show final result
std::cout << "\nFinal Set content:\n";
for (const Node& n : sortedNodes) std::cout << n << '\n';
}
Finally, thanks to suggestions and comments of the users I implemented a solution using Boost multi index with 2 index. One hashed unique index and one ordered-non-unique index. Despite this I marked the answer above as accepted because is the most standard solution.
#include <iostream>
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/identity.hpp>
#include <boost/multi_index/member.hpp>
#include <boost/multi_index/hashed_index.hpp>
#include <boost/multi_index/key_extractors.hpp>
using namespace ::boost;
using namespace ::boost::multi_index;
struct IndexByCost {};
struct IndexByWorldPosition {};
class Node
{
public:
Node(int _val, int _i) : value(_val), index(_i) {}
int value; //NON UNIQUE
unsigned int index; //UNIQUE
friend inline std::ostream &operator<<(std::ostream &os, const Node &dt)
{
os << dt.index << ": [" << dt.value << "]";
return os;
}
};
using MagicalMultiSet = boost::multi_index_container<
Node*, // the data type stored
boost::multi_index::indexed_by< // list of indexes
boost::multi_index::hashed_unique< //hashed index wo
boost::multi_index::tag<IndexByWorldPosition>, // give that index a name
boost::multi_index::member<Node, unsigned int, &Node::index> // what will be the index's key
>,
boost::multi_index::ordered_non_unique< //ordered index over 'i1'
boost::multi_index::tag<IndexByCost>, // give that index a name
boost::multi_index::member<Node, int, &Node::value> // what will be the index's key
>
>
>;
int main(int argc, char const *argv[])
{
MagicalMultiSet container;
Node n1{24, 1};
Node n2{12, 2};
Node n3{214,3};
Node n4{224,4};
Node n5{221,5};
Node n6{221,6};
auto & indexByCost = container.get<IndexByCost>();
auto & indexByWorldPosition = container.get<IndexByWorldPosition>();
indexByCost.insert(&n1);
indexByCost.insert(&n2);
indexByCost.insert(&n3);
indexByCost.insert(&n4);
indexByCost.insert(&n5);
for(auto &it: indexByCost)
std::cout << *it << std::endl;
auto it = indexByCost.begin();
std::cout << "Best Node " << **it << std::endl;
indexByCost.erase(indexByCost.begin());
it = indexByCost.begin();
std::cout << "Best Node After erasing the first one: " << **it << std::endl;
std::cout << "What if we modify the value of the nodes?" << std::endl;
n2.value = 1;
std::cout << "Container view from index by world position" << std::endl;
for(auto &it: indexByWorldPosition)
std::cout << *it << std::endl;
auto found = indexByWorldPosition.find(2);
if(found != indexByWorldPosition.end() )
std::cout << "Okey found n2 by index" << std::endl;
found = indexByWorldPosition.find(1);
if(found != indexByWorldPosition.end() )
std::cout << "Okey found n1 by index" << std::endl;
std::cout << "Imagine we update the n1 cost" << std::endl;
n1.value = 10000;
indexByWorldPosition.erase(found);
indexByWorldPosition.insert(&n1);
std::cout << "Container view from index by cost " << std::endl;
for(auto &it: indexByCost)
std::cout << *it << std::endl;
return 0;
}
Consider the functions
#include <iostream>
#include <boost/bind.hpp>
#include <boost/asio.hpp>
void foo(const uint64_t begin, uint64_t *result)
{
uint64_t prev[] = {begin, 0};
for (uint64_t i = 0; i < 1000000000; ++i)
{
const auto tmp = (prev[0] + prev[1]) % 1000;
prev[1] = prev[0];
prev[0] = tmp;
}
*result = prev[0];
}
void batch(boost::asio::thread_pool &pool, const uint64_t a[])
{
uint64_t r[] = {0, 0};
boost::asio::post(pool, boost::bind(foo, a[0], &r[0]));
boost::asio::post(pool, boost::bind(foo, a[1], &r[1]));
pool.join();
std::cerr << "foo(" << a[0] << "): " << r[0] << " foo(" << a[1] << "): " << r[1] << std::endl;
}
where foo is a simple "pure" function that performs a calculation on begin and writes the result to the pointer *result.
This function gets called with different inputs from batch. Here dispatching each call to another CPU core might be beneficial.
Now assume the batch function gets called several 10 000 times. Therefore a thread pool would be nice which is shared between all the sequential batch calls.
Trying this with (for the sake of simplicity only 3 calls)
int main(int argn, char **)
{
boost::asio::thread_pool pool(2);
const uint64_t a[] = {2, 4};
batch(pool, a);
const uint64_t b[] = {3, 5};
batch(pool, b);
const uint64_t c[] = {7, 9};
batch(pool, c);
}
leads to the result
foo(2): 2 foo(4): 4
foo(3): 0 foo(5): 0
foo(7): 0 foo(9): 0
Where all three lines appear at the same time, while the computation of foo takes ~3s.
I assume that only the first join really waits for the pool to complete all jobs.
The others have invalid results. (The not initialized values)
What is the best practice here to reuse the thread pool?
The best practice is not to reuse the pool (what would be the use of pooling, if you keep creating new pools?).
If you want to be sure you "time" the batches together, I'd suggest using when_all on futures:
Live On Coliru
#define BOOST_THREAD_PROVIDES_FUTURE_WHEN_ALL_WHEN_ANY
#include <iostream>
#include <boost/bind.hpp>
#include <boost/asio.hpp>
#include <boost/thread.hpp>
uint64_t foo(uint64_t begin) {
uint64_t prev[] = {begin, 0};
for (uint64_t i = 0; i < 1000000000; ++i) {
const auto tmp = (prev[0] + prev[1]) % 1000;
prev[1] = prev[0];
prev[0] = tmp;
}
return prev[0];
}
void batch(boost::asio::thread_pool &pool, const uint64_t a[2])
{
using T = boost::packaged_task<uint64_t>;
T tasks[] {
T(boost::bind(foo, a[0])),
T(boost::bind(foo, a[1])),
};
auto all = boost::when_all(
tasks[0].get_future(),
tasks[1].get_future());
for (auto& t : tasks)
post(pool, std::move(t));
auto [r0, r1] = all.get();
std::cerr << "foo(" << a[0] << "): " << r0.get() << " foo(" << a[1] << "): " << r1.get() << std::endl;
}
int main() {
boost::asio::thread_pool pool(2);
const uint64_t a[] = {2, 4};
batch(pool, a);
const uint64_t b[] = {3, 5};
batch(pool, b);
const uint64_t c[] = {7, 9};
batch(pool, c);
}
Prints
foo(2): 2 foo(4): 4
foo(3): 503 foo(5): 505
foo(7): 507 foo(9): 509
I would consider
generalizing
message queuing
Generalized
Make it somewhat more flexible by not hardcoding batch sizes. After all, the pool size is already fixed, we don't need to "make sure batches fit" or something:
Live On Coliru
#define BOOST_THREAD_PROVIDES_FUTURE_WHEN_ALL_WHEN_ANY
#include <iostream>
#include <boost/bind.hpp>
#include <boost/asio.hpp>
#include <boost/thread.hpp>
#include <boost/thread/future.hpp>
struct Result { uint64_t begin, result; };
Result foo(uint64_t begin) {
uint64_t prev[] = {begin, 0};
for (uint64_t i = 0; i < 1000000000; ++i) {
const auto tmp = (prev[0] + prev[1]) % 1000;
prev[1] = prev[0];
prev[0] = tmp;
}
return { begin, prev[0] };
}
void batch(boost::asio::thread_pool &pool, std::vector<uint64_t> const a)
{
using T = boost::packaged_task<Result>;
std::vector<T> tasks;
tasks.reserve(a.size());
for(auto begin : a)
tasks.emplace_back(boost::bind(foo, begin));
std::vector<boost::unique_future<T::result_type> > futures;
for (auto& t : tasks) {
futures.push_back(t.get_future());
post(pool, std::move(t));
}
for (auto& fut : boost::when_all(futures.begin(), futures.end()).get()) {
auto r = fut.get();
std::cerr << "foo(" << r.begin << "): " << r.result << " ";
}
std::cout << std::endl;
}
int main() {
boost::asio::thread_pool pool(2);
batch(pool, {2});
batch(pool, {4, 3, 5});
batch(pool, {7, 9});
}
Prints
foo(2): 2
foo(4): 4 foo(3): 503 foo(5): 505
foo(7): 507 foo(9): 509
Generalized2: Variadics Simplify
Contrary to popular believe (and honestly, what usually happens) this time we can leverage variadics to get rid of all the intermediate vectors (every single one of them):
Live On Coliru
void batch(boost::asio::thread_pool &pool, T... a)
{
auto launch = [&pool](uint64_t begin) {
boost::packaged_task<Result> pt(boost::bind(foo, begin));
auto fut = pt.get_future();
post(pool, std::move(pt));
return fut;
};
for (auto& r : {launch(a).get()...}) {
std::cerr << "foo(" << r.begin << "): " << r.result << " ";
}
std::cout << std::endl;
}
If you insist on outputting the results in time, you can still add when_all into the mix (requiring a bit more heroics to unpack the tuple):
Live On Coliru
template <typename...T>
void batch(boost::asio::thread_pool &pool, T... a)
{
auto launch = [&pool](uint64_t begin) {
boost::packaged_task<Result> pt(boost::bind(foo, begin));
auto fut = pt.get_future();
post(pool, std::move(pt));
return fut;
};
std::apply([](auto&&... rfut) {
Result results[] {rfut.get()...};
for (auto& r : results) {
std::cerr << "foo(" << r.begin << "): " << r.result << " ";
}
}, boost::when_all(launch(a)...).get());
std::cout << std::endl;
}
Both still print the same result
Message Queuing
This is very natural to boost, and sort of skips most complexity. If you also want to report per batched group, you'd have to coordinate:
Live On Coliru
#include <iostream>
#include <boost/asio.hpp>
#include <memory>
struct Result { uint64_t begin, result; };
Result foo(uint64_t begin) {
uint64_t prev[] = {begin, 0};
for (uint64_t i = 0; i < 1000000000; ++i) {
const auto tmp = (prev[0] + prev[1]) % 1000;
prev[1] = prev[0];
prev[0] = tmp;
}
return { begin, prev[0] };
}
using Group = std::shared_ptr<size_t>;
void batch(boost::asio::thread_pool &pool, std::vector<uint64_t> begins) {
auto group = std::make_shared<std::vector<Result> >(begins.size());
for (size_t i=0; i < begins.size(); ++i) {
post(pool, [i,begin=begins.at(i),group] {
(*group)[i] = foo(begin);
if (group.unique()) {
for (auto& r : *group) {
std::cout << "foo(" << r.begin << "): " << r.result << " ";
std::cout << std::endl;
}
}
});
}
}
int main() {
boost::asio::thread_pool pool(2);
batch(pool, {2});
batch(pool, {4, 3, 5});
batch(pool, {7, 9});
pool.join();
}
Note this is having concurrent access to group, which is safe due to the limitations on element accesses.
Prints:
foo(2): 2
foo(4): 4 foo(3): 503 foo(5): 505
foo(7): 507 foo(9): 509
I just ran into this advanced executor example which is hidden from the documentation:
I realized just now that Asio comes with a fork_executor example which does exactly this: you can "group" tasks and join the executor (which represents that group) instead of the pool. I've missed this for the longest time since none of the executor examples are listed in the HTML documentation – sehe 21 mins ago
So without further ado, here's that sample applied to your question:
Live On Coliru
#define BOOST_BIND_NO_PLACEHOLDERS
#include <boost/asio/thread_pool.hpp>
#include <boost/asio/ts/executor.hpp>
#include <condition_variable>
#include <memory>
#include <mutex>
#include <queue>
#include <thread>
// A fixed-size thread pool used to implement fork/join semantics. Functions
// are scheduled using a simple FIFO queue. Implementing work stealing, or
// using a queue based on atomic operations, are left as tasks for the reader.
class fork_join_pool : public boost::asio::execution_context {
public:
// The constructor starts a thread pool with the specified number of
// threads. Note that the thread_count is not a fixed limit on the pool's
// concurrency. Additional threads may temporarily be added to the pool if
// they join a fork_executor.
explicit fork_join_pool(std::size_t thread_count = std::thread::hardware_concurrency()*2)
: use_count_(1), threads_(thread_count)
{
try {
// Ask each thread in the pool to dequeue and execute functions
// until it is time to shut down, i.e. the use count is zero.
for (thread_count_ = 0; thread_count_ < thread_count; ++thread_count_) {
boost::asio::dispatch(threads_, [&] {
std::unique_lock<std::mutex> lock(mutex_);
while (use_count_ > 0)
if (!execute_next(lock))
condition_.wait(lock);
});
}
} catch (...) {
stop_threads();
threads_.join();
throw;
}
}
// The destructor waits for the pool to finish executing functions.
~fork_join_pool() {
stop_threads();
threads_.join();
}
private:
friend class fork_executor;
// The base for all functions that are queued in the pool.
struct function_base {
std::shared_ptr<std::size_t> work_count_;
void (*execute_)(std::shared_ptr<function_base>& p);
};
// Execute the next function from the queue, if any. Returns true if a
// function was executed, and false if the queue was empty.
bool execute_next(std::unique_lock<std::mutex>& lock) {
if (queue_.empty())
return false;
auto p(queue_.front());
queue_.pop();
lock.unlock();
execute(lock, p);
return true;
}
// Execute a function and decrement the outstanding work.
void execute(std::unique_lock<std::mutex>& lock,
std::shared_ptr<function_base>& p) {
std::shared_ptr<std::size_t> work_count(std::move(p->work_count_));
try {
p->execute_(p);
lock.lock();
do_work_finished(work_count);
} catch (...) {
lock.lock();
do_work_finished(work_count);
throw;
}
}
// Increment outstanding work.
void
do_work_started(const std::shared_ptr<std::size_t>& work_count) noexcept {
if (++(*work_count) == 1)
++use_count_;
}
// Decrement outstanding work. Notify waiting threads if we run out.
void
do_work_finished(const std::shared_ptr<std::size_t>& work_count) noexcept {
if (--(*work_count) == 0) {
--use_count_;
condition_.notify_all();
}
}
// Dispatch a function, executing it immediately if the queue is already
// loaded. Otherwise adds the function to the queue and wakes a thread.
void do_dispatch(std::shared_ptr<function_base> p,
const std::shared_ptr<std::size_t>& work_count) {
std::unique_lock<std::mutex> lock(mutex_);
if (queue_.size() > thread_count_ * 16) {
do_work_started(work_count);
lock.unlock();
execute(lock, p);
} else {
queue_.push(p);
do_work_started(work_count);
condition_.notify_one();
}
}
// Add a function to the queue and wake a thread.
void do_post(std::shared_ptr<function_base> p,
const std::shared_ptr<std::size_t>& work_count) {
std::lock_guard<std::mutex> lock(mutex_);
queue_.push(p);
do_work_started(work_count);
condition_.notify_one();
}
// Ask all threads to shut down.
void stop_threads() {
std::lock_guard<std::mutex> lock(mutex_);
--use_count_;
condition_.notify_all();
}
std::mutex mutex_;
std::condition_variable condition_;
std::queue<std::shared_ptr<function_base>> queue_;
std::size_t use_count_;
std::size_t thread_count_;
boost::asio::thread_pool threads_;
};
// A class that satisfies the Executor requirements. Every function or piece of
// work associated with a fork_executor is part of a single, joinable group.
class fork_executor {
public:
fork_executor(fork_join_pool& ctx)
: context_(ctx), work_count_(std::make_shared<std::size_t>(0)) {}
fork_join_pool& context() const noexcept { return context_; }
void on_work_started() const noexcept {
std::lock_guard<std::mutex> lock(context_.mutex_);
context_.do_work_started(work_count_);
}
void on_work_finished() const noexcept {
std::lock_guard<std::mutex> lock(context_.mutex_);
context_.do_work_finished(work_count_);
}
template <class Func, class Alloc>
void dispatch(Func&& f, const Alloc& a) const {
auto p(std::allocate_shared<exFun<Func>>(
typename std::allocator_traits<Alloc>::template rebind_alloc<char>(a),
std::move(f), work_count_));
context_.do_dispatch(p, work_count_);
}
template <class Func, class Alloc> void post(Func f, const Alloc& a) const {
auto p(std::allocate_shared<exFun<Func>>(
typename std::allocator_traits<Alloc>::template rebind_alloc<char>(a),
std::move(f), work_count_));
context_.do_post(p, work_count_);
}
template <class Func, class Alloc>
void defer(Func&& f, const Alloc& a) const {
post(std::forward<Func>(f), a);
}
friend bool operator==(const fork_executor& a, const fork_executor& b) noexcept {
return a.work_count_ == b.work_count_;
}
friend bool operator!=(const fork_executor& a, const fork_executor& b) noexcept {
return a.work_count_ != b.work_count_;
}
// Block until all work associated with the executor is complete. While it
// is waiting, the thread may be borrowed to execute functions from the
// queue.
void join() const {
std::unique_lock<std::mutex> lock(context_.mutex_);
while (*work_count_ > 0)
if (!context_.execute_next(lock))
context_.condition_.wait(lock);
}
private:
template <class Func> struct exFun : fork_join_pool::function_base {
explicit exFun(Func f, const std::shared_ptr<std::size_t>& w)
: function_(std::move(f)) {
work_count_ = w;
execute_ = [](std::shared_ptr<fork_join_pool::function_base>& p) {
Func tmp(std::move(static_cast<exFun*>(p.get())->function_));
p.reset();
tmp();
};
}
Func function_;
};
fork_join_pool& context_;
std::shared_ptr<std::size_t> work_count_;
};
// Helper class to automatically join a fork_executor when exiting a scope.
class join_guard {
public:
explicit join_guard(const fork_executor& ex) : ex_(ex) {}
join_guard(const join_guard&) = delete;
join_guard(join_guard&&) = delete;
~join_guard() { ex_.join(); }
private:
fork_executor ex_;
};
//------------------------------------------------------------------------------
#include <algorithm>
#include <iostream>
#include <random>
#include <vector>
#include <boost/bind.hpp>
static void foo(const uint64_t begin, uint64_t *result)
{
uint64_t prev[] = {begin, 0};
for (uint64_t i = 0; i < 1000000000; ++i) {
const auto tmp = (prev[0] + prev[1]) % 1000;
prev[1] = prev[0];
prev[0] = tmp;
}
*result = prev[0];
}
void batch(fork_join_pool &pool, const uint64_t (&a)[2])
{
uint64_t r[] = {0, 0};
{
fork_executor fork(pool);
join_guard join(fork);
boost::asio::post(fork, boost::bind(foo, a[0], &r[0]));
boost::asio::post(fork, boost::bind(foo, a[1], &r[1]));
// fork.join(); // or let join_guard destructor run
}
std::cerr << "foo(" << a[0] << "): " << r[0] << " foo(" << a[1] << "): " << r[1] << std::endl;
}
int main() {
fork_join_pool pool;
batch(pool, {2, 4});
batch(pool, {3, 5});
batch(pool, {7, 9});
}
Prints:
foo(2): 2 foo(4): 4
foo(3): 503 foo(5): 505
foo(7): 507 foo(9): 509
Things to note:
executors can overlap/nest: you can use several joinable fork_executors on a single fork_join_pool and they will join the distinct groups of tasks for each executor
You can get that sense easily when looking at the library example (which does a recursive divide-and-conquer merge sort).
I had a similar problem and ended up using latches. In this case the code would would be (I also switched from bind to lambdas):
void batch(boost::asio::thread_pool &pool, const uint64_t a[])
{
uint64_t r[] = {0, 0};
boost::latch latch(2);
boost::asio::post(pool, [&](){ foo(a[0], &r[0]); latch.count_down();});
boost::asio::post(pool, [&](){ foo(a[1], &r[1]); latch.count_down();});
latch.wait();
std::cerr << "foo(" << a[0] << "): " << r[0] << " foo(" << a[1] << "): " << r[1] << std::endl;
}
https://godbolt.org/z/oceP6jjs7
There are tons of answers for sorting a vector of struct in regards to a member variable. That is easy with std::sort and a predicate function, comparing the structs member. Really easy.
But I have a different question. Assume that I have the following struct:
struct Test {
int a{};
int b{};
int toSort{};
};
and a vector of that struct, like for example:
std::vector<Test> tv{ {1,1,9},{2,2,8},{3,3,7},{4,4,6},{5,5,5} };
I do not want to sort the vectors elements, but only the values in the member variable. So the expected output should be equal to:
std::vector<Test> tvSorted{ {1,1,5},{2,2,6},{3,3,7},{4,4,8},{5,5,9} };
I wanted to have the solution to be somehow a generic solution. Then I came up with a (sorry for that) preprocessor-macro-solution. Please see the following example code:
#include <iostream>
#include <vector>
#include <algorithm>
struct Test {
int a{};
int b{};
int toSort{};
};
#define SortSpecial(vec,Struct,Member) \
do { \
std::vector<decltype(Struct::Member)> vt{}; \
std::transform(vec.begin(), vec.end(), std::back_inserter(vt), [](const Struct& s) {return s.Member; }); \
std::sort(vt.begin(), vt.end()); \
std::for_each(vec.begin(), vec.end(), [&vt, i = 0U](Struct & s) mutable {s.Member = vt[i++]; }); \
} while (false)
int main()
{
// Define a vector of struct Test
std::vector<Test> tv{ {1,1,9},{2,2,8},{3,3,7},{4,4,6},{5,5,5} };
for (const Test& t : tv) std::cout << t.a << " " << t.b << " " << t.toSort << "\n";
// Call sort macro
SortSpecial(tv, Test, toSort);
std::cout << "\n\nSorted\n";
for (const Test& t : tv) std::cout << t.a << " " << t.b << " " << t.toSort << "\n";
}
Since macros shouldn't be used in C++, here my questions:
1. Is a solution with the algorithm library possible?
2. Or can this be achieved via templates?
To translate your current solution to a template solution is fairly straight forward.
template <typename T, typename ValueType>
void SpecialSort(std::vector<T>& vec, ValueType T::* mPtr) {
std::vector<ValueType> vt;
std::transform(vec.begin(), vec.end(), std::back_inserter(vt), [&](const T& s) {return s.*mPtr; });
std::sort(vt.begin(), vt.end());
std::for_each(vec.begin(), vec.end(), [&, i = 0U](T& s) mutable {s.*mPtr = vt[i++]; });
}
And we can call it by passing in the vector and a pointer-to-member.
SpecialSort(tv, &Test::toSort);
Somewhow like this (You just need to duplicate, rename and edit the "switchToShort" funtion for the rest of the variables if you want):
#include <iostream>
#include <vector>
struct Test {
int a{};
int b{};
int toSort{};
};
void switchToShort(Test &a, Test &b) {
if (a.toSort > b.toSort) {
int temp = a.toSort;
a.toSort = b.toSort;
b.toSort = temp;
}
}
//void switchToA(Test& a, Test& b) { ... }
//void switchToB(Test& a, Test& b) { ... }
inline void sortMemeberValues(std::vector<Test>& data, void (*funct)(Test&, Test&)) {
for (int i = 0; i < data.size(); i++) {
for (int j = i + 1; j < data.size(); j++) {
(*funct)(data[i], data[j]);
}
}
}
int main() {
std::vector<Test> tv { { 1, 1, 9 }, { 2, 2, 8 }, { 3,3 ,7 }, { 4, 4, 6 }, { 5, 5, 5} };
sortMemeberValues(tv, switchToShort);
//sortMemeberValues(tv, switchToA);
//sortMemeberValues(tv, switchToB);
for (const Test& t : tv) std::cout << t.a << " " << t.b << " " << t.toSort << "\n";
}
With range-v3 (and soon ranges in C++20), you might simply do:
auto r = tv | ranges::view::transform(&Test::toSort);
std::sort(r.begin(), r.end());
Demo
This code is a linear search program using arrays. Out of curiosity, I was wondering how this code could be rewritten using STL vectors in place of arrays but still have the same output.
#include <iostream>
#include <string>
using namespace std;
template <typename T>
int linearSearch(T list[], int key, int arraySize)
{
for (int i = 0; i < arraySize; i++)
{
if (key == list[i])
return i;
}
return -1;
}
int main()
{
int intArray[] =
{
1, 2, 3, 4, 8, 15, 23, 31
};
cout << "linearSearch(intArray, 3, 8) is " << linearSearch(intArray, 3, 8) << endl;
cout << "linearSearch(intArray, 10, 8) is " << linearSearch(intArray, 10, 8) << endl;
return 0;
}
you can do it by changing your parameter type and in main.
#include <iostream>
#include <string>
#include <vector>
using namespace std;
template <typename T>
int linearSearch(vector<T> list, int key)
{
for (size_t i = 0; i < list.size(); i++)
{
if (key == list[i])
return i;
}
return -1;
}
int main()
{
int intArray[] =
{
1, 2, 3, 4, 8, 15, 23, 31
};
vector<int> list(intArray, intArray+8);
cout << "linearSearch(list, 3,) is " << linearSearch(list, 3) << endl;
cout << "linearSearch(list, 10) is " << linearSearch(list, 10) << endl;
return 0;
}
This could work (it is based on the STL implementation):
#include <iostream>
#include <string>
#include <vector>
using namespace std;
template <typename ForwardIter, typename Type>
int linearSearch(ForwardIter beg, ForwardIter end, Type key )
{
int i = 0;
for (;beg != end; ++beg)
{
if (key == *beg)
return i;
i++;
}
return -1;
}
int main()
{
vector< int > vec = { 1, 2, 3, 4, 5, 6, 7 };
cout << "linearSearch 1 is " << linearSearch(vec.begin(), vec.end(), 4) << endl;
cout << "linearSearch 2 is " << linearSearch(vec.begin()+2, vec.end(), 1) << endl;
return 0;
}
Note: it can also work for, std::list and std::deque. I think it will produce correct results even in a normal array.
template <typename T>
int linearSearch(const vector<T> &list, const T &key)
{
auto itr = std::find(list.begin(), list.end(), key);
if (itr != list.end())
return std::distance(list.begin(), itr);
else
return -1;
}
int main()
{
int intArray[] = {1, 2, 3, 4, 8, 15, 23, 31};
std::vector<int> vec(intArray, intArray + 8);
int i = linearSearch(vec, 15);
}
Note: C++11 is enabled
With as few changes as possible you could do this:
#include <iostream>
#include <string>
#include <vector>
using namespace std;
// Using const std::vector<T> & to prevent making a copy of the container
template <typename T>
int linearSearch(const std::vector<T> &list, int key)
{
for (size_t i = 0; i < list.size(); i++)
{
if (key == list[i])
return i;
}
return -1;
}
int main()
{
std::vector<int> arr = { 1 ,2, 3, 4, 8, 15, 23, 31 } ;
cout << "linearSearch(intArray, 3) is " << linearSearch(arr, 3) << endl;
cout << "linearSearch(intArray, 10) is " << linearSearch(arr, 10) << endl;
return 0;
}
I would recommend not using using namespace std;.
template <typename T>
int linearSearch(T list, int key)
Changing the two first lines of your code as above, as well as replacing arraySize with list.size() should suffice for any kind of container supporting operator [] (including vectors), and indices as consecutive int.
Note that while your template tries to abstract the content of the array as the typename T, it implicitely assumes it is int in the type of key. A more generic implementation would be:
template <typename T>
int linearSearch(T list, typename T::value_type key)
Another issue in this solution is the passing mode of list. We can overcome this issue by converting it to a reference like so:
// includes ...
#include <type_traits>
using namespace std;
template <typename T>
int linearSearch(add_lvalue_reference<T> list, typename T::value_type key){
for (size_t i = 0; i < list.size(); i++) {
if (key == list[i])
return i;
}
return -1;
}
Please look at the following example that uses STL linear search algorithm. And see possible implementations of std::find and an example of usage it for a vector here: https://en.cppreference.com/w/cpp/algorithm/find
It would give you a good answer on your question.
#include <algorithm>
#include <iostream>
#include <vector>
int main() {
std::vector<int> intsCollection {1, 2, 3, 4, 8, 15, 23, 31};
std::vector<int>::iterator val1 = std::find(intsCollection.begin(), intsCollection.end(), 3);
int pos1 = (val1 != intsCollection.end()) ? (val1 - intsCollection.begin()) : -1;
std::vector<int>::iterator val2 = std::find(intsCollection.begin(), intsCollection.end(), 10);
int pos2 = (val2 != intsCollection.end()) ? (val2 - intsCollection.begin()) : -1;
std::cout << "linearSearch(intsCollection, 3, 8) is " << pos1 << std::endl;
std::cout << "linearSearch(intsCollection, 10, 8) is " << pos2 << std::endl;
}