Skip to content

Commit 39c552b

Browse files
author
Roman Sorokin
committed
add size
1 parent 52a3f44 commit 39c552b

File tree

8 files changed

+570
-11
lines changed

8 files changed

+570
-11
lines changed

pkg/glinq/distinct.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package glinq
33
// Distinct removes duplicates from Stream.
44
// T must be comparable, otherwise code will not compile.
55
// This is a function (not a method) because methods cannot have their own type constraints.
6+
//
7+
// SIZE: Loses size (unknown how many duplicates exist).
68
func Distinct[T comparable](enum Enumerable[T]) Stream[T] {
79
return &stream[T]{
810
sourceFactory: func() func() (T, bool) {
@@ -23,10 +25,13 @@ func Distinct[T comparable](enum Enumerable[T]) Stream[T] {
2325
}
2426
}
2527
},
28+
size: nil, // LOSE: unknown how many duplicates
2629
}
2730
}
2831

2932
// DistinctBy removes duplicates by key extracted by keySelector.
33+
//
34+
// SIZE: Loses size (unknown how many duplicates exist).
3035
func (s *stream[T]) DistinctBy(keySelector func(T) any) Stream[T] {
3136
return &stream[T]{
3237
sourceFactory: func() func() (T, bool) {
@@ -49,5 +54,6 @@ func (s *stream[T]) DistinctBy(keySelector func(T) any) Stream[T] {
4954
}
5055
}
5156
},
57+
size: nil, // LOSE: unknown how many duplicates
5258
}
5359
}

pkg/glinq/kv.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ func FromMap[K comparable, V any](m map[K]V) Stream[KeyValue[K, V]] {
2727
keys = append(keys, key)
2828
}
2929

30+
size := len(m)
3031
return &stream[KeyValue[K, V]]{
3132
sourceFactory: func() func() (KeyValue[K, V], bool) {
3233
index := 0 // Fresh index for each iterator
@@ -41,6 +42,7 @@ func FromMap[K comparable, V any](m map[K]V) Stream[KeyValue[K, V]] {
4142
return KeyValue[K, V]{Key: key, Value: value}, true
4243
}
4344
},
45+
size: &size,
4446
}
4547
}
4648

@@ -64,6 +66,7 @@ func FromMapSafe[K comparable, V any](m map[K]V) Stream[KeyValue[K, V]] {
6466
pairs = append(pairs, KeyValue[K, V]{Key: key, Value: value})
6567
}
6668

69+
size := len(pairs)
6770
return &stream[KeyValue[K, V]]{
6871
sourceFactory: func() func() (KeyValue[K, V], bool) {
6972
index := 0 // Fresh index for each iterator
@@ -76,11 +79,19 @@ func FromMapSafe[K comparable, V any](m map[K]V) Stream[KeyValue[K, V]] {
7679
return result, true
7780
}
7881
},
82+
size: &size,
7983
}
8084
}
8185

8286
// Keys extracts only keys from Enumerable[KeyValue].
87+
// SIZE: Preserves size if source is Sizable (1-to-1 transformation).
8388
func Keys[K comparable, V any](enum Enumerable[KeyValue[K, V]]) Stream[K] {
89+
var size *int
90+
if sizable, ok := enum.(Sizable[KeyValue[K, V]]); ok {
91+
if s, known := sizable.Size(); known {
92+
size = &s
93+
}
94+
}
8495
return &stream[K]{
8596
sourceFactory: func() func() (K, bool) {
8697
return func() (K, bool) {
@@ -92,11 +103,19 @@ func Keys[K comparable, V any](enum Enumerable[KeyValue[K, V]]) Stream[K] {
92103
return kv.Key, true
93104
}
94105
},
106+
size: size,
95107
}
96108
}
97109

98110
// Values extracts only values from Enumerable[KeyValue].
111+
// SIZE: Preserves size if source is Sizable (1-to-1 transformation).
99112
func Values[K comparable, V any](enum Enumerable[KeyValue[K, V]]) Stream[V] {
113+
var size *int
114+
if sizable, ok := enum.(Sizable[KeyValue[K, V]]); ok {
115+
if s, known := sizable.Size(); known {
116+
size = &s
117+
}
118+
}
100119
return &stream[V]{
101120
sourceFactory: func() func() (V, bool) {
102121
return func() (V, bool) {
@@ -108,6 +127,7 @@ func Values[K comparable, V any](enum Enumerable[KeyValue[K, V]]) Stream[V] {
108127
return kv.Value, true
109128
}
110129
},
130+
size: size,
111131
}
112132
}
113133

@@ -158,6 +178,8 @@ func GroupBy[T any, K comparable](enum Enumerable[T], keySelector func(T) K) Str
158178
pairs = append(pairs, KeyValue[K, []T]{Key: key, Value: values})
159179
}
160180

181+
// SIZE: Unknown - depends on how many unique keys exist
182+
size := len(pairs)
161183
return &stream[KeyValue[K, []T]]{
162184
sourceFactory: func() func() (KeyValue[K, []T], bool) {
163185
index := 0 // Fresh index for each iterator
@@ -170,5 +192,6 @@ func GroupBy[T any, K comparable](enum Enumerable[T], keySelector func(T) K) Str
170192
return result, true
171193
}
172194
},
195+
size: &size, // Actually we know it after materialization
173196
}
174197
}

pkg/glinq/operators.go

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package glinq
33
import "sort"
44

55
// Where filters elements by predicate.
6+
//
7+
// SIZE: Loses size (unknown how many elements pass filter).
68
func (s *stream[T]) Where(predicate func(T) bool) Stream[T] {
79
return &stream[T]{
810
sourceFactory: func() func() (T, bool) {
@@ -20,12 +22,15 @@ func (s *stream[T]) Where(predicate func(T) bool) Stream[T] {
2022
}
2123
}
2224
},
25+
size: nil, // LOSE: unknown how many pass filter
2326
}
2427
}
2528

2629
// Select transforms elements to the same type.
2730
// Supports method chaining.
2831
//
32+
// SIZE: Preserves size (1-to-1 transformation).
33+
//
2934
// Example:
3035
//
3136
// doubled := From([]int{1, 2, 3}).
@@ -45,12 +50,15 @@ func (s *stream[T]) Select(mapper func(T) T) Stream[T] {
4550
return mapper(value), true
4651
}
4752
},
53+
size: s.size, // PRESERVE: 1-to-1 transformation
4854
}
4955
}
5056

5157
// SelectWithIndex transforms elements to the same type, providing index to mapper function.
5258
// Supports method chaining.
5359
//
60+
// SIZE: Preserves size (1-to-1 transformation).
61+
//
5462
// Example:
5563
//
5664
// doubled := From([]int{1, 2, 3}).
@@ -73,12 +81,15 @@ func (s *stream[T]) SelectWithIndex(mapper func(T, int) T) Stream[T] {
7381
return result, true
7482
}
7583
},
84+
size: s.size, // PRESERVE: 1-to-1 transformation
7685
}
7786
}
7887

7988
// Map transforms elements to a different type.
8089
// This is a function (not a method) because in Go methods cannot have their own type parameters.
8190
//
91+
// SIZE: Preserves size if source is Sizable (1-to-1 transformation).
92+
//
8293
// Example:
8394
//
8495
// strings := Select(
@@ -87,6 +98,12 @@ func (s *stream[T]) SelectWithIndex(mapper func(T, int) T) Stream[T] {
8798
// ).ToSlice()
8899
// // []string{"num_1", "num_2", "num_3"}
89100
func Select[T, R any](enum Enumerable[T], mapper func(T) R) Stream[R] {
101+
var size *int
102+
if sizable, ok := enum.(Sizable[T]); ok {
103+
if s, known := sizable.Size(); known {
104+
size = &s
105+
}
106+
}
90107
return &stream[R]{
91108
sourceFactory: func() func() (R, bool) {
92109
return func() (R, bool) {
@@ -98,12 +115,15 @@ func Select[T, R any](enum Enumerable[T], mapper func(T) R) Stream[R] {
98115
return mapper(value), true
99116
}
100117
},
118+
size: size, // PRESERVE if possible
101119
}
102120
}
103121

104122
// SelectWithIndex transforms elements to a different type, providing index to mapper function.
105123
// This is a function (not a method) because in Go methods cannot have their own type parameters.
106124
//
125+
// SIZE: Preserves size if source is Sizable (1-to-1 transformation).
126+
//
107127
// Example:
108128
//
109129
// strings := SelectWithIndex(
@@ -112,6 +132,12 @@ func Select[T, R any](enum Enumerable[T], mapper func(T) R) Stream[R] {
112132
// ).ToSlice()
113133
// // []string{"num_1_at_0", "num_2_at_1", "num_3_at_2"}
114134
func SelectWithIndex[T, R any](enum Enumerable[T], mapper func(T, int) R) Stream[R] {
135+
var size *int
136+
if sizable, ok := enum.(Sizable[T]); ok {
137+
if s, known := sizable.Size(); known {
138+
size = &s
139+
}
140+
}
115141
return &stream[R]{
116142
sourceFactory: func() func() (R, bool) {
117143
index := 0 // Fresh counter
@@ -126,11 +152,26 @@ func SelectWithIndex[T, R any](enum Enumerable[T], mapper func(T, int) R) Stream
126152
return result, true
127153
}
128154
},
155+
size: size, // PRESERVE if possible
129156
}
130157
}
131158

132159
// Take takes the first n elements from Stream.
160+
//
161+
// SIZE: Calculated as min(sourceSize, n) if source size known, else n.
133162
func (s *stream[T]) Take(n int) Stream[T] {
163+
var newSize *int
164+
if s.size != nil {
165+
size := *s.size
166+
if size < n {
167+
newSize = &size
168+
} else {
169+
newSize = &n
170+
}
171+
} else {
172+
newSize = &n // Don't know source size, but result won't exceed n
173+
}
174+
134175
return &stream[T]{
135176
sourceFactory: func() func() (T, bool) {
136177
source := s.sourceFactory() // Get fresh source
@@ -149,11 +190,24 @@ func (s *stream[T]) Take(n int) Stream[T] {
149190
return value, true
150191
}
151192
},
193+
size: newSize, // CALCULATED: min(source, n)
152194
}
153195
}
154196

155197
// Skip skips the first n elements from Stream.
198+
//
199+
// SIZE: Calculated as max(0, sourceSize - n) if source size known, else unknown.
156200
func (s *stream[T]) Skip(n int) Stream[T] {
201+
var newSize *int
202+
if s.size != nil {
203+
size := *s.size - n
204+
if size < 0 {
205+
size = 0
206+
}
207+
newSize = &size
208+
}
209+
// else: nil (unknown)
210+
157211
return &stream[T]{
158212
sourceFactory: func() func() (T, bool) {
159213
source := s.sourceFactory() // Get fresh source
@@ -170,6 +224,7 @@ func (s *stream[T]) Skip(n int) Stream[T] {
170224
return source()
171225
}
172226
},
227+
size: newSize, // CALCULATED: max(0, source - n) or nil
173228
}
174229
}
175230

@@ -207,6 +262,8 @@ func heapifyDown[T any](items []T, i, n int, less func(a, b T) bool) {
207262
// TakeOrderedBy returns the first n elements ordered by the less function.
208263
// Uses a heap-based algorithm for efficient processing.
209264
//
265+
// SIZE: Calculated as min(sourceSize, n) if source size known, else n.
266+
//
210267
// Example:
211268
//
212269
// numbers := []int{5, 2, 8, 1, 9, 3}
@@ -217,6 +274,21 @@ func TakeOrderedBy[T any](enum Enumerable[T], n int, less func(a, b T) bool) Str
217274
return Empty[T]()
218275
}
219276

277+
var size *int
278+
if sizable, ok := enum.(Sizable[T]); ok {
279+
if s, known := sizable.Size(); known {
280+
if s < n {
281+
size = &s
282+
} else {
283+
size = &n
284+
}
285+
} else {
286+
size = &n // Don't know source size, but result won't exceed n
287+
}
288+
} else {
289+
size = &n // Don't know source size, but result won't exceed n
290+
}
291+
220292
return &stream[T]{
221293
sourceFactory: func() func() (T, bool) {
222294
var heap []T
@@ -269,6 +341,7 @@ func TakeOrderedBy[T any](enum Enumerable[T], n int, less func(a, b T) bool) Str
269341
return result, true
270342
}
271343
},
344+
size: size, // CALCULATED: min(source, n)
272345
}
273346
}
274347

@@ -286,17 +359,21 @@ func TakeOrderedDescendingBy[T any](enum Enumerable[T], n int, less func(a, b T)
286359

287360
// Reverse reverses the order of elements in the Stream.
288361
// NOTE: Reverse materializes the entire stream (partially lazy).
362+
//
363+
// SIZE: Preserves size (1-to-1 transformation, materializes).
289364
func (s *stream[T]) Reverse() Stream[T] {
290365
items := s.ToSlice()
291366
for i, j := 0, len(items)-1; i < j; i, j = i+1, j-1 {
292367
items[i], items[j] = items[j], items[i]
293368
}
294-
return From(items)
369+
return From(items) // From preserves size
295370
}
296371

297372
// SelectMany transforms each element into a sequence and flattens the resulting sequences.
298373
// This is a function (not a method) because in Go methods cannot have their own type parameters.
299374
//
375+
// SIZE: Loses size (1-to-many transformation, unknown result count).
376+
//
300377
// Example:
301378
//
302379
// numbers := [][]int{{1, 2}, {3, 4}, {5}}
@@ -336,5 +413,6 @@ func SelectMany[T, R any](enum Enumerable[T], selector func(T) Enumerable[R]) St
336413
}
337414
}
338415
},
416+
size: nil, // LOSE: 1-to-many transformation
339417
}
340418
}

pkg/glinq/ordering.go

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,20 @@ import "sort"
44

55
// orderBy is a common sorting function used by OrderBy and OrderByDescending.
66
func (s *stream[T]) orderBy(ascending bool, comparator func(T, T) int) Stream[T] {
7-
return &stream[T]{
8-
sourceFactory: func() func() (T, bool) {
9-
sorted := s.ToSlice()
7+
sorted := s.ToSlice()
108

11-
sort.Slice(sorted, func(i, j int) bool {
12-
cmp := comparator(sorted[i], sorted[j])
13-
if ascending {
14-
return cmp < 0
15-
}
16-
return cmp > 0
17-
})
9+
sort.Slice(sorted, func(i, j int) bool {
10+
cmp := comparator(sorted[i], sorted[j])
11+
if ascending {
12+
return cmp < 0
13+
}
14+
return cmp > 0
15+
})
1816

17+
// SIZE: Preserves size (1-to-1 transformation, materializes)
18+
size := len(sorted)
19+
return &stream[T]{
20+
sourceFactory: func() func() (T, bool) {
1921
index := 0 // Fresh index for each iterator
2022
return func() (T, bool) {
2123
if index >= len(sorted) {
@@ -27,6 +29,7 @@ func (s *stream[T]) orderBy(ascending bool, comparator func(T, T) int) Stream[T]
2729
return result, true
2830
}
2931
},
32+
size: &size, // PRESERVE: 1-to-1 transformation
3033
}
3134
}
3235

0 commit comments

Comments
 (0)