用 C++ 11 來實現 LINQ to Object


  在 C# 里面 LINQ 是基於擴展方法來構建的,擴展的是 IEnumerable<T> 接口。有關擴展方法的好處在這里我就不做多的說明了,我默認看到此文章的讀者都是喜歡 C# 並且理解 C# 這門語言的美妙的人~

  在 LINQ 的擴展方法里面,返回的依舊是個 IEnumerable<T> 接口的對象,於是 LINQ 擁有了鏈式調用的風格。如:

List<int> list = new List<int>(){1,2,3,4,5,6,7,8,9};
var query = list
    .Where(x => x % 2 ==0)
    .Select(x => x * x)
    .Take(3);

//

  在每一次調用中,實際上是將上一個迭代器對象重新包裝(裝飾)了一遍,詳細請看這篇文章。這樣的好處就是可以實現延遲執行(畢竟返回的對象是迭代器),當迭代的時候才真正開始運算。

 

基於這樣的思想,我們可以用 C++ 來實現一個 LINQ:

  由於 C++ 沒有擴展方法,我們需要先將 STL 容器轉換為一個 linq_enumerable 對象,里面保存着 STL 的迭代器。而在每一次的 LINQ 函數調用中,都將當前迭代器對象包裝(裝飾)一次,並重新返回一個 linq_enumerable 對象。

  我們可以用 from 函數來實現轉換:

    template<typename TContainer>
    auto from(const TContainer& c)->linq_enumerable<decltype(std::begin(c))>
    {
        return linq_enumerable<decltype(std::begin(c))>(std::begin(c), std::end(c));
    }

  linq_enumerable 類如下:

 1     template<typename TIterator>
 2     class linq_enumerable
 3     {
 4     private:
 5         TIterator _begin;
 6         TIterator _end;
 7 
 8     public:
 9         linq_enumerable(const TIterator& b, const TIterator& e) :
10             _begin(b), _end(e)
11         {}
12 
13         TIterator begin()const
14         {
15             return _begin;
16         }
17 
18         TIterator end()const
19         {
20             return _end;
21         }
22     };

   然后我們來測試一下:

 1 int main()
 2 {
 3     {
 4         vector<int> v = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
 5 
 6         for (auto x : from(v))
 7         {
 8             cout << x << endl;
 9         }
10     }
11 
12     {
13         vector<int> v = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
14 
15         for (auto x : from(from(from(from(v)))))
16         {
17             cout << x << endl;
18         }
19     }
20     return 0;
21 }

  好了,到這里我們已經將所有的准備工作都做好了。接下來的一個個 LINQ 函數,都是在基於這之上一點點增加的。

 

那么,我們就先從最簡單的 select 開始吧:

  首先,select() 是 linq_enumerable 對象的成員函數,它接收一個 lambda 函數 ,然后將當前 linq_enumerable 對象的迭代器對象包裝為 select_iterator ,最后返回linq_enumerable 對象。

        template<typename TFunction>
        auto select(const TFunction& f)const->linq_enumerable<select_iterator<TIterator, TFunction>>
        {
            return linq_enumerable<select_iterator<TIterator, TFunction>>(
                select_iterator<TIterator,TFunction>(_begin,f),
                select_iterator<TIterator,TFunction>(_end,f)
                );
        }

  select_iterator 對象的成員應該要有 被包裝的迭代器、lambda 函數對象,同時還要重載 ++  *  ==  !=  這幾種操作符(自增、取值、等於、不等於)。select_iterator 類的實現如下:

 1     template<typename TIterator,typename TFunction>
 2     class select_iterator
 3     {
 4         typedef select_iterator<TIterator, TFunction> TSelf;
 5         
 6     private:
 7         TIterator iterator;
 8         TFunction f;
 9 
10     public:
11         select_iterator(const TIterator& i, const TFunction& _f) :
12             iterator(i), f(_f)
13         {}
14 
15         TSelf& operator++()
16         {
17             ++iterator;
18             return *this;
19         }
20 
21         auto operator*()const->decltype(f(*iterator))
22         {
23             return f(*iterator);
24         }
25 
26         bool operator==(const TSelf& it)const
27         {
28             return it.iterator == iterator;
29         }
30 
31         bool operator!=(const TSelf& it)const
32         {
33             return it.iterator != iterator;
34         }
35     };

  現在我們可以再來測試一下:

 1     {
 2         vector<int> v = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
 3         auto q = from(v).select([](int x) { return x + 10; });
 4         
 5         vector<int> xs = { 11, 12, 13, 14, 15, 16, 17, 18, 19 };
 6         
 7         assert(std::equal(xs.begin(), xs.end(), q.begin()));
 8     }
 9 
10     {
11         vector<int> v = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
12         auto q = from(v).select([](int x) { return x * x; });
13 
14         vector<int> xs = { 1, 4, 9, 16, 25, 36, 49, 64, 81 };
15 
16         assert(std::equal(xs.begin(), xs.end(), q.begin()));
17     }

  我們看到了預期的結果,此時變量 q 的類型是類似於 linq_enumerable<select_iterator<std::vector<int>::iterator>> 這樣(忽略TFunction類型),LINQ 函數的執行過程實際上只是將 迭代器 對象包裝了一次。

 

那么我們再來看看 where:

  where_iterator 對象和 select_iterator 對象很相似,稍微有點區別的地方在於 自增 和 取值 操作。當 where_iterator 自增時,它會有一個謂詞條件,若沒滿足這個條件則會繼續自增,以此過濾掉不滿足條件的元素。where_iterator 的取值操作就很簡單了,直接對它所包裝的 迭代器對象進行 * 操作即可。實現如下:

 1     template<typename TIterator,typename TFunction>
 2     class where_iterator
 3     {
 4         typedef where_iterator<TIterator, TFunction> TSelf;
 5 
 6     private:
 7         TIterator iterator;
 8         TIterator end;
 9         TFunction f;
10 
11     public:
12         where_iterator(const TIterator& i, const TIterator& e, const TFunction& _f) :
13             iterator(i), end(e), f(_f)
14         {
15             while (iterator != end && !f(*iterator))
16             {
17                 ++iterator;
18             }
19         }
20 
21         TSelf& operator++()
22         {
23             if (iterator == end) return *this;
24             ++iterator;
25             while (iterator != end && !f(*iterator))
26             {
27                 ++iterator;
28             }
29             return *this;
30         }
31 
32         iterator_type<TIterator> operator*()const
33         {
34             return *iterator;
35         }
36 
37         bool operator==(const TSelf& it)const
38         {
39             return it.iterator == iterator;
40         }
41 
42         bool operator!=(const TSelf& it)const
43         {
44             return iterator != it.iterator;
45         }
46     };

  在取值操作中,返回值類型是 迭代器所指向的元素的類型,在這里我用 iterator_type 來實現。

    template<typename TIterator>
    using iterator_type = decltype(**(TIterator*)nullptr);

  nullptr 是 C++ 11 標准中用來表示空指針的常量值,可以將其強制轉換為指向 TIterator 的指針,然后對其解引用得到一個不存在的 TIterator 對象   *(TIterator*)nullptr ,而再對 迭代器對象進行解引用,即可得到 迭代器所指向的元素。最后對其使用 decltype 操作,得到元素類型。對了,我們還要實現 linq_enumerable 對象的 where 函數:

        template<typename TFunction>
        auto where(const TFunction& f)const->linq_enumerable<where_iterator<TIterator,TFunction>>
        {
            return linq_enumerable<where_iterator<TIterator, TFunction>>(
                where_iterator<TIterator, TFunction>(_begin,_end,f),
                where_iterator<TIterator, TFunction>(_end,_end,f)
                );
        }

  where 也完成了,我們趕緊來測試一下:

 1     {
 2         vector<int> v = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
 3         auto q = from(v).where([](int x) { return x % 2 == 1; });
 4 
 5         vector<int> xs = { 1, 3, 5, 7, 9 };
 6 
 7         assert(std::equal(xs.begin(), xs.end(), q.begin()));
 8     }
 9 
10     {
11         vector<int> v = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
12         auto q = from(v).where([](int x) { return x > 5; });
13 
14         vector<int> xs = { 6, 7, 8, 9 };
15 
16         assert(std::equal(xs.begin(), xs.end(), q.begin()));
17     }
18 
19     {
20         vector<int> v = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
21         auto q = from(v)
22             .where([](int x) { return x % 2 == 1; })
23             .select([](int x) { return x * 10; });
24 
25         vector<int> xs = { 10, 30, 50, 70, 90 };
26 
27         assert(std::equal(xs.begin(), xs.end(), q.begin()));
28     }
29 
30     {
31         vector<int> v = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
32         auto q = from(v)
33             .where([](int x) { return x % 2 == 1; })
34             .where([](int x) { return x > 5; })
35             .select([](int x) { return x * 10; });
36 
37         vector<int> xs = { 70, 90 };
38 
39         assert(std::equal(xs.begin(), xs.end(), q.begin()));
40     }

  看到這樣的結果,是不是覺得已經要大功告成了。簡單來說,差不多是這樣。LINQ 的各個函數之間是獨立存在的,假如你只需要用到 LINQ 的 過濾 和 投影 的話,那么可以說我們已經完成了 LINQ to Object 的實現.....

 

在微軟的官方文檔中有 101個 LINQ 實例,要方便一點也可以看這里。這 101個例子幾乎包括了所有的 LINQ 操作,我們可以據此將 LINQ 操作分為以下幾類:

  1、Restriction Operators.  如:Where

  2、Projection Operators. 如:Select

  3、Partitioning Operator. 如:Take

  4、Ordering Operators. 如:OrderBy

  5、Grouping Operators. 如:GroupBy

  6、Set Operators. 如:Distinct

  7、Conversion Operators. 如:ToList

  8、Element Operators. 如:First

  9、Generation Operators. 如:Range

  10、Quantifiers. 如:Any

  11、Aggregate Operators. 如:Count

  12、Miscellaneous Operators. 如:Concat

  13、Join Operators. 如:Cross Join 和 Group Join

 

  在本文中,我會每一類給出一個實現,同一類別其他操作的實現細節大家可以看我的代碼

  take 函數與 select 和 where 類似,也是先將迭代器包裝成 take_iterator ,然后返回 linq_enumerable 對象。

        auto take(int count)const->linq_enumerable<take_iterator<TIterator>>
        {
            return linq_enumerable<take_iterator<TIterator>>(
                take_iterator<TIterator>(_begin,_end,count),
                take_iterator<TIterator>(_end,_end,count)
                );
        }

  take_iterator 類實現如下:

 1     template<typename TIterator>
 2     class take_iterator
 3     {
 4         typedef take_iterator<TIterator> TSelf;
 5 
 6     private:
 7         TIterator iterator;
 8         TIterator end;
 9         int count;
10         int current;
11 
12     public:
13         take_iterator(const TIterator& i, const TIterator& e, int c) :
14             iterator(i), end(e), count(c), current(0)
15         {
16             if (current == count)
17             {
18                 iterator = end;
19             }
20         }
21 
22         iterator_type<TIterator> operator*()const
23         {
24             return *iterator;
25         }
26 
27         TSelf& operator++()
28         {
29             if (++current == count)
30             {
31                 iterator = end;
32             }
33             else
34             {
35                 ++iterator;
36             }
37             return *this;
38         }
39 
40         bool operator==(const TSelf& it)const
41         {
42             return iterator == it.iterator;
43         }
44 
45         bool operator!=(const TSelf& it)const
46         {
47             return iterator != it.iterator;
48         }
49     };
View Code

   與 take 函數非常相似的還有 skip 、take_while、skip_while,所以實現細節這里我就不寫了,我的代碼在這里

  上面的幾類函數具有延遲執行的特性,LINQ 當中還有一些立即執行的函數。就是 Conversion Operators 、Element Operators 和 Aggregate Operators 這幾類函數,在使用這幾類函數操作時,會立即執行計算出結果。

  to_vector:

        std::vector<TElement> to_vector()const
        {
            std::vector<TElement> v;
            for (auto it = _begin; it != _end; ++it)
            {
                v.push_back(*it);
            }
            return std::move(v);
        }

   first:

        TElement first()const
        {
            if (empty())
            {
                throw linq_exception("Failed to get a value from an empty collection");
            }
            return *_begin;
        }

  count:

        int count()const
        {
            int counter = 0;
            for (auto it = _begin; it != _end; ++it)
            {
                ++counter;
            }
            return counter;
        }

  TElement 類型的定義為:

        typedef typename std::remove_cv<typename std::remove_reference<iterator_type<TIterator>>::type>::type TElement;

  這幾類函數還有 to_list、to_set、to_map,first_or_default、last、last_or_default、element_at,sum、min、max、average、aggregate ,實現方式都大同小異。

  還有幾類函數的實現稍微有點難度,它們是 分組、排序、連接。所以我們先放松一下,然后再集中精力往下討論。

 

  我們先從分組說起,分組函數接收一個 lambda,然后對每一個元素執行 lambda后返回的結果為分組的 key,key 相同的元素會被組合到一起為一個序列。我們用一個 pair 來保存這個 key 和 序列 (pari<key,linq_enumerable>),最后返回的結果是由 pair 組成的序列。雖然這種實現看起來不怎么優雅,但其實和 C# 的實現類似,只不過C#有 yield 和 擴展方法,就顯得很優雅了。

 1         template<typename TFunction>
 2         auto group_by(const TFunction& keySelector)const
 3             ->linq<std::pair<decltype(keySelector(*(TElement*)nullptr)), linq<TElement>>>
 4         {
 5             typedef decltype(keySelector(*(TElement*)nullptr)) TKey;
 6             typedef std::vector<TElement> TValueVector;
 7             typedef std::shared_ptr<TValueVector> TValueVectorPtr;
 8 
 9             std::map<TKey, TValueVectorPtr> map;
10             for (auto it = _begin; it != _end; ++it)
11             {
12                 auto value = *it;
13                 auto key = keySelector(value);
14                 auto it2 = map.find(key);
15                 if (it2 == map.end())
16                 {
17                     auto xs = std::make_shared<TValueVector>();
18                     xs->push_back(value);
19                     map.insert(std::make_pair(key, xs));
20                 }
21                 else
22                 {
23                     it2->second->push_back(value);
24                 }
25             }
26 
27             auto result = std::make_shared<std::vector<std::pair<TKey, linq<TElement>>>>();
28             for (auto p : map)
29             {
30                 result->push_back(std::pair<TKey, linq<TElement>>(p.first, from_values(p.second)));
31             }
32             return from_values(result);
33         }
group_by

 測試代碼如下:

    {
        int xs[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
        vector<int> ys = { 0, 1 };

        auto g = from(xs)
            .group_by([](int x) { return x % 2; })
            .select([](std::pair<int, linq<int>> p) { return p.first; });

        assert(std::equal(ys.begin(), ys.end(), g.begin()));

        vector<int> ys2 = { 6, 8 };
        auto g2 = from(xs)
            .group_by([](int x) { return x % 2; })
            .select([](std::pair<int, linq<int>> p) { return p.second; })
            .first()
            .where([](int x) { return x > 5; });

        assert(std::equal(ys2.begin(), ys2.end(), g2.begin()));
    }

 

  相對於分組來說,其實排序的實現是最簡單的,因為我在分組的實現中,使用了 map 這種數據結構作為臨時變量,STL 中的 map 是用紅黑樹實現的,在插入數據的時候就已經保持有序了。因此排序的實現,只需要對分組的結果投影出 pair 的 second 部分,然后將其組成一個 linq_enumerable 對象就可以了。

        template<typename TFunction>
        linq<TElement> order_by(const TFunction& f)const
        {
            typedef typename std::remove_reference<decltype(f(*(TElement*)nullptr))>::type TKey;
            return group_by(f)
                .select([](const std::pair<TKey, linq<TElement>>& p) { return p.second; })
                .aggregate([](const linq<TElement>& a, const linq<TElement>& b) { return a.concat(b); });
        }

 

    {        
        vector<int> a = { 5, 3, 1, 4, 8, 2, 7, 6, 9 };
        vector<int> b = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
        auto q = from(a).order_by([](int x) { return x; });
        
        assert(std::equal(b.begin(), b.end(), q.begin()));
    }

   

  最后就是連接,我實現的是內連接。連接函數有4個參數,分別是 linq_enumerable 、keySelector1、keySelector2、resultSelector。在這里的實現我用的是非常簡單的方法(沒有像C#那樣),C#用的是 lookup 來實現, 我在這里就是直接取了笛卡爾積的子集,因此效率上會有點不足。

 1         template<typename TIterator2, typename TFunction1, typename TFunction2, typename TFunction3>
 2         auto join(const linq_enumerable<TIterator2>& e,
 3             const TFunction1& keySelector1,
 4             const TFunction2& keySelector2,
 5             const TFunction3& resultSelector)const
 6             ->linq<decltype(resultSelector(*(TElement*)nullptr, **(TIterator2*)nullptr))>
 7         {
 8             typedef decltype(resultSelector(*(TElement*)nullptr, **(TIterator2*)nullptr)) TResultValue;
 9             auto result = std::make_shared<std::vector<TResultValue>>();
10 
11             for (auto it1 = _begin; it1 != _end; ++it1)
12             {
13                 auto value1 = *it1;
14                 auto key1 = keySelector1(value1);
15                 for (auto it2 = e.begin(); it2 != e.end(); ++it2)
16                 {
17                     auto value2 = *it2;
18                     auto key2 = keySelector2(value2);
19                     if (key1 != key2) continue;
20 
21                     result->push_back(resultSelector(value1, value2));
22                 }
23             }
24             return from_values(result);
25         }
join

  最后的最后,就是測試代碼了!

 1 struct person
 2 {
 3     string name;
 4 };
 5 
 6 struct pet
 7 {
 8     string name;
 9     person owner;
10 };
11 
12     person fek = { "爾康, 福"};
13 
14     person ylc = { "良辰, 葉" };
15     person hmj = { "美景, 花" };
16     person lks = { "看山, 劉" };
17     person lat = { "傲天, 龍" };
18     person persons[] = { ylc, hmj, lks, lat };
19 
20     pet dog = { "斯派克", ylc };
21     pet cat = { "湯姆", ylc };
22     pet mouse = { "傑瑞", hmj };
23     pet bird = { "憤怒的小鳥", lks };
24     pet pig = { "風口上的豬", fek };
25     pet pets[] = { dog, cat, mouse, bird, pig };
26 
27     auto person_name = [](const person& p) { return p.name; };
28     auto pet_owner_name = [](const pet& p) { return p.owner.name; };
29     auto result = [](const person& p, const pet& pp) { return std::make_tuple(p.name, pp.name); };
30 
31     /*
32         良辰, 葉 : 斯派克   
33         良辰, 葉 : 湯姆
34         美景, 花 : 傑瑞  
35         看山, 劉 : 憤怒的小鳥
36     */
37     for (auto x : from(persons).join(from(pets), person_name, pet_owner_name, result))
38     {
39         cout << get<0>(x) << "" << get<1>(x) << endl;
40     }

   

  文章寫到這里,也就差不多要結束了。寫這文章最初的目的是學習並記錄,而且最好的學習方式是說出來,愛因斯坦就說了,如果你不能簡單說清楚,那就是還沒有完全弄明白。所以......其實我也並非完全弄明白了,但我還是試圖講清楚一部分的東西吧!

  我是個兩年多經驗的野生菜鳥猿,如今在業余學習一下 C++,大家有想法可以提出來一起探討一下,一起進步!

 

(完)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM