Skip to content

Commit 9e60b89

Browse files
committed
AggregateBy added
1 parent 3f96daa commit 9e60b89

File tree

10 files changed

+317
-70
lines changed

10 files changed

+317
-70
lines changed

aggregateby.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package go2linq
2+
3+
import (
4+
"iter"
5+
6+
"github.com/solsw/errorhelper"
7+
"github.com/solsw/generichelper"
8+
)
9+
10+
// [AggregateBy] applies an accumulator function over a sequence, grouping results by key.
11+
// 'seed' is the initial accumulator value for each key.
12+
// Key values are compared using [generichelper.DeepEqual]. 'source' is enumerated immediately.
13+
//
14+
// [AggregateBy]: https://learn.microsoft.com/dotnet/api/system.linq.enumerable.aggregateby
15+
func AggregateBy[Source, Key, Accumulate any](source iter.Seq[Source], keySelector func(Source) Key,
16+
seed Accumulate, accumulator func(Accumulate, Source) Accumulate) (iter.Seq[generichelper.Tuple2[Key, Accumulate]], error) {
17+
return AggregateByEq(source, keySelector, seed, accumulator, generichelper.DeepEqual[Key])
18+
}
19+
20+
// [AggregateByEq] applies an accumulator function over a sequence, grouping results by key.
21+
// 'seed' is the initial accumulator value for each key.
22+
// Key values are compared using 'keyEqual'. 'source' is enumerated immediately.
23+
//
24+
// [AggregateByEq]: https://learn.microsoft.com/dotnet/api/system.linq.enumerable.aggregateby
25+
func AggregateByEq[Source, Key, Accumulate any](source iter.Seq[Source],
26+
keySelector func(Source) Key, seed Accumulate, accumulator func(Accumulate, Source) Accumulate,
27+
keyEqual func(Key, Key) bool) (iter.Seq[generichelper.Tuple2[Key, Accumulate]], error) {
28+
return AggregateBySelEq(source, keySelector, func(Key) Accumulate { return seed }, accumulator, keyEqual)
29+
}
30+
31+
// [AggregateBySel] applies an accumulator function over a sequence, grouping results by key.
32+
// 'seedSelector' is a factory for the initial accumulator value for each particular key.
33+
// Key values are compared using [generichelper.DeepEqual]. 'source' is enumerated immediately.
34+
//
35+
// [AggregateBySel]: https://learn.microsoft.com/dotnet/api/system.linq.enumerable.aggregateby
36+
func AggregateBySel[Source, Key, Accumulate any](source iter.Seq[Source], keySelector func(Source) Key,
37+
seedSelector func(Key) Accumulate, accumulator func(Accumulate, Source) Accumulate) (iter.Seq[generichelper.Tuple2[Key, Accumulate]], error) {
38+
return AggregateBySelEq(source, keySelector, seedSelector, accumulator, generichelper.DeepEqual[Key])
39+
}
40+
41+
// [AggregateBySelEq] applies an accumulator function over a sequence, grouping results by key.
42+
// 'seedSelector' is a factory for the initial accumulator value for each particular key.
43+
// Key values are compared using 'keyEqual'. 'source' is enumerated immediately.
44+
//
45+
// [AggregateBySelEq]: https://learn.microsoft.com/dotnet/api/system.linq.enumerable.aggregateby
46+
func AggregateBySelEq[Source, Key, Accumulate any](source iter.Seq[Source], keySelector func(Source) Key,
47+
seedSelector func(Key) Accumulate, accumulator func(Accumulate, Source) Accumulate,
48+
keyEqual func(Key, Key) bool) (iter.Seq[generichelper.Tuple2[Key, Accumulate]], error) {
49+
if source == nil {
50+
return nil, errorhelper.CallerError(ErrNilSource)
51+
}
52+
if keySelector == nil || seedSelector == nil {
53+
return nil, errorhelper.CallerError(ErrNilSelector)
54+
}
55+
if accumulator == nil {
56+
return nil, errorhelper.CallerError(ErrNilAccumulator)
57+
}
58+
if keyEqual == nil {
59+
return nil, errorhelper.CallerError(ErrNilEqual)
60+
}
61+
gg, _ := GroupByEq(source, keySelector, keyEqual)
62+
r, _ := Select(gg, func(g Grouping[Key, Source]) generichelper.Tuple2[Key, Accumulate] {
63+
ag, _ := AggregateSeed(g.Values(), seedSelector(g.key), accumulator)
64+
return generichelper.Tuple2[Key, Accumulate]{Item1: g.key, Item2: ag}
65+
})
66+
return r, nil
67+
}

aggregateby_test.go

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
package go2linq
2+
3+
import (
4+
"errors"
5+
"iter"
6+
"testing"
7+
8+
"github.com/solsw/generichelper"
9+
)
10+
11+
func TestAggregateBy_string_int_int(t *testing.T) {
12+
type args struct {
13+
source iter.Seq[string]
14+
keySelector func(string) int
15+
seed int
16+
accumulator func(int, string) int
17+
}
18+
tests := []struct {
19+
name string
20+
args args
21+
want iter.Seq[generichelper.Tuple2[int, int]]
22+
wantErr bool
23+
}{
24+
{name: "Regular",
25+
args: args{
26+
source: VarToSeq("one", "two", "three", "four", "five", "six"),
27+
keySelector: func(s string) int { return len(s) },
28+
seed: 0,
29+
accumulator: func(ac int, el string) int { return ac + len(el) },
30+
},
31+
want: VarToSeq(
32+
generichelper.NewTuple2(3, 9),
33+
generichelper.NewTuple2(5, 5),
34+
generichelper.NewTuple2(4, 8),
35+
),
36+
},
37+
}
38+
for _, tt := range tests {
39+
t.Run(tt.name, func(t *testing.T) {
40+
got, err := AggregateBy(tt.args.source, tt.args.keySelector, tt.args.seed, tt.args.accumulator)
41+
if (err != nil) != tt.wantErr {
42+
t.Errorf("AggregateBy() error = %v, wantErr %v", err, tt.wantErr)
43+
return
44+
}
45+
equal, _ := SequenceEqual(got, tt.want)
46+
if !equal {
47+
t.Errorf("AggregateBy() = %v, want %v", StringDef(got), StringDef(tt.want))
48+
}
49+
})
50+
}
51+
}
52+
53+
func TestAggregateByEq_string_int_int(t *testing.T) {
54+
type args struct {
55+
source iter.Seq[string]
56+
keySelector func(string) int
57+
seed int
58+
accumulator func(int, string) int
59+
keyEqual func(int, int) bool
60+
}
61+
tests := []struct {
62+
name string
63+
args args
64+
want iter.Seq[generichelper.Tuple2[int, int]]
65+
wantErr bool
66+
}{
67+
{name: "Regular",
68+
args: args{
69+
source: VarToSeq("one", "two", "three", "four", "five", "six"),
70+
keySelector: func(s string) int { return len(s) },
71+
seed: 0,
72+
accumulator: func(ac int, el string) int { return ac + len(el) },
73+
keyEqual: func(a, b int) bool { return a == b },
74+
},
75+
want: VarToSeq(
76+
generichelper.NewTuple2(3, 9),
77+
generichelper.NewTuple2(5, 5),
78+
generichelper.NewTuple2(4, 8),
79+
),
80+
},
81+
}
82+
for _, tt := range tests {
83+
t.Run(tt.name, func(t *testing.T) {
84+
got, err := AggregateByEq(tt.args.source, tt.args.keySelector, tt.args.seed, tt.args.accumulator, tt.args.keyEqual)
85+
if (err != nil) != tt.wantErr {
86+
t.Errorf("AggregateByEq() error = %v, wantErr %v", err, tt.wantErr)
87+
return
88+
}
89+
equal, _ := SequenceEqual(got, tt.want)
90+
if !equal {
91+
t.Errorf("AggregateByEq() = %v, want %v", StringDef(got), StringDef(tt.want))
92+
}
93+
})
94+
}
95+
}
96+
97+
func TestAggregateBySelEq_string_int_int(t *testing.T) {
98+
type args struct {
99+
source iter.Seq[string]
100+
keySelector func(string) int
101+
seedSelector func(int) int
102+
accumulator func(int, string) int
103+
keyEqual func(int, int) bool
104+
}
105+
tests := []struct {
106+
name string
107+
args args
108+
want iter.Seq[generichelper.Tuple2[int, int]]
109+
wantErr bool
110+
expectedErr error
111+
}{
112+
{name: "NilSource",
113+
args: args{
114+
source: nil,
115+
keySelector: func(s string) int { return len(s) },
116+
seedSelector: func(k int) int { return k },
117+
accumulator: func(ac int, el string) int { return ac + len(el) },
118+
keyEqual: func(a, b int) bool { return a == b },
119+
},
120+
wantErr: true,
121+
expectedErr: ErrNilSource,
122+
},
123+
{name: "NilKeySelector",
124+
args: args{
125+
source: VarToSeq("one", "two", "three", "four", "five", "six"),
126+
keySelector: nil,
127+
seedSelector: func(k int) int { return k },
128+
accumulator: func(ac int, el string) int { return ac + len(el) },
129+
keyEqual: func(a, b int) bool { return a == b },
130+
},
131+
wantErr: true,
132+
expectedErr: ErrNilSelector,
133+
},
134+
{name: "NilSeedSelector",
135+
args: args{
136+
source: VarToSeq("one", "two", "three", "four", "five", "six"),
137+
keySelector: func(s string) int { return len(s) },
138+
seedSelector: nil,
139+
accumulator: func(ac int, el string) int { return ac + len(el) },
140+
keyEqual: func(a, b int) bool { return a == b },
141+
},
142+
wantErr: true,
143+
expectedErr: ErrNilSelector,
144+
},
145+
{name: "NilAccumulator",
146+
args: args{
147+
source: VarToSeq("one", "two", "three", "four", "five", "six"),
148+
keySelector: func(s string) int { return len(s) },
149+
seedSelector: func(k int) int { return k },
150+
accumulator: nil,
151+
keyEqual: func(a, b int) bool { return a == b },
152+
},
153+
wantErr: true,
154+
expectedErr: ErrNilAccumulator,
155+
},
156+
{name: "NilEqual",
157+
args: args{
158+
source: VarToSeq("one", "two", "three", "four", "five", "six"),
159+
keySelector: func(s string) int { return len(s) },
160+
seedSelector: func(k int) int { return k },
161+
accumulator: func(ac int, el string) int { return ac + len(el) },
162+
keyEqual: nil,
163+
},
164+
wantErr: true,
165+
expectedErr: ErrNilEqual,
166+
},
167+
{name: "Regular",
168+
args: args{
169+
source: VarToSeq("one", "two", "three", "four", "five", "six"),
170+
keySelector: func(s string) int { return len(s) },
171+
seedSelector: func(k int) int { return k },
172+
accumulator: func(ac int, el string) int { return ac + len(el) },
173+
keyEqual: func(a, b int) bool { return a == b },
174+
},
175+
want: VarToSeq(
176+
generichelper.NewTuple2(3, 12),
177+
generichelper.NewTuple2(5, 10),
178+
generichelper.NewTuple2(4, 12),
179+
),
180+
},
181+
}
182+
for _, tt := range tests {
183+
t.Run(tt.name, func(t *testing.T) {
184+
got, err := AggregateBySelEq(tt.args.source, tt.args.keySelector, tt.args.seedSelector, tt.args.accumulator, tt.args.keyEqual)
185+
if (err != nil) != tt.wantErr {
186+
t.Errorf("AggregateBySelEq() error = %v, wantErr %v", err, tt.wantErr)
187+
return
188+
}
189+
if tt.wantErr {
190+
if !errors.Is(err, tt.expectedErr) {
191+
t.Errorf("AggregateByEq() error = %v, expectedErr %v", err, tt.expectedErr)
192+
}
193+
return
194+
}
195+
equal, _ := SequenceEqual(got, tt.want)
196+
if !equal {
197+
t.Errorf("AggregateBySelEq() = %v, want %v", StringDef(got), StringDef(tt.want))
198+
}
199+
})
200+
}
201+
}

distinctby.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,25 +26,25 @@ func DistinctBy[Source, Key any](source iter.Seq[Source], keySelector func(Sourc
2626
}
2727

2828
// [DistinctByEq] returns distinct elements from a sequence according to
29-
// a specified key selector function and using a specified 'equal' to compare keys.
29+
// a specified key selector function and using a specified 'keyEqual' to compare keys.
3030
//
3131
// [DistinctByEq]: https://learn.microsoft.com/dotnet/api/system.linq.enumerable.distinctby
3232
func DistinctByEq[Source, Key any](source iter.Seq[Source],
33-
keySelector func(Source) Key, equal func(Key, Key) bool) (iter.Seq[Source], error) {
33+
keySelector func(Source) Key, keyEqual func(Key, Key) bool) (iter.Seq[Source], error) {
3434
if source == nil {
3535
return nil, errorhelper.CallerError(ErrNilSource)
3636
}
3737
if keySelector == nil {
3838
return nil, errorhelper.CallerError(ErrNilSelector)
3939
}
40-
if equal == nil {
40+
if keyEqual == nil {
4141
return nil, errorhelper.CallerError(ErrNilEqual)
4242
}
4343
return func(yield func(Source) bool) {
4444
var seen []Key
4545
for s := range source {
4646
k := keySelector(s)
47-
if !elInElelEq(k, seen, equal) {
47+
if !elInElelEq(k, seen, keyEqual) {
4848
seen = append(seen, k)
4949
if !yield(s) {
5050
return

exceptby.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,24 +37,24 @@ func ExceptBy[Source, Key any](first iter.Seq[Source], second iter.Seq[Key], key
3737
//
3838
// [ExceptByEq]: https://learn.microsoft.com/dotnet/api/system.linq.enumerable.exceptby
3939
func ExceptByEq[Source, Key any](first iter.Seq[Source], second iter.Seq[Key],
40-
keySelector func(Source) Key, equal func(Key, Key) bool) (iter.Seq[Source], error) {
40+
keySelector func(Source) Key, keyEqual func(Key, Key) bool) (iter.Seq[Source], error) {
4141
if first == nil || second == nil {
4242
return nil, errorhelper.CallerError(ErrNilSource)
4343
}
4444
if keySelector == nil {
4545
return nil, errorhelper.CallerError(ErrNilSelector)
4646
}
47-
if equal == nil {
47+
if keyEqual == nil {
4848
return nil, errorhelper.CallerError(ErrNilEqual)
4949
}
5050
return func(yield func(Source) bool) {
5151
distinct1, _ := Distinct(first)
5252
var once sync.Once
5353
var distinct2 []Key
5454
for s := range distinct1 {
55-
once.Do(func() { deq2, _ := DistinctEq(second, equal); distinct2 = slices.Collect(deq2) })
55+
once.Do(func() { deq2, _ := DistinctEq(second, keyEqual); distinct2 = slices.Collect(deq2) })
5656
k := keySelector(s)
57-
if !elInElelEq(k, distinct2, equal) {
57+
if !elInElelEq(k, distinct2, keyEqual) {
5858
if !yield(s) {
5959
return
6060
}

0 commit comments

Comments
 (0)