Skip to content

Commit a42c058

Browse files
author
Roman Sorokin
committed
add new methods ready for release
1 parent 3e0d8d5 commit a42c058

File tree

8 files changed

+769
-1
lines changed

8 files changed

+769
-1
lines changed

examples/basic/main.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,5 +300,43 @@ func main() {
300300
).ToSlice()
301301
fmt.Printf("Except of %v and %v: %v\n\n", []int{1, 2, 3}, []int{2, 3}, except)
302302

303+
// Example 32: TakeWhile - take elements while condition is true
304+
fmt.Println("Example 32: TakeWhile - Take elements while condition is true")
305+
numbers19 := []int{1, 2, 3, 4, 5, 6, 7, 8}
306+
takenWhile := glinq.From(numbers19).
307+
TakeWhile(func(x int) bool { return x < 5 }).
308+
ToSlice()
309+
fmt.Printf("Input: %v\n", numbers19)
310+
fmt.Printf("TakeWhile(x < 5): %v\n\n", takenWhile)
311+
312+
// Example 33: SkipWhile - skip elements while condition is true
313+
fmt.Println("Example 33: SkipWhile - Skip elements while condition is true")
314+
numbers20 := []int{1, 2, 3, 4, 5, 6, 7, 8}
315+
skippedWhile := glinq.From(numbers20).
316+
SkipWhile(func(x int) bool { return x < 5 }).
317+
ToSlice()
318+
fmt.Printf("Input: %v\n", numbers20)
319+
fmt.Printf("SkipWhile(x < 5): %v\n\n", skippedWhile)
320+
321+
// Example 34: TakeWhile and SkipWhile combined
322+
fmt.Println("Example 34: TakeWhile and SkipWhile combined")
323+
numbers21 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
324+
combinedWhile := glinq.From(numbers21).
325+
SkipWhile(func(x int) bool { return x < 3 }).
326+
TakeWhile(func(x int) bool { return x < 7 }).
327+
ToSlice()
328+
fmt.Printf("Input: %v\n", numbers21)
329+
fmt.Printf("SkipWhile(x < 3) -> TakeWhile(x < 7): %v\n\n", combinedWhile)
330+
331+
// Example 35: TakeWhile with chaining
332+
fmt.Println("Example 35: TakeWhile with chaining")
333+
numbers22 := []int{2, 4, 6, 3, 8, 10, 12}
334+
chained := glinq.From(numbers22).
335+
TakeWhile(func(x int) bool { return x%2 == 0 }).
336+
Select(func(x int) int { return x * 2 }).
337+
ToSlice()
338+
fmt.Printf("Input: %v\n", numbers22)
339+
fmt.Printf("TakeWhile(even) -> Select(x*2): %v\n\n", chained)
340+
303341
fmt.Println("\n=== End of Examples ===")
304342
}

pkg/glinq/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ result := glinq.From([]int{1, 2, 3, 4, 5}).
2525
## Main Operations
2626

2727
- **Stream Creation**: `From`, `Empty`, `Range`, `FromEnumerable`, `FromMap`
28-
- **Filtering**: `Where`, `DistinctBy`, `Take`, `Skip`
28+
- **Filtering**: `Where`, `DistinctBy`, `Take`, `TakeWhile`, `Skip`, `SkipWhile`
2929
- **Transformation**: `Select`, `SelectWithIndex`, `SelectMany`
3030
- **Ordering**: `OrderBy`, `OrderByDescending`, `Reverse`
3131
- **Grouping**: `GroupBy`

pkg/glinq/doc.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,14 @@
44
// All operations (Where, Select, Take, Skip) are executed lazily and do not start until a terminal
55
// operation (ToSlice, First, Count, Any, All, ForEach) is called.
66
//
7+
// Thread Safety:
8+
// Stream operations are NOT thread-safe. A Stream should not be used concurrently
9+
// by multiple goroutines without external synchronization. However, each Stream
10+
// operation returns a new Stream instance, so you can safely use different Stream
11+
// instances in different goroutines. Modifying the underlying data structure
12+
// (slice or map) while iterating may lead to undefined behavior.
13+
// For concurrent modifications, use FromSafe() or FromMapSafe() to create isolated snapshots.
14+
//
715
// Example usage:
816
//
917
// numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
@@ -25,7 +33,9 @@
2533
// - Where: filter by predicate
2634
// - Select: transform elements
2735
// - Take: first n elements
36+
// - TakeWhile: take elements while predicate returns true
2837
// - Skip: skip first n elements
38+
// - SkipWhile: skip elements while predicate returns true
2939
// - Reverse: reverse order of elements (materializes stream)
3040
// - SelectMany: flatten sequences (function, not method)
3141
// - GroupBy: group elements by key (function, returns KeyValue pairs)

pkg/glinq/negative_values_test.go

Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
package glinq
2+
3+
import (
4+
"testing"
5+
)
6+
7+
// TestTake_NegativeValue tests that Take with negative values returns empty stream
8+
func TestTake_NegativeValue(t *testing.T) {
9+
t.Run("Take with negative value", func(t *testing.T) {
10+
s := From([]int{1, 2, 3, 4, 5})
11+
result := s.Take(-1).ToSlice()
12+
13+
if len(result) != 0 {
14+
t.Errorf("Expected empty slice for Take(-1), got %v", result)
15+
}
16+
})
17+
18+
t.Run("Take with negative value on empty stream", func(t *testing.T) {
19+
s := Empty[int]()
20+
result := s.Take(-5).ToSlice()
21+
22+
if len(result) != 0 {
23+
t.Errorf("Expected empty slice for Take(-5) on empty stream, got %v", result)
24+
}
25+
})
26+
27+
t.Run("Take with zero value", func(t *testing.T) {
28+
s := From([]int{1, 2, 3})
29+
result := s.Take(0).ToSlice()
30+
31+
if len(result) != 0 {
32+
t.Errorf("Expected empty slice for Take(0), got %v", result)
33+
}
34+
})
35+
36+
t.Run("Take with large negative value", func(t *testing.T) {
37+
s := From([]int{1, 2, 3})
38+
result := s.Take(-1000).ToSlice()
39+
40+
if len(result) != 0 {
41+
t.Errorf("Expected empty slice for Take(-1000), got %v", result)
42+
}
43+
})
44+
45+
t.Run("Take negative preserves size information", func(t *testing.T) {
46+
s := From([]int{1, 2, 3})
47+
result := s.Take(-1)
48+
49+
size, ok := result.Size()
50+
if !ok || size != 0 {
51+
t.Errorf("Expected size 0 for Take(-1), got size=%d, ok=%v", size, ok)
52+
}
53+
})
54+
}
55+
56+
// TestSkip_NegativeValue tests that Skip with negative values treats them as 0
57+
func TestSkip_NegativeValue(t *testing.T) {
58+
t.Run("Skip with negative value", func(t *testing.T) {
59+
s := From([]int{1, 2, 3, 4, 5})
60+
result := s.Skip(-1).ToSlice()
61+
62+
expected := []int{1, 2, 3, 4, 5}
63+
if len(result) != len(expected) {
64+
t.Errorf("Expected %v for Skip(-1), got %v", expected, result)
65+
}
66+
for i, v := range result {
67+
if v != expected[i] {
68+
t.Errorf("Expected %d at index %d, got %d", expected[i], i, v)
69+
}
70+
}
71+
})
72+
73+
t.Run("Skip with large negative value", func(t *testing.T) {
74+
s := From([]int{1, 2, 3})
75+
result := s.Skip(-1000).ToSlice()
76+
77+
expected := []int{1, 2, 3}
78+
if len(result) != len(expected) {
79+
t.Errorf("Expected %v for Skip(-1000), got %v", expected, result)
80+
}
81+
})
82+
83+
t.Run("Skip with zero value", func(t *testing.T) {
84+
s := From([]int{1, 2, 3})
85+
result := s.Skip(0).ToSlice()
86+
87+
expected := []int{1, 2, 3}
88+
if len(result) != len(expected) {
89+
t.Errorf("Expected %v for Skip(0), got %v", expected, result)
90+
}
91+
})
92+
93+
t.Run("Skip negative preserves size information", func(t *testing.T) {
94+
s := From([]int{1, 2, 3, 4, 5})
95+
result := s.Skip(-1)
96+
97+
size, ok := result.Size()
98+
if !ok || size != 5 {
99+
t.Errorf("Expected size 5 for Skip(-1), got size=%d, ok=%v", size, ok)
100+
}
101+
})
102+
103+
t.Run("Skip more than size returns empty", func(t *testing.T) {
104+
s := From([]int{1, 2, 3})
105+
result := s.Skip(10).ToSlice()
106+
107+
if len(result) != 0 {
108+
t.Errorf("Expected empty slice for Skip(10) on stream of size 3, got %v", result)
109+
}
110+
})
111+
112+
t.Run("Skip exactly size returns empty", func(t *testing.T) {
113+
s := From([]int{1, 2, 3})
114+
result := s.Skip(3).ToSlice()
115+
116+
if len(result) != 0 {
117+
t.Errorf("Expected empty slice for Skip(3) on stream of size 3, got %v", result)
118+
}
119+
})
120+
}
121+
122+
// TestRange_NegativeValue tests that Range with negative count returns empty stream
123+
func TestRange_NegativeValue(t *testing.T) {
124+
t.Run("Range with negative count", func(t *testing.T) {
125+
result := Range(1, -1).ToSlice()
126+
127+
if len(result) != 0 {
128+
t.Errorf("Expected empty slice for Range(1, -1), got %v", result)
129+
}
130+
})
131+
132+
t.Run("Range with zero count", func(t *testing.T) {
133+
result := Range(1, 0).ToSlice()
134+
135+
if len(result) != 0 {
136+
t.Errorf("Expected empty slice for Range(1, 0), got %v", result)
137+
}
138+
})
139+
140+
t.Run("Range with large negative count", func(t *testing.T) {
141+
result := Range(1, -1000).ToSlice()
142+
143+
if len(result) != 0 {
144+
t.Errorf("Expected empty slice for Range(1, -1000), got %v", result)
145+
}
146+
})
147+
148+
t.Run("Range negative preserves size information", func(t *testing.T) {
149+
s := Range(1, -5)
150+
151+
size, ok := s.Size()
152+
if !ok || size != 0 {
153+
t.Errorf("Expected size 0 for Range(1, -5), got size=%d, ok=%v", size, ok)
154+
}
155+
})
156+
157+
t.Run("Range with positive count works normally", func(t *testing.T) {
158+
result := Range(1, 5).ToSlice()
159+
expected := []int{1, 2, 3, 4, 5}
160+
161+
if len(result) != len(expected) {
162+
t.Errorf("Expected %v for Range(1, 5), got %v", expected, result)
163+
}
164+
for i, v := range result {
165+
if v != expected[i] {
166+
t.Errorf("Expected %d at index %d, got %d", expected[i], i, v)
167+
}
168+
}
169+
})
170+
}
171+
172+
// TestChunk_NegativeValue tests that Chunk with negative or zero size returns nil
173+
func TestChunk_NegativeValue(t *testing.T) {
174+
t.Run("Chunk with negative size", func(t *testing.T) {
175+
s := From([]int{1, 2, 3, 4, 5})
176+
result := s.Chunk(-1)
177+
178+
if result != nil {
179+
t.Errorf("Expected nil for Chunk(-1), got %v", result)
180+
}
181+
})
182+
183+
t.Run("Chunk with zero size", func(t *testing.T) {
184+
s := From([]int{1, 2, 3, 4, 5})
185+
result := s.Chunk(0)
186+
187+
if result != nil {
188+
t.Errorf("Expected nil for Chunk(0), got %v", result)
189+
}
190+
})
191+
192+
t.Run("Chunk with large negative size", func(t *testing.T) {
193+
s := From([]int{1, 2, 3, 4, 5})
194+
result := s.Chunk(-1000)
195+
196+
if result != nil {
197+
t.Errorf("Expected nil for Chunk(-1000), got %v", result)
198+
}
199+
})
200+
201+
t.Run("Chunk with positive size works normally", func(t *testing.T) {
202+
s := From([]int{1, 2, 3, 4, 5, 6, 7})
203+
result := s.Chunk(3)
204+
205+
expected := [][]int{{1, 2, 3}, {4, 5, 6}, {7}}
206+
if len(result) != len(expected) {
207+
t.Errorf("Expected %v for Chunk(3), got %v", expected, result)
208+
}
209+
})
210+
}
211+
212+
// TestTakeOrderedBy_NegativeValue tests that TakeOrderedBy with negative values returns empty stream
213+
func TestTakeOrderedBy_NegativeValue(t *testing.T) {
214+
t.Run("TakeOrderedBy with negative value", func(t *testing.T) {
215+
s := From([]int{5, 2, 8, 1, 9, 3})
216+
result := TakeOrderedBy(s, -1, func(a, b int) bool { return a < b }).ToSlice()
217+
218+
if len(result) != 0 {
219+
t.Errorf("Expected empty slice for TakeOrderedBy(-1), got %v", result)
220+
}
221+
})
222+
223+
t.Run("TakeOrderedBy with zero value", func(t *testing.T) {
224+
s := From([]int{5, 2, 8, 1, 9, 3})
225+
result := TakeOrderedBy(s, 0, func(a, b int) bool { return a < b }).ToSlice()
226+
227+
if len(result) != 0 {
228+
t.Errorf("Expected empty slice for TakeOrderedBy(0), got %v", result)
229+
}
230+
})
231+
232+
t.Run("TakeOrderedBy with positive value works normally", func(t *testing.T) {
233+
s := From([]int{5, 2, 8, 1, 9, 3})
234+
result := TakeOrderedBy(s, 3, func(a, b int) bool { return a < b }).ToSlice()
235+
236+
// TakeOrderedBy returns the 3 smallest elements in sorted order
237+
if len(result) != 3 {
238+
t.Errorf("Expected length 3 for TakeOrderedBy(3), got %d", len(result))
239+
}
240+
// Check that we got 3 elements and they are sorted
241+
if len(result) >= 1 && result[0] != 1 {
242+
t.Errorf("Expected smallest element to be 1, got %d", result[0])
243+
}
244+
// Verify all elements are from the source
245+
validElements := map[int]bool{1: true, 2: true, 3: true, 5: true, 8: true, 9: true}
246+
for _, v := range result {
247+
if !validElements[v] {
248+
t.Errorf("Got unexpected element %d", v)
249+
}
250+
}
251+
// Verify they are in ascending order
252+
for i := 1; i < len(result); i++ {
253+
if result[i-1] > result[i] {
254+
t.Errorf("Result not sorted: %v", result)
255+
}
256+
}
257+
})
258+
}
259+
260+
// TestNegativeValues_Chain tests chaining operations with negative values
261+
func TestNegativeValues_Chain(t *testing.T) {
262+
t.Run("Chain with Take negative", func(t *testing.T) {
263+
s := From([]int{1, 2, 3, 4, 5})
264+
result := s.Where(func(x int) bool { return x > 2 }).
265+
Take(-1).
266+
Select(func(x int) int { return x * 2 }).
267+
ToSlice()
268+
269+
if len(result) != 0 {
270+
t.Errorf("Expected empty slice for chain with Take(-1), got %v", result)
271+
}
272+
})
273+
274+
t.Run("Chain with Skip negative", func(t *testing.T) {
275+
s := From([]int{1, 2, 3, 4, 5})
276+
result := s.Skip(-1).
277+
Where(func(x int) bool { return x > 2 }).
278+
Take(2).
279+
ToSlice()
280+
281+
expected := []int{3, 4}
282+
if len(result) != len(expected) {
283+
t.Errorf("Expected %v for chain with Skip(-1), got %v", expected, result)
284+
}
285+
})
286+
287+
t.Run("Multiple negative operations", func(t *testing.T) {
288+
s := From([]int{1, 2, 3})
289+
result := s.Take(-5).Skip(-10).ToSlice()
290+
291+
if len(result) != 0 {
292+
t.Errorf("Expected empty slice for Take(-5).Skip(-10), got %v", result)
293+
}
294+
})
295+
}

0 commit comments

Comments
 (0)