|
2 | 2 |
|
3 | 3 | LINQ-like API for Go with support for lazy evaluation. |
4 | 4 |
|
5 | | -glinq provides a functional approach to working with slices and maps in Go, inspired by Microsoft LINQ. |
6 | | -All operations are executed lazily and do not start until a terminal operation is called. |
| 5 | +[](https://pkg.go.dev/github.com/CreateLab/glinq) |
| 6 | +[](https://goreportcard.com/report/github.com/CreateLab/glinq) |
7 | 7 |
|
8 | 8 | ## Features |
9 | 9 |
|
10 | 10 | - **Lazy Evaluation**: All intermediate operations are executed only when the result is materialized |
11 | 11 | - **Type Safe**: Full support for generics (Go 1.18+) |
12 | 12 | - **Composable**: Operations can be easily combined into chains |
13 | 13 | - **Zero Dependencies**: No external dependencies required |
14 | | -- **Map Support**: Built-in support for working with maps |
| 14 | +- **Extensible**: Works with any type implementing `Enumerable` interface |
15 | 15 |
|
16 | 16 | ## Installation |
17 | 17 |
|
@@ -40,326 +40,78 @@ func main() { |
40 | 40 | } |
41 | 41 | ``` |
42 | 42 |
|
43 | | -## Usage Examples |
| 43 | +## Basic Examples |
44 | 44 |
|
45 | | -### Filtering (Where) |
| 45 | +### Filtering and Transformation |
46 | 46 |
|
47 | 47 | ```go |
48 | | -numbers := []int{1, 2, 3, 4, 5} |
49 | | -evens := glinq.From(numbers). |
| 48 | +// Filter even numbers |
| 49 | +evens := glinq.From([]int{1, 2, 3, 4, 5}). |
50 | 50 | Where(func(x int) bool { return x%2 == 0 }). |
51 | 51 | ToSlice() |
52 | 52 | // [2, 4] |
53 | | -``` |
54 | | - |
55 | | -### Transformation |
56 | | - |
57 | | -glinq provides **two ways** to transform elements: |
58 | | - |
59 | | -#### Select (Method) - Same Type Transformation |
60 | | - |
61 | | -The `Select` **method** transforms elements to the same type and supports method chaining: |
62 | | - |
63 | | -```go |
64 | | -numbers := []int{1, 2, 3} |
65 | | -squared := glinq.From(numbers). |
66 | | - Select(func(x int) int { return x * x }). |
67 | | - ToSlice() |
68 | | -// [1, 4, 9] |
69 | | -``` |
70 | | - |
71 | | -#### Select (Function) - Different Type Transformation |
72 | | - |
73 | | -The `Select` **function** transforms elements to a different type. It's a standalone function (not a method) because Go methods cannot have their own type parameters: |
74 | 53 |
|
75 | | -```go |
76 | | -numbers := []int{1, 2, 3} |
| 54 | +// Transform to strings |
77 | 55 | strings := glinq.Select( |
78 | | - glinq.From(numbers), |
| 56 | + glinq.From([]int{1, 2, 3}), |
79 | 57 | func(x int) string { return fmt.Sprintf("num_%d", x) }, |
80 | 58 | ).ToSlice() |
81 | 59 | // []string{"num_1", "num_2", "num_3"} |
82 | 60 | ``` |
83 | 61 |
|
84 | | -#### SelectWithIndex (Method) - Same Type Transformation with Index |
85 | | - |
86 | | -The `SelectWithIndex` **method** transforms elements to the same type, providing the element index to the mapper function: |
| 62 | +### Sorting and Ordering |
87 | 63 |
|
88 | 64 | ```go |
89 | | -numbers := []int{1, 2, 3} |
90 | | -result := glinq.From(numbers). |
91 | | - SelectWithIndex(func(x int, idx int) int { return x * idx }). |
| 65 | +// Sort ascending |
| 66 | +sorted := glinq.From([]int{5, 2, 8, 1, 9}). |
| 67 | + OrderBy(func(a, b int) int { return a - b }). |
92 | 68 | ToSlice() |
93 | | -// []int{0, 2, 6} |
94 | | -``` |
| 69 | +// [1, 2, 5, 8, 9] |
95 | 70 |
|
96 | | -#### SelectWithIndex (Function) - Different Type Transformation with Index |
97 | | - |
98 | | -The `SelectWithIndex` **function** transforms elements to a different type, providing the element index to the mapper function: |
99 | | - |
100 | | -```go |
101 | | -numbers := []int{1, 2, 3} |
102 | | -strings := glinq.SelectWithIndex( |
103 | | - glinq.From(numbers), |
104 | | - func(x int, idx int) string { return fmt.Sprintf("num_%d_at_%d", x, idx) }, |
105 | | -).ToSlice() |
106 | | -// []string{"num_1_at_0", "num_2_at_1", "num_3_at_2"} |
107 | | -``` |
108 | | - |
109 | | -### Limiting Elements (Take and Skip) |
110 | | - |
111 | | -```go |
112 | | -numbers := []int{1, 2, 3, 4, 5} |
113 | | - |
114 | | -// Take first 3 elements |
115 | | -first3 := glinq.From(numbers).Take(3).ToSlice() |
| 71 | +// Get top 3 smallest |
| 72 | +top3 := glinq.From([]int{5, 2, 8, 1, 9, 3}). |
| 73 | + TakeOrdered(3). |
| 74 | + ToSlice() |
116 | 75 | // [1, 2, 3] |
117 | | - |
118 | | -// Skip first 2 elements |
119 | | -rest := glinq.From(numbers).Skip(2).ToSlice() |
120 | | -// [3, 4, 5] |
121 | 76 | ``` |
122 | 77 |
|
123 | | -### Working with Maps |
| 78 | +### Removing Duplicates |
124 | 79 |
|
125 | 80 | ```go |
126 | | -data := map[string]int{ |
127 | | - "apple": 5, |
128 | | - "banana": 3, |
129 | | - "orange": 8, |
130 | | -} |
131 | | - |
132 | | -filtered := glinq.FromMap(data). |
133 | | - Where(func(kv glinq.KeyValue[string, int]) bool { |
134 | | - return kv.Value > 4 |
135 | | - }). |
136 | | - ToMap() |
137 | | -// map[apple:5 orange:8] |
138 | | -``` |
139 | | - |
140 | | -### Condition Checking (Any and All) |
141 | | - |
142 | | -```go |
143 | | -numbers := []int{1, 2, 3, 4, 5} |
144 | | - |
145 | | -hasEven := glinq.From(numbers).Any(func(x int) bool { |
146 | | - return x%2 == 0 |
147 | | -}) |
148 | | -// true |
149 | | - |
150 | | -allPositive := glinq.From(numbers).All(func(x int) bool { |
151 | | - return x > 0 |
152 | | -}) |
153 | | -// true |
154 | | -``` |
155 | | - |
156 | | -### Counting Elements (Count) |
157 | | - |
158 | | -```go |
159 | | -numbers := []int{1, 2, 3, 4, 5} |
160 | | -count := glinq.From(numbers). |
161 | | - Where(func(x int) bool { return x > 2 }). |
162 | | - Count() |
163 | | -// 3 |
164 | | -``` |
165 | | - |
166 | | -### Executing Action for Each Element (ForEach) |
167 | | - |
168 | | -```go |
169 | | -numbers := []int{1, 2, 3} |
170 | | -glinq.From(numbers).ForEach(func(x int) { |
171 | | - fmt.Println(x) |
172 | | -}) |
173 | | -// 1 |
174 | | -// 2 |
175 | | -// 3 |
176 | | -``` |
177 | | - |
178 | | -### Getting First Element (First) |
179 | | - |
180 | | -```go |
181 | | -numbers := []int{1, 2, 3} |
182 | | -first, ok := glinq.From(numbers).First() |
183 | | -// first = 1, ok = true |
184 | | -``` |
185 | | - |
186 | | -### Splitting into Chunks (Chunk) |
187 | | - |
188 | | -```go |
189 | | -numbers := []int{1, 2, 3, 4, 5, 6, 7} |
190 | | -chunks := glinq.From(numbers).Chunk(3) |
191 | | -// [][]int{{1, 2, 3}, {4, 5, 6}, {7}} |
192 | | -``` |
193 | | - |
194 | | -### Getting Last Element (Last) |
195 | | - |
196 | | -```go |
197 | | -numbers := []int{1, 2, 3, 4, 5} |
198 | | -last, ok := glinq.From(numbers).Last() |
199 | | -// last = 5, ok = true |
200 | | -``` |
201 | | - |
202 | | -### Summing Elements (Sum) |
| 81 | +// Remove duplicates |
| 82 | +unique := glinq.Distinct(glinq.From([]int{1, 2, 2, 3, 3, 4})).ToSlice() |
| 83 | +// [1, 2, 3, 4] |
203 | 84 |
|
204 | | -```go |
205 | | -numbers := []int{1, 2, 3, 4, 5} |
206 | | -sum := glinq.Sum(glinq.From(numbers)) |
207 | | -// 15 |
208 | | -``` |
209 | | - |
210 | | -### Finding Minimum (Min) |
211 | | - |
212 | | -glinq provides **two ways** to find minimum: |
213 | | - |
214 | | -#### Min (Function) - For Ordered Types |
215 | | - |
216 | | -The `Min` **function** works with ordered types (int, uint, float, string): |
217 | | - |
218 | | -```go |
219 | | -numbers := []int{5, 2, 8, 1, 9} |
220 | | -min, ok := glinq.Min(glinq.From(numbers)) |
221 | | -// min = 1, ok = true |
222 | | -``` |
223 | | - |
224 | | -#### Min (Method) - With Comparator |
225 | | - |
226 | | -The `Min` **method** works with any type using a comparator function: |
227 | | - |
228 | | -```go |
229 | | -type Person struct { Age int; Name string } |
230 | | -people := []Person{{Age: 30, Name: "Alice"}, {Age: 25, Name: "Bob"}} |
231 | | -youngest, ok := glinq.From(people).Min(func(a, b Person) int { |
232 | | - return a.Age - b.Age |
233 | | -}) |
234 | | -// youngest = Person{Age: 25, Name: "Bob"}, ok = true |
235 | | -``` |
236 | | - |
237 | | -### Aggregating Elements (Aggregate) |
238 | | - |
239 | | -The `Aggregate` method applies an accumulator function over the Stream. The seed parameter is the initial accumulator value: |
240 | | - |
241 | | -```go |
242 | | -numbers := []int{1, 2, 3, 4, 5} |
243 | | -sum := glinq.From(numbers).Aggregate(0, func(acc, x int) int { return acc + x }) |
244 | | -// 15 |
245 | | - |
246 | | -numbers := []int{2, 3, 4} |
247 | | -product := glinq.From(numbers).Aggregate(1, func(acc, x int) int { return acc * x }) |
248 | | -// 24 |
249 | | - |
250 | | -words := []string{"Hello", " ", "World", "!"} |
251 | | -concatenated := glinq.From(words).Aggregate("", func(acc, x string) string { return acc + x }) |
252 | | -// "Hello World!" |
253 | | -``` |
254 | | - |
255 | | -### Finding Maximum (Max) |
256 | | - |
257 | | -glinq provides **two ways** to find maximum: |
258 | | - |
259 | | -#### Max (Function) - For Ordered Types |
260 | | - |
261 | | -The `Max` **function** works with ordered types (int, uint, float, string): |
262 | | - |
263 | | -```go |
264 | | -numbers := []int{5, 2, 8, 1, 9} |
265 | | -max, ok := glinq.Max(glinq.From(numbers)) |
266 | | -// max = 9, ok = true |
| 85 | +// Remove duplicates by key |
| 86 | +type Person struct { ID int; Name string } |
| 87 | +people := []Person{{1, "Alice"}, {1, "Alice2"}, {2, "Bob"}} |
| 88 | +uniquePeople := glinq.From(people). |
| 89 | + DistinctBy(func(p Person) any { return p.ID }). |
| 90 | + ToSlice() |
267 | 91 | ``` |
268 | 92 |
|
269 | | -#### Max (Method) - With Comparator |
270 | | - |
271 | | -The `Max` **method** works with any type using a comparator function: |
| 93 | +### Set Operations |
272 | 94 |
|
273 | 95 | ```go |
274 | | -type Person struct { Age int; Name string } |
275 | | -people := []Person{{Age: 30, Name: "Alice"}, {Age: 25, Name: "Bob"}} |
276 | | -oldest, ok := glinq.From(people).Max(func(a, b Person) int { |
277 | | - return a.Age - b.Age |
278 | | -}) |
279 | | -// oldest = Person{Age: 30, Name: "Alice"}, ok = true |
280 | | -``` |
281 | | - |
282 | | -### Lazy Evaluation Demonstration |
| 96 | +set1 := glinq.From([]int{1, 2, 3}) |
| 97 | +set2 := glinq.From([]int{3, 4, 5}) |
283 | 98 |
|
284 | | -```go |
285 | | -// Thanks to lazy evaluation, the filter is applied only to necessary elements |
286 | | -result := glinq.Range(1, 1000000). |
287 | | - Where(func(x int) bool { return x%2 == 0 }). |
288 | | - Take(3). |
289 | | - ToSlice() |
290 | | -// [2, 4, 6] |
291 | | -// Only ~6 elements processed, not a million! |
| 99 | +union := glinq.Union(set1, set2).ToSlice() // [1, 2, 3, 4, 5] |
| 100 | +intersect := glinq.Intersect(set1, set2).ToSlice() // [3] |
| 101 | +except := glinq.Except(set1, set2).ToSlice() // [1, 2] |
292 | 102 | ``` |
293 | 103 |
|
294 | | -## API Reference |
295 | | - |
296 | | -glinq provides both **methods** (on `Stream[T]` interface) and **standalone functions**. Methods support method chaining, while functions are used when type parameters are needed. |
297 | | - |
298 | | -### Creator Functions |
299 | | - |
300 | | -These functions create a new `Stream[T]`: |
301 | | - |
302 | | -- `From[T any](slice []T) Stream[T]` - create Stream from slice |
303 | | -- `Empty[T any]() Stream[T]` - create empty Stream |
304 | | -- `Range(start, count int) Stream[int]` - create Stream of integers |
305 | | -- `FromMap[K, V](m map[K]V) Stream[KeyValue[K, V]]` - create Stream from map |
306 | | - |
307 | | -### Stream Methods (Operators) |
308 | | - |
309 | | -These methods transform the Stream and return a new `Stream[T]`: |
310 | | - |
311 | | -- `Where(predicate func(T) bool) Stream[T]` - filter by condition |
312 | | -- `Select(mapper func(T) T) Stream[T]` - transform elements to the same type |
313 | | -- `SelectWithIndex(mapper func(T, int) T) Stream[T]` - transform elements to the same type with index |
314 | | -- `Take(n int) Stream[T]` - take first n elements |
315 | | -- `Skip(n int) Stream[T]` - skip first n elements |
316 | | - |
317 | | -### Stream Methods (Terminal Operations) |
318 | | - |
319 | | -These methods materialize the Stream: |
320 | | - |
321 | | -- `ToSlice() []T` - convert Stream to slice |
322 | | -- `Chunk(size int) [][]T` - split Stream into chunks of specified size |
323 | | -- `First() (T, bool)` - get first element |
324 | | -- `Last() (T, bool)` - get last element |
325 | | -- `Count() int` - count number of elements |
326 | | -- `Any(predicate func(T) bool) bool` - check if any element exists |
327 | | -- `All(predicate func(T) bool) bool` - check if all elements satisfy condition |
328 | | -- `ForEach(action func(T))` - execute action for each element |
329 | | -- `Min(comparator func(T, T) int) (T, bool)` - find minimum element using comparator (works with any type) |
330 | | -- `Max(comparator func(T, T) int) (T, bool)` - find maximum element using comparator (works with any type) |
331 | | -- `Aggregate(seed T, accumulator func(T, T) T) T` - apply accumulator function over Stream |
332 | | - |
333 | | -### Transformation Functions |
334 | | - |
335 | | -These standalone functions transform Stream to different types: |
336 | | - |
337 | | -- `Select[T, R any](s Stream[T], mapper func(T) R) Stream[R]` - transform elements to a different type (function version) |
338 | | -- `SelectWithIndex[T, R any](s Stream[T], mapper func(T, int) R) Stream[R]` - transform elements to a different type with index (function version) |
339 | | - |
340 | | -### Map Helper Functions |
341 | | - |
342 | | -These functions work with `Stream[KeyValue[K, V]]`: |
343 | | - |
344 | | -- `Keys[K, V](s Stream[KeyValue[K, V]]) Stream[K]` - extract keys from KeyValue pairs |
345 | | -- `Values[K, V](s Stream[KeyValue[K, V]]) Stream[V]` - extract values from KeyValue pairs |
346 | | -- `ToMap[K, V](s Stream[KeyValue[K, V]]) map[K]V` - convert Stream[KeyValue] to map |
347 | | - |
348 | | -### Terminal Functions |
349 | | - |
350 | | -These standalone functions materialize Streams: |
351 | | - |
352 | | -- `ToMapBy[T, K, V](s Stream[T], keySelector func(T) K, valueSelector func(T) V) map[K]V` - convert Stream[T] to map using selectors |
353 | | - |
354 | | -### Numeric Functions |
| 104 | +## Documentation |
355 | 105 |
|
356 | | -These functions work with numeric and ordered types: |
| 106 | +📚 **[Full Documentation & Wiki](docs/WIKI.md)** - Complete API reference, examples, and guides |
357 | 107 |
|
358 | | -- `Sum[T Numeric](s Stream[T]) T` - calculate sum of all elements (works with int, uint, float types) |
359 | | -- `Min[T Ordered](s Stream[T]) (T, bool)` - find minimum element for ordered types (works with int, uint, float, string types) |
360 | | -- `Max[T Ordered](s Stream[T]) (T, bool)` - find maximum element for ordered types (works with int, uint, float, string types) |
| 108 | +### Quick Links |
361 | 109 |
|
362 | | -**Note**: For custom types or complex comparisons, use the `Min` and `Max` methods with comparator functions instead. |
| 110 | +- [Getting Started](docs/WIKI.md#getting-started) |
| 111 | +- [API Reference](docs/WIKI.md#api-reference) |
| 112 | +- [Architecture](docs/WIKI.md#architecture) |
| 113 | +- [Examples](docs/WIKI.md#examples) |
| 114 | +- [Best Practices](docs/WIKI.md#best-practices) |
363 | 115 |
|
364 | 116 | ## Requirements |
365 | 117 |
|
|
0 commit comments