Skip to content

Commit 268e101

Browse files
committed
add Stream: Count, AnyMatchm NoneMatch, AllMatch
1 parent 3018433 commit 268e101

File tree

2 files changed

+267
-1
lines changed

2 files changed

+267
-1
lines changed

stream.go

+69-1
Original file line numberDiff line numberDiff line change
@@ -273,12 +273,12 @@ func (s Stream[T]) Intersperse(e T) Stream[T] {
273273

274274
go func() {
275275
defer close(outstream)
276+
276277
if s.stream == nil {
277278
return
278279
}
279280

280281
// this is to get around the inability to test generic types for nil in Go 1.18
281-
// nolint: wsl
282282
select {
283283
case val, ok := <-s.stream:
284284
if !ok {
@@ -318,6 +318,74 @@ func (s Stream[T]) GroupBy(classifier Function[T, Any]) map[Any][]T {
318318
return resultMap
319319
}
320320

321+
// Count the number of elements in the stream.
322+
//
323+
// This is a continuous terminal operation and hence expects
324+
// the producer to close the stream in order to complete (or
325+
// it will block).
326+
func (s Stream[T]) Count() int {
327+
if s.stream == nil {
328+
return 0
329+
}
330+
331+
count := 0
332+
for range s.stream {
333+
count++
334+
}
335+
336+
return count
337+
}
338+
339+
// AllMatch returns whether all of the elements in the stream
340+
// satisfy the predicate.
341+
//
342+
// This is a continuous terminal operation and hence expects
343+
// the producer to close the stream in order to complete (or
344+
// it will block).
345+
func (s Stream[T]) AllMatch(p Predicate[T]) bool {
346+
if s.stream == nil {
347+
return false
348+
}
349+
350+
for val := range s.stream {
351+
if !p(val) {
352+
return false
353+
}
354+
}
355+
356+
return true
357+
}
358+
359+
// AnyMatch returns whether any of the elements in the stream
360+
// satisfies the predicate.
361+
//
362+
// This is a continuous terminal operation and hence expects
363+
// the producer to close the stream in order to complete (or
364+
// it will block).
365+
func (s Stream[T]) AnyMatch(p Predicate[T]) bool {
366+
if s.stream == nil {
367+
return false
368+
}
369+
370+
for val := range s.stream {
371+
if p(val) {
372+
return true
373+
}
374+
}
375+
376+
return false
377+
}
378+
379+
// NoneMatch returns whether none of the elements in the stream
380+
// satisfies the predicate. It is the opposite of AnyMatch.
381+
//
382+
// This is a continuous terminal operation and hence expects
383+
// the producer to close the stream in order to complete (or
384+
// it will block).
385+
func (s Stream[T]) NoneMatch(p Predicate[T]) bool {
386+
return !s.AnyMatch(p)
387+
}
388+
321389
// ForEach executes the given consumer function for each entry in this stream.
322390
//
323391
// This is a continuous terminal operation. It will only complete if the producer closes the stream.

stream_test.go

+198
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,204 @@ func TestStream_GroupBy(t *testing.T) {
484484
}
485485
}
486486

487+
func TestStream_Count(t *testing.T) {
488+
tt := map[string]struct {
489+
stream chan int
490+
want int
491+
}{
492+
"Should return 0 for a nil channel": {
493+
stream: nil,
494+
want: 0,
495+
},
496+
"Should return 0 for an empty closed channel": {
497+
stream: func() chan int {
498+
c := make(chan int)
499+
go func() {
500+
defer close(c)
501+
}()
502+
return c
503+
}(),
504+
want: 0,
505+
},
506+
"Should return 3 for a size 3 closed channel": {
507+
stream: func() chan int {
508+
c := make(chan int, 1)
509+
go func() {
510+
defer close(c)
511+
c <- 1
512+
c <- 2
513+
c <- 1
514+
}()
515+
return c
516+
}(),
517+
want: 3,
518+
},
519+
}
520+
521+
for name, tc := range tt {
522+
tc := tc
523+
524+
t.Run(name, func(t *testing.T) {
525+
s := Stream[int]{
526+
stream: tc.stream,
527+
}
528+
if got := s.Count(); got != tc.want {
529+
t.Errorf("Stream.Count() = %v, want %v", got, tc.want)
530+
}
531+
})
532+
}
533+
}
534+
535+
func TestStream_AnyMatch(t *testing.T) {
536+
dataGenerator := func() chan any {
537+
c := make(chan any, 2)
538+
go func() {
539+
defer close(c)
540+
c <- "a"
541+
c <- false
542+
c <- "b"
543+
c <- -17
544+
c <- "c"
545+
}()
546+
return c
547+
}
548+
549+
tt := map[string]struct {
550+
stream chan any
551+
predicate Predicate[any]
552+
want bool
553+
}{
554+
"Should not match any when channel is nil": {
555+
stream: nil,
556+
predicate: True[any](),
557+
want: false,
558+
},
559+
"Should not match any": {
560+
stream: dataGenerator(),
561+
predicate: func(e any) bool { return e == "not in here" },
562+
want: false,
563+
},
564+
"Should match any": {
565+
stream: dataGenerator(),
566+
predicate: func(e any) bool { return e == "b" },
567+
want: true,
568+
},
569+
}
570+
571+
for name, tc := range tt {
572+
tc := tc
573+
574+
t.Run(name, func(t *testing.T) {
575+
s := Stream[any]{
576+
stream: tc.stream,
577+
}
578+
if got := s.AnyMatch(tc.predicate); got != tc.want {
579+
t.Errorf("Stream.AnyMatch() = %v, want %v", got, tc.want)
580+
}
581+
})
582+
}
583+
}
584+
585+
func TestStream_NoneMatch(t *testing.T) {
586+
dataGenerator := func() chan any {
587+
c := make(chan any, 2)
588+
go func() {
589+
defer close(c)
590+
c <- "a"
591+
c <- false
592+
c <- "b"
593+
c <- -17
594+
c <- "c"
595+
}()
596+
return c
597+
}
598+
599+
tt := map[string]struct {
600+
stream chan any
601+
predicate Predicate[any]
602+
want bool
603+
}{
604+
"Should satisfy when channel is nil": {
605+
stream: nil,
606+
predicate: True[any](),
607+
want: true,
608+
},
609+
"Should satisfy": {
610+
stream: dataGenerator(),
611+
predicate: func(e any) bool { return e == "not in here" },
612+
want: true,
613+
},
614+
"Should not satisfy": {
615+
stream: dataGenerator(),
616+
predicate: func(e any) bool { return e == "b" },
617+
want: false,
618+
},
619+
}
620+
621+
for name, tc := range tt {
622+
tc := tc
623+
624+
t.Run(name, func(t *testing.T) {
625+
s := Stream[any]{
626+
stream: tc.stream,
627+
}
628+
if got := s.NoneMatch(tc.predicate); got != tc.want {
629+
t.Errorf("Stream.NoneMatch() = %v, want %v", got, tc.want)
630+
}
631+
})
632+
}
633+
}
634+
635+
func TestStream_AllMatch(t *testing.T) {
636+
dataGenerator := func() chan any {
637+
c := make(chan any, 2)
638+
go func() {
639+
defer close(c)
640+
c <- "a"
641+
c <- false
642+
c <- "b"
643+
c <- -17
644+
c <- "c"
645+
}()
646+
return c
647+
}
648+
649+
tt := map[string]struct {
650+
stream chan any
651+
predicate Predicate[any]
652+
want bool
653+
}{
654+
"Should not match all when channel is nil": {
655+
stream: nil,
656+
predicate: True[any](),
657+
want: false,
658+
},
659+
"Should match all": {
660+
stream: dataGenerator(),
661+
predicate: func(e any) bool { return e != "not in here" },
662+
want: true,
663+
},
664+
"Should not match all": {
665+
stream: dataGenerator(),
666+
predicate: func(e any) bool { return e == "b" },
667+
want: false,
668+
},
669+
}
670+
671+
for name, tc := range tt {
672+
tc := tc
673+
674+
t.Run(name, func(t *testing.T) {
675+
s := Stream[any]{
676+
stream: tc.stream,
677+
}
678+
if got := s.AllMatch(tc.predicate); got != tc.want {
679+
t.Errorf("Stream.AllMatch() = %v, want %v", got, tc.want)
680+
}
681+
})
682+
}
683+
}
684+
487685
func TestStream_ForEach(t *testing.T) {
488686
computeSumTotal := func(callCount, total *int) Consumer[int] {
489687
return func(value int) {

0 commit comments

Comments
 (0)