分享

[C++]一份Linq to object的C++实现

 quasiceo 2014-01-18

一份Linq to object的C++实现

几个月的构想+0.5小时的设计+4小时的linq.h编码+3小时的测试编码。

大量使用C++11的特性,在GCC 4.7.2下编译通过。

 

关于实现相关的描述就不说了,我表达能力差,恐怕讲清楚还需要好几个小时。具体使用参见测试码。

上代码:

(1) linq.h

View Code

 

(2) 测试代码main.cpp (比我的代码更烂的是我的英语)

复制代码
#include "pch.h" 

#include "linq.h"

#include <vector>
#include <string>
#include <algorithm>

template<typename T>
void printC(const T& v)
{
    for (auto i : v) cout << i << ',';
    cout << endl;
}

template<typename T>
void print(const T& v)
{
    cout << v << endl;
}

bool startsWith(const std::string& s, const std::string& prefix)
{
    return s.find(prefix) == 0;
}

void featureTest()
{
    // 1. standard
    {
        auto query = range(10)
            .where([](int i){ return i % 2; })
            .select([](int i){ return i + 1; });
        auto ref = {2, 4, 6, 8, 10};
        assert(std::equal(ref.begin(), ref.end(), query.begin()));
    }
    // 2. deferred range
    {
        assert(range(123LL, 1000000000000LL).first() == 123);
    }
    // 3. deferred action
    {
        int selectCnt = 0;

        auto query = range(1, 1000)
            .where([](int i){ return i % 2 == 0; })
            .select([&selectCnt](int i)
                { 
                    ++selectCnt;
                    return i; 
                })
            .where([](int i) { return i % 4 == 0; })
            .head(2);
        auto query2 = query;

        for (auto i : query) {}
        assert(selectCnt == 4);

        for (auto i : query2) {}
        assert(selectCnt == 8);
    }
    // 4. copy semantic
    {
        auto query = range(10).head(5);
        auto query2 = query;

        auto ref = {0, 1, 2, 3, 4};
        assert(std::equal(ref.begin(), ref.end(), query.begin()));
        assert(std::equal(ref.begin(), ref.end(), query2.begin()));

        auto iter = query.begin();
        ++iter;
        auto iter2 = iter;

        ref = {1, 2, 3, 4};
        assert(std::equal(ref.begin(), ref.end(), iter));
        assert(std::equal(ref.begin(), ref.end(), iter2));
    }
    // 5. always reference the neweast data of dataSource
    {
        std::vector<std::string> dataSrc{"A_abc", "A_def", "B_abc", "B_def"};

        auto query = from(dataSrc)
            .head(3)
            .where([](const std::string& s) { return startsWith(s, "B_"); });

        auto ref = {"B_abc"};
        assert(std::equal(ref.begin(), ref.end(), query.begin()));

        dataSrc.clear();
        dataSrc.shrink_to_fit();
        dataSrc = {"B#_abc", "B_123", "B_1234", "B_321", "B_111"};
        ref = {"B_123", "B_1234"};
        assert(std::equal(ref.begin(), ref.end(), query.begin()));
    }
    // 6. invoke the operator new as least as possible
    {
    }
    // 7. you can use query after the dataSource has been destroyed, by the use of 'reserve'
    {
        Enumerable<int> query;
        {
            std::vector<int> v{1, 2, 3, 4};
            // query = from(v).select([](int i){ return i % 2; });
            query = from(v).reserve().select([](int i){ return i % 2; });

            auto ref = {1, 0, 1, 0};
            assert(std::equal(ref.begin(), ref.end(), query.begin()));
        }

        auto ref = {1, 0, 1, 0};
        assert(std::equal(ref.begin(), ref.end(), query.begin()));
    }
    // 8. add action to an exist query
    {
        auto query = range(10).where([](int i){ return i < 5;});
        auto ref = {0, 1, 2, 3, 4};
        assert(std::equal(ref.begin(), ref.end(), query.begin()));

        auto query2 = query.select([](int i){ return i * i; });
        ref = {0, 1, 4, 9, 16};
        assert(std::equal(ref.begin(), ref.end(), query2.begin()));
    }
}

void functionTest()
{
    // 1. from, select, where, cast
    {
        int a[]{5, 6, 7, 8, 9};
        auto query = from(a)
            .where([](int i){ return i % 3; })
            .select([](int i) { return i * i;});
        auto ref = {25, 49, 64};
        assert(std::equal(ref.begin(), ref.end(), query.begin()));
    }
    // 2. range, all, any
    {
        assert(range(10).all([](int i){ return i >= 0;}));
        assert(!range(10).all([](int i){ return i > 0;}));
        assert(from(std::vector<std::string>{"_a", "b"})
                .any([](const std::string& s){ return startsWith(s, "_"); }));
        assert(!from(std::vector<std::string>{"@a", "b"})
                .any([](const std::string& s){ return startsWith(s, "_"); }));
    }
    // 3. cast, average
    {
        assert(range(1, 5).average() == 2);
        assert(range(1, 5).cast<float>().average() == 2.5);
    }
    // 4. contain, count
    {
        int a[]{1, 2, 1, 1, 3, 2, };
        assert(from(a).contain(3));
        assert(!from(a).contain(4));
        assert(from(a).count(1) == 3);
        assert(from(a).count([](int i) { return i % 2; }) == 4);
    }
    // 5. first, last, head, tail
    {
        int a[]{3, 5, 7, 9, 11};
        assert(from(a).first() == 3);
        assert(from(a).last() == 11);

        auto ref = {3, 5};
        auto query = from(a).head(2);
        assert(std::equal(ref.begin(), ref.end(), query.begin()));

        ref = {7, 9, 11};
        query = from(a).tail(3);
        assert(std::equal(ref.begin(), ref.end(), query.begin()));
    }
    // 6. groupBy
    {
        auto query = range(10).groupBy([](int i) { return i % 3;});
        int refs[][4] = {
            {0, 3, 6, 9},
            {1, 4, 7,},
            {2, 5, 8,},
        };
        int n = 0;
        for (auto i : query) {
            assert(i.first == refs[n][0]);
            assert(std::equal(i.second.begin(), i.second.end(), refs[n]));
            ++n;
        }
        assert(n == 3);
    }
    // 7. takeUntil, skipUntil
    {
        auto query = range(10).takeUntil([](int i){ return i > 5; });
        auto ref = {0, 1, 2, 3, 4, 5};
        assert(std::equal(ref.begin(), ref.end(), query.begin()));

        query = range(10).skipUntil([](int i){ return i > 5; });
        ref = { 6, 7, 8, 9};
        assert(std::equal(ref.begin(), ref.end(), query.begin()));
    }
    // 8. max, min
    {
        int a[]{3, 2, 5, 8, 10, -3};
        assert(from(a).min() == -3);
        assert(from(a).max() == 10);
    }
    // 9. reduce
    {
        assert(range(1, 11).reduce([](int a, int b){ return a + b; }) == 55);
        assert(range(1, 11).reduce([](int a, int b){ return a * b; }, 0) == 0);
        assert(range(1, 11).reduce([](int a, int b){ return a * b; }, 1) == 3628800);
    }
    // 10. unique, sort, random
    {
        int a[]{3, 5, 5, 4, 2, 1, 2};

        auto query = from(a).unique();
        auto ref = {3, 5, 4, 2, 1};
        assert(std::equal(ref.begin(), ref.end(), query.begin()));

        ref = {5, 4, 3, 2, 1};
        query = query.sort().sort([](int a, int b){ return a > b; });
        assert(std::equal(ref.begin(), ref.end(), query.begin()));
        query = query.random();
        assert(!std::equal(ref.begin(), ref.end(), query.begin()));
    }
    // 11. intersect, _union
    {
        int a[]{3, 5, 11};
        auto query = range(10).intersect(from(a));
        auto ref = {3, 5};
        assert(std::equal(ref.begin(), ref.end(), query.begin()));

        ref = {3, 4, 5, 6};
        query = query._union(range(4, 7));
        assert(std::equal(ref.begin(), ref.end(), query.begin()));
    }
}

int main()
{
    featureTest();
    functionTest();
}
复制代码

为什么不把它提交到git hub之类的专门代码仓库?一则我没有用过,二则,这种代码是我的write-only构想实践码,不提供后续维护的:)

posted @ 2012-10-20 23:28 Scan. 阅读(1116) 评论(17) 编辑 收藏

评论列表
  
#1楼[楼主] 2012-10-21 21:20 Scan.  
实现linq的延迟特性的最佳设施是c#的yield return,我老早就考虑用lambda表达式作为yield的替代方案。
最后终于在python和c++中各实现了一次linq。
  
#2楼 2012-10-23 12:45 代震军  
  
#3楼 2012-10-23 12:55 陈梓瀚(vczh)  
尽管我只看了test case,我还是觉得你的接口还是做得很漂亮的,再接再厉。
  
#4楼[楼主] 2012-10-23 13:03 Scan.  
@陈梓瀚(vczh)
谢谢老大鼓励!!!!
  
#5楼 2012-12-29 11:06 yesme  
vs2010通不过
  
#6楼[楼主] 2012-12-29 11:14 Scan.  
@yesme
所以才说需要GCC 4.7.2以上啊。vc2010支持的c++11特性太少了。
  
#7楼 2013-03-30 12:45 yu_yu  
  
#8楼 2013-05-30 15:46 qicosmos  
支持!挺好的。
有没有linq to sql 的c++版本呢
  
#9楼 2013-06-08 17:00 qicosmos  
挺好的,能否把编程思想说一下?
  
#10楼[楼主] 2013-06-12 15:12 Scan.  
@qicosmos
我记不太清了,不过才几百行代码,感兴趣的话自己看看吧。特别的注意减少内存分配和延迟迭代就是了。
上面那个7楼的方案更专业。
  
#11楼 2013-06-14 16:32 qicosmos  
大概思路是,先将集合转成枚举器,枚举器中保存闭包,在外面移动迭代器重复调用闭包,闭包中嵌套了闭包,层层调用实现方法的组合。
其实有更简单的方案,我也实现了一个c++ linq。
  
#12楼 2013-06-14 17:03 qicosmos  
c++中实现yield,似乎比较麻烦啊,boost.coroutine库中有宏定义了一个yield,但是用起来有点麻烦,有更好用的yield吗?
  
#13楼[楼主] 2013-06-14 17:08 Scan.  
@qicosmos
你的方案的url呢?参考一下呵呵
  
#14楼 2013-06-14 17:16 qicosmos  
不好意思,公司内部的项目中用到了,不方便公开。
  
#15楼[楼主] 2013-06-14 17:28 Scan.  
@qicosmos
嗯好的,理解
  
#16楼 2013-12-06 11:12 共享天涯  
恕我眼拙,对模板这一块不太熟。
博主能否说一下你的这个linq实现有哪些缺陷之类,
这样大家如果愿意尝试使用时也可以避免一下。
  
#17楼[楼主] 2013-12-06 11:15 Scan.  
@共享天涯
玩具,非产品级,请尝试更好的方案

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多