Skip to content

Commit 75eb674

Browse files
author
Roman Sorokin
committed
update project
1 parent d6a5b88 commit 75eb674

File tree

10 files changed

+728
-38
lines changed

10 files changed

+728
-38
lines changed

.golangci.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,7 @@ linters:
99
- govet
1010
- ineffassign
1111
- staticcheck
12-
- typecheck
1312
- unused
14-
- gofmt
1513
- goimports
1614
- misspell
1715
- gocritic

README.md

Lines changed: 143 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,13 @@ evens := glinq.From(numbers).
5252
// [2, 4]
5353
```
5454

55-
### Transformation (Select)
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:
5662

5763
```go
5864
numbers := []int{1, 2, 3}
@@ -62,6 +68,19 @@ squared := glinq.From(numbers).
6268
// [1, 4, 9]
6369
```
6470

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+
75+
```go
76+
numbers := []int{1, 2, 3}
77+
strings := glinq.Select(
78+
glinq.From(numbers),
79+
func(x int) string { return fmt.Sprintf("num_%d", x) },
80+
).ToSlice()
81+
// []string{"num_1", "num_2", "num_3"}
82+
```
83+
6584
### Limiting Elements (Take and Skip)
6685

6786
```go
@@ -139,6 +158,84 @@ first, ok := glinq.From(numbers).First()
139158
// first = 1, ok = true
140159
```
141160

161+
### Splitting into Chunks (Chunk)
162+
163+
```go
164+
numbers := []int{1, 2, 3, 4, 5, 6, 7}
165+
chunks := glinq.From(numbers).Chunk(3)
166+
// [][]int{{1, 2, 3}, {4, 5, 6}, {7}}
167+
```
168+
169+
### Getting Last Element (Last)
170+
171+
```go
172+
numbers := []int{1, 2, 3, 4, 5}
173+
last, ok := glinq.From(numbers).Last()
174+
// last = 5, ok = true
175+
```
176+
177+
### Summing Elements (Sum)
178+
179+
```go
180+
numbers := []int{1, 2, 3, 4, 5}
181+
sum := glinq.Sum(glinq.From(numbers))
182+
// 15
183+
```
184+
185+
### Finding Minimum (Min)
186+
187+
glinq provides **two ways** to find minimum:
188+
189+
#### Min (Function) - For Ordered Types
190+
191+
The `Min` **function** works with ordered types (int, uint, float, string):
192+
193+
```go
194+
numbers := []int{5, 2, 8, 1, 9}
195+
min, ok := glinq.Min(glinq.From(numbers))
196+
// min = 1, ok = true
197+
```
198+
199+
#### Min (Method) - With Comparator
200+
201+
The `Min` **method** works with any type using a comparator function:
202+
203+
```go
204+
type Person struct { Age int; Name string }
205+
people := []Person{{Age: 30, Name: "Alice"}, {Age: 25, Name: "Bob"}}
206+
youngest, ok := glinq.From(people).Min(func(a, b Person) int {
207+
return a.Age - b.Age
208+
})
209+
// youngest = Person{Age: 25, Name: "Bob"}, ok = true
210+
```
211+
212+
### Finding Maximum (Max)
213+
214+
glinq provides **two ways** to find maximum:
215+
216+
#### Max (Function) - For Ordered Types
217+
218+
The `Max` **function** works with ordered types (int, uint, float, string):
219+
220+
```go
221+
numbers := []int{5, 2, 8, 1, 9}
222+
max, ok := glinq.Max(glinq.From(numbers))
223+
// max = 9, ok = true
224+
```
225+
226+
#### Max (Method) - With Comparator
227+
228+
The `Max` **method** works with any type using a comparator function:
229+
230+
```go
231+
type Person struct { Age int; Name string }
232+
people := []Person{{Age: 30, Name: "Alice"}, {Age: 25, Name: "Bob"}}
233+
oldest, ok := glinq.From(people).Max(func(a, b Person) int {
234+
return a.Age - b.Age
235+
})
236+
// oldest = Person{Age: 30, Name: "Alice"}, ok = true
237+
```
238+
142239
### Lazy Evaluation Demonstration
143240

144241
```go
@@ -151,36 +248,72 @@ result := glinq.Range(1, 1000000).
151248
// Only ~6 elements processed, not a million!
152249
```
153250

154-
## Supported Operations
251+
## API Reference
252+
253+
glinq provides both **methods** (on `Stream[T]` interface) and **standalone functions**. Methods support method chaining, while functions are used when type parameters are needed.
155254

156-
### Creators
255+
### Creator Functions
256+
257+
These functions create a new `Stream[T]`:
157258

158259
- `From[T any](slice []T) Stream[T]` - create Stream from slice
159260
- `Empty[T any]() Stream[T]` - create empty Stream
160261
- `Range(start, count int) Stream[int]` - create Stream of integers
161262
- `FromMap[K, V](m map[K]V) Stream[KeyValue[K, V]]` - create Stream from map
162263

163-
### Operators
264+
### Stream Methods (Operators)
265+
266+
These methods transform the Stream and return a new `Stream[T]`:
164267

165268
- `Where(predicate func(T) bool) Stream[T]` - filter by condition
166-
- `Select[R any](mapper func(T) R) Stream[R]` - transform elements
269+
- `Select(mapper func(T) T) Stream[T]` - transform elements to the same type
167270
- `Take(n int) Stream[T]` - take first n elements
168271
- `Skip(n int) Stream[T]` - skip first n elements
169272

170-
### Terminal Operations
273+
### Stream Methods (Terminal Operations)
274+
275+
These methods materialize the Stream:
171276

172277
- `ToSlice() []T` - convert Stream to slice
278+
- `Chunk(size int) [][]T` - split Stream into chunks of specified size
173279
- `First() (T, bool)` - get first element
280+
- `Last() (T, bool)` - get last element
174281
- `Count() int` - count number of elements
175282
- `Any(predicate func(T) bool) bool` - check if any element exists
176283
- `All(predicate func(T) bool) bool` - check if all elements satisfy condition
177284
- `ForEach(action func(T))` - execute action for each element
285+
- `Min(comparator func(T, T) int) (T, bool)` - find minimum element using comparator (works with any type)
286+
- `Max(comparator func(T, T) int) (T, bool)` - find maximum element using comparator (works with any type)
287+
288+
### Transformation Functions
289+
290+
These standalone functions transform Stream to different types:
291+
292+
- `Select[T, R any](s Stream[T], mapper func(T) R) Stream[R]` - transform elements to a different type (function version)
293+
294+
### Map Helper Functions
295+
296+
These functions work with `Stream[KeyValue[K, V]]`:
178297

179-
### Helper Functions
298+
- `Keys[K, V](s Stream[KeyValue[K, V]]) Stream[K]` - extract keys from KeyValue pairs
299+
- `Values[K, V](s Stream[KeyValue[K, V]]) Stream[V]` - extract values from KeyValue pairs
300+
- `ToMap[K, V](s Stream[KeyValue[K, V]]) map[K]V` - convert Stream[KeyValue] to map
180301

181-
- `Keys[K, V](stream Stream[KeyValue[K, V]]) Stream[K]` - extract keys
182-
- `Values[K, V](stream Stream[KeyValue[K, V]]) Stream[V]` - extract values
183-
- `ToMap[K, V](stream Stream[KeyValue[K, V]]) map[K]V` - convert to map
302+
### Terminal Functions
303+
304+
These standalone functions materialize Streams:
305+
306+
- `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
307+
308+
### Numeric Functions
309+
310+
These functions work with numeric and ordered types:
311+
312+
- `Sum[T Numeric](s Stream[T]) T` - calculate sum of all elements (works with int, uint, float types)
313+
- `Min[T Ordered](s Stream[T]) (T, bool)` - find minimum element for ordered types (works with int, uint, float, string types)
314+
- `Max[T Ordered](s Stream[T]) (T, bool)` - find maximum element for ordered types (works with int, uint, float, string types)
315+
316+
**Note**: For custom types or complex comparisons, use the `Min` and `Max` methods with comparator functions instead.
184317

185318
## Requirements
186319

@@ -201,7 +334,3 @@ go run examples/basic/main.go
201334
## License
202335

203336
MIT
204-
205-
## Author
206-
207-
your-username

examples/basic/main.go

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -29,35 +29,35 @@ func main() {
2929
fmt.Printf("Input: %v\n", numbers2)
3030
fmt.Printf("Filtered (> 5) and mapped (x * 2): %v\n\n", result)
3131

32-
// Пример 3: Map - преобразование в другой тип (int -> string)
33-
fmt.Println("Example 3: Map (int -> string)")
32+
// Пример 3: Select - преобразование в другой тип (int -> string)
33+
fmt.Println("Example 3: Select (int -> string)")
3434
numbers3 := []int{1, 2, 3, 4, 5}
35-
strings := glinq.Map(
35+
strings := glinq.Select(
3636
glinq.From(numbers3),
3737
func(x int) string { return fmt.Sprintf("Number: %d", x) },
3838
).ToSlice()
3939
fmt.Printf("Input: %v\n", numbers3)
4040
fmt.Printf("Strings: %v\n\n", strings)
4141

42-
// Пример 4: Map с Where (combined)
43-
fmt.Println("Example 4: Where + Map (different type)")
42+
// Пример 4: Select с Where (combined)
43+
fmt.Println("Example 4: Where + Select (different type)")
4444
numbers4 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
45-
filtered := glinq.Map(
45+
filtered := glinq.Select(
4646
glinq.From(numbers4).Where(func(x int) bool { return x%2 == 0 }),
4747
func(x int) string { return fmt.Sprintf("Even: %d", x) },
4848
).ToSlice()
4949
fmt.Printf("Input: %v\n", numbers4)
5050
fmt.Printf("Even numbers as strings: %v\n\n", filtered)
5151

52-
// Пример 5: Map в структуру
53-
fmt.Println("Example 5: Map to struct")
52+
// Пример 5: Select в структуру
53+
fmt.Println("Example 5: Select to struct")
5454
type User struct {
5555
ID int
5656
Name string
5757
}
5858

5959
ids := []int{1, 2, 3}
60-
users := glinq.Map(
60+
users := glinq.Select(
6161
glinq.From(ids),
6262
func(id int) User {
6363
return User{ID: id, Name: fmt.Sprintf("User%d", id)}
@@ -109,10 +109,10 @@ func main() {
109109
ToSlice()
110110
fmt.Printf("Result: %v\n\n", lazyResult)
111111

112-
// Пример 9: Lazy evaluation с Map
113-
fmt.Println("Example 9: Lazy evaluation with Map")
112+
// Пример 9: Lazy evaluation с Select
113+
fmt.Println("Example 9: Lazy evaluation with Select")
114114
fmt.Println("Notice: only 3 elements are processed due to Take(3)")
115-
lazy := glinq.Map(
115+
lazy := glinq.Select(
116116
glinq.Range(1, 20).
117117
Where(func(x int) bool {
118118
fmt.Printf(" Checking %d\n", x)
@@ -156,18 +156,18 @@ func main() {
156156
fmt.Printf("Input: %v\n", numbers7)
157157
fmt.Printf("Skip(2) -> Where(even) -> Select(x*x) -> Take(3): %v\n\n", complex)
158158

159-
// Пример 13: Комбинация Skip, Map, Take
160-
fmt.Println("Example 13: Skip + Map + Take")
159+
// Пример 13: Комбинация Skip, Select, Take
160+
fmt.Println("Example 13: Skip + Select + Take")
161161
numbers8 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
162-
combined := glinq.Map(
162+
combined := glinq.Select(
163163
glinq.From(numbers8).
164164
Skip(3).
165165
Where(func(x int) bool { return x < 8 }).
166166
Take(3),
167167
func(x int) string { return fmt.Sprintf("[%d]", x*10) },
168168
).ToSlice()
169169
fmt.Printf("Input: %v\n", numbers8)
170-
fmt.Printf("Skip(3) -> Where(x < 8) -> Take(3) -> Map: %v\n", combined)
170+
fmt.Printf("Skip(3) -> Where(x < 8) -> Take(3) -> Select: %v\n", combined)
171171

172172
fmt.Println("\n=== End of Examples ===")
173173
}

pkg/glinq/chunk_test.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package glinq
2+
3+
import (
4+
"reflect"
5+
"testing"
6+
)
7+
8+
func TestChunk(t *testing.T) {
9+
t.Run("Chunk with exact division", func(t *testing.T) {
10+
slice := []int{1, 2, 3, 4, 5, 6}
11+
result := From(slice).Chunk(3)
12+
13+
expected := [][]int{{1, 2, 3}, {4, 5, 6}}
14+
if !reflect.DeepEqual(result, expected) {
15+
t.Errorf("Expected %v, got %v", expected, result)
16+
}
17+
})
18+
19+
t.Run("Chunk with remainder", func(t *testing.T) {
20+
slice := []int{1, 2, 3, 4, 5, 6, 7}
21+
result := From(slice).Chunk(3)
22+
23+
expected := [][]int{{1, 2, 3}, {4, 5, 6}, {7}}
24+
if !reflect.DeepEqual(result, expected) {
25+
t.Errorf("Expected %v, got %v", expected, result)
26+
}
27+
})
28+
29+
t.Run("Chunk with single element chunks", func(t *testing.T) {
30+
slice := []int{1, 2, 3}
31+
result := From(slice).Chunk(1)
32+
33+
expected := [][]int{{1}, {2}, {3}}
34+
if !reflect.DeepEqual(result, expected) {
35+
t.Errorf("Expected %v, got %v", expected, result)
36+
}
37+
})
38+
39+
t.Run("Chunk larger than slice", func(t *testing.T) {
40+
slice := []int{1, 2, 3}
41+
result := From(slice).Chunk(10)
42+
43+
expected := [][]int{{1, 2, 3}}
44+
if !reflect.DeepEqual(result, expected) {
45+
t.Errorf("Expected %v, got %v", expected, result)
46+
}
47+
})
48+
49+
t.Run("Chunk empty slice", func(t *testing.T) {
50+
slice := []int{}
51+
result := From(slice).Chunk(3)
52+
53+
if len(result) != 0 {
54+
t.Errorf("Expected empty result, got %v", result)
55+
}
56+
})
57+
58+
t.Run("Chunk with zero size", func(t *testing.T) {
59+
slice := []int{1, 2, 3}
60+
result := From(slice).Chunk(0)
61+
62+
if result != nil {
63+
t.Errorf("Expected nil, got %v", result)
64+
}
65+
})
66+
67+
t.Run("Chunk with negative size", func(t *testing.T) {
68+
slice := []int{1, 2, 3}
69+
result := From(slice).Chunk(-1)
70+
71+
if result != nil {
72+
t.Errorf("Expected nil, got %v", result)
73+
}
74+
})
75+
76+
t.Run("Chunk with filtered stream", func(t *testing.T) {
77+
slice := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
78+
result := From(slice).
79+
Where(func(x int) bool { return x%2 == 0 }).
80+
Chunk(2)
81+
82+
expected := [][]int{{2, 4}, {6, 8}, {10}}
83+
if !reflect.DeepEqual(result, expected) {
84+
t.Errorf("Expected %v, got %v", expected, result)
85+
}
86+
})
87+
88+
t.Run("Chunk with strings", func(t *testing.T) {
89+
slice := []string{"a", "b", "c", "d", "e"}
90+
result := From(slice).Chunk(2)
91+
92+
expected := [][]string{{"a", "b"}, {"c", "d"}, {"e"}}
93+
if !reflect.DeepEqual(result, expected) {
94+
t.Errorf("Expected %v, got %v", expected, result)
95+
}
96+
})
97+
}

0 commit comments

Comments
 (0)