Repository: elliotchance/pie
Pull request #106 (2019-05-05 18:21:35)

Reduce
A reduce function as found in languages such as Python, Java. 

See #87

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/elliotchance/pie/106)
<!-- Reviewable:end -->


DylanMeeus: I'm unsure how updating the readme broke the build. Any ideas?

Patch:
diff --git a/README.md b/README.md
index e76dcbe..f27df12 100644
--- a/README.md
+++ b/README.md
@@ -139,6 +139,7 @@ This will only generate `myInts.Average`, `myInts.Sum` and `myStrings.Filter`.
 | `FilterNot`  | ✓      | ✓      | ✓     |      | n        | A new slice containing only the elements that returned false from the condition. |
 | `First`      | ✓      | ✓      | ✓     |      | 1        | The first element, or a zeroed value. |
 | `FirstOr`    | ✓      | ✓      | ✓     |      | 1        | The first element, or a default value. |
+| `Intersect`       | ✓      | ✓        |       |      | n | Intersect returns elements which exists in all slices. |
 | `Join`       | ✓      |        |       |      | n        | A string from joining each of the elements. |
 | `JSONString` | ✓      | ✓      | ✓     |      | n        | The JSON encoded string. |
 | `Keys`       |        |        |       | ✓    | n        | Returns all keys in the map (in random order). |
@@ -149,9 +150,11 @@ This will only generate `myInts.Average`, `myInts.Sum` and `myStrings.Filter`.
 | `Max`        | ✓      | ✓      |       |      | n        | The maximum value, or a zeroes value. |
 | `Median`     |        | ✓      |       |      | n⋅log(n) | Median returns the value separating the higher half from the lower half of a data sample. |
 | `Min`        | ✓      | ✓      |       |      | n        | The minimum value, or a zeroed value. |
+| `Product`    |        | ✓      |       |      | n        | Product of all elements. |
 | `Random`     | ✓      | ✓      | ✓     |      | 1        | Select a random element, or a zeroed value if empty. |
+| `Reduce`     | ✓      | ✓      |       |      | n        | Continously apply a function to the slice (left to right), reducing it to a single value. |
 | `Reverse`    | ✓      | ✓      | ✓     |      | n        | Reverse elements. |
-| `Send`     | ✓      | ✓      | ✓     |      | n        | Send all element to channel. |
+| `Send`       | ✓      | ✓      | ✓     |      | n        | Send all element to channel. |
 | `Sort`       | ✓      | ✓      |       |      | n⋅log(n) | Return a new sorted slice. |
 | `Sum`        |        | ✓      |       |      | n        | Sum (total) of all elements. |
 | `Shuffle`    | ✓      | ✓      | ✓     |      | n        | Returns a new shuffled slice. |
diff --git a/functions/intersect.go b/functions/intersect.go
new file mode 100644
index 0000000..0e5ac17
--- /dev/null
+++ b/functions/intersect.go
@@ -0,0 +1,36 @@
+package functions
+
+// Intersect returns items that exist in all lists.
+//
+// It returns slice without any duplicates.
+// If zero slice arguments are provided, then nil is returned.
+func (ss SliceType) Intersect(slices ...SliceType) (ss2 SliceType) {
+	if slices == nil {
+		return nil
+	}
+
+	var uniqs = make([]map[ElementType]struct{}, len(slices))
+	for i := 0; i < len(slices); i++ {
+		m := make(map[ElementType]struct{})
+		for _, el := range slices[i] {
+			m[el] = struct{}{}
+		}
+		uniqs[i] = m
+	}
+
+	var containsInAll = false
+	for _, el := range ss.Unique() {
+		for _, u := range uniqs {
+			if _, exists := u[el]; !exists {
+				containsInAll = false
+				break
+			}
+			containsInAll = true
+		}
+		if containsInAll {
+			ss2 = append(ss2, el)
+		}
+	}
+
+	return
+}
diff --git a/functions/main.go b/functions/main.go
index c9ed4bf..50feb2e 100644
--- a/functions/main.go
+++ b/functions/main.go
@@ -33,6 +33,7 @@ var Functions = []struct {
 	{"FilterNot", "filter_not.go", ForAll},
 	{"First", "first.go", ForAll},
 	{"FirstOr", "first_or.go", ForAll},
+	{"Intersect", "intersect.go", ForNumbersAndStrings},
 	{"Join", "join.go", ForStrings},
 	{"JSONString", "json_string.go", ForAll},
 	{"Keys", "keys.go", ForMaps},
@@ -43,7 +44,9 @@ var Functions = []struct {
 	{"Max", "max.go", ForNumbersAndStrings},
 	{"Median", "median.go", ForNumbers},
 	{"Min", "min.go", ForNumbersAndStrings},
+	{"Product", "product.go", ForNumbers},
 	{"Random", "random.go", ForAll},
+	{"Reduce", "reduce.go", ForNumbersAndStrings},
 	{"Reverse", "reverse.go", ForAll},
 	{"Send", "send.go", ForAll},
 	{"Sort", "sort.go", ForNumbersAndStrings},
diff --git a/functions/product.go b/functions/product.go
new file mode 100644
index 0000000..1b538fc
--- /dev/null
+++ b/functions/product.go
@@ -0,0 +1,14 @@
+package functions
+
+// Product is the product of all of the elements.
+func (ss SliceType) Product() (product ElementType) {
+	if len(ss) == 0 {
+		return
+	}
+	product = ss[0]
+	for _, s := range ss[1:] {
+		product *= s
+	}
+
+	return
+}
diff --git a/functions/reduce.go b/functions/reduce.go
new file mode 100644
index 0000000..7fa3a8e
--- /dev/null
+++ b/functions/reduce.go
@@ -0,0 +1,17 @@
+package functions
+
+// Reduce continually applies the provided function
+// over the slice. Reducing the elements to a single value.
+//
+// Returns a zero value of ElementType if there are no elements in the slice. It will panic if the reducer is nil and the slice has more than one element (required to invoke reduce).
+// Otherwise returns result of applying reducer from left to right.
+func (ss SliceType) Reduce(reducer func(ElementType, ElementType) ElementType) (el ElementType) {
+	if len(ss) == 0 {
+		return
+	}
+	el = ss[0]
+	for _, s := range ss[1:] {
+		el = reducer(el, s)
+	}
+	return
+}
diff --git a/pie/float64s_pie.go b/pie/float64s_pie.go
index 1628567..e6cfa60 100755
--- a/pie/float64s_pie.go
+++ b/pie/float64s_pie.go
@@ -186,6 +186,41 @@ func (ss Float64s) FirstOr(defaultValue float64) float64 {
 	return ss[0]
 }
 
+// Intersect returns items that exist in all lists.
+//
+// It returns slice without any duplicates.
+// If zero slice arguments are provided, then nil is returned.
+func (ss Float64s) Intersect(slices ...Float64s) (ss2 Float64s) {
+	if slices == nil {
+		return nil
+	}
+
+	var uniqs = make([]map[float64]struct{}, len(slices))
+	for i := 0; i < len(slices); i++ {
+		m := make(map[float64]struct{})
+		for _, el := range slices[i] {
+			m[el] = struct{}{}
+		}
+		uniqs[i] = m
+	}
+
+	var containsInAll = false
+	for _, el := range ss.Unique() {
+		for _, u := range uniqs {
+			if _, exists := u[el]; !exists {
+				containsInAll = false
+				break
+			}
+			containsInAll = true
+		}
+		if containsInAll {
+			ss2 = append(ss2, el)
+		}
+	}
+
+	return
+}
+
 // JSONString returns the JSON encoded array as a string.
 //
 // One important thing to note is that it will treat a nil slice as an empty
@@ -295,6 +330,19 @@ func (ss Float64s) Min() (min float64) {
 	return
 }
 
+// Product is the product of all of the elements.
+func (ss Float64s) Product() (product float64) {
+	if len(ss) == 0 {
+		return
+	}
+	product = ss[0]
+	for _, s := range ss[1:] {
+		product *= s
+	}
+
+	return
+}
+
 // Random returns a random element by your rand.Source, or zero
 func (ss Float64s) Random(source rand.Source) float64 {
 	n := len(ss)
@@ -311,6 +359,22 @@ func (ss Float64s) Random(source rand.Source) float64 {
 	return ss[i]
 }
 
+// Reduce continually applies the provided function
+// over the slice. Reducing the elements to a single value.
+//
+// Returns a zero value of float64 if there are no elements in the slice. It will panic if the reducer is nil and the slice has more than one element (required to invoke reduce).
+// Otherwise returns result of applying reducer from left to right.
+func (ss Float64s) Reduce(reducer func(float64, float64) float64) (el float64) {
+	if len(ss) == 0 {
+		return
+	}
+	el = ss[0]
+	for _, s := range ss[1:] {
+		el = reducer(el, s)
+	}
+	return
+}
+
 // Reverse returns a new copy of the slice with the elements ordered in reverse.
 // This is useful when combined with Sort to get a descending sort order:
 //
diff --git a/pie/ints_pie.go b/pie/ints_pie.go
index 79cce7b..4542326 100755
--- a/pie/ints_pie.go
+++ b/pie/ints_pie.go
@@ -186,6 +186,41 @@ func (ss Ints) FirstOr(defaultValue int) int {
 	return ss[0]
 }
 
+// Intersect returns items that exist in all lists.
+//
+// It returns slice without any duplicates.
+// If zero slice arguments are provided, then nil is returned.
+func (ss Ints) Intersect(slices ...Ints) (ss2 Ints) {
+	if slices == nil {
+		return nil
+	}
+
+	var uniqs = make([]map[int]struct{}, len(slices))
+	for i := 0; i < len(slices); i++ {
+		m := make(map[int]struct{})
+		for _, el := range slices[i] {
+			m[el] = struct{}{}
+		}
+		uniqs[i] = m
+	}
+
+	var containsInAll = false
+	for _, el := range ss.Unique() {
+		for _, u := range uniqs {
+			if _, exists := u[el]; !exists {
+				containsInAll = false
+				break
+			}
+			containsInAll = true
+		}
+		if containsInAll {
+			ss2 = append(ss2, el)
+		}
+	}
+
+	return
+}
+
 // JSONString returns the JSON encoded array as a string.
 //
 // One important thing to note is that it will treat a nil slice as an empty
@@ -295,6 +330,19 @@ func (ss Ints) Min() (min int) {
 	return
 }
 
+// Product is the product of all of the elements.
+func (ss Ints) Product() (product int) {
+	if len(ss) == 0 {
+		return
+	}
+	product = ss[0]
+	for _, s := range ss[1:] {
+		product *= s
+	}
+
+	return
+}
+
 // Random returns a random element by your rand.Source, or zero
 func (ss Ints) Random(source rand.Source) int {
 	n := len(ss)
@@ -311,6 +359,22 @@ func (ss Ints) Random(source rand.Source) int {
 	return ss[i]
 }
 
+// Reduce continually applies the provided function
+// over the slice. Reducing the elements to a single value.
+//
+// Returns a zero value of int if there are no elements in the slice. It will panic if the reducer is nil and the slice has more than one element (required to invoke reduce).
+// Otherwise returns result of applying reducer from left to right.
+func (ss Ints) Reduce(reducer func(int, int) int) (el int) {
+	if len(ss) == 0 {
+		return
+	}
+	el = ss[0]
+	for _, s := range ss[1:] {
+		el = reducer(el, s)
+	}
+	return
+}
+
 // Reverse returns a new copy of the slice with the elements ordered in reverse.
 // This is useful when combined with Sort to get a descending sort order:
 //
diff --git a/pie/strings_pie.go b/pie/strings_pie.go
index 9c28f3f..928648b 100755
--- a/pie/strings_pie.go
+++ b/pie/strings_pie.go
@@ -166,6 +166,41 @@ func (ss Strings) FirstOr(defaultValue string) string {
 	return ss[0]
 }
 
+// Intersect returns items that exist in all lists.
+//
+// It returns slice without any duplicates.
+// If zero slice arguments are provided, then nil is returned.
+func (ss Strings) Intersect(slices ...Strings) (ss2 Strings) {
+	if slices == nil {
+		return nil
+	}
+
+	var uniqs = make([]map[string]struct{}, len(slices))
+	for i := 0; i < len(slices); i++ {
+		m := make(map[string]struct{})
+		for _, el := range slices[i] {
+			m[el] = struct{}{}
+		}
+		uniqs[i] = m
+	}
+
+	var containsInAll = false
+	for _, el := range ss.Unique() {
+		for _, u := range uniqs {
+			if _, exists := u[el]; !exists {
+				containsInAll = false
+				break
+			}
+			containsInAll = true
+		}
+		if containsInAll {
+			ss2 = append(ss2, el)
+		}
+	}
+
+	return
+}
+
 // Join returns a string from joining each of the elements.
 func (ss Strings) Join(glue string) (s string) {
 	for i, element := range ss {
@@ -280,6 +315,22 @@ func (ss Strings) Random(source rand.Source) string {
 	return ss[i]
 }
 
+// Reduce continually applies the provided function
+// over the slice. Reducing the elements to a single value.
+//
+// Returns a zero value of string if there are no elements in the slice. It will panic if the reducer is nil and the slice has more than one element (required to invoke reduce).
+// Otherwise returns result of applying reducer from left to right.
+func (ss Strings) Reduce(reducer func(string, string) string) (el string) {
+	if len(ss) == 0 {
+		return
+	}
+	el = ss[0]
+	for _, s := range ss[1:] {
+		el = reducer(el, s)
+	}
+	return
+}
+
 // Reverse returns a new copy of the slice with the elements ordered in reverse.
 // This is useful when combined with Sort to get a descending sort order:
 //
diff --git a/template.go b/template.go
index 469028f..2c5a0a1 100644
--- a/template.go
+++ b/template.go
@@ -216,6 +216,43 @@ func (ss SliceType) FirstOr(defaultValue ElementType) ElementType {
 
 	return ss[0]
 }
+`,
+	"Intersect": `package functions
+
+// Intersect returns items that exist in all lists.
+//
+// It returns slice without any duplicates.
+// If zero slice arguments are provided, then nil is returned.
+func (ss SliceType) Intersect(slices ...SliceType) (ss2 SliceType) {
+	if slices == nil {
+		return nil
+	}
+
+	var uniqs = make([]map[ElementType]struct{}, len(slices))
+	for i := 0; i < len(slices); i++ {
+		m := make(map[ElementType]struct{})
+		for _, el := range slices[i] {
+			m[el] = struct{}{}
+		}
+		uniqs[i] = m
+	}
+
+	var containsInAll = false
+	for _, el := range ss.Unique() {
+		for _, u := range uniqs {
+			if _, exists := u[el]; !exists {
+				containsInAll = false
+				break
+			}
+			containsInAll = true
+		}
+		if containsInAll {
+			ss2 = append(ss2, el)
+		}
+	}
+
+	return
+}
 `,
 	"JSONString": `package functions
 
@@ -382,6 +419,21 @@ func (ss SliceType) Min() (min ElementType) {
 
 	return
 }
+`,
+	"Product": `package functions
+
+// Product is the product of all of the elements.
+func (ss SliceType) Product() (product ElementType) {
+	if len(ss) == 0 {
+		return
+	}
+	product = ss[0]
+	for _, s := range ss[1:] {
+		product *= s
+	}
+
+	return
+}
 `,
 	"Random": `package functions
 
@@ -404,6 +456,24 @@ func (ss SliceType) Random(source rand.Source) ElementType {
 	i := rnd.Intn(n)
 	return ss[i]
 }
+`,
+	"Reduce": `package functions
+
+// Reduce continually applies the provided function
+// over the slice. Reducing the elements to a single value.
+//
+// Returns a zero value of ElementType if there are no elements in the slice. It will panic if the reducer is nil and the slice has more than one element (required to invoke reduce).
+// Otherwise returns result of applying reducer from left to right.
+func (ss SliceType) Reduce(reducer func(ElementType, ElementType) ElementType) (el ElementType) {
+	if len(ss) == 0 {
+		return
+	}
+	el = ss[0]
+	for _, s := range ss[1:] {
+		el = reducer(el, s)
+	}
+	return
+}
 `,
 	"Reverse": `package functions
 


Test patch:
diff --git a/pie/float64s_test.go b/pie/float64s_test.go
index ea49565..08613a6 100644
--- a/pie/float64s_test.go
+++ b/pie/float64s_test.go
@@ -161,10 +161,10 @@ func TestFloat64s_Last(t *testing.T) {
 }
 
 var float64sStatsTests = []struct {
-	ss            Float64s
-	min, max, sum float64
-	len           int
-	average       float64
+	ss                     Float64s
+	min, max, sum, product float64
+	len                    int
+	average                float64
 }{
 	{
 		nil,
@@ -173,6 +173,7 @@ var float64sStatsTests = []struct {
 		0,
 		0,
 		0,
+		0,
 	},
 	{
 		[]float64{},
@@ -181,12 +182,14 @@ var float64sStatsTests = []struct {
 		0,
 		0,
 		0,
+		0,
 	},
 	{
 		[]float64{1.5},
 		1.5,
 		1.5,
 		1.5,
+		1.5,
 		1,
 		1.5,
 	},
@@ -195,6 +198,7 @@ var float64sStatsTests = []struct {
 		1.9,
 		5.1,
 		12.3,
+		66.0858,
 		4,
 		3.075,
 	},
@@ -227,6 +231,15 @@ func TestFloat64s_Sum(t *testing.T) {
 	}
 }
 
+func TestFloat64s_Product(t *testing.T) {
+	for _, test := range float64sStatsTests {
+		t.Run("", func(t *testing.T) {
+			defer assertImmutableFloat64s(t, &test.ss)()
+			assert.Equal(t, test.product, Float64s(test.ss).Product())
+		})
+	}
+}
+
 func TestFloat64s_Len(t *testing.T) {
 	for _, test := range float64sStatsTests {
 		t.Run("", func(t *testing.T) {
@@ -733,6 +746,41 @@ func TestFloat64s_Random(t *testing.T) {
 	}
 }
 
+var float64sReduceTests = []struct {
+	ss       Float64s
+	expected float64
+	reducer  func(a, b float64) float64
+}{
+	{
+		Float64s{1, 2, 3},
+		6,
+		func(a, b float64) float64 { return a + b },
+	},
+	{
+		Float64s{1, 2, 3},
+		-4,
+		func(a, b float64) float64 { return a - b },
+	},
+	{
+		Float64s{},
+		0,
+		func(a, b float64) float64 { return a - b },
+	},
+	{
+		Float64s{1},
+		1,
+		func(a, b float64) float64 { return a - b },
+	},
+}
+
+func TestFloat64s_Reduce(t *testing.T) {
+	for _, test := range float64sReduceTests {
+		t.Run("", func(t *testing.T) {
+			assert.Equal(t, test.expected, test.ss.Reduce(test.reducer))
+		})
+	}
+}
+
 var float64sAbsTests = []struct {
 	ss  Float64s
 	abs Float64s
@@ -807,3 +855,49 @@ func TestFloat64s_Send(t *testing.T) {
 		})
 	}
 }
+
+var float64sIntersectTests = []struct {
+	ss       Float64s
+	params   []Float64s
+	expected Float64s
+}{
+	{
+		nil,
+		nil,
+		nil,
+	},
+	{
+		Float64s{1.2, 3.2},
+		nil,
+		nil,
+	},
+	{
+		nil,
+		[]Float64s{{1.2, 3.2, 5.5}, {5.5, 1.2}},
+		nil,
+	},
+	{
+		Float64s{1.2, 3.2},
+		[]Float64s{{1.2}, {3.2}},
+		nil,
+	},
+	{
+		Float64s{1.2, 3.2},
+		[]Float64s{{1.2}},
+		Float64s{1.2},
+	},
+	{
+		Float64s{1.2, 3.2},
+		[]Float64s{{1.2, 3.2, 5.5}, {5.5, 1.2}},
+		Float64s{1.2},
+	},
+}
+
+func TestFloat64s_Intersect(t *testing.T) {
+	for _, test := range float64sIntersectTests {
+		t.Run("", func(t *testing.T) {
+			defer assertImmutableFloat64s(t, &test.ss)()
+			assert.Equal(t, test.expected, test.ss.Intersect(test.params...))
+		})
+	}
+}
diff --git a/pie/ints_test.go b/pie/ints_test.go
index 42cba48..dcc1874 100644
--- a/pie/ints_test.go
+++ b/pie/ints_test.go
@@ -153,9 +153,9 @@ func TestInts_Last(t *testing.T) {
 }
 
 var intsStatsTests = []struct {
-	ss                 []int
-	min, max, sum, len int
-	average            float64
+	ss                          []int
+	min, max, sum, product, len int
+	average                     float64
 }{
 	{
 		nil,
@@ -164,6 +164,7 @@ var intsStatsTests = []struct {
 		0,
 		0,
 		0,
+		0,
 	},
 	{
 		[]int{},
@@ -172,6 +173,7 @@ var intsStatsTests = []struct {
 		0,
 		0,
 		0,
+		0,
 	},
 	{
 		[]int{1},
@@ -180,12 +182,14 @@ var intsStatsTests = []struct {
 		1,
 		1,
 		1,
+		1,
 	},
 	{
 		[]int{2, 3, 5, 1},
 		1,
 		5,
 		11,
+		30,
 		4,
 		2.75,
 	},
@@ -215,6 +219,14 @@ func TestInts_Sum(t *testing.T) {
 	}
 }
 
+func TestInts_Product(t *testing.T) {
+	for _, test := range intsStatsTests {
+		t.Run("", func(t *testing.T) {
+			assert.Equal(t, test.product, Ints(test.ss).Product())
+		})
+	}
+}
+
 func TestInts_Len(t *testing.T) {
 	for _, test := range intsStatsTests {
 		t.Run("", func(t *testing.T) {
@@ -719,6 +731,41 @@ func TestInts_Random(t *testing.T) {
 	}
 }
 
+var intsReduceTests = []struct {
+	ss       Ints
+	expected int
+	reducer  func(a, b int) int
+}{
+	{
+		Ints{1, 2, 3},
+		6,
+		func(a, b int) int { return a + b },
+	},
+	{
+		Ints{1, 2, 3},
+		-4,
+		func(a, b int) int { return a - b },
+	},
+	{
+		Ints{},
+		0,
+		func(a, b int) int { return a - b },
+	},
+	{
+		Ints{1},
+		1,
+		func(a, b int) int { return a - b },
+	},
+}
+
+func TestInts_Reduce(t *testing.T) {
+	for _, test := range intsReduceTests {
+		t.Run("", func(t *testing.T) {
+			assert.Equal(t, test.expected, test.ss.Reduce(test.reducer))
+		})
+	}
+}
+
 func TestInts_Abs(t *testing.T) {
 	assert.Equal(t, Ints{1, 5, 7}, Ints{-1, 5, -7}.Abs())
 	assert.Equal(t, Ints{689845, 688969, 220373, 89437, 308836}, Ints{-689845, -688969, -220373, -89437, 308836}.Abs())
@@ -773,3 +820,49 @@ func TestInts_Send(t *testing.T) {
 		})
 	}
 }
+
+var intsIntersectTests = []struct {
+	ss       Ints
+	params   []Ints
+	expected Ints
+}{
+	{
+		nil,
+		nil,
+		nil,
+	},
+	{
+		Ints{1, 3},
+		nil,
+		nil,
+	},
+	{
+		nil,
+		[]Ints{{1, 3, 5}, {5, 1}},
+		nil,
+	},
+	{
+		Ints{1, 3},
+		[]Ints{{1}, {3}},
+		nil,
+	},
+	{
+		Ints{1, 3},
+		[]Ints{{1}},
+		Ints{1},
+	},
+	{
+		Ints{1, 3},
+		[]Ints{{1, 3, 5}, {5, 1}},
+		Ints{1},
+	},
+}
+
+func TestInts_Intersect(t *testing.T) {
+	for _, test := range intsIntersectTests {
+		t.Run("", func(t *testing.T) {
+			defer assertImmutableInts(t, &test.ss)()
+			assert.Equal(t, test.expected, test.ss.Intersect(test.params...))
+		})
+	}
+}
diff --git a/pie/strings_test.go b/pie/strings_test.go
index c36f7a1..909ba21 100644
--- a/pie/strings_test.go
+++ b/pie/strings_test.go
@@ -705,6 +705,36 @@ func TestStrings_Random(t *testing.T) {
 	}
 }
 
+var stringsReduceTests = []struct {
+	ss       Strings
+	expected string
+	reducer  func(a, b string) string
+}{
+	{
+		Strings{"Hello", " ", "World"},
+		"Hello World",
+		func(a, b string) string { return a + b },
+	},
+	{
+		Strings{},
+		"",
+		func(a, b string) string { return a + b },
+	},
+	{
+		Strings{"Hello"},
+		"Hello",
+		func(a, b string) string { return a + b },
+	},
+}
+
+func TestStrings_Reduce(t *testing.T) {
+	for _, test := range stringsReduceTests {
+		t.Run("", func(t *testing.T) {
+			assert.Equal(t, test.expected, test.ss.Reduce(test.reducer))
+		})
+	}
+}
+
 var stringsSendTests = []struct {
 	ss            Strings
 	recieveDelay  time.Duration
@@ -753,3 +783,49 @@ func TestStrings_Send(t *testing.T) {
 		})
 	}
 }
+
+var stringsIntersectTests = []struct {
+	ss       Strings
+	params   []Strings
+	expected Strings
+}{
+	{
+		nil,
+		nil,
+		nil,
+	},
+	{
+		Strings{"foo", "bar"},
+		nil,
+		nil,
+	},
+	{
+		nil,
+		[]Strings{{"foo", "bar", "baz"}, {"baz", "foo"}},
+		nil,
+	},
+	{
+		Strings{"foo", "bar"},
+		[]Strings{{"bar"}, {"foo"}},
+		nil,
+	},
+	{
+		Strings{"foo", "bar"},
+		[]Strings{{"bar"}},
+		Strings{"bar"},
+	},
+	{
+		Strings{"foo", "bar"},
+		[]Strings{{"foo", "bar", "baz"}, {"baz", "foo"}},
+		Strings{"foo"},
+	},
+}
+
+func TestStrings_Intersect(t *testing.T) {
+	for _, test := range stringsIntersectTests {
+		t.Run("", func(t *testing.T) {
+			defer assertImmutableStrings(t, &test.ss)()
+			assert.Equal(t, test.expected, test.ss.Intersect(test.params...))
+		})
+	}
+}