Skip to content

Commit 0f3840e

Browse files
author
Roman Sorokin
committed
update project
1 parent 863fb19 commit 0f3840e

File tree

8 files changed

+356
-2
lines changed

8 files changed

+356
-2
lines changed

README.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,31 @@ strings := glinq.Select(
8181
// []string{"num_1", "num_2", "num_3"}
8282
```
8383

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:
87+
88+
```go
89+
numbers := []int{1, 2, 3}
90+
result := glinq.From(numbers).
91+
SelectWithIndex(func(x int, idx int) int { return x * idx }).
92+
ToSlice()
93+
// []int{0, 2, 6}
94+
```
95+
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+
84109
### Limiting Elements (Take and Skip)
85110

86111
```go
@@ -209,6 +234,24 @@ youngest, ok := glinq.From(people).Min(func(a, b Person) int {
209234
// youngest = Person{Age: 25, Name: "Bob"}, ok = true
210235
```
211236

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+
212255
### Finding Maximum (Max)
213256

214257
glinq provides **two ways** to find maximum:
@@ -267,6 +310,7 @@ These methods transform the Stream and return a new `Stream[T]`:
267310

268311
- `Where(predicate func(T) bool) Stream[T]` - filter by condition
269312
- `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
270314
- `Take(n int) Stream[T]` - take first n elements
271315
- `Skip(n int) Stream[T]` - skip first n elements
272316

@@ -284,12 +328,14 @@ These methods materialize the Stream:
284328
- `ForEach(action func(T))` - execute action for each element
285329
- `Min(comparator func(T, T) int) (T, bool)` - find minimum element using comparator (works with any type)
286330
- `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
287332

288333
### Transformation Functions
289334

290335
These standalone functions transform Stream to different types:
291336

292337
- `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)
293339

294340
### Map Helper Functions
295341

examples/basic/main.go

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,84 @@ func main() {
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) -> Select: %v\n", combined)
170+
fmt.Printf("Skip(3) -> Where(x < 8) -> Take(3) -> Select: %v\n\n", combined)
171+
172+
// Пример 14: SelectWithIndex - метод с индексом
173+
fmt.Println("Example 14: SelectWithIndex (method with index)")
174+
numbers9 := []int{1, 2, 3, 4, 5}
175+
indexed := glinq.From(numbers9).
176+
SelectWithIndex(func(x int, idx int) int { return x * idx }).
177+
ToSlice()
178+
fmt.Printf("Input: %v\n", numbers9)
179+
fmt.Printf("SelectWithIndex(x * idx): %v\n\n", indexed)
180+
181+
// Пример 15: SelectWithIndex - функция с индексом (разные типы)
182+
fmt.Println("Example 15: SelectWithIndex function (different types with index)")
183+
numbers10 := []int{10, 20, 30}
184+
indexedStrings := glinq.SelectWithIndex(
185+
glinq.From(numbers10),
186+
func(x int, idx int) string { return fmt.Sprintf("Value_%d_at_Index_%d", x, idx) },
187+
).ToSlice()
188+
fmt.Printf("Input: %v\n", numbers10)
189+
fmt.Printf("SelectWithIndex to string: %v\n\n", indexedStrings)
190+
191+
// Пример 16: SelectWithIndex с Where
192+
fmt.Println("Example 16: Where + SelectWithIndex")
193+
numbers11 := []int{1, 2, 3, 4, 5, 6}
194+
filteredIndexed := glinq.SelectWithIndex(
195+
glinq.From(numbers11).
196+
Where(func(x int) bool { return x%2 == 0 }),
197+
func(x int, idx int) string {
198+
return fmt.Sprintf("Even[%d]=%d", idx, x)
199+
},
200+
).ToSlice()
201+
fmt.Printf("Input: %v\n", numbers11)
202+
fmt.Printf("Where(even) -> SelectWithIndex: %v\n\n", filteredIndexed)
203+
204+
// Пример 17: Aggregate - сумма
205+
fmt.Println("Example 17: Aggregate - Sum")
206+
numbers12 := []int{1, 2, 3, 4, 5}
207+
sum := glinq.From(numbers12).Aggregate(0, func(acc, x int) int { return acc + x })
208+
fmt.Printf("Input: %v\n", numbers12)
209+
fmt.Printf("Aggregate sum: %d\n\n", sum)
210+
211+
// Пример 18: Aggregate - произведение
212+
fmt.Println("Example 18: Aggregate - Product")
213+
numbers13 := []int{2, 3, 4}
214+
product := glinq.From(numbers13).Aggregate(1, func(acc, x int) int { return acc * x })
215+
fmt.Printf("Input: %v\n", numbers13)
216+
fmt.Printf("Aggregate product: %d\n\n", product)
217+
218+
// Пример 19: Aggregate - конкатенация строк
219+
fmt.Println("Example 19: Aggregate - String concatenation")
220+
words := []string{"Hello", " ", "World", "!"}
221+
concatenated := glinq.From(words).Aggregate("", func(acc, x string) string { return acc + x })
222+
fmt.Printf("Input: %v\n", words)
223+
fmt.Printf("Aggregate concatenation: '%s'\n\n", concatenated)
224+
225+
// Пример 20: Aggregate с фильтрацией
226+
fmt.Println("Example 20: Aggregate with Where filter")
227+
numbers14 := []int{1, 2, 3, 4, 5, 6, 7, 8}
228+
sumOfEvens := glinq.From(numbers14).
229+
Where(func(x int) bool { return x%2 == 0 }).
230+
Aggregate(0, func(acc, x int) int { return acc + x })
231+
fmt.Printf("Input: %v\n", numbers14)
232+
fmt.Printf("Sum of even numbers: %d\n\n", sumOfEvens)
233+
234+
// Пример 21: Aggregate с кастомным типом
235+
fmt.Println("Example 21: Aggregate with custom type")
236+
type Point struct {
237+
X, Y int
238+
}
239+
points := []Point{{1, 2}, {3, 4}, {5, 6}}
240+
totalPoint := glinq.From(points).Aggregate(
241+
Point{0, 0},
242+
func(acc, p Point) Point {
243+
return Point{acc.X + p.X, acc.Y + p.Y}
244+
},
245+
)
246+
fmt.Printf("Input points: %+v\n", points)
247+
fmt.Printf("Aggregate sum: Point{X:%d, Y:%d}\n\n", totalPoint.X, totalPoint.Y)
171248

172249
fmt.Println("\n=== End of Examples ===")
173250
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
module github.com/CreateLab/glinq
22

3-
go 1.25.0
3+
go 1.23.0

pkg/glinq/operators.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,32 @@ func (s *stream[T]) Select(mapper func(T) T) Stream[T] {
4242
}
4343
}
4444

45+
// SelectWithIndex transforms elements to the same type, providing index to mapper function.
46+
// Supports method chaining.
47+
//
48+
// Example:
49+
//
50+
// doubled := From([]int{1, 2, 3}).
51+
// SelectWithIndex(func(x int, idx int) int { return x * idx }).
52+
// ToSlice()
53+
// // []int{0, 2, 6}
54+
func (s *stream[T]) SelectWithIndex(mapper func(T, int) T) Stream[T] {
55+
oldSource := s.source
56+
index := 0
57+
return &stream[T]{
58+
source: func() (T, bool) {
59+
value, ok := oldSource()
60+
if !ok {
61+
var zero T
62+
return zero, false
63+
}
64+
result := mapper(value, index)
65+
index++
66+
return result, true
67+
},
68+
}
69+
}
70+
4571
// Map transforms elements to a different type.
4672
// This is a function (not a method) because in Go methods cannot have their own type parameters.
4773
//
@@ -65,6 +91,32 @@ func Select[T, R any](s Stream[T], mapper func(T) R) Stream[R] {
6591
}
6692
}
6793

94+
// SelectWithIndex transforms elements to a different type, providing index to mapper function.
95+
// This is a function (not a method) because in Go methods cannot have their own type parameters.
96+
//
97+
// Example:
98+
//
99+
// strings := SelectWithIndex(
100+
// From([]int{1, 2, 3}),
101+
// func(x int, idx int) string { return fmt.Sprintf("num_%d_at_%d", x, idx) },
102+
// ).ToSlice()
103+
// // []string{"num_1_at_0", "num_2_at_1", "num_3_at_2"}
104+
func SelectWithIndex[T, R any](s Stream[T], mapper func(T, int) R) Stream[R] {
105+
index := 0
106+
return &stream[R]{
107+
source: func() (R, bool) {
108+
value, ok := s.Next()
109+
if !ok {
110+
var zero R
111+
return zero, false
112+
}
113+
result := mapper(value, index)
114+
index++
115+
return result, true
116+
},
117+
}
118+
}
119+
68120
// Take takes the first n elements from Stream.
69121
func (s *stream[T]) Take(n int) Stream[T] {
70122
oldSource := s.source

pkg/glinq/operators_test.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,3 +168,83 @@ func TestLazyEvaluation(t *testing.T) {
168168
t.Errorf("expected length 3, got %d", len(result))
169169
}
170170
}
171+
172+
func TestSelectWithIndex(t *testing.T) {
173+
t.Run("Transform same type with index", func(t *testing.T) {
174+
input := []int{1, 2, 3, 4, 5}
175+
result := From(input).
176+
SelectWithIndex(func(x int, idx int) int { return x * idx }).
177+
ToSlice()
178+
179+
expected := []int{0, 2, 6, 12, 20}
180+
if !reflect.DeepEqual(result, expected) {
181+
t.Errorf("Expected %v, got %v", expected, result)
182+
}
183+
})
184+
185+
t.Run("Chaining with Where and SelectWithIndex", func(t *testing.T) {
186+
input := []int{1, 2, 3, 4, 5}
187+
result := From(input).
188+
Where(func(x int) bool { return x > 2 }).
189+
SelectWithIndex(func(x int, idx int) int { return x + idx*10 }).
190+
ToSlice()
191+
192+
expected := []int{3, 14, 25}
193+
if !reflect.DeepEqual(result, expected) {
194+
t.Errorf("Expected %v, got %v", expected, result)
195+
}
196+
})
197+
}
198+
199+
func TestSelectWithIndexFunction(t *testing.T) {
200+
t.Run("Transform int to string with index", func(t *testing.T) {
201+
input := []int{1, 2, 3}
202+
result := SelectWithIndex(
203+
From(input),
204+
func(x int, idx int) string { return fmt.Sprintf("num_%d_at_%d", x, idx) },
205+
).ToSlice()
206+
207+
expected := []string{"num_1_at_0", "num_2_at_1", "num_3_at_2"}
208+
if !reflect.DeepEqual(result, expected) {
209+
t.Errorf("Expected %v, got %v", expected, result)
210+
}
211+
})
212+
213+
t.Run("Transform to struct with index", func(t *testing.T) {
214+
type User struct {
215+
ID int
216+
Name string
217+
}
218+
219+
input := []int{1, 2, 3}
220+
result := SelectWithIndex(
221+
From(input),
222+
func(id int, idx int) User {
223+
return User{ID: id, Name: fmt.Sprintf("User%d_Index%d", id, idx)}
224+
},
225+
).ToSlice()
226+
227+
if len(result) != 3 {
228+
t.Errorf("Expected 3 users, got %d", len(result))
229+
}
230+
if result[0].ID != 1 || result[0].Name != "User1_Index0" {
231+
t.Errorf("Unexpected user: %+v", result[0])
232+
}
233+
if result[1].ID != 2 || result[1].Name != "User2_Index1" {
234+
t.Errorf("Unexpected user: %+v", result[1])
235+
}
236+
})
237+
238+
t.Run("Chaining with Where and SelectWithIndex function", func(t *testing.T) {
239+
input := []int{1, 2, 3, 4, 5}
240+
result := SelectWithIndex(
241+
From(input).Where(func(x int) bool { return x > 2 }),
242+
func(x int, idx int) string { return fmt.Sprintf("Number_%d_Index_%d", x, idx) },
243+
).ToSlice()
244+
245+
expected := []string{"Number_3_Index_0", "Number_4_Index_1", "Number_5_Index_2"}
246+
if !reflect.DeepEqual(result, expected) {
247+
t.Errorf("Expected %v, got %v", expected, result)
248+
}
249+
})
250+
}

pkg/glinq/stream.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ type Stream[T any] interface {
3535
// Max returns the maximum element using comparator function.
3636
// Comparator should return negative value if first < second, 0 if equal, positive if first > second.
3737
Max(comparator func(T, T) int) (T, bool)
38+
// SelectWithIndex transforms elements to the same type, providing index to mapper function.
39+
SelectWithIndex(mapper func(T, int) T) Stream[T]
40+
// Aggregate applies an accumulator function over the Stream.
41+
// The seed parameter is the initial accumulator value.
42+
// The accumulator function is invoked for each element.
43+
Aggregate(seed T, accumulator func(T, T) T) T
3844
}
3945

4046
// stream represents the internal implementation of Stream.

pkg/glinq/terminal.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,32 @@ func (s *stream[T]) Max(comparator func(T, T) int) (T, bool) {
185185
return maxVal, found
186186
}
187187

188+
// Aggregate applies an accumulator function over the Stream.
189+
// The seed parameter is the initial accumulator value.
190+
// The accumulator function is invoked for each element.
191+
// Returns the final accumulator value.
192+
//
193+
// Example:
194+
//
195+
// numbers := []int{1, 2, 3, 4, 5}
196+
// sum := From(numbers).Aggregate(0, func(acc, x int) int { return acc + x })
197+
// // 15
198+
//
199+
// numbers := []int{1, 2, 3}
200+
// product := From(numbers).Aggregate(1, func(acc, x int) int { return acc * x })
201+
// // 6
202+
func (s *stream[T]) Aggregate(seed T, accumulator func(T, T) T) T {
203+
result := seed
204+
for {
205+
value, ok := s.source()
206+
if !ok {
207+
break
208+
}
209+
result = accumulator(result, value)
210+
}
211+
return result
212+
}
213+
188214
// ToMapBy materializes Stream[T] into a map using selectors for key and value.
189215
//
190216
// Example:

0 commit comments

Comments
 (0)