Skip to content

go-andiamo/streams

Repository files navigation

Streams

GoDoc Latest Version codecov Go Report Card

A very light Streams implementation in Golang

Installation

To install Streams, use go get:

go get github.com/go-andiamo/streams

To update Streams to the latest version, run:

go get -u github.com/go-andiamo/streams

Interfaces

Stream Interface
Method and description Returns
AllMatch(p Predicate[T])
    returns whether all elements of this stream match the provided predicate
    if the provided predicate is nil or the stream is empty, always returns false
bool
AnyMatch(p Predicate[T])
    returns whether any elements of this stream match the provided predicate
    if the provided predicate is nil or the stream is empty, always returns false
bool
Append(items ...T)
    creates a new stream with all the elements of this stream followed by the specified elements
Stream[T]
AsSlice()
    returns the underlying slice
[]T
Concat(add Stream[T])
    creates a new stream with all the elements of this stream followed by all the elements of the added stream
Stream[T]
Count(p Predicate[T])
    returns the count of elements that match the provided predicate
    If the predicate is nil, returns the count of all elements
int
Difference(other Stream[T], c Comparator[T])
    creates a new stream that is the set difference between this and the supplied other stream
    equality of elements is determined using the provided comparator (if the provided comparator is nil, the result is always empty)
Stream[T]
Distinct()
    creates a new stream of distinct elements in this stream
Stream[T]
Filter(p Predicate[T])
    creates a new stream of elements in this stream that match the provided predicate
    if the provided predicate is nil, all elements in this stream are returned
Stream[T]
FirstMatch(p Predicate[T])
    returns an optional of the first element that matches the provided predicate<
    if no elements match the provided predicate, an empty (not present) optional is returned
    if the provided predicate is nil, the first element in this stream is returned
*Optional[T]
ForEach(c Consumer[T])
    performs an action on each element of this stream
    the action to be performed is defined by the provided consumer
    if the provided consumer is nil, nothing is performed
error
Has(v T, c Comparator[T])
    returns whether this stream contains an element that is equal to the element value provided
    equality is determined using the provided comparator
    if the provided comparator is nil, always returns false
bool
Intersection(other Stream[T], c Comparator[T])
    creates a new stream that is the set intersection of this and the supplied other stream
    equality of elements is determined using the provided comparator (if the provided comparator is nil, the result is always empty)
Stream[T]
Iterator(ps ...Predicate[T])
    returns an iterator (pull) function
    the iterator function can be used in for loops, for example
    next := strm.Iterator()
    for v, ok := next(); ok; v, ok = next() {
        fmt.Println(v)
    }
    Iterator can also optionally accept varargs of Predicate - which, if specified, are logically OR-ed on each pull to ensure that pulled elements match
func() (T, bool)
LastMatch(p Predicate[T])
    returns an optional of the last element that matches the provided predicate
    if no elements match the provided predicate, an empty (not present) optional is returned
    if the provided predicate is nil, the last element in this stream is returned
*Optional[T]
Len()
    returns the length (number of elements) of this stream
int
Limit(maxSize int)
    creates a new stream whose number of elements is limited to the value provided
    if the maximum size is greater than the length of this stream, all elements are returned
Stream[T]
Max(c Comparator[T])
    returns the maximum element of this stream according to the provided comparator
    if the provided comparator is nil or the stream is empty, an empty (not present) optional is returned
*Optional[T]
Min(c Comparator[T])
    returns the minimum element of this stream according to the provided comparator
    if the provided comparator is nil or the stream is empty, an empty (not present) optional is returned
*Optional[T]
MinMax(c Comparator[T])
    returns the minimum and maximum element of this stream according to the provided comparator
    if the provided comparator is nil or the stream is empty, an empty (not present) optional is returned for both
(*Optional[T], *Optional[T])
NoneMatch(p Predicate[T])
    returns whether none of the elements of this stream match the provided predicate
    if the provided predicate is nil or the stream is empty, always returns true
bool
NthMatch(p Predicate[T], nth int)
    returns an optional of the nth matching element (1 based) according to the provided predicate
    if the nth argument is negative, the nth is taken as relative to the last
    if the provided predicate is nil, any element is taken as matching if no elements match in the specified position, an empty (not present) optional is returned
*Optional[T]
Reverse()
    creates a new stream composed of elements from this stream but in reverse order
Stream[T]
Skip(n int)
    creates a new stream consisting of this stream after discarding the first n elements
    if the specified n to skip is equal to or greater than the number of elements in this stream, an empty stream is returned
Stream[T]
Slice(start int, count int)
    creates a new stream composed of elements from this stream starting at the specified start and including the specified count (or to the end)
    the start is zero based (and less than zero is ignored)
    if the specified count is negative, items are selected from the start and then backwards by the count
Stream[T]
Sorted(c Comparator[T])
    creates a new stream consisting of the elements of this stream, sorted according to the provided comparator
    if the provided comparator is nil, the elements are not sorted
Stream[T]
SymmetricDifference(other Stream[T], c Comparator[T])
    creates a new stream that is the set symmetric difference between this and the supplied other stream
    equality of elements is determined using the provided comparator (if the provided comparator is nil, the result is always empty)
Stream[T]
Union(other Stream[T], c Comparator[T])
    creates a new stream that is the set union of this and the supplied other stream
    equality of elements is determined using the provided comparator (if the provided comparator is nil, the result is always empty)
Stream[T]
Unique(c Comparator[T])
    creates a new stream of unique elements in this stream
    uniqueness is determined using the provided comparator
    if provided comparator is nil but the value type of elements in this stream are directly mappable (i.e. primitive or non-pointer types) then Distinct is used as the result, otherwise returns an empty stream
Stream[T]
Constructors
Of[T any](values ...T) Stream[T]
    creates a new stream of the values provided
OfSlice[T any](s []T) Stream[T]
    creates a new stream around a slice
    Note: Once created, If the slice changes the stream does not
NewStreamableSlice[T any](sl *[]T) Stream[T]
    creates a Stream from a pointer to a slice
    It differs from casting a slice to Streamable in that if the underlying slice changes, so does the Stream
Casting as Streamable
sl := []string{"a", "b", "c"}
s := Streamable[string](sl)
    casts a slice as a Stream
    Note: Once cast, if the slice changes the stream does not
Comparator Interface
Method and description Returns
Compare(v1, v2 T)
    compares the two values lexicographically, i.e.:
    • the result should be 0 if v1 == v2
    • the result should be -1 if v1 < v2
    • the result should be 1 if v1 > v2
int
Greater(v1, v2 T)
    returns true if v1 > v2, otherwise false
bool
GreaterOrEqual(v1, v2 T)
    returns true if v1 >= v2, otherwise false
bool
Equal(v1, v2 T)
    returns true if v1 == v2, otherwise false
bool
Less(v1, v2 T)
    returns true if v1 < v2, otherwise false
bool
LessOrEqual(v1, v2 T)
    returns true if v1 <= v2, otherwise false
bool
NotEqual(v1, v2 T)
    returns true if v1 != v2, otherwise false
bool
Reversed()
    creates a new comparator that imposes the reverse ordering to this comparator
    the reversal is against less/greater as well as against equality/non-equality
Comparator[T]
Then(other Comparator[T])
    creates a new comparator from this comparator, with a following then comparator that is used when the initial comparison yields equal
Comparator[T]
Constructors
NewComparator[T any](f ComparatorFunc[T]) Comparator[T]
    creates a new Comparator from the function provided
    where the comparator function is:
    type ComparatorFunc[T any] func(v1, v2 T) int
    which returns:
    • 0 if v1 == v2
    • -1 if v1 < v2
    • 1 if v1 > v2
Consumer Interface
Method and description Returns
Accept(v T)
    is called by the user of the consumer to supply a value
error
AndThen(after Consumer[T])
    creates a new consumer from the current with a subsequent action to be performed
    multiple consumers can be chained together as one using this method
Consumer[T]
Constructors
NewConsumer[T any](f ConsumerFunc[T]) Consumer[T]
    creates a new Consumer from the function provided
    where the consumer function is:
    type ConsumerFunc[T any] func(v T) error
Predicate Interface
Method and description Returns
Test(v T)
    evaluates this predicate against the supplied value
bool
And(other Predicate[T])
    creates a composed predicate that represents a short-circuiting logical AND of this predicate and another
Predicate[T]
Or(other Predicate[T])
    creates a composed predicate that represents a short-circuiting logical OR of this predicate and another
Predicate[T]
Negate()
    creates a composed predicate that represents a logical NOT of this predicate
Predicate[T]
Constructors
NewPredicate[T any](f PredicateFunc[T]) Predicate[T]
    creates a new Predicate from the function provided
    where the predicate function is:
    type PredicateFunc[T any] func(v T) bool

Mapper Interfaces

Mapper Interface
Method and description Returns
Map(in Stream[T])
    converts the values in the input Stream and produces a Stream of output types
(Stream[R], error)
Constructors
NewMapper[T any, R any](c Converter[T, R]) Mapper[T, R]
    creates a new Mapper that will use the provided Converter
    NewMapper panics if a nil Converter is supplied
Converter Interface
Method and description Returns
Convert(v T)
    converts a value of type T and returns a value of type R
(R, error)
Constructors
NewConverter[T any, R any](f ConverterFunc[T, R]) Converter[T, R]
    creates a new Converter from the function provided
    where the converter function is:
    type ConverterFunc[T any, R any] func(v T) (R, error)

Reducer Interfaces

Reducer Interface
Method and description Returns
Reduce(s Stream[T])
    performs a reduction of the supplied Stream
R
Constructors
NewReducer[T any, R any](accumulator Accumulator[T, R]) Reducer[T, R]
    creates a new Reducer that will use the supplied Accumulator
    NewReducer panics if a nil Accumulator is supplied
Accumulator Interface
Method and description Returns
Apply(t T, r R)
    adds the value of T to R, and returns the new R
R
Constructors
NewAccumulator[T any, R any](f AccumulatorFunc[T, R]) Accumulator[T, R]
    creates a new Accumulator from the function provided
    where the accumulator function is:
    type AccumulatorFunc[T any, R any] func(t T, r R) R

Examples

Find first match...
package main

import (
    . "github.com/go-andiamo/streams"
    "strings"
)

func main() {
    s := Of("a", "B", "c", "D", "e", "F")
    upperPredicate := NewPredicate(func(v string) bool {
        return strings.ToUpper(v) == v
    })
    o := s.FirstMatch(upperPredicate)
    o.IfPresentOtherwise(
        func(v string) {
            println(`Found: "` + v + `"`)
        },
        func() {
            println(`Did not find an uppercase`)
        },
    )
}

try on go-playground

Find last match...
package main

import (
    . "github.com/go-andiamo/streams"
    "strings"
)

func main() {
    s := Of("a", "B", "c", "D", "e", "F")
    upperPredicate := NewPredicate(func(v string) bool {
        return strings.ToUpper(v) == v
    })
    o := s.LastMatch(upperPredicate.Negate())
    o.IfPresentOtherwise(
        func(v string) {
            println(`Found: "` + v + `"`)
        },
        func() {
            println(`Did not find a lowercase`)
        },
    )
}

try on go-playground

Nth match...
package main

import (
    "fmt"
    . "github.com/go-andiamo/streams"
    "strings"
)

func main() {
    s := Of("a", "B", "c", "D", "e", "F", "g", "H", "i", "J")
    upperPredicate := NewPredicate(func(v string) bool {
        return strings.ToUpper(v) == v
    })

    for nth := -6; nth < 7; nth++ {
        s.NthMatch(upperPredicate, nth).IfPresentOtherwise(
            func(v string) {
                fmt.Printf("Found \"%s\" at nth pos %d\n", v, nth)
            },
            func() {
                fmt.Printf("No match at nth pos %d\n", nth)
            },
        )
    }
}

try on go-playground

Sort descending & print...
package main

import (
    . "github.com/go-andiamo/streams"
)

func main() {
    s := Of("311", "AAAA", "30", "3", "1", "Baaa", "4000", "0400", "40", "Aaaa", "BBBB", "4", "01", "2", "0101", "201", "20")
    _ = s.Sorted(StringComparator.Reversed()).ForEach(NewConsumer(func(v string) error {
        println(v)
        return nil
    }))
}

try on go-playground

Compound sort...
package main

import (
    "fmt"
    . "github.com/go-andiamo/streams"
)

func main() {
    type myStruct struct {
        value    int
        priority int
    }
    myComparator := NewComparator(func(v1, v2 myStruct) int {
        return IntComparator.Compare(v1.value, v2.value)
    }).Then(NewComparator(func(v1, v2 myStruct) int {
        return IntComparator.Compare(v1.priority, v2.priority)
    }).Reversed())
    s := Of(
        myStruct{
            value:    2,
            priority: 2,
        },
        myStruct{
            value:    2,
            priority: 0,
        },
        myStruct{
            value:    2,
            priority: 1,
        },
        myStruct{
            value:    1,
            priority: 2,
        },
        myStruct{
            value:    1,
            priority: 1,
        },
        myStruct{
            value:    1,
            priority: 0,
        },
    )
    _ = s.Sorted(myComparator).ForEach(NewConsumer(func(v myStruct) error {
        fmt.Printf("Value: %d, Priority: %d\n", v.value, v.priority)
        return nil
    }))
}

try on go-playground

Min and max...
package main

import (
    "fmt"
    . "github.com/go-andiamo/streams"
)

func main() {
    type myStruct struct {
        value    int
        priority int
    }
    myComparator := NewComparator(func(v1, v2 myStruct) int {
        return IntComparator.Compare(v1.value, v2.value)
    }).Then(NewComparator(func(v1, v2 myStruct) int {
        return IntComparator.Compare(v1.priority, v2.priority)
    }))
    s := Of(
        myStruct{
            value:    2,
            priority: 2,
        },
        myStruct{
            value:    2,
            priority: 0,
        },
        myStruct{
            value:    2,
            priority: 1,
        },
        myStruct{
            value:    1,
            priority: 2,
        },
        myStruct{
            value:    1,
            priority: 1,
        },
        myStruct{
            value:    1,
            priority: 0,
        },
    )
    min := s.Min(myComparator)
    min.IfPresentOtherwise(
        func(v myStruct) {
            fmt.Printf("Min... Value: %d, Priority: %d\n", v.value, v.priority)
        },
        func() {
            println("No min found!")
        })
    max := s.Max(myComparator)
    max.IfPresentOtherwise(
        func(v myStruct) {
            fmt.Printf("Max... Value: %d, Priority: %d\n", v.value, v.priority)
        },
        func() {
            println("No max found!")
        })
}

try on go-playground

Set intersection, union, difference and symmetric difference...
package main

import (
    . "github.com/go-andiamo/streams"
)

func main() {
    s1 := Of("a", "B", "c", "C", "d", "D", "d")
    s2 := Of("e", "E", "a", "A", "b")
    println("Intersection...")
    _ = s1.Unique(StringInsensitiveComparator).Intersection(s2.Unique(StringInsensitiveComparator), StringInsensitiveComparator).
        ForEach(NewConsumer(func(v string) error {
            println(v)
            return nil
        }))
    println("Union...")
    _ = s1.Unique(StringInsensitiveComparator).Union(s2.Unique(StringInsensitiveComparator), StringInsensitiveComparator).
        ForEach(NewConsumer(func(v string) error {
            println(v)
            return nil
        }))
    println("Symmetric Difference...")
    _ = s1.Unique(StringInsensitiveComparator).SymmetricDifference(s2.Unique(StringInsensitiveComparator), StringInsensitiveComparator).
        ForEach(NewConsumer(func(v string) error {
            println(v)
            return nil
        }))
    println("Difference (s1 to s2)...")
    _ = s1.Unique(StringInsensitiveComparator).Difference(s2.Unique(StringInsensitiveComparator), StringInsensitiveComparator).
        ForEach(NewConsumer(func(v string) error {
            println(v)
            return nil
        }))
    println("Difference (s2 to s1)...")
    _ = s2.Unique(StringInsensitiveComparator).Difference(s1.Unique(StringInsensitiveComparator), StringInsensitiveComparator).
        ForEach(NewConsumer(func(v string) error {
            println(v)
            return nil
        }))
}

try on go-playground

Map...
package main

import (
    . "github.com/go-andiamo/streams"
)

func main() {
    type character struct {
        name string
        age  int
    }
    characters := OfSlice([]character{
        {
            `Frodo Baggins`,
            50,
        },
        {
            `Samwise Gamgee`,
            38,
        },
        {
            `Gandalf`,
            2000,
        },
        {
            `Aragorn`,
            87,
        },
        {
            `Legolas`,
            200,
        },
        {
            `Gimli`,
            139,
        },
        {
            `Meridoc Brandybuck`,
            36,
        },
        {
            `Peregrin Took`,
            28,
        },
        {
            `Boromir`,
            40,
        },
    })

    m := NewMapper(NewConverter[character, string](func(v character) (string, error) {
        return v.name, nil
    }))
    names, _ := m.Map(characters)
    _ = names.Sorted(StringComparator).ForEach(NewConsumer(func(v string) error {
        println(v)
        return nil
    }))
}

try on go-playground

Reduce...
package main

import (
    "fmt"
    . "github.com/go-andiamo/streams"
)

func main() {
    type account struct {
        currency string
        acNo     string
        balance  float64
    }
    accounts := OfSlice([]account{
        {
            `GBP`,
            `1051065`,
            50.01,
        },
        {
            `USD`,
            `1931132`,
            259.98,
        },
        {
            `EUR`,
            `1567807`,
            313.25,
        },
        {
            `EUR`,
            `1009321`,
            50.01,
        },
        {
            `USD`,
            `1573756`,
            12.02,
        },
        {
            `GBP`,
            `1456044`,
            99.99,
        },
    })

    accum := NewAccumulator[account, map[string]float64](func(v account, r map[string]float64) map[string]float64 {
        if r == nil {
            r = map[string]float64{}
        }
        if cv, ok := r[v.currency]; ok {
            r[v.currency] = cv + v.balance
        } else {
            r[v.currency] = v.balance
        }
        return r
    })
    r := NewReducer(accum)
    for k, v := range r.Reduce(accounts) {
        fmt.Printf("%s %f\n", k, v)
    }
}

try on go-playground

Filter with composed predicate...
package main

import (
    . "github.com/go-andiamo/streams"
    "regexp"
    "strings"
)

func main() {
    s := Of("aaa", "", "AAA", "012", "bBbB", "Ccc", "CCC", "D", "EeE", "eee", " ", "  ", "A12")

    pNotEmpty := NewPredicate(func(v string) bool {
        return len(strings.Trim(v, " ")) > 0
    })
    rxNum := regexp.MustCompile(`^[0-9]+$`)
    pNumeric := NewPredicate(func(v string) bool {
        return rxNum.MatchString(v)
    })
    rxUpper := regexp.MustCompile(`^[A-Z]+$`)
    pAllUpper := NewPredicate(func(v string) bool {
        return rxUpper.MatchString(v)
    })
    rxLower := regexp.MustCompile(`^[a-z]+$`)
    pAllLower := NewPredicate(func(v string) bool {
        return rxLower.MatchString(v)
    })
    // only want strings that are non-empty and all numeric, all upper or all lower... 
    pFinal := pNotEmpty.And(pNumeric.Or(pAllUpper).Or(pAllLower))

    _ = s.Filter(pFinal).ForEach(NewConsumer(func(v string) error {
        println(v)
        return nil
    }))
}

try on go-playground

Distinct vs Unique...
package main

import (
    . "github.com/go-andiamo/streams"
)

func main() {
    s := Of("a", "A", "b", "B", "c", "C")

    println("Distinct...")
    _ = s.Distinct().ForEach(NewConsumer(func(v string) error {
        println(v)
        return nil
    }))
    println("Unique (case insensitive)...")
    _ = s.Unique(StringInsensitiveComparator).ForEach(NewConsumer(func(v string) error {
        println(v)
        return nil
    }))
}

try on go-playground

Distinct vs Unique (structs)...
package main

import (
    "fmt"
    . "github.com/go-andiamo/streams"
    "strings"
)

type MyStruct struct {
    value    string
    priority int
}

func main() {
    s1 := OfSlice([]MyStruct{
        {
            "A",
            1,
        },
        {
            "A",
            2,
        },
        {
            "A",
            1,
        },
        {
            "A",
            2,
        },
    })

    println("\nStruct Distinct...")
    _ = s1.Distinct().ForEach(NewConsumer(func(v MyStruct) error {
        fmt.Printf("Value: %s, Priority: %d\n", v.value, v.priority)
        return nil
    }))
    println("\nStruct Unique (no comparator)...")
    _ = s1.Unique(nil).ForEach(NewConsumer(func(v MyStruct) error {
        fmt.Printf("Value: %s, Priority: %d\n", v.value, v.priority)
        return nil
    }))

    s2 := OfSlice([]*MyStruct{
        {
            "A",
            1,
        },
        {
            "A",
            2,
        },
        {
            "A",
            1,
        },
        {
            "A",
            2,
        },
    })

    println("\nStruct Ptr Distinct...")
    _ = s2.Distinct().ForEach(NewConsumer(func(v *MyStruct) error {
        fmt.Printf("Value: %s, Priority: %d\n", v.value, v.priority)
        return nil
    }))
    println("\nStruct Ptr Unique (no comparator)...")
    _ = s2.Unique(nil).ForEach(NewConsumer(func(v *MyStruct) error {
        fmt.Printf("Value: %s, Priority: %d\n", v.value, v.priority)
        return nil
    }))
    cmp := NewComparator(func(v1, v2 *MyStruct) int {
        return strings.Compare(v1.value, v2.value)
    }).Then(NewComparator(func(v1, v2 *MyStruct) int {
        return IntComparator.Compare(v1.priority, v2.priority)
    }))
    println("\nStruct Ptr Unique (with comparator)...")
    _ = s2.Unique(cmp).ForEach(NewConsumer(func(v *MyStruct) error {
        fmt.Printf("Value: %s, Priority: %d\n", v.value, v.priority)
        return nil
    }))
}

try on go-playground

For each...
package main

import (
    . "github.com/go-andiamo/streams"
)

var stringValuePrinter = NewConsumer(func(v string) error {
    println(v)
    return nil
})

func main() {
    s := Of("a", "b", "c", "d")

    _ = s.ForEach(stringValuePrinter)
}

try on go-playground

Iterator...
package main

import (
    . "github.com/go-andiamo/streams"
)

func main() {
    s := Of("a", "b", "c", "d")

    next := s.Iterator()
    for v, ok := next(); ok; v, ok = next() {
        println(v)
    }
}

try on go-playground

Iterator with predicate...
package main

import (
    . "github.com/go-andiamo/streams"
    "strings"
)

func main() {
    s := Of("a", "B", "c", "D", "e", "F", "g", "H", "i", "J")
    upper := NewPredicate(func(v string) bool {
        return strings.ToUpper(v) == v
    })

    next := s.Iterator(upper)
    for v, ok := next(); ok; v, ok = next() {
        println(v)
    }
}

try on go-playground