Repository: elliotchance/pie
Pull request #81 (2019-05-02 19:43:01)

Add Random function
* elliotchance/pie#76

Selects a random element from a slice, or if the slice is empty the zero element

<!-- 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/81)
<!-- Reviewable:end -->


codecov-io: # [Codecov](https://codecov.io/gh/elliotchance/pie/pull/81?src=pr&el=h1) Report
> :exclamation: No coverage uploaded for pull request base (`master@71a8d56`). [Click here to learn what that means](https://docs.codecov.io/docs/error-reference#section-missing-base-commit).
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/elliotchance/pie/pull/81/graphs/tree.svg?width=650&token=OvugwUQGM3&height=150&src=pr)](https://codecov.io/gh/elliotchance/pie/pull/81?src=pr&el=tree)

```diff
@@           Coverage Diff           @@
##             master    #81   +/-   ##
=======================================
  Coverage          ?   100%           
=======================================
  Files             ?      7           
  Lines             ?    735           
  Branches          ?      0           
=======================================
  Hits              ?    735           
  Misses            ?      0           
  Partials          ?      0
```


| [Impacted Files](https://codecov.io/gh/elliotchance/pie/pull/81?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pie/float64s\_pie.go](https://codecov.io/gh/elliotchance/pie/pull/81/diff?src=pr&el=tree#diff-cGllL2Zsb2F0NjRzX3BpZS5nbw==) | `100% <100%> (ø)` | |
| [pie/carpointers\_pie.go](https://codecov.io/gh/elliotchance/pie/pull/81/diff?src=pr&el=tree#diff-cGllL2NhcnBvaW50ZXJzX3BpZS5nbw==) | `100% <100%> (ø)` | |
| [pie/cars\_pie.go](https://codecov.io/gh/elliotchance/pie/pull/81/diff?src=pr&el=tree#diff-cGllL2NhcnNfcGllLmdv) | `100% <100%> (ø)` | |
| [pie/ints\_pie.go](https://codecov.io/gh/elliotchance/pie/pull/81/diff?src=pr&el=tree#diff-cGllL2ludHNfcGllLmdv) | `100% <100%> (ø)` | |
| [pie/strings\_pie.go](https://codecov.io/gh/elliotchance/pie/pull/81/diff?src=pr&el=tree#diff-cGllL3N0cmluZ3NfcGllLmdv) | `100% <100%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/elliotchance/pie/pull/81?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/elliotchance/pie/pull/81?src=pr&el=footer). Last update [71a8d56...dbe235f](https://codecov.io/gh/elliotchance/pie/pull/81?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

rdooley: You are correct! One second
rdooley: It is definitely possible (mea culpa)

Patch:
diff --git a/README.md b/README.md
index 9ad1ffe..f9e146b 100644
--- a/README.md
+++ b/README.md
@@ -42,7 +42,7 @@ package main
 import (
     "fmt"
     "strings"
-    
+
     "github.com/elliotchance/pie/pie"
 )
 
@@ -100,7 +100,7 @@ cars.Unselect(func (car Car) {
     }).
     Transform(func (car Car) Car {
         car.Name = strings.ToUpper(car.Name)
-        
+
         return car
     }).
     Last()
@@ -146,6 +146,7 @@ This will only generate `myInts.Average`, `myInts.Sum` and `myStrings.Select`.
 | `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. |
+| `Random`     | ✓      | ✓      | ✓     |      | 1        | Select a random element, or a zeroed value if empty. |
 | `Reverse`    | ✓      | ✓      | ✓     |      | n        | Reverse elements. |
 | `Select`     | ✓      | ✓      | ✓     |      | n        | A new slice containing only the elements that returned true from the condition. |
 | `Sort`       | ✓      | ✓      |       |      | n⋅log(n) | Return a new sorted slice. |
diff --git a/functions/main.go b/functions/main.go
index bc65ad3..41c70d2 100644
--- a/functions/main.go
+++ b/functions/main.go
@@ -40,6 +40,7 @@ var Functions = []struct {
 	{"Max", "max.go", ForNumbersAndStrings},
 	{"Median", "median.go", ForNumbers},
 	{"Min", "min.go", ForNumbersAndStrings},
+	{"Random", "random.go", ForAll},
 	{"Reverse", "reverse.go", ForAll},
 	{"Select", "select.go", ForAll},
 	{"Sort", "sort.go", ForNumbersAndStrings},
diff --git a/functions/random.go b/functions/random.go
new file mode 100644
index 0000000..51f07e0
--- /dev/null
+++ b/functions/random.go
@@ -0,0 +1,21 @@
+package functions
+
+import (
+	"math/rand"
+)
+
+// Random returns a random element by your rand.Source, or zero
+func (ss SliceType) Random(source rand.Source) ElementType {
+	n := len(ss)
+
+	// Avoid the extra allocation.
+	if n < 1 {
+		return ElementZeroValue
+	}
+	if n < 2 {
+		return ss[0]
+	}
+	rnd := rand.New(source)
+	i := rnd.Intn(n)
+	return ss[i]
+}
diff --git a/pie/carpointers_pie.go b/pie/carpointers_pie.go
index b5f65f1..cf2905e 100755
--- a/pie/carpointers_pie.go
+++ b/pie/carpointers_pie.go
@@ -158,6 +158,22 @@ func (ss carPointers) Len() int {
 	return len(ss)
 }
 
+// Random returns a random element by your rand.Source, or zero
+func (ss carPointers) Random(source rand.Source) *car {
+	n := len(ss)
+
+	// Avoid the extra allocation.
+	if n < 1 {
+		return &car{}
+	}
+	if n < 2 {
+		return ss[0]
+	}
+	rnd := rand.New(source)
+	i := rnd.Intn(n)
+	return ss[i]
+}
+
 // 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/cars_pie.go b/pie/cars_pie.go
index 1005065..dd9ce24 100755
--- a/pie/cars_pie.go
+++ b/pie/cars_pie.go
@@ -158,6 +158,22 @@ func (ss cars) Len() int {
 	return len(ss)
 }
 
+// Random returns a random element by your rand.Source, or zero
+func (ss cars) Random(source rand.Source) car {
+	n := len(ss)
+
+	// Avoid the extra allocation.
+	if n < 1 {
+		return car{}
+	}
+	if n < 2 {
+		return ss[0]
+	}
+	rnd := rand.New(source)
+	i := rnd.Intn(n)
+	return ss[i]
+}
+
 // 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/float64s_pie.go b/pie/float64s_pie.go
index 5874feb..3de3ff3 100755
--- a/pie/float64s_pie.go
+++ b/pie/float64s_pie.go
@@ -249,6 +249,22 @@ func (ss Float64s) Min() (min float64) {
 	return
 }
 
+// Random returns a random element by your rand.Source, or zero
+func (ss Float64s) Random(source rand.Source) float64 {
+	n := len(ss)
+
+	// Avoid the extra allocation.
+	if n < 1 {
+		return 0
+	}
+	if n < 2 {
+		return ss[0]
+	}
+	rnd := rand.New(source)
+	i := rnd.Intn(n)
+	return ss[i]
+}
+
 // 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 e00b361..c4370dc 100755
--- a/pie/ints_pie.go
+++ b/pie/ints_pie.go
@@ -249,6 +249,22 @@ func (ss Ints) Min() (min int) {
 	return
 }
 
+// Random returns a random element by your rand.Source, or zero
+func (ss Ints) Random(source rand.Source) int {
+	n := len(ss)
+
+	// Avoid the extra allocation.
+	if n < 1 {
+		return 0
+	}
+	if n < 2 {
+		return ss[0]
+	}
+	rnd := rand.New(source)
+	i := rnd.Intn(n)
+	return ss[i]
+}
+
 // 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 5732cf7..e55e2fd 100755
--- a/pie/strings_pie.go
+++ b/pie/strings_pie.go
@@ -218,6 +218,22 @@ func (ss Strings) Min() (min string) {
 	return
 }
 
+// Random returns a random element by your rand.Source, or zero
+func (ss Strings) Random(source rand.Source) string {
+	n := len(ss)
+
+	// Avoid the extra allocation.
+	if n < 1 {
+		return ""
+	}
+	if n < 2 {
+		return ss[0]
+	}
+	rnd := rand.New(source)
+	i := rnd.Intn(n)
+	return ss[i]
+}
+
 // 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 d5021cf..d7d878d 100644
--- a/template.go
+++ b/template.go
@@ -331,6 +331,28 @@ func (ss SliceType) Min() (min ElementType) {
 
 	return
 }
+`,
+	"Random": `package functions
+
+import (
+	"math/rand"
+)
+
+// Random returns a random element by your rand.Source, or zero
+func (ss SliceType) Random(source rand.Source) ElementType {
+	n := len(ss)
+
+	// Avoid the extra allocation.
+	if n < 1 {
+		return ElementZeroValue
+	}
+	if n < 2 {
+		return ss[0]
+	}
+	rnd := rand.New(source)
+	i := rnd.Intn(n)
+	return ss[i]
+}
 `,
 	"Reverse": `package functions
 


Test patch:
diff --git a/pie/carpointers_test.go b/pie/carpointers_test.go
index c9229e9..28518d2 100644
--- a/pie/carpointers_test.go
+++ b/pie/carpointers_test.go
@@ -582,3 +582,49 @@ func TestCarPointers_Each(t *testing.T) {
 	})
 	assert.Equal(t, []string{"bar", "Baz"}, names)
 }
+
+var carPointersRandomTests = []struct {
+	ss       carPointers
+	expected *car
+	source   rand.Source
+}{
+	{
+		nil,
+		&car{},
+		nil,
+	},
+	{
+		nil,
+		&car{},
+		rand.NewSource(0),
+	},
+	{
+		carPointers{},
+		&car{},
+		rand.NewSource(0),
+	},
+	{
+		carPointers{&car{"bar", "yellow"}, &car{"Baz", "black"}, &car{"foo", "red"}},
+		&car{"bar", "yellow"},
+		rand.NewSource(0),
+	},
+	{
+		carPointers{&car{"bar", "yellow"}, &car{"Baz", "black"}, &car{"foo", "red"}},
+		&car{"foo", "red"},
+		rand.NewSource(1),
+	},
+	{
+		carPointers{&car{"bar", "yellow"}},
+		&car{"bar", "yellow"},
+		rand.NewSource(0),
+	},
+}
+
+func TestCarPointers_Random(t *testing.T) {
+	for _, test := range carPointersRandomTests {
+		t.Run("", func(t *testing.T) {
+			defer assertImmutableCarPointers(t, &test.ss)()
+			assert.Equal(t, test.expected, test.ss.Random(test.source))
+		})
+	}
+}
diff --git a/pie/cars_test.go b/pie/cars_test.go
index 5b64340..d278179 100644
--- a/pie/cars_test.go
+++ b/pie/cars_test.go
@@ -566,3 +566,49 @@ func TestCars_Each(t *testing.T) {
 	})
 	assert.Equal(t, []string{"bar", "Baz"}, names)
 }
+
+var carsRandomTests = []struct {
+	ss       cars
+	expected car
+	source   rand.Source
+}{
+	{
+		nil,
+		car{},
+		nil,
+	},
+	{
+		nil,
+		car{},
+		rand.NewSource(0),
+	},
+	{
+		cars{},
+		car{},
+		rand.NewSource(0),
+	},
+	{
+		cars{car{"bar", "yellow"}, car{"Baz", "black"}, car{"foo", "red"}},
+		car{"bar", "yellow"},
+		rand.NewSource(0),
+	},
+	{
+		cars{car{"bar", "yellow"}, car{"Baz", "black"}, car{"foo", "red"}},
+		car{"foo", "red"},
+		rand.NewSource(1),
+	},
+	{
+		cars{car{"bar", "yellow"}},
+		car{"bar", "yellow"},
+		rand.NewSource(0),
+	},
+}
+
+func TestCars_Random(t *testing.T) {
+	for _, test := range carsRandomTests {
+		t.Run("", func(t *testing.T) {
+			defer assertImmutableCars(t, &test.ss)()
+			assert.Equal(t, test.expected, test.ss.Random(test.source))
+		})
+	}
+}
diff --git a/pie/float64s_test.go b/pie/float64s_test.go
index 42bd299..9c853ca 100644
--- a/pie/float64s_test.go
+++ b/pie/float64s_test.go
@@ -686,6 +686,52 @@ func TestFloat64s_Each(t *testing.T) {
 	assert.Equal(t, []float64{435.34, 8923.1}, values)
 }
 
+var float64sRandomTests = []struct {
+	ss       Float64s
+	expected float64
+	source   rand.Source
+}{
+	{
+		nil,
+		0.0,
+		nil,
+	},
+	{
+		nil,
+		0.0,
+		rand.NewSource(0),
+	},
+	{
+		Float64s{},
+		0.0,
+		rand.NewSource(0),
+	},
+	{
+		Float64s{12.3, 2.34, 4.56},
+		12.3,
+		rand.NewSource(0),
+	},
+	{
+		Float64s{12.3, 2.34, 4.56},
+		4.56,
+		rand.NewSource(1),
+	},
+	{
+		Float64s{12.3},
+		12.3,
+		rand.NewSource(0),
+	},
+}
+
+func TestFloat64s_Random(t *testing.T) {
+	for _, test := range float64sRandomTests {
+		t.Run("", func(t *testing.T) {
+			defer assertImmutableFloat64s(t, &test.ss)()
+			assert.Equal(t, test.expected, test.ss.Random(test.source))
+		})
+	}
+}
+
 var float64sAbsTests = []struct {
 	ss  Float64s
 	abs Float64s
diff --git a/pie/ints_test.go b/pie/ints_test.go
index e0d95e2..d67a91b 100644
--- a/pie/ints_test.go
+++ b/pie/ints_test.go
@@ -672,7 +672,53 @@ func TestInts_Each(t *testing.T) {
 	assert.Equal(t, []int{435, 8923}, values)
 }
 
-func TestMyInts_Abs(t *testing.T) {
+var intsRandomTests = []struct {
+	ss       Ints
+	expected int
+	source   rand.Source
+}{
+	{
+		nil,
+		0,
+		nil,
+	},
+	{
+		nil,
+		0,
+		rand.NewSource(0),
+	},
+	{
+		Ints{},
+		0,
+		rand.NewSource(0),
+	},
+	{
+		Ints{1, 2, 4},
+		4,
+		rand.NewSource(1),
+	},
+	{
+		Ints{1, 2, 4},
+		1,
+		rand.NewSource(0),
+	},
+	{
+		Ints{12},
+		12,
+		rand.NewSource(0),
+	},
+}
+
+func TestInts_Random(t *testing.T) {
+	for _, test := range intsRandomTests {
+		t.Run("", func(t *testing.T) {
+			defer assertImmutableInts(t, &test.ss)()
+			assert.Equal(t, test.expected, test.ss.Random(test.source))
+		})
+	}
+}
+
+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())
 	assert.Equal(t, Ints{1, 2}, Ints{1, 2}.Abs())
diff --git a/pie/strings_test.go b/pie/strings_test.go
index 52c9973..39d0353 100644
--- a/pie/strings_test.go
+++ b/pie/strings_test.go
@@ -657,3 +657,49 @@ func TestStrings_Each(t *testing.T) {
 	})
 	assert.Equal(t, []string{"baz", "qux"}, values)
 }
+
+var stringsRandomTests = []struct {
+	ss       Strings
+	expected string
+	source   rand.Source
+}{
+	{
+		nil,
+		"",
+		nil,
+	},
+	{
+		nil,
+		"",
+		rand.NewSource(0),
+	},
+	{
+		Strings{},
+		"",
+		rand.NewSource(0),
+	},
+	{
+		Strings{"foo", "bar", "baz"},
+		"baz",
+		rand.NewSource(1),
+	},
+	{
+		Strings{"foo", "bar", "baz"},
+		"foo",
+		rand.NewSource(0),
+	},
+	{
+		Strings{"foo"},
+		"foo",
+		rand.NewSource(0),
+	},
+}
+
+func TestStrings_Random(t *testing.T) {
+	for _, test := range stringsRandomTests {
+		t.Run("", func(t *testing.T) {
+			defer assertImmutableStrings(t, &test.ss)()
+			assert.Equal(t, test.expected, test.ss.Random(test.source))
+		})
+	}
+}