Best way to do flattening of nested getter result inside getter - c++

sometimes I have the task of finding if some nested getter inside a value returned by getter has some property.
classic c++ would be something like:
for (const auto& label: labels)
for (const auto& artist: label.artists())
if (artist.name = "Arcade Fire")
return true;
return false;
What is the best way to do this with ranges?
I think something like this may work:
labels| transform (&Label::artists) | join | transform(&Artist::name) | join | find_if_fn([](){/*...*/}) ;
But it is quite long, (partially because instead of .member_fn you must write Class:member_fn.
Is there a shorter way to do this?

I think this:
using namespace ranges;
auto rng = labels | view::transform(&Label::artists) | view::join;
return find(rng, "Arcade Fire", &Artist::name) != end(rng);
gets the job done in a fairly straightforward way. A view::filter formulation:
using namespace ranges;
auto rng = labels | view::transform(&Label::artists) | view::join |
view::filter([](const Artist& a){ return a.name() == "Arcade Fire"; });
return !empty(rng);
Is a bit wordier, but probably has similar performance. It's also fairly clear how to generalize it from "Is there a foo?" to "Return all of the foos."

Related

Is optimizing small std::strings wiht c-style functions often pointless?

Let's say we've got the following piece of code and we've decided to optimise it a bit:
/// BM_NormalString
bool value = false;
std::string str;
str = "orthogonal";
if (str == "orthogonal") {
value = true;
}
So far we've come up with two pretty simple and straightforward strategies:
/// BM_charString
bool value = false;
char *str = new char[11];
std::strcpy(str, "orthogonal");
if (std::strcmp(str, "orthogonal") == 0) {
value = true;
}
delete[] str;
/// BM_charStringMalloc
bool value = false;
char *str = (char *) std::malloc(11);
std::strcpy(str, "orthogonal");
if (std::strcmp(str, "orthogonal") == 0) {
value = true;
}
free(str);
If we try and benchmark our three approaches we, quite surprisingly, won't see much of a difference.
Although bench-marking it locally gives me even more surprisingly disconcerting results:
| Benchmark | Time | CPU | Iterations |
|----------------------|------------------|-----------|---------------|
| BM_charString | 52.1 ns | 51.6 ns | 11200000 |
| BM_charStringMalloc | 47.4 ns | 47.5 ns | 15448276 |
| **BM_NormalString** | 17.1 ns | 16.9 ns | 40727273 |
Would you say then, that for such rather small strings there's almost no point in going 'bare metal' style (by using C-style string functions)?
For small strings, there's no point using dynamic storage. The allocation itself is slower than the comparison. Standard library implementers know this and have optimised std::basic_string to not use dynamic storage with small strings.
Using C-strings is not an "optimisation".

Best way to join string with transform in a standard way

I'm looking for the best way to join a container with transformation as a string. The old style, that I'm using looks like this:
std::string MyJoin(const std::vector<Foo>& foos) {
std::string str;
for (size_t i = 0; i < foos.size(); ++i) {
if (i != 0) {
str += ",";
}
str += Stringify(foos[i]);
}
return str;
}
This works well, and performance is ok, but I was wondering if there is an equally performant standard way. I tried Range-V3:
std::string MyJoin(const std::vector<Foo>& foos) {
return foos | transform(Stringify) | cache1 | join(',') | to<std::string>;
}
It is a beautiful single liner, easy to read, but it was half performant from the old one. I tested with a million element of foos.
Am I doing something wrong? or the old way is the way to go? I'm wondering if there is a wonderful solution, that I couldn't find in other answers.
Thanks.

Can I use C++20 ranges to break when matched count is greater than some threshold?

Consider the following pre ranges code:
std::vector<int> v(1000*1000);
bool count_gt_5_v1(int val){
return std::count(v.begin(), v.end(), val)>5;
}
It looks nicer than the raw loop, but it can be very inefficient if val is very common in v.
Is there any way to use C++20 ranges so that iteration will stop after I encounter val 6 times.
In other words I am looking for a way to introduce a break when my condition is satisfied.
I have this abomination, that seems to work, but it is much much uglier than raw for loop.
bool count_gt_5_v2(int val){
int cnt=0;
auto span = std::ranges::views::take_while(v,[&cnt, &val]
(const auto elem)
{
cnt+=elem==val;
return cnt<6;
});
std::ranges::distance(span);
return cnt==6;
}
Link to full code: https://godbolt.org/z/86djdK
You could do this:
auto matches = v | rv::filter([=](int i){ return i == val; })
| rv::take(6);
return ranges::distance(matches) == 6;
Or, better:
auto matches = v | rv::filter([=](int i){ return i == val; });
return not ranges::empty(matches | rv::drop(5));
This attempt:
std::ranges::views::take_while(v, [&cnt, &val](const auto elem){
cnt+=elem==val;
return cnt<6;
});
doesn't meet the requirements of take_while. All of the predicates in ranges have to be equality-preserving - same inputs, same output. Here, that's not the case - if we call the predicate twice on a single element, we'd get different output. So that's undefined behavior.

Use of STL algorithms in a tile matching game logic

I have made a tile matching game. It has a Board object that holds a vector of card objects and delegates events to them. Board event handling has following code in it:
// Counting logic-driving card states
int cardFaceUpCounter = 0;
std::vector<Card*> faceUpCards(2);
// Checking for logic-driving card states
for (auto& card : this->boardMatrix)
{
if (this->isCardInAnim(&card))
{
return;
}
if (this->isCardFaceUp(&card))
{
++cardFaceUpCounter;
faceUpCards[cardFaceUpCounter - 1] = &card;
}
}
I've just finished studying Beautiful C++ by Kate Gregory on Pluralsight.
She argues that we should avoid writing loops, and should use the STL algorithm header as much as possible.
I find her argument and approach very compelling, thus I have tried to refactor my latest pet project to reflect her teachings.
The example above is where I just can't see how I can use STL algorithms to both communicate the intent better and keep the performance - single loop instead of two or three loops, albeit hidden in the algorithm calls.
The second question would be if single loop efficiency can't be achieved using STL algorithms would you still prefer that approach for readability sake.
Here is an example of what I was thinking.
int cardFaceUpCounter = 0;
std::vector<Card*> faceUpCards(2);
if (std::any_of(boardMatrix.begin(), boardMatrix.end(), [&](auto& card) {
if (isCardFaceUp(&card))
faceUpCards[cardFaceUpCounter++] = &card;
return isCardInAnim(&card);
})) return;
With range-v3, it would be something like:
std::vector<Card*> faceUpCards = this->boardMatrix
| ranges::view::take_while([this](const auto& card){ return !isCardInAnim(&card);})
| ranges::view::filter([this](const auto& card){ return isCardFaceUp(&card); })
| ranges::view::transform([](auto& e){ return &e; });
With only STL, I would do something like:
auto it = std::find_if(boardMatrix.begin(), boardMatrix.end(),
[this](const auto& card){ return isCardInAnim(&card);});
std::vector<Card*> faceUpCards(std::distance(boardMatrix.begin(), it), nullptr);
std::transform(boardMatrix.begin(), it,
faceUpCards.begin(),
[](auto& card){ return &card;});
faceUpCards.erase(std::remove_if(faceUpCards.begin(), faceUpCards.end(),
[this](const auto& card){ return !isCardFaceUp(&card); }),
faceUpCards.end());

range v3 flattening a sequence

So I recently watched this talk on c++:
https://www.youtube.com/watch?v=mFUXNMfaciE
And I was very interested on trying it out. So after some toy program I am stuck on how to properly flatten a vector of vectors into a vector. According to the documentation here: https://ericniebler.github.io/range-v3/ This is possible using ranges::view::for_each. However I just can't seem to get it to work. Here is some minimal code.
#include <range/v3/all.hpp>
#include <iostream>
#include <vector>
int main()
{
auto nums = std::vector<std::vector<int>>{
{0, 1, 2, 3},
{5, 6, 7, 8},
{10, 20},
{30},
{55}
};
auto filtered = nums
| ranges::view::for_each([](std::vector<int> num) { return ranges::yield_from(num); })
| ranges::view::remove_if([](int i) { return i % 2 == 1; })
| ranges::view::transform([](int i) { return std::to_string(i); });
for (const auto i : filtered)
{
std::cout << i << std::endl;
}
}
range-v3 error messages tend to be pretty horrible, so much so that this one is actually better than most:
prog.cc: In lambda function:
prog.cc:16:90: error: no match for call to '(const ranges::v3::yield_from_fn) (std::vector<int>&)'
| ranges::view::for_each([](std::vector<int> num) { return ranges::yield_from(num); })
^
In file included from /opt/wandbox/range-v3/include/range/v3/view.hpp:38:0,
from /opt/wandbox/range-v3/include/range/v3/all.hpp:21,
from prog.cc:1:
/opt/wandbox/range-v3/include/range/v3/view/for_each.hpp:133:17: note: candidate: template<class Rng, int _concept_requires_132, typename std::enable_if<((_concept_requires_132 == 43) || ranges::v3::concepts::models<ranges::v3::concepts::View, T>()), int>::type <anonymous> > Rng ranges::v3::yield_from_fn::operator()(Rng) const
Rng operator()(Rng rng) const
^~~~~~~~
to someone with a bit of knowledge of range-v3's concepts emulation layer, this "clearly" states that the call to yield_from failed because the type of the argument you passed to it - std::vector<int> - does not satisfy the View concept.
The View concept characterizes a subset of ranges that do not own their elements, and therefore have all operations - move/copy construction/assignment, begin, end, and default construction - computable in O(1). The range composition algrebra in range-v3 works only on views to avoid having to deal with element lifetimes and to provide predictable performance.
yield_from rejects the std::vectors you are trying to pass since they are not views, but you could easily provide views by (1) taking the vectors as lvalues instead of by value in for_each, and (2) yielding view::all of those lvalues [DEMO]:
auto filtered = nums
| ranges::view::for_each([](std::vector<int>& num) {
return ranges::yield_from(ranges::view::all(num)); })
| ranges::view::remove_if([](int i) { return i % 2 == 1; })
| ranges::view::transform([](int i) { return std::to_string(i); });
But in this simple case, flattening a range of ranges of elements into a range of elements already has a purpose-specific view in range-v3: view::join. You may as well use that [DEMO]:
auto filtered = nums
| ranges::view::join
| ranges::view::remove_if([](int i) { return i % 2 == 1; })
| ranges::view::transform([](int i) { return std::to_string(i); });