开发者

C++ LINQ-like iterator operations

Having been tainted by Linq, I'm reluctant to give it up. However, for some things I just need to use C++.

The real strength of linq as a linq-consumer (i.e. to me) lies not in expression trees (which are complex to manipulate), but the ease with which I can mix and 开发者_StackOverflow中文版match various functions. Do the equivalents of .Where, .Select and .SelectMany, .Skip and .Take and .Concat exist for C++-style iterators?

These would be extremely handy for all sorts of common code I write.

I don't care about LINQ-specifics, the key issue here is to be able to express algorithms at a higher level, not for C++ code to look like C# 3.0. I'd like to be able to express "the result is formed by the concatenation first n elements of each sequence" and then reuse such an expression wherever a new sequence is required - without needed to manually (and greedily) instantiate intermediates.


I am working on (C# LINQ)-like C++ header-only library.

Here it is: http://code.google.com/p/boolinq/

I'd like to get any feedback...

UPDATE:

Here is new link to boolinq 2.0: https://github.com/k06a/boolinq

All source code is based in single header file - https://github.com/k06a/boolinq/blob/master/boolinq/boolinq.h

It is super short: less than 800 lines for about 60 different operations!


I'd like to recommend the P-Stade.Oven library for your reference. This is a strongly boostified library working on STL ranges and featuring many LINQ-like functions including the equivalents of .Where, .Select .Skip .Take and .Concat.


I don't have concrete experience with LINQ, but the Boost.Iterator library seems to approach what you're referring to.

The idea is to have functions (IIUC, in LINQ, they take the form of extension methods, but that's not fundamental), taking an iterator and a function, combining them to create a new iterator.

LINQ "Where" maps to make_filter_iterator:

std::vector<int> vec = ...;
// An iterator skipping values less than "2":
boost::make_filter_iterator(_1 > 2, vec.begin())

LINQ "Select" maps to make_transform_iterator:

using namespace boost::lambda;
//An iterator over strings of length corresponding to the value
//of each element in "vec"
//For example, 2 yields "**", 3 "***" and so on.
boost::make_transform_iterator(construct<std::string>('*', _1), vec.begin())

And they can be composed:

//An iterator over strings of length corresponding to the value of each element
// in "vec", excluding those less than 2
std::vector<int> vec = ...;
boost::make_transform_iterator(construct<std::string>('*', _1), 
    boost::make_filter_iterator(_1 > 2, vec.begin())
)

However, there are a few annoying things with this:

  • The type returned by make_xxx_iterator(some_functor, some_other_iterator) is xxx_iterator<type_of_some_functor, type_of_some_iterator>
  • The type of a functor created using boost::bind, lambda, or phoenix quickly becomes unmanageably large and cumbersome to write.

That's why I avoided in the code above to assign the result of make_xxx_iterator to a variable. C++0x "auto" feature will be pretty welcome there.

But still, a C++ iterator can't live "alone": they have to come in pairs to be useful. So, even with "auto", it's still a mouthful:

auto begin = make_transform_iterator(construct<std::string>('*', _1), 
    make_filter_iterator(_1 > 2, vec.begin())
);
auto end = make_transform_iterator(construct<std::string>('*', _1), 
    make_filter_iterator(_1 > 2, vec.end())
);

Avoiding the use of lambda makes things verbose, but manageable:

struct MakeStringOf{
    MakeStringOf(char C) : m_C(C){}
    char m_C;

    std::string operator()(int i){return std::string(m_C, i);}
};

struct IsGreaterThan{
    IsGreaterThan(int I) : m_I(I){}
    int m_I;

    bool operator()(int i){return i > m_I;}
};

typedef boost::filter_iterator<
   IsGreaterThan, 
   std::vector<int>::iterator
> filtered;

typedef boost::transform_iterator<
   MakeStringOf, 
   filtered
> filtered_and_transformed;

filtered_and_transformed begin(
    MakeStringOf('*'), 
    filtered(IsGreaterThan(2), vec.begin())
);

filtered_and_transformed end(
    MakeStringOf('*'), 
    filtered(IsGreaterThan(2), vec.end())
);

The (not-yet)Boost.RangeEx library is promising in this respect, in that it allows to combine the two iterators in a single range. Something like:

auto filtered_and_transformed = make_transform_range(
    make_filter_range(vec, _1 > 2),
    construct<std::string>('*', _1)
);


See this Google Groups thread.

vector<int> numbers = {1, 2, 3, 4, 8, 5, 9 , 24, 19, 15, 12 } 
auto query = 
    from(numbers).
        where([](int i) { return i < 15 && i > 10}). 
        select(fields::full_object); 

I couldn't find anything more or less "official" or widely accepted, but you can try contacting the author of the original post.


With Boost.Range and Linq in C++11, you can write Linq queries in a very similar way:

std::vector<int> numbers = { 1, 2, 3, 4 };
auto r = LINQ(from(x, numbers) where(x > 2) select(x * x));
for (auto x : r) printf("%i\n", x);

Will output:

9
16


Here is another alternative that is simply a wrapper around boost and stl algorithms, and thus you get all the performance benefits of those implementations.

It works like this:

std::vector<int> xs;
auto count = from(xs)
   .select([](int x){return x*x;})
   .where([](int x){return x > 16;})
   .count();
auto xs2 = from(xs)
   .select([](int x){return x*x;})
   .to<std::vector<int>>();

Note that some methods return a proxy for empty ranges, e.g.

std::vector<int> xs;
auto max = from(xs)
   .select([](int x){return x*x;})
   .where([](int x){return x > 16;})
   .max()
   .value_or(0);


Personally, I have used cpplinq from time to time. It is pretty good. It does not try to be a perfect translation of LINQ by any means, and maintains enough of a C++ identity, if you will, that makes it pretty strong in and of itself. Plus, you do not take any dependencies, other than C++11 standards-wise, that is. As long as you can stomach that, you're good to go with cpplinq.


The Abseil library has many https://github.com/abseil/abseil-cpp/blob/master/absl/algorithm/container.h has many container functions: c_all_of, c_any_of, c_none_of, c_find, c_count, c_count_if, c_replace_copy, c_unique_copy, c_min_element, c_max_element, c_accumulate

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜