Repository: elliotchance/pie
Pull request #147 (2019-05-18 22:06:32)

Adds JSONBytesIndent and JSONStringIndent functions
Like `JSONBytes/JSONString` except `json.MarshalIndent` is used.

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


codecov-io: # [Codecov](https://codecov.io/gh/elliotchance/pie/pull/147?src=pr&el=h1) Report
> :exclamation: No coverage uploaded for pull request base (`master@d758bde`). [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/147/graphs/tree.svg?width=650&token=OvugwUQGM3&height=150&src=pr)](https://codecov.io/gh/elliotchance/pie/pull/147?src=pr&el=tree)

```diff
@@           Coverage Diff           @@
##             master   #147   +/-   ##
=======================================
  Coverage          ?   100%           
=======================================
  Files             ?      8           
  Lines             ?   1528           
  Branches          ?      0           
=======================================
  Hits              ?   1528           
  Misses            ?      0           
  Partials          ?      0
```


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

------

[Continue to review full report at Codecov](https://codecov.io/gh/elliotchance/pie/pull/147?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/147?src=pr&el=footer). Last update [d758bde...709ebac](https://codecov.io/gh/elliotchance/pie/pull/147?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/README.md b/README.md
index 38f8bf0..0429981 100644
--- a/README.md
+++ b/README.md
@@ -195,8 +195,10 @@ The letters in brackets indicate:
 | `Intersect`       | ✓      | ✓      |        |      | n        | Intersect returns elements which exists in all slices. |
 | `Ints` (S)        | ✓      | ✓      | ✓      |      | n        | Transforms each element into an int. |
 | `Join`            | ✓      |        |        |      | n        | A string from joining each of the elements. |
-| `JSONBytes `      | ✓      | ✓      | ✓      |      | n        | The JSON encoded bytes. |
+| `JSONBytes`       | ✓      | ✓      | ✓      |      | n        | The JSON encoded bytes. |
+| `JSONBytesIndent` | ✓      | ✓      | ✓      |      | n        | The indented JSON encoded bytes. |
 | `JSONString`      | ✓      | ✓      | ✓      |      | n        | The JSON encoded string. |
+| `JSONStringIndent`| ✓      | ✓      | ✓      |      | n        | The indented JSON encoded string. |
 | `Keys`            |        |        |        | ✓    | n        | Returns all keys in the map (in random order). |
 | `Last`            | ✓      | ✓      | ✓      |      | 1        | The last element, or a zeroed value. |
 | `LastOr`          | ✓      | ✓      | ✓      |      | 1        | The last element, or a default value. |
@@ -205,6 +207,7 @@ The letters in brackets indicate:
 | `Max`             | ✓      | ✓      |        |      | n        | The maximum value, or a zeroes value. |
 | `Median`          |        | ✓      |        |      | 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. |
+| `Mode`            | ✓      | ✓      | ✓      |      | n        | Returns a slice of items with the highest frequency. |
 | `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. |
diff --git a/functions/drop_top.go b/functions/drop_top.go
index 94f6a37..dcdbc11 100644
--- a/functions/drop_top.go
+++ b/functions/drop_top.go
@@ -8,7 +8,10 @@ func (ss SliceType) DropTop(n int) (drop SliceType) {
 		return
 	}
 
-	drop = ss[n:]
+	// Copy ss, to make sure no memory is overlapping between input and
+	// output. See issue #145.
+	drop = make([]ElementType, len(ss)-n)
+	copy(drop, ss[n:])
 
 	return
 }
diff --git a/functions/json_bytes_indent.go b/functions/json_bytes_indent.go
new file mode 100644
index 0000000..b1bc67d
--- /dev/null
+++ b/functions/json_bytes_indent.go
@@ -0,0 +1,21 @@
+package functions
+
+import (
+	"encoding/json"
+)
+
+// JSONBytesIndent returns the JSON encoded array as bytes with indent applied.
+//
+// One important thing to note is that it will treat a nil slice as an empty
+// slice to ensure that the JSON value return is always an array. See
+// json.MarshalIndent for details.
+func (ss SliceType) JSONBytesIndent(prefix, indent string) []byte {
+	if ss == nil {
+		return []byte("[]")
+	}
+
+	// An error should not be possible.
+	data, _ := json.MarshalIndent(ss, prefix, indent)
+
+	return data
+}
diff --git a/functions/json_string_indent.go b/functions/json_string_indent.go
new file mode 100644
index 0000000..7fab573
--- /dev/null
+++ b/functions/json_string_indent.go
@@ -0,0 +1,21 @@
+package functions
+
+import (
+	"encoding/json"
+)
+
+// JSONStringIndent returns the JSON encoded array as a string with indent applied.
+//
+// One important thing to note is that it will treat a nil slice as an empty
+// slice to ensure that the JSON value return is always an array. See
+// json.MarshalIndent for details.
+func (ss SliceType) JSONStringIndent(prefix, indent string) string {
+	if ss == nil {
+		return "[]"
+	}
+
+	// An error should not be possible.
+	data, _ := json.MarshalIndent(ss, prefix, indent)
+
+	return string(data)
+}
diff --git a/functions/main.go b/functions/main.go
index 767072d..ca5e938 100644
--- a/functions/main.go
+++ b/functions/main.go
@@ -43,7 +43,9 @@ var Functions = []struct {
 	{"Ints", "ints.go", ForAll},
 	{"Join", "join.go", ForStrings},
 	{"JSONBytes", "json_bytes.go", ForAll},
+	{"JSONBytesIndent", "json_bytes_indent.go", ForAll},
 	{"JSONString", "json_string.go", ForAll},
+	{"JSONStringIndent", "json_string_indent.go", ForAll},
 	{"Keys", "keys.go", ForMaps},
 	{"Last", "last.go", ForAll},
 	{"LastOr", "last_or.go", ForAll},
@@ -52,6 +54,7 @@ var Functions = []struct {
 	{"Max", "max.go", ForNumbersAndStrings},
 	{"Median", "median.go", ForNumbers},
 	{"Min", "min.go", ForNumbersAndStrings},
+	{"Mode", "mode.go", ForAll},
 	{"Product", "product.go", ForNumbers},
 	{"Random", "random.go", ForAll},
 	{"Reduce", "reduce.go", ForNumbersAndStrings},
diff --git a/functions/mode.go b/functions/mode.go
new file mode 100644
index 0000000..d9f8e59
--- /dev/null
+++ b/functions/mode.go
@@ -0,0 +1,31 @@
+package functions
+
+// Mode returns a new slice containing the most frequently occuring values.
+//
+// The number of items returned may be the same as the input or less. It will
+// never return zero items unless the input slice has zero items.
+func (ss SliceType) Mode() SliceType {
+	if len(ss) == 0 {
+		return nil
+	}
+	values := make(map[ElementType]int, 0)
+	for _, s := range ss {
+		values[s]++
+	}
+
+	var maxFrequency int
+	for _, v := range values {
+		if v > maxFrequency {
+			maxFrequency = v
+		}
+	}
+
+	var maxValues SliceType
+	for k, v := range values {
+		if v == maxFrequency {
+			maxValues = append(maxValues, k)
+		}
+	}
+
+	return maxValues
+}
diff --git a/pie/carpointers_pie.go b/pie/carpointers_pie.go
index 2df30d0..b5a7e87 100755
--- a/pie/carpointers_pie.go
+++ b/pie/carpointers_pie.go
@@ -127,7 +127,10 @@ func (ss carPointers) DropTop(n int) (drop carPointers) {
 		return
 	}
 
-	drop = ss[n:]
+	// Copy ss, to make sure no memory is overlapping between input and
+	// output. See issue #145.
+	drop = make([]*car, len(ss)-n)
+	copy(drop, ss[n:])
 
 	return
 }
@@ -276,6 +279,22 @@ func (ss carPointers) JSONBytes() []byte {
 	return data
 }
 
+// JSONBytesIndent returns the JSON encoded array as bytes with indent applied.
+//
+// One important thing to note is that it will treat a nil slice as an empty
+// slice to ensure that the JSON value return is always an array. See
+// json.MarshalIndent for details.
+func (ss carPointers) JSONBytesIndent(prefix, indent string) []byte {
+	if ss == nil {
+		return []byte("[]")
+	}
+
+	// An error should not be possible.
+	data, _ := json.MarshalIndent(ss, prefix, indent)
+
+	return data
+}
+
 // 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
@@ -291,6 +310,22 @@ func (ss carPointers) JSONString() string {
 	return string(data)
 }
 
+// JSONStringIndent returns the JSON encoded array as a string with indent applied.
+//
+// One important thing to note is that it will treat a nil slice as an empty
+// slice to ensure that the JSON value return is always an array. See
+// json.MarshalIndent for details.
+func (ss carPointers) JSONStringIndent(prefix, indent string) string {
+	if ss == nil {
+		return "[]"
+	}
+
+	// An error should not be possible.
+	data, _ := json.MarshalIndent(ss, prefix, indent)
+
+	return string(data)
+}
+
 // Last returns the last element, or zero. Also see LastOr().
 func (ss carPointers) Last() *car {
 	return ss.LastOr(&car{})
@@ -329,6 +364,36 @@ func (ss carPointers) Map(fn func(*car) *car) (ss2 carPointers) {
 	return
 }
 
+// Mode returns a new slice containing the most frequently occuring values.
+//
+// The number of items returned may be the same as the input or less. It will
+// never return zero items unless the input slice has zero items.
+func (ss carPointers) Mode() carPointers {
+	if len(ss) == 0 {
+		return nil
+	}
+	values := make(map[*car]int, 0)
+	for _, s := range ss {
+		values[s]++
+	}
+
+	var maxFrequency int
+	for _, v := range values {
+		if v > maxFrequency {
+			maxFrequency = v
+		}
+	}
+
+	var maxValues carPointers
+	for k, v := range values {
+		if v == maxFrequency {
+			maxValues = append(maxValues, k)
+		}
+	}
+
+	return maxValues
+}
+
 // Random returns a random element by your rand.Source, or zero
 func (ss carPointers) Random(source rand.Source) *car {
 	n := len(ss)
diff --git a/pie/cars_pie.go b/pie/cars_pie.go
index 71bea5d..e02025c 100755
--- a/pie/cars_pie.go
+++ b/pie/cars_pie.go
@@ -127,7 +127,10 @@ func (ss cars) DropTop(n int) (drop cars) {
 		return
 	}
 
-	drop = ss[n:]
+	// Copy ss, to make sure no memory is overlapping between input and
+	// output. See issue #145.
+	drop = make([]car, len(ss)-n)
+	copy(drop, ss[n:])
 
 	return
 }
@@ -276,6 +279,22 @@ func (ss cars) JSONBytes() []byte {
 	return data
 }
 
+// JSONBytesIndent returns the JSON encoded array as bytes with indent applied.
+//
+// One important thing to note is that it will treat a nil slice as an empty
+// slice to ensure that the JSON value return is always an array. See
+// json.MarshalIndent for details.
+func (ss cars) JSONBytesIndent(prefix, indent string) []byte {
+	if ss == nil {
+		return []byte("[]")
+	}
+
+	// An error should not be possible.
+	data, _ := json.MarshalIndent(ss, prefix, indent)
+
+	return data
+}
+
 // 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
@@ -291,6 +310,22 @@ func (ss cars) JSONString() string {
 	return string(data)
 }
 
+// JSONStringIndent returns the JSON encoded array as a string with indent applied.
+//
+// One important thing to note is that it will treat a nil slice as an empty
+// slice to ensure that the JSON value return is always an array. See
+// json.MarshalIndent for details.
+func (ss cars) JSONStringIndent(prefix, indent string) string {
+	if ss == nil {
+		return "[]"
+	}
+
+	// An error should not be possible.
+	data, _ := json.MarshalIndent(ss, prefix, indent)
+
+	return string(data)
+}
+
 // Last returns the last element, or zero. Also see LastOr().
 func (ss cars) Last() car {
 	return ss.LastOr(car{})
@@ -329,6 +364,36 @@ func (ss cars) Map(fn func(car) car) (ss2 cars) {
 	return
 }
 
+// Mode returns a new slice containing the most frequently occuring values.
+//
+// The number of items returned may be the same as the input or less. It will
+// never return zero items unless the input slice has zero items.
+func (ss cars) Mode() cars {
+	if len(ss) == 0 {
+		return nil
+	}
+	values := make(map[car]int, 0)
+	for _, s := range ss {
+		values[s]++
+	}
+
+	var maxFrequency int
+	for _, v := range values {
+		if v > maxFrequency {
+			maxFrequency = v
+		}
+	}
+
+	var maxValues cars
+	for k, v := range values {
+		if v == maxFrequency {
+			maxValues = append(maxValues, k)
+		}
+	}
+
+	return maxValues
+}
+
 // Random returns a random element by your rand.Source, or zero
 func (ss cars) Random(source rand.Source) car {
 	n := len(ss)
diff --git a/pie/float64s_pie.go b/pie/float64s_pie.go
index c4e63d6..fb173c5 100755
--- a/pie/float64s_pie.go
+++ b/pie/float64s_pie.go
@@ -165,7 +165,10 @@ func (ss Float64s) DropTop(n int) (drop Float64s) {
 		return
 	}
 
-	drop = ss[n:]
+	// Copy ss, to make sure no memory is overlapping between input and
+	// output. See issue #145.
+	drop = make([]float64, len(ss)-n)
+	copy(drop, ss[n:])
 
 	return
 }
@@ -349,6 +352,22 @@ func (ss Float64s) JSONBytes() []byte {
 	return data
 }
 
+// JSONBytesIndent returns the JSON encoded array as bytes with indent applied.
+//
+// One important thing to note is that it will treat a nil slice as an empty
+// slice to ensure that the JSON value return is always an array. See
+// json.MarshalIndent for details.
+func (ss Float64s) JSONBytesIndent(prefix, indent string) []byte {
+	if ss == nil {
+		return []byte("[]")
+	}
+
+	// An error should not be possible.
+	data, _ := json.MarshalIndent(ss, prefix, indent)
+
+	return data
+}
+
 // 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
@@ -364,6 +383,22 @@ func (ss Float64s) JSONString() string {
 	return string(data)
 }
 
+// JSONStringIndent returns the JSON encoded array as a string with indent applied.
+//
+// One important thing to note is that it will treat a nil slice as an empty
+// slice to ensure that the JSON value return is always an array. See
+// json.MarshalIndent for details.
+func (ss Float64s) JSONStringIndent(prefix, indent string) string {
+	if ss == nil {
+		return "[]"
+	}
+
+	// An error should not be possible.
+	data, _ := json.MarshalIndent(ss, prefix, indent)
+
+	return string(data)
+}
+
 // Last returns the last element, or zero. Also see LastOr().
 func (ss Float64s) Last() float64 {
 	return ss.LastOr(0)
@@ -499,6 +534,36 @@ func (ss Float64s) Min() (min float64) {
 	return
 }
 
+// Mode returns a new slice containing the most frequently occuring values.
+//
+// The number of items returned may be the same as the input or less. It will
+// never return zero items unless the input slice has zero items.
+func (ss Float64s) Mode() Float64s {
+	if len(ss) == 0 {
+		return nil
+	}
+	values := make(map[float64]int, 0)
+	for _, s := range ss {
+		values[s]++
+	}
+
+	var maxFrequency int
+	for _, v := range values {
+		if v > maxFrequency {
+			maxFrequency = v
+		}
+	}
+
+	var maxValues Float64s
+	for k, v := range values {
+		if v == maxFrequency {
+			maxValues = append(maxValues, k)
+		}
+	}
+
+	return maxValues
+}
+
 // Product is the product of all of the elements.
 func (ss Float64s) Product() (product float64) {
 	if len(ss) == 0 {
diff --git a/pie/ints_pie.go b/pie/ints_pie.go
index 24e2ebb..e03e624 100755
--- a/pie/ints_pie.go
+++ b/pie/ints_pie.go
@@ -165,7 +165,10 @@ func (ss Ints) DropTop(n int) (drop Ints) {
 		return
 	}
 
-	drop = ss[n:]
+	// Copy ss, to make sure no memory is overlapping between input and
+	// output. See issue #145.
+	drop = make([]int, len(ss)-n)
+	copy(drop, ss[n:])
 
 	return
 }
@@ -349,6 +352,22 @@ func (ss Ints) JSONBytes() []byte {
 	return data
 }
 
+// JSONBytesIndent returns the JSON encoded array as bytes with indent applied.
+//
+// One important thing to note is that it will treat a nil slice as an empty
+// slice to ensure that the JSON value return is always an array. See
+// json.MarshalIndent for details.
+func (ss Ints) JSONBytesIndent(prefix, indent string) []byte {
+	if ss == nil {
+		return []byte("[]")
+	}
+
+	// An error should not be possible.
+	data, _ := json.MarshalIndent(ss, prefix, indent)
+
+	return data
+}
+
 // 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
@@ -364,6 +383,22 @@ func (ss Ints) JSONString() string {
 	return string(data)
 }
 
+// JSONStringIndent returns the JSON encoded array as a string with indent applied.
+//
+// One important thing to note is that it will treat a nil slice as an empty
+// slice to ensure that the JSON value return is always an array. See
+// json.MarshalIndent for details.
+func (ss Ints) JSONStringIndent(prefix, indent string) string {
+	if ss == nil {
+		return "[]"
+	}
+
+	// An error should not be possible.
+	data, _ := json.MarshalIndent(ss, prefix, indent)
+
+	return string(data)
+}
+
 // Last returns the last element, or zero. Also see LastOr().
 func (ss Ints) Last() int {
 	return ss.LastOr(0)
@@ -499,6 +534,36 @@ func (ss Ints) Min() (min int) {
 	return
 }
 
+// Mode returns a new slice containing the most frequently occuring values.
+//
+// The number of items returned may be the same as the input or less. It will
+// never return zero items unless the input slice has zero items.
+func (ss Ints) Mode() Ints {
+	if len(ss) == 0 {
+		return nil
+	}
+	values := make(map[int]int, 0)
+	for _, s := range ss {
+		values[s]++
+	}
+
+	var maxFrequency int
+	for _, v := range values {
+		if v > maxFrequency {
+			maxFrequency = v
+		}
+	}
+
+	var maxValues Ints
+	for k, v := range values {
+		if v == maxFrequency {
+			maxValues = append(maxValues, k)
+		}
+	}
+
+	return maxValues
+}
+
 // Product is the product of all of the elements.
 func (ss Ints) Product() (product int) {
 	if len(ss) == 0 {
diff --git a/pie/strings_pie.go b/pie/strings_pie.go
index 687c5cc..5b7a86d 100755
--- a/pie/strings_pie.go
+++ b/pie/strings_pie.go
@@ -141,7 +141,10 @@ func (ss Strings) DropTop(n int) (drop Strings) {
 		return
 	}
 
-	drop = ss[n:]
+	// Copy ss, to make sure no memory is overlapping between input and
+	// output. See issue #145.
+	drop = make([]string, len(ss)-n)
+	copy(drop, ss[n:])
 
 	return
 }
@@ -338,6 +341,22 @@ func (ss Strings) JSONBytes() []byte {
 	return data
 }
 
+// JSONBytesIndent returns the JSON encoded array as bytes with indent applied.
+//
+// One important thing to note is that it will treat a nil slice as an empty
+// slice to ensure that the JSON value return is always an array. See
+// json.MarshalIndent for details.
+func (ss Strings) JSONBytesIndent(prefix, indent string) []byte {
+	if ss == nil {
+		return []byte("[]")
+	}
+
+	// An error should not be possible.
+	data, _ := json.MarshalIndent(ss, prefix, indent)
+
+	return data
+}
+
 // 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
@@ -353,6 +372,22 @@ func (ss Strings) JSONString() string {
 	return string(data)
 }
 
+// JSONStringIndent returns the JSON encoded array as a string with indent applied.
+//
+// One important thing to note is that it will treat a nil slice as an empty
+// slice to ensure that the JSON value return is always an array. See
+// json.MarshalIndent for details.
+func (ss Strings) JSONStringIndent(prefix, indent string) string {
+	if ss == nil {
+		return "[]"
+	}
+
+	// An error should not be possible.
+	data, _ := json.MarshalIndent(ss, prefix, indent)
+
+	return string(data)
+}
+
 // Last returns the last element, or zero. Also see LastOr().
 func (ss Strings) Last() string {
 	return ss.LastOr("")
@@ -423,6 +458,36 @@ func (ss Strings) Min() (min string) {
 	return
 }
 
+// Mode returns a new slice containing the most frequently occuring values.
+//
+// The number of items returned may be the same as the input or less. It will
+// never return zero items unless the input slice has zero items.
+func (ss Strings) Mode() Strings {
+	if len(ss) == 0 {
+		return nil
+	}
+	values := make(map[string]int, 0)
+	for _, s := range ss {
+		values[s]++
+	}
+
+	var maxFrequency int
+	for _, v := range values {
+		if v > maxFrequency {
+			maxFrequency = v
+		}
+	}
+
+	var maxValues Strings
+	for k, v := range values {
+		if v == maxFrequency {
+			maxValues = append(maxValues, k)
+		}
+	}
+
+	return maxValues
+}
+
 // Random returns a random element by your rand.Source, or zero
 func (ss Strings) Random(source rand.Source) string {
 	n := len(ss)
diff --git a/template.go b/template.go
index 7098b9b..e30cc75 100644
--- a/template.go
+++ b/template.go
@@ -183,7 +183,10 @@ func (ss SliceType) DropTop(n int) (drop SliceType) {
 		return
 	}
 
-	drop = ss[n:]
+	// Copy ss, to make sure no memory is overlapping between input and
+	// output. See issue #145.
+	drop = make([]ElementType, len(ss)-n)
+	copy(drop, ss[n:])
 
 	return
 }
@@ -402,6 +405,28 @@ func (ss SliceType) JSONBytes() []byte {
 
 	return data
 }
+`,
+	"JSONBytesIndent": `package functions
+
+import (
+	"encoding/json"
+)
+
+// JSONBytesIndent returns the JSON encoded array as bytes with indent applied.
+//
+// One important thing to note is that it will treat a nil slice as an empty
+// slice to ensure that the JSON value return is always an array. See
+// json.MarshalIndent for details.
+func (ss SliceType) JSONBytesIndent(prefix, indent string) []byte {
+	if ss == nil {
+		return []byte("[]")
+	}
+
+	// An error should not be possible.
+	data, _ := json.MarshalIndent(ss, prefix, indent)
+
+	return data
+}
 `,
 	"JSONString": `package functions
 
@@ -423,6 +448,28 @@ func (ss SliceType) JSONString() string {
 
 	return string(data)
 }
+`,
+	"JSONStringIndent": `package functions
+
+import (
+	"encoding/json"
+)
+
+// JSONStringIndent returns the JSON encoded array as a string with indent applied.
+//
+// One important thing to note is that it will treat a nil slice as an empty
+// slice to ensure that the JSON value return is always an array. See
+// json.MarshalIndent for details.
+func (ss SliceType) JSONStringIndent(prefix, indent string) string {
+	if ss == nil {
+		return "[]"
+	}
+
+	// An error should not be possible.
+	data, _ := json.MarshalIndent(ss, prefix, indent)
+
+	return string(data)
+}
 `,
 	"Join": `package functions
 
@@ -609,6 +656,38 @@ func (ss SliceType) Min() (min ElementType) {
 
 	return
 }
+`,
+	"Mode": `package functions
+
+// Mode returns a new slice containing the most frequently occuring values.
+//
+// The number of items returned may be the same as the input or less. It will
+// never return zero items unless the input slice has zero items.
+func (ss SliceType) Mode() SliceType {
+	if len(ss) == 0 {
+		return nil
+	}
+	values := make(map[ElementType]int, 0)
+	for _, s := range ss {
+		values[s]++
+	}
+
+	var maxFrequency int
+	for _, v := range values {
+		if v > maxFrequency {
+			maxFrequency = v
+		}
+	}
+
+	var maxValues SliceType
+	for k, v := range values {
+		if v == maxFrequency {
+			maxValues = append(maxValues, k)
+		}
+	}
+
+	return maxValues
+}
 `,
 	"Product": `package functions
 


Test patch:
diff --git a/pie/carpointers_test.go b/pie/carpointers_test.go
index c44ae69..1b729c4 100644
--- a/pie/carpointers_test.go
+++ b/pie/carpointers_test.go
@@ -181,34 +181,58 @@ func TestCarPointers_Last(t *testing.T) {
 var carPointersStatsTests = []struct {
 	ss       carPointers
 	min, max *car
+	mode     carPointers
 	len      int
 }{
 	{
 		nil,
 		&car{},
 		&car{},
+		carPointers{},
 		0,
 	},
 	{
 		carPointers{},
 		&car{},
 		&car{},
+		carPointers{},
 		0,
 	},
 	{
 		carPointers{&car{"foo", "red"}},
 		&car{"foo", "red"},
 		&car{"foo", "red"},
+		carPointers{&car{"foo", "red"}},
 		1,
 	},
 	{
 		carPointers{&car{"bar", "yellow"}, &car{"Baz", "black"}, &car{"qux", "cyan"}, &car{"foo", "red"}},
 		&car{"Baz", "black"},
 		&car{"qux", "cyan"},
+		carPointers{&car{"bar", "yellow"}, &car{"Baz", "black"}, &car{"qux", "cyan"}, &car{"foo", "red"}},
 		4,
 	},
 }
 
+func TestCarPointers_Mode(t *testing.T) {
+	cmp := func(a, b carPointers) bool {
+		m := make(map[car]struct{})
+		for _, i := range a {
+			m[*i] = struct{}{}
+		}
+		for _, i := range b {
+			if _, ok := m[*i]; !ok {
+				return false
+			}
+		}
+		return true
+	}
+	for _, test := range carPointersStatsTests {
+		t.Run("", func(t *testing.T) {
+			assert.True(t, cmp(test.mode, test.ss.Mode()))
+		})
+	}
+}
 func TestCarPointers_Len(t *testing.T) {
 	for _, test := range carPointersStatsTests {
 		t.Run("", func(t *testing.T) {
@@ -258,6 +282,68 @@ func TestCarPointers_JSONString(t *testing.T) {
 	}
 }
 
+var carPointersJSONIndentTests = []struct {
+	ss         carPointers
+	jsonString string
+}{
+	{
+		nil,
+		`[]`, // Instead of null.
+	},
+	{
+		carPointers{},
+		`[]`,
+	},
+	{
+		carPointers{&car{"foo", "red"}},
+		`[
+  {
+    "Name": "foo",
+    "Color": "red"
+  }
+]`,
+	},
+	{
+		carPointers{&car{"bar", "yellow"}, &car{"Baz", "black"}, &car{"qux", "cyan"}, &car{"foo", "red"}},
+		`[
+  {
+    "Name": "bar",
+    "Color": "yellow"
+  },
+  {
+    "Name": "Baz",
+    "Color": "black"
+  },
+  {
+    "Name": "qux",
+    "Color": "cyan"
+  },
+  {
+    "Name": "foo",
+    "Color": "red"
+  }
+]`,
+	},
+}
+
+func TestCarPointers_JSONBytesIndent(t *testing.T) {
+	for _, test := range carPointersJSONIndentTests {
+		t.Run("", func(t *testing.T) {
+			defer assertImmutableCarPointers(t, &test.ss)()
+			assert.Equal(t, []byte(test.jsonString), test.ss.JSONBytesIndent("", "  "))
+		})
+	}
+}
+
+func TestCarPointers_JSONStringIndent(t *testing.T) {
+	for _, test := range carPointersJSONIndentTests {
+		t.Run("", func(t *testing.T) {
+			defer assertImmutableCarPointers(t, &test.ss)()
+			assert.Equal(t, test.jsonString, test.ss.JSONStringIndent("", "  "))
+		})
+	}
+}
+
 var carPointersSortTests = []struct {
 	ss        carPointers
 	sorted    carPointers
diff --git a/pie/cars_test.go b/pie/cars_test.go
index 51deef3..89fd84f 100644
--- a/pie/cars_test.go
+++ b/pie/cars_test.go
@@ -168,34 +168,59 @@ func TestCars_Last(t *testing.T) {
 var carsStatsTests = []struct {
 	ss       cars
 	min, max car
+	mode     cars
 	len      int
 }{
 	{
 		nil,
 		car{},
 		car{},
+		cars{car{}},
 		0,
 	},
 	{
 		cars{},
 		car{},
 		car{},
+		cars{car{}},
 		0,
 	},
 	{
 		cars{car{"foo", "red"}},
 		car{"foo", "red"},
 		car{"foo", "red"},
+		cars{car{"foo", "red"}},
 		1,
 	},
 	{
 		cars{car{"bar", "yellow"}, car{"Baz", "black"}, car{"qux", "cyan"}, car{"foo", "red"}},
 		car{"Baz", "black"},
 		car{"qux", "cyan"},
+		cars{car{"bar", "yellow"}, car{"Baz", "black"}, car{"qux", "cyan"}, car{"foo", "red"}},
 		4,
 	},
 }
 
+func TestCars_Mode(t *testing.T) {
+	cmp := func(a, b cars) bool {
+		m := make(map[car]struct{})
+		for _, i := range a {
+			m[i] = struct{}{}
+		}
+		for _, i := range b {
+			if _, ok := m[i]; !ok {
+				return false
+			}
+		}
+		return true
+	}
+	for _, test := range carsStatsTests {
+		t.Run("", func(t *testing.T) {
+			assert.True(t, cmp(test.mode, cars(test.ss).Mode()))
+		})
+	}
+}
+
 func TestCars_Len(t *testing.T) {
 	for _, test := range carsStatsTests {
 		t.Run("", func(t *testing.T) {
@@ -244,6 +269,68 @@ func TestCars_JSONString(t *testing.T) {
 	}
 }
 
+var carsJSONIndentTests = []struct {
+	ss         cars
+	jsonString string
+}{
+	{
+		nil,
+		`[]`, // Instead of null.
+	},
+	{
+		cars{},
+		`[]`,
+	},
+	{
+		cars{car{"foo", "red"}},
+		`[
+  {
+    "Name": "foo",
+    "Color": "red"
+  }
+]`,
+	},
+	{
+		cars{car{"bar", "yellow"}, car{"Baz", "black"}, car{"qux", "cyan"}, car{"foo", "red"}},
+		`[
+  {
+    "Name": "bar",
+    "Color": "yellow"
+  },
+  {
+    "Name": "Baz",
+    "Color": "black"
+  },
+  {
+    "Name": "qux",
+    "Color": "cyan"
+  },
+  {
+    "Name": "foo",
+    "Color": "red"
+  }
+]`,
+	},
+}
+
+func TestCars_JSONBytesIndent(t *testing.T) {
+	for _, test := range carsJSONIndentTests {
+		t.Run("", func(t *testing.T) {
+			defer assertImmutableCars(t, &test.ss)()
+			assert.Equal(t, []byte(test.jsonString), test.ss.JSONBytesIndent("", "  "))
+		})
+	}
+}
+
+func TestCars_JSONStringIndent(t *testing.T) {
+	for _, test := range carsJSONIndentTests {
+		t.Run("", func(t *testing.T) {
+			defer assertImmutableCars(t, &test.ss)()
+			assert.Equal(t, test.jsonString, test.ss.JSONStringIndent("", "  "))
+		})
+	}
+}
+
 var carsSortTests = []struct {
 	ss        cars
 	sorted    cars
diff --git a/pie/float64s_test.go b/pie/float64s_test.go
index e680eef..9934e91 100644
--- a/pie/float64s_test.go
+++ b/pie/float64s_test.go
@@ -164,6 +164,7 @@ var float64sStatsTests = []struct {
 	ss                     Float64s
 	min, max, sum, product float64
 	len                    int
+	mode                   Float64s
 	average                float64
 }{
 	{
@@ -173,6 +174,7 @@ var float64sStatsTests = []struct {
 		0,
 		0,
 		0,
+		nil,
 		0,
 	},
 	{
@@ -182,6 +184,7 @@ var float64sStatsTests = []struct {
 		0,
 		0,
 		0,
+		Float64s{},
 		0,
 	},
 	{
@@ -191,6 +194,7 @@ var float64sStatsTests = []struct {
 		1.5,
 		1.5,
 		1,
+		Float64s{1.5},
 		1.5,
 	},
 	{
@@ -200,6 +204,7 @@ var float64sStatsTests = []struct {
 		12.3,
 		66.0858,
 		4,
+		Float64s{2.2, 3.1, 5.1, 1.9},
 		3.075,
 	},
 }
@@ -222,6 +227,26 @@ func TestFloat64s_Max(t *testing.T) {
 	}
 }
 
+func TestFloat64s_Mode(t *testing.T) {
+	cmp := func(a, b Float64s) bool {
+		m := make(map[float64]struct{})
+		for _, i := range a {
+			m[i] = struct{}{}
+		}
+		for _, i := range b {
+			if _, ok := m[i]; !ok {
+				return false
+			}
+		}
+		return true
+	}
+	for _, test := range float64sStatsTests {
+		t.Run("", func(t *testing.T) {
+			assert.True(t, cmp(test.mode, Float64s(test.ss).Mode()))
+		})
+	}
+}
+
 func TestFloat64s_Sum(t *testing.T) {
 	for _, test := range float64sStatsTests {
 		t.Run("", func(t *testing.T) {
@@ -297,6 +322,52 @@ func TestFloat64s_JSONString(t *testing.T) {
 	}
 }
 
+var float64sJSONIndentTests = []struct {
+	ss         Float64s
+	jsonString string
+}{
+	{
+		nil,
+		`[]`, // Instead of null.
+	},
+	{
+		Float64s{},
+		`[]`,
+	},
+	{
+		Float64s{12.3},
+		`[
+  12.3
+]`,
+	},
+	{
+		Float64s{23, -2.5, 3424, 12.3},
+		`[
+  23,
+  -2.5,
+  3424,
+  12.3
+]`,
+	},
+}
+
+func TestFloat64s_JSONBytesIndent(t *testing.T) {
+	for _, test := range float64sJSONIndentTests {
+		t.Run("", func(t *testing.T) {
+			defer assertImmutableFloat64s(t, &test.ss)()
+			assert.Equal(t, []byte(test.jsonString), test.ss.JSONBytesIndent("", "  "))
+		})
+	}
+}
+func TestFloat64s_JSONStringIndent(t *testing.T) {
+	for _, test := range float64sJSONIndentTests {
+		t.Run("", func(t *testing.T) {
+			defer assertImmutableFloat64s(t, &test.ss)()
+			assert.Equal(t, test.jsonString, test.ss.JSONStringIndent("", "  "))
+		})
+	}
+}
+
 var float64sSortTests = []struct {
 	ss        Float64s
 	sorted    Float64s
diff --git a/pie/ints_test.go b/pie/ints_test.go
index 9294f79..bd92ccd 100644
--- a/pie/ints_test.go
+++ b/pie/ints_test.go
@@ -155,6 +155,7 @@ func TestInts_Last(t *testing.T) {
 var intsStatsTests = []struct {
 	ss                          []int
 	min, max, sum, product, len int
+	mode                        Ints
 	average                     float64
 }{
 	{
@@ -164,6 +165,7 @@ var intsStatsTests = []struct {
 		0,
 		0,
 		0,
+		nil,
 		0,
 	},
 	{
@@ -173,6 +175,7 @@ var intsStatsTests = []struct {
 		0,
 		0,
 		0,
+		Ints{},
 		0,
 	},
 	{
@@ -182,6 +185,7 @@ var intsStatsTests = []struct {
 		1,
 		1,
 		1,
+		Ints{1},
 		1,
 	},
 	{
@@ -191,6 +195,7 @@ var intsStatsTests = []struct {
 		11,
 		30,
 		4,
+		Ints{2, 3, 5, 1},
 		2.75,
 	},
 }
@@ -211,6 +216,27 @@ func TestInts_Max(t *testing.T) {
 	}
 }
 
+func TestInts_Mode(t *testing.T) {
+	cmp := func(a, b Ints) bool {
+		m := make(map[int]struct{})
+		for _, i := range a {
+			m[i] = struct{}{}
+		}
+		for _, i := range b {
+			if _, ok := m[i]; !ok {
+				return false
+			}
+		}
+		return true
+	}
+	for _, test := range intsStatsTests {
+		t.Run("", func(t *testing.T) {
+			//assert.Equal(t, test.mode, Ints(test.ss).Mode())
+			assert.True(t, cmp(test.mode, Ints(test.ss).Mode()))
+		})
+	}
+}
+
 func TestInts_Sum(t *testing.T) {
 	for _, test := range intsStatsTests {
 		t.Run("", func(t *testing.T) {
@@ -283,6 +309,53 @@ func TestInts_JSONBytes(t *testing.T) {
 	}
 }
 
+var intsJSONIndentTests = []struct {
+	ss         Ints
+	jsonString string
+}{
+	{
+		nil,
+		`[]`, // Instead of null.
+	},
+	{
+		Ints{},
+		`[]`,
+	},
+	{
+		Ints{12},
+		`[
+  12
+]`,
+	},
+	{
+		Ints{23, -2, 3424, 12},
+		`[
+  23,
+  -2,
+  3424,
+  12
+]`,
+	},
+}
+
+func TestInts_JSONStringIndent(t *testing.T) {
+	for _, test := range intsJSONIndentTests {
+		t.Run("", func(t *testing.T) {
+			defer assertImmutableInts(t, &test.ss)()
+			assert.Equal(t, test.jsonString, test.ss.JSONStringIndent("", "  "))
+		})
+	}
+}
+
+func TestInts_JSONBytesIndent(t *testing.T) {
+	for _, test := range intsJSONIndentTests {
+		t.Run("", func(t *testing.T) {
+			defer assertImmutableInts(t, &test.ss)()
+			assert.Equal(t, []byte(test.jsonString), test.ss.JSONBytesIndent("", "  "))
+		})
+	}
+}
+
 var intsSortTests = []struct {
 	ss        Ints
 	sorted    Ints
diff --git a/pie/strings_test.go b/pie/strings_test.go
index c59a5e1..6d9b2ae 100644
--- a/pie/strings_test.go
+++ b/pie/strings_test.go
@@ -164,30 +164,35 @@ func TestStrings_Last(t *testing.T) {
 var stringsStatsTests = []struct {
 	ss       Strings
 	min, max string
+	mode     Strings
 	len      int
 }{
 	{
 		nil,
 		"",
 		"",
+		nil,
 		0,
 	},
 	{
 		[]string{},
 		"",
 		"",
+		Strings{},
 		0,
 	},
 	{
 		[]string{"foo"},
 		"foo",
 		"foo",
+		Strings{"foo"},
 		1,
 	},
 	{
 		[]string{"bar", "Baz", "qux", "foo"},
 		"Baz",
 		"qux",
+		Strings{"bar", "Baz", "qux", "foo"},
 		4,
 	},
 }
@@ -210,6 +215,26 @@ func TestStrings_Max(t *testing.T) {
 	}
 }
 
+func TestStrings_Mode(t *testing.T) {
+	cmp := func(a, b Strings) bool {
+		m := make(map[string]struct{})
+		for _, i := range a {
+			m[i] = struct{}{}
+		}
+		for _, i := range b {
+			if _, ok := m[i]; !ok {
+				return false
+			}
+		}
+		return true
+	}
+	for _, test := range stringsStatsTests {
+		t.Run("", func(t *testing.T) {
+			assert.True(t, cmp(test.mode, Strings(test.ss).Mode()))
+		})
+	}
+}
+
 func TestStrings_Len(t *testing.T) {
 	for _, test := range stringsStatsTests {
 		t.Run("", func(t *testing.T) {
@@ -258,6 +283,52 @@ func TestStrings_JSONString(t *testing.T) {
 	}
 }
 
+var stringsJSONIndentTests = []struct {
+	ss         Strings
+	jsonString string
+}{
+	{
+		nil,
+		`[]`, // Instead of null.
+	},
+	{
+		Strings{},
+		`[]`,
+	},
+	{
+		Strings{"foo"},
+		`[
+  "foo"
+]`,
+	},
+	{
+		Strings{"bar", "Baz", "qux", "foo"},
+		`[
+  "bar",
+  "Baz",
+  "qux",
+  "foo"
+]`,
+	},
+}
+
+func TestStrings_JSONBytesIndent(t *testing.T) {
+	for _, test := range stringsJSONIndentTests {
+		t.Run("", func(t *testing.T) {
+			defer assertImmutableStrings(t, &test.ss)()
+			assert.Equal(t, []byte(test.jsonString), test.ss.JSONBytesIndent("", "  "))
+		})
+	}
+}
+func TestStrings_JSONStringIndent(t *testing.T) {
+	for _, test := range stringsJSONIndentTests {
+		t.Run("", func(t *testing.T) {
+			defer assertImmutableStrings(t, &test.ss)()
+			assert.Equal(t, test.jsonString, test.ss.JSONStringIndent("", "  "))
+		})
+	}
+}
+
 var stringsSortTests = []struct {
 	ss        Strings
 	sorted    Strings
@@ -1156,6 +1227,31 @@ func TestStrings_DropTop(t *testing.T) {
 	}
 }
 
+// Make sure that input and output of DropTop don't share the same memory.
+func TestDropTopNonDestructive(t *testing.T) {
+	abc := Strings{"A", "B", "C"}
+
+	abc1 := abc.DropTop(0)
+	abc1[0] = "a"
+
+	if x, expected := abc.Join(""), "ABC"; x != expected {
+		t.Errorf("Expected %q, got %q", expected, x)
+	}
+	if x, expected := abc1.Join(""), "aBC"; x != expected {
+		t.Errorf("Expected %q, got %q", expected, x)
+	}
+
+	bc := abc.DropTop(1)
+	bc[0] = "D"
+
+	if x, expected := abc.Join(""), "ABC"; x != expected {
+		t.Errorf("Expected %q, got %q", expected, x)
+	}
+	if x, expected := bc.Join(""), "DC"; x != expected {
+		t.Errorf("Expected %q, got %q", expected, x)
+	}
+}
+
 var stringsSubSliceTests = []struct {
 	ss       Strings
 	start    int