Skip to content

Commit f410f84

Browse files
authored
add Stream.Drop* (#24)
* add Stream.Drop* * add Stream: Head* / Last* / Take* / Drop* / StartsWith / EndsWith
1 parent b2683ec commit f410f84

File tree

4 files changed

+1105
-18
lines changed

4 files changed

+1105
-18
lines changed

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,8 @@ Streams:
211211
- All/Any/None -Match
212212
- Intersperse
213213
- Distinct
214+
- Head* / Last* / Take* / Drop*
215+
- StartsWith / EndsWith
214216
- ForEach / Peek
215217
- ...
216218
- ComparableStream

errors.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package fuego
22

33
// PanicMissingChannel signifies that the Stream is missing a channel.
4-
const PanicMissingChannel = "stream creation requires a channel"
4+
const PanicMissingChannel = "stream requires a channel"
55

66
// PanicNoSuchElement signifies that the requested element is not present.
77
// Examples: when the Stream is empty, or when an Optional does not have a value.

stream.go

+251
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package fuego
55
import (
66
"fmt"
77

8+
"github.com/google/go-cmp/cmp"
89
"go.uber.org/zap"
910
)
1011

@@ -386,6 +387,256 @@ func (s Stream[T]) NoneMatch(p Predicate[T]) bool {
386387
return !s.AnyMatch(p)
387388
}
388389

390+
// Drop the first 'n' elements of this stream and returns a new stream.
391+
//
392+
// This function streams continuously until the in-stream is closed at
393+
// which point the out-stream will be closed too.
394+
func (s Stream[T]) Drop(n uint64) Stream[T] {
395+
return s.DropWhile(func() func(e T) bool {
396+
count := uint64(0)
397+
return func(e T) bool {
398+
count++
399+
return count <= n
400+
}
401+
}())
402+
}
403+
404+
// DropWhile drops the first elements of this stream while the predicate
405+
// is satisfied and returns a new stream.
406+
//
407+
// This function streams continuously until the in-stream is closed at
408+
// which point the out-stream will be closed too.
409+
func (s Stream[T]) DropWhile(p Predicate[T]) Stream[T] {
410+
outstream := make(chan T, cap(s.stream))
411+
412+
go func() {
413+
defer close(outstream)
414+
415+
if s.stream == nil {
416+
return
417+
}
418+
419+
// drop elements as required
420+
for val := range s.stream {
421+
if p(val) {
422+
continue
423+
}
424+
outstream <- val
425+
426+
break
427+
}
428+
429+
// flush the remainder to outstream
430+
for val := range s.stream {
431+
outstream <- val
432+
}
433+
}()
434+
435+
return NewConcurrentStream(outstream, s.concurrency)
436+
}
437+
438+
// DropUntil drops the first elements of this stream until the predicate
439+
// is satisfied and returns a new stream.
440+
//
441+
// This function streams continuously until the in-stream is closed at
442+
// which point the out-stream will be closed too.
443+
func (s Stream[T]) DropUntil(p Predicate[T]) Stream[T] {
444+
return s.DropWhile(p.Negate())
445+
}
446+
447+
// Last returns the last Entry in this stream.
448+
//
449+
// This function streams continuously until the in-stream is closed at
450+
// which point the out-stream will be closed too.
451+
func (s Stream[T]) Last() T {
452+
return s.LastN(1)[0]
453+
}
454+
455+
// LastN returns a slice of the last n elements in this stream.
456+
//
457+
// This function streams continuously until the in-stream is closed at
458+
// which point the out-stream will be closed too.
459+
func (s Stream[T]) LastN(n uint64) []T {
460+
const flushTriggerDefault = uint64(100)
461+
462+
if s.stream == nil {
463+
panic(PanicMissingChannel)
464+
}
465+
466+
if n < 1 {
467+
panic(PanicNoSuchElement)
468+
}
469+
470+
val, ok := <-s.stream
471+
if !ok {
472+
panic(PanicNoSuchElement)
473+
}
474+
475+
result := []T{val}
476+
477+
count := uint64(len(result))
478+
flushTrigger := flushTriggerDefault
479+
480+
if n > flushTrigger {
481+
flushTrigger = n
482+
}
483+
484+
for val = range s.stream {
485+
result = append(result, val)
486+
if count++; count > flushTrigger {
487+
// this is simply to reduce the number of
488+
// slice resizing operations
489+
result = result[uint64(len(result))-n:]
490+
count = 0
491+
}
492+
}
493+
494+
if uint64(len(result)) > n {
495+
return result[uint64(len(result))-n:]
496+
}
497+
498+
return result
499+
}
500+
501+
// Head returns the first Entry in this stream.
502+
//
503+
// This function only consumes at most one element from the stream.
504+
func (s Stream[T]) Head() T {
505+
head := s.HeadN(1)
506+
if len(head) != 1 {
507+
panic(PanicNoSuchElement)
508+
}
509+
510+
return head[0]
511+
}
512+
513+
// HeadN returns a slice of the first n elements in this stream.
514+
//
515+
// This function only consumes at most 'n' elements from the stream.
516+
func (s Stream[T]) HeadN(n uint64) []T {
517+
return Collect(
518+
s.Take(n),
519+
NewCollector(
520+
func() []T { return []T{} },
521+
func(e1 []T, e2 T) []T { return append(e1, e2) },
522+
IdentityFinisher[[]T],
523+
))
524+
}
525+
526+
// Take returns a stream of the first 'n' elements of this stream.
527+
//
528+
// This function streams continuously until the 'n' elements are picked
529+
// or the in-stream is closed at which point the out-stream
530+
// will be closed too.
531+
func (s Stream[T]) Take(n uint64) Stream[T] {
532+
counterIsLessThanOrEqualTo := func(maxCount uint64) Predicate[T] {
533+
counter := uint64(0)
534+
535+
return func(t T) bool {
536+
counter++
537+
return counter <= maxCount
538+
}
539+
}
540+
541+
return s.TakeWhile(counterIsLessThanOrEqualTo(n))
542+
}
543+
544+
// Limit is a synonym for Take.
545+
func (s Stream[T]) Limit(n uint64) Stream[T] {
546+
return s.Take(n)
547+
}
548+
549+
// TakeWhile returns a stream of the first elements of this
550+
// stream while the predicate is satisfied.
551+
//
552+
// This function streams continuously until the in-stream is closed at
553+
// which point the out-stream will be closed too.
554+
func (s Stream[T]) TakeWhile(p Predicate[T]) Stream[T] {
555+
if s.stream == nil {
556+
panic(PanicMissingChannel)
557+
}
558+
559+
outstream := make(chan T, cap(s.stream))
560+
561+
go func() {
562+
defer close(outstream)
563+
564+
for val := range s.stream {
565+
if !p(val) {
566+
return
567+
}
568+
outstream <- val
569+
}
570+
}()
571+
572+
return NewConcurrentStream(outstream, s.concurrency)
573+
}
574+
575+
// TakeUntil returns a stream of the first elements
576+
// of this stream until the predicate is satisfied.
577+
//
578+
// This function streams continuously until the in-stream is closed at
579+
// which point the out-stream will be closed too.
580+
func (s Stream[T]) TakeUntil(p Predicate[T]) Stream[T] {
581+
return s.TakeWhile(p.Negate())
582+
}
583+
584+
// StartsWith returns true when this stream starts
585+
// with the elements in the supplied slice.
586+
//
587+
// This function only consume as much data from the stream as
588+
// is necessary to prove (or disprove) it starts with the supplied
589+
// slice data.
590+
func (s Stream[T]) StartsWith(slice []T) bool {
591+
startElements := s.HeadN(uint64(len(slice)))
592+
if len(slice) == 0 || len(startElements) != len(slice) {
593+
return false
594+
}
595+
596+
for idx, el := range slice {
597+
if !cmp.Equal(el, startElements[idx]) {
598+
return false
599+
}
600+
}
601+
602+
return true
603+
}
604+
605+
// EndsWith returns true when this stream ends
606+
// with the supplied elements.
607+
//
608+
// This is a potentially expensive method since it has
609+
// to consume all the elements in the Stream.
610+
//
611+
// This function streams continuously until the in-stream is closed at
612+
// which point the out-stream will be closed too.
613+
func (s Stream[T]) EndsWith(slice []T) bool {
614+
if len(slice) == 0 {
615+
return false
616+
}
617+
618+
endElements := func() []T {
619+
defer func() {
620+
// TODO: this doesn't look great... Need to re-write LastN like HeadN as a collect of TakeRight (to be implemented)
621+
_ = recover()
622+
}()
623+
624+
return s.LastN(uint64(len(slice)))
625+
}()
626+
627+
if len(endElements) != len(slice) {
628+
return false
629+
}
630+
631+
for idx, el := range slice {
632+
if !cmp.Equal(el, endElements[idx]) {
633+
return false
634+
}
635+
}
636+
637+
return true
638+
}
639+
389640
// ForEach executes the given consumer function for each entry in this stream.
390641
//
391642
// This is a continuous terminal operation. It will only complete if the producer closes the stream.

0 commit comments

Comments
 (0)