1 // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
2 
3 #if !defined(CPPLINQ_LINQ_GROUPBY_HPP)
4 #define CPPLINQ_LINQ_GROUPBY_HPP
5 #pragma once
6 
7 namespace cpplinq
8 {
9 
10 template <class Iter, class Key>
11 struct group
12 {
13     Key key;
14     Iter start;
15     Iter fin;
16 
17     typedef Iter iterator;
18     typedef Iter const_iterator;
19 
groupcpplinq::group20     group(){}
21 
groupcpplinq::group22     group(const Key& key) : key(key)
23     {
24     }
25 
begincpplinq::group26     Iter begin() const { return start; }
endcpplinq::group27     Iter end() const { return fin; }
28 };
29 
30 struct default_equality
31 {
32     template <class T>
operator ()cpplinq::default_equality33     bool operator()(const T& a, const T& b) const {
34         return a == b;
35     }
36 };
37 struct default_less
38 {
39     template<class T>
operator ()cpplinq::default_less40     bool operator()(const T& a, const T& b) const {
41         return a < b;
42     }
43 };
44 
45 // progressively constructs grouping as user iterates over groups and elements
46 //   within each group. Performs this task by building a std::list of element
47 //   iterators with equal elements within each group.
48 //
49 // invariants:
50 //   - relative order of groups corresponds to relative order of each group's first
51 //     element, as they appeared in the input sequence.
52 //   - relative order of elements within a group correspond to relative order
53 //     as they appeared in the input sequence.
54 //
55 // requires:
56 //   Iter must be a forward iterator.
57 template <class Collection, class KeyFn, class Compare = default_less>
58 class linq_groupby
59 {
60     typedef typename Collection::cursor
61         inner_cursor;
62 
63     typedef typename util::result_of<KeyFn(typename inner_cursor::element_type)>::type
64         key_type;
65 
66     typedef std::list<typename inner_cursor::element_type>
67         element_list_type;
68 
69     typedef group<typename element_list_type::iterator, key_type>
70         group_type;
71 
72     typedef std::list<group_type>
73         group_list_type;
74 
75 private:
76     struct impl_t
77     {
78         // TODO: would be faster to use a chunked list, where
79         //   pointers are invalidated but iterators are not. Need
80         //   benchmarks first
81 
82         element_list_type                           elements;
83         std::list<group_type>                       groups;
84         std::map<key_type, group_type*, Compare>    groupIndex;
85 
86 
87 
88         KeyFn keySelector;
89         Compare comp;
90 
impl_tcpplinq::linq_groupby::impl_t91         impl_t(inner_cursor cur,
92                KeyFn keySelector,
93                Compare comp = Compare())
94         : keySelector(keySelector)
95         , groupIndex(comp)
96         {
97             // TODO: make lazy
98             insert_all(std::move(cur));
99         }
100 
insert_allcpplinq::linq_groupby::impl_t101         void insert_all(inner_cursor cur)
102         {
103             while(!cur.empty()) {
104                 insert(cur.get());
105                 cur.inc();
106             }
107         }
insertcpplinq::linq_groupby::impl_t108         void insert(typename inner_cursor::reference_type element)
109         {
110             key_type key = keySelector(element);
111             auto groupPos = groupIndex.find(key);
112             if(groupPos == groupIndex.end()) {
113                 // new group
114                 bool firstGroup = groups.empty();
115 
116                 elements.push_back(element);
117                 if(!firstGroup) {
118                     // pop new element out of previous group
119                     --groups.back().fin;
120                 }
121 
122                 // build new group
123                 groups.push_back(group_type(key));
124                 group_type& newGroup = groups.back();
125 
126                 groupIndex.insert( std::make_pair(key, &newGroup) );
127 
128                 newGroup.fin = elements.end();
129                 --(newGroup.start = newGroup.fin);
130             } else {
131                 // add to existing group at end
132                 elements.insert(groupPos->second->end(), element);
133             }
134         }
135     };
136 
137 public:
138     struct cursor {
139         typedef group_type
140             element_type;
141 
142         typedef element_type
143             reference_type;
144 
145         typedef forward_cursor_tag
146             cursor_category;
147 
cursorcpplinq::linq_groupby::cursor148         cursor(inner_cursor   cur,
149                KeyFn          keyFn,
150                Compare        comp = Compare())
151         {
152             impl.reset(new impl_t(cur, keyFn, comp));
153             inner   = impl->groups.begin();
154             fin     = impl->groups.end();
155         }
156 
forgetcpplinq::linq_groupby::cursor157         void forget() { } // nop on forward-only cursors
emptycpplinq::linq_groupby::cursor158         bool empty() const {
159             return inner == fin;
160         }
inccpplinq::linq_groupby::cursor161         void inc() {
162             if (inner == fin) {
163                 throw std::logic_error("attempt to iterate past end of range");
164             }
165             ++inner;
166         }
getcpplinq::linq_groupby::cursor167         reference_type get() const {
168             return *inner;
169         }
170 
171     private:
172         std::shared_ptr<impl_t> impl;
173         typename std::list<group_type>::iterator inner;
174         typename std::list<group_type>::iterator fin;
175     };
176 
linq_groupby(Collection c,KeyFn keyFn,Compare comp=Compare ())177     linq_groupby(Collection     c,
178                  KeyFn          keyFn,
179                  Compare        comp = Compare())
180     : c(c), keyFn(keyFn), comp(comp)
181     {
182     }
183 
get_cursor() const184     cursor get_cursor() const { return cursor(c.get_cursor(), keyFn, comp); }
185 
186 private:
187     Collection c;
188     KeyFn keyFn;
189     Compare comp;
190 };
191 
192 }
193 
194 #endif // !defined(CPPLINQ_LINQ_GROUPBY_HPP)
195 
196