Skip to content

Commit 7281b8a

Browse files
author
Roman Sorokin
committed
update project
1 parent f335e2d commit 7281b8a

File tree

14 files changed

+1560
-478
lines changed

14 files changed

+1560
-478
lines changed

README.md

Lines changed: 42 additions & 290 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,16 @@
22

33
LINQ-like API for Go with support for lazy evaluation.
44

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+
[![Go Reference](https://pkg.go.dev/badge/github.com/CreateLab/glinq.svg)](https://pkg.go.dev/github.com/CreateLab/glinq)
6+
[![Go Report Card](https://goreportcard.com/badge/github.com/CreateLab/glinq)](https://goreportcard.com/report/github.com/CreateLab/glinq)
77

88
## Features
99

1010
- **Lazy Evaluation**: All intermediate operations are executed only when the result is materialized
1111
- **Type Safe**: Full support for generics (Go 1.18+)
1212
- **Composable**: Operations can be easily combined into chains
1313
- **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
1515

1616
## Installation
1717

@@ -40,326 +40,78 @@ func main() {
4040
}
4141
```
4242

43-
## Usage Examples
43+
## Basic Examples
4444

45-
### Filtering (Where)
45+
### Filtering and Transformation
4646

4747
```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}).
5050
Where(func(x int) bool { return x%2 == 0 }).
5151
ToSlice()
5252
// [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:
7453

75-
```go
76-
numbers := []int{1, 2, 3}
54+
// Transform to strings
7755
strings := glinq.Select(
78-
glinq.From(numbers),
56+
glinq.From([]int{1, 2, 3}),
7957
func(x int) string { return fmt.Sprintf("num_%d", x) },
8058
).ToSlice()
8159
// []string{"num_1", "num_2", "num_3"}
8260
```
8361

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
8763

8864
```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 }).
9268
ToSlice()
93-
// []int{0, 2, 6}
94-
```
69+
// [1, 2, 5, 8, 9]
9570

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()
11675
// [1, 2, 3]
117-
118-
// Skip first 2 elements
119-
rest := glinq.From(numbers).Skip(2).ToSlice()
120-
// [3, 4, 5]
12176
```
12277

123-
### Working with Maps
78+
### Removing Duplicates
12479

12580
```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]
20384

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()
26791
```
26892

269-
#### Max (Method) - With Comparator
270-
271-
The `Max` **method** works with any type using a comparator function:
93+
### Set Operations
27294

27395
```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})
28398

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]
292102
```
293103

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
355105

356-
These functions work with numeric and ordered types:
106+
📚 **[Full Documentation & Wiki](docs/WIKI.md)** - Complete API reference, examples, and guides
357107

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
361109

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)
363115

364116
## Requirements
365117

0 commit comments

Comments
 (0)