Repository: elliotchance/pie
Pull request #142 (2019-05-14 17:09:33)

Mode implementation
See #68

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


DylanMeeus: travis keeps failing either on `go generate` or on `gofmt`.
I notice that go generate does not generate correct code for gofmt in this instance. I suspect that to be the issue with the travis builds here. (either the formatting is wrong or the generate looks out of sync). I'll take a look at this later unless you see an obvious mistake :) 
DylanMeeus: I ran gofmt on all files before generating 
DylanMeeus: I run `gofmt -w .`, which formats the files. Then I run `go generate` and the formatting is gone again. So one of them always seems to be out of sync :confused: 

elliotchance: @DylanMeeus the command you are using is only formatting one package: `github.com/elliotchance/pie`. However, the package that needs to be formatted is `github.com/elliotchance/pie/pie`.

You should use this for all packages:

```bash
go fmt ./...
```
DylanMeeus: Hey Elliot, my understanding was that `gofmt -w .` would recursively format everything down from the current repository. I'll try that out tonight ;-)
DylanMeeus: @elliotchance running `go fmt ./...` has the same result as `gofmt -w .`. So running `go fmt ./...` at root changes the files, then I regenerate them and they are different once again. Any idea why the generator does not format correctly? 

Patch:
diff --git a/README.md b/README.md
index f0a6671..e662251 100644
--- a/README.md
+++ b/README.md
@@ -203,8 +203,9 @@ The letters in brackets indicate:
 | `Len`             | ✓      | ✓      | ✓      |      | 1        | Number of elements. |
 | `Map`	            | ✓      | ✓      | ✓      |      | n        | A new slice where each element has been mapped (transformed). |
 | `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. |
+| `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. |
@@ -217,6 +218,7 @@ The letters in brackets indicate:
 | `SortStableUsing` | ✓      |        | ✓      |      | n⋅log(n) | Return a new sorted slice, using custom comparator, keeping the original order of equal elements. |
 | `SortUsing`       | ✓      |        | ✓      |      | n⋅log(n) | Return a new sorted slice, using custom comparator. |
 | `Strings` (S)     | ✓      | ✓      | ✓      |      | n        | Transforms each element into a string. |
+| `SubSlice`        | ✓      | ✓      | ✓      |      | n        | Return the subSlice from start to end(excluded). |
 | `Sum`             |        | ✓      |        |      | n        | Sum (total) of all elements. |
 | `Top`             | ✓      | ✓      | ✓      |      | n        | Gets several elements from top(head of slice).|
 | `ToStrings`       | ✓      | ✓      | ✓      |      | n        | Transforms each element to a string. |
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/main.go b/functions/main.go
index 53f7570..daa30b8 100644
--- a/functions/main.go
+++ b/functions/main.go
@@ -52,6 +52,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},
@@ -64,6 +65,7 @@ var Functions = []struct {
 	{"SortStableUsing", "sort_stable_using.go", ForStrings | ForStructs},
 	{"SortUsing", "sort_using.go", ForStrings | ForStructs},
 	{"Strings", "strings.go", ForAll},
+	{"SubSlice", "sub_slice.go", ForAll},
 	{"Sum", "sum.go", ForNumbers},
 	{"Top", "top.go", ForAll},
 	{"ToStrings", "to_strings.go", ForAll},
diff --git a/functions/median.go b/functions/median.go
index b8441c6..6e1631f 100644
--- a/functions/median.go
+++ b/functions/median.go
@@ -4,22 +4,63 @@ package functions
 // data sample.
 //
 // Zero is returned if there are no elements in the slice.
+//
+// If the number of elements is even, then the ElementType mean of the two "median values"
+// is returned.
 func (ss SliceType) Median() ElementType {
-	l := len(ss)
-
-	switch {
-	case l == 0:
+	n := len(ss)
+	if n == 0 {
 		return ElementZeroValue
-
-	case l == 1:
+	}
+	if n == 1 {
 		return ss[0]
 	}
 
-	sorted := ss.Sort()
+	// This implementation aims at linear time O(n) on average.
+	// It uses the same idea as QuickSort, but makes only 1 recursive
+	// call instead of 2. See also Quickselect.
+
+	work := make(SliceType, len(ss))
+	copy(work, ss)
 
-	if l%2 != 0 {
-		return sorted[l/2]
+	limit1, limit2 := n/2, n/2+1
+	if n%2 == 0 {
+		limit1, limit2 = n/2-1, n/2+1
 	}
 
-	return (sorted[l/2-1] + sorted[l/2]) / 2
+	var rec func(a, b int)
+	rec = func(a, b int) {
+		if b-a <= 1 {
+			return
+		}
+		ipivot := (a + b) / 2
+		pivot := work[ipivot]
+		work[a], work[ipivot] = work[ipivot], work[a]
+		j := a
+		k := b
+		for j+1 < k {
+			if work[j+1] < pivot {
+				work[j+1], work[j] = work[j], work[j+1]
+				j++
+			} else {
+				work[j+1], work[k-1] = work[k-1], work[j+1]
+				k--
+			}
+		}
+		// 1 or 0 recursive calls
+		if j > limit1 {
+			rec(a, j)
+		}
+		if j+1 < limit2 {
+			rec(j+1, b)
+		}
+	}
+
+	rec(0, len(work))
+
+	if n%2 == 1 {
+		return work[n/2]
+	} else {
+		return (work[n/2-1] + work[n/2]) / 2
+	}
 }
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/functions/sub_slice.go b/functions/sub_slice.go
new file mode 100644
index 0000000..1bd8d33
--- /dev/null
+++ b/functions/sub_slice.go
@@ -0,0 +1,32 @@
+package functions
+
+// SubSlice will return the subSlice from start to end(excluded)
+//
+// Condition 1: If start < 0 or end < 0, nil is returned.
+// Condition 2: If start >= end, nil is returned.
+// Condition 3: Return all elements that exist in the range provided,
+// if start or end is out of bounds, zero items will be placed.
+func (ss SliceType) SubSlice(start int, end int) (subSlice SliceType) {
+	if start < 0 || end < 0 {
+		return
+	}
+
+	if start >= end {
+		return
+	}
+
+	length := ss.Len()
+	if start < length {
+		if end <= length {
+			subSlice = ss[start:end]
+		} else {
+			zeroArray := make([]ElementType, end-length)
+			subSlice = ss[start:length].Append(zeroArray[:]...)
+		}
+	} else {
+		zeroArray := make([]ElementType, end-start)
+		subSlice = zeroArray[:]
+	}
+
+	return
+}
diff --git a/pie/carpointers_pie.go b/pie/carpointers_pie.go
index 33c0dad..af063eb 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
 }
@@ -329,6 +332,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)
@@ -509,6 +542,37 @@ func (ss carPointers) Strings() Strings {
 	return result
 }
 
+// SubSlice will return the subSlice from start to end(excluded)
+//
+// Condition 1: If start < 0 or end < 0, nil is returned.
+// Condition 2: If start >= end, nil is returned.
+// Condition 3: Return all elements that exist in the range provided,
+// if start or end is out of bounds, zero items will be placed.
+func (ss carPointers) SubSlice(start int, end int) (subSlice carPointers) {
+	if start < 0 || end < 0 {
+		return
+	}
+
+	if start >= end {
+		return
+	}
+
+	length := ss.Len()
+	if start < length {
+		if end <= length {
+			subSlice = ss[start:end]
+		} else {
+			zeroArray := make([]*car, end-length)
+			subSlice = ss[start:length].Append(zeroArray[:]...)
+		}
+	} else {
+		zeroArray := make([]*car, end-start)
+		subSlice = zeroArray[:]
+	}
+
+	return
+}
+
 // Top will return n elements from head of the slice
 // if the slice has less elements then n that'll return all elements
 // if n < 0 it'll return empty slice.
diff --git a/pie/cars_pie.go b/pie/cars_pie.go
index 315ce55..ca381b6 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
 }
@@ -329,6 +332,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)
@@ -509,6 +542,37 @@ func (ss cars) Strings() Strings {
 	return result
 }
 
+// SubSlice will return the subSlice from start to end(excluded)
+//
+// Condition 1: If start < 0 or end < 0, nil is returned.
+// Condition 2: If start >= end, nil is returned.
+// Condition 3: Return all elements that exist in the range provided,
+// if start or end is out of bounds, zero items will be placed.
+func (ss cars) SubSlice(start int, end int) (subSlice cars) {
+	if start < 0 || end < 0 {
+		return
+	}
+
+	if start >= end {
+		return
+	}
+
+	length := ss.Len()
+	if start < length {
+		if end <= length {
+			subSlice = ss[start:end]
+		} else {
+			zeroArray := make([]car, end-length)
+			subSlice = ss[start:length].Append(zeroArray[:]...)
+		}
+	} else {
+		zeroArray := make([]car, end-start)
+		subSlice = zeroArray[:]
+	}
+
+	return
+}
+
 // Top will return n elements from head of the slice
 // if the slice has less elements then n that'll return all elements
 // if n < 0 it'll return empty slice.
diff --git a/pie/float64s_pie.go b/pie/float64s_pie.go
index 1066398..99cd149 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
 }
@@ -422,24 +425,65 @@ func (ss Float64s) Max() (max float64) {
 // data sample.
 //
 // Zero is returned if there are no elements in the slice.
+//
+// If the number of elements is even, then the float64 mean of the two "median values"
+// is returned.
 func (ss Float64s) Median() float64 {
-	l := len(ss)
-
-	switch {
-	case l == 0:
+	n := len(ss)
+	if n == 0 {
 		return 0
-
-	case l == 1:
+	}
+	if n == 1 {
 		return ss[0]
 	}
 
-	sorted := ss.Sort()
+	// This implementation aims at linear time O(n) on average.
+	// It uses the same idea as QuickSort, but makes only 1 recursive
+	// call instead of 2. See also Quickselect.
 
-	if l%2 != 0 {
-		return sorted[l/2]
+	work := make(Float64s, len(ss))
+	copy(work, ss)
+
+	limit1, limit2 := n/2, n/2+1
+	if n%2 == 0 {
+		limit1, limit2 = n/2-1, n/2+1
 	}
 
-	return (sorted[l/2-1] + sorted[l/2]) / 2
+	var rec func(a, b int)
+	rec = func(a, b int) {
+		if b-a <= 1 {
+			return
+		}
+		ipivot := (a + b) / 2
+		pivot := work[ipivot]
+		work[a], work[ipivot] = work[ipivot], work[a]
+		j := a
+		k := b
+		for j+1 < k {
+			if work[j+1] < pivot {
+				work[j+1], work[j] = work[j], work[j+1]
+				j++
+			} else {
+				work[j+1], work[k-1] = work[k-1], work[j+1]
+				k--
+			}
+		}
+		// 1 or 0 recursive calls
+		if j > limit1 {
+			rec(a, j)
+		}
+		if j+1 < limit2 {
+			rec(j+1, b)
+		}
+	}
+
+	rec(0, len(work))
+
+	if n%2 == 1 {
+		return work[n/2]
+	} else {
+		return (work[n/2-1] + work[n/2]) / 2
+	}
 }
 
 // Min is the minimum value, or zero.
@@ -458,6 +502,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 {
@@ -673,6 +747,37 @@ func (ss Float64s) Strings() Strings {
 	return result
 }
 
+// SubSlice will return the subSlice from start to end(excluded)
+//
+// Condition 1: If start < 0 or end < 0, nil is returned.
+// Condition 2: If start >= end, nil is returned.
+// Condition 3: Return all elements that exist in the range provided,
+// if start or end is out of bounds, zero items will be placed.
+func (ss Float64s) SubSlice(start int, end int) (subSlice Float64s) {
+	if start < 0 || end < 0 {
+		return
+	}
+
+	if start >= end {
+		return
+	}
+
+	length := ss.Len()
+	if start < length {
+		if end <= length {
+			subSlice = ss[start:end]
+		} else {
+			zeroArray := make([]float64, end-length)
+			subSlice = ss[start:length].Append(zeroArray[:]...)
+		}
+	} else {
+		zeroArray := make([]float64, end-start)
+		subSlice = zeroArray[:]
+	}
+
+	return
+}
+
 // Sum is the sum of all of the elements.
 func (ss Float64s) Sum() (sum float64) {
 	for _, s := range ss {
diff --git a/pie/ints_pie.go b/pie/ints_pie.go
index 7710fd9..9e7f506 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
 }
@@ -422,24 +425,65 @@ func (ss Ints) Max() (max int) {
 // data sample.
 //
 // Zero is returned if there are no elements in the slice.
+//
+// If the number of elements is even, then the int mean of the two "median values"
+// is returned.
 func (ss Ints) Median() int {
-	l := len(ss)
-
-	switch {
-	case l == 0:
+	n := len(ss)
+	if n == 0 {
 		return 0
-
-	case l == 1:
+	}
+	if n == 1 {
 		return ss[0]
 	}
 
-	sorted := ss.Sort()
+	// This implementation aims at linear time O(n) on average.
+	// It uses the same idea as QuickSort, but makes only 1 recursive
+	// call instead of 2. See also Quickselect.
 
-	if l%2 != 0 {
-		return sorted[l/2]
+	work := make(Ints, len(ss))
+	copy(work, ss)
+
+	limit1, limit2 := n/2, n/2+1
+	if n%2 == 0 {
+		limit1, limit2 = n/2-1, n/2+1
 	}
 
-	return (sorted[l/2-1] + sorted[l/2]) / 2
+	var rec func(a, b int)
+	rec = func(a, b int) {
+		if b-a <= 1 {
+			return
+		}
+		ipivot := (a + b) / 2
+		pivot := work[ipivot]
+		work[a], work[ipivot] = work[ipivot], work[a]
+		j := a
+		k := b
+		for j+1 < k {
+			if work[j+1] < pivot {
+				work[j+1], work[j] = work[j], work[j+1]
+				j++
+			} else {
+				work[j+1], work[k-1] = work[k-1], work[j+1]
+				k--
+			}
+		}
+		// 1 or 0 recursive calls
+		if j > limit1 {
+			rec(a, j)
+		}
+		if j+1 < limit2 {
+			rec(j+1, b)
+		}
+	}
+
+	rec(0, len(work))
+
+	if n%2 == 1 {
+		return work[n/2]
+	} else {
+		return (work[n/2-1] + work[n/2]) / 2
+	}
 }
 
 // Min is the minimum value, or zero.
@@ -458,6 +502,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 {
@@ -673,6 +747,37 @@ func (ss Ints) Strings() Strings {
 	return result
 }
 
+// SubSlice will return the subSlice from start to end(excluded)
+//
+// Condition 1: If start < 0 or end < 0, nil is returned.
+// Condition 2: If start >= end, nil is returned.
+// Condition 3: Return all elements that exist in the range provided,
+// if start or end is out of bounds, zero items will be placed.
+func (ss Ints) SubSlice(start int, end int) (subSlice Ints) {
+	if start < 0 || end < 0 {
+		return
+	}
+
+	if start >= end {
+		return
+	}
+
+	length := ss.Len()
+	if start < length {
+		if end <= length {
+			subSlice = ss[start:end]
+		} else {
+			zeroArray := make([]int, end-length)
+			subSlice = ss[start:length].Append(zeroArray[:]...)
+		}
+	} else {
+		zeroArray := make([]int, end-start)
+		subSlice = zeroArray[:]
+	}
+
+	return
+}
+
 // Sum is the sum of all of the elements.
 func (ss Ints) Sum() (sum int) {
 	for _, s := range ss {
diff --git a/pie/strings_pie.go b/pie/strings_pie.go
index e379a38..1c1318d 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
 }
@@ -423,6 +426,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)
@@ -639,6 +672,37 @@ func (ss Strings) Strings() Strings {
 	return result
 }
 
+// SubSlice will return the subSlice from start to end(excluded)
+//
+// Condition 1: If start < 0 or end < 0, nil is returned.
+// Condition 2: If start >= end, nil is returned.
+// Condition 3: Return all elements that exist in the range provided,
+// if start or end is out of bounds, zero items will be placed.
+func (ss Strings) SubSlice(start int, end int) (subSlice Strings) {
+	if start < 0 || end < 0 {
+		return
+	}
+
+	if start >= end {
+		return
+	}
+
+	length := ss.Len()
+	if start < length {
+		if end <= length {
+			subSlice = ss[start:end]
+		} else {
+			zeroArray := make([]string, end-length)
+			subSlice = ss[start:length].Append(zeroArray[:]...)
+		}
+	} else {
+		zeroArray := make([]string, end-start)
+		subSlice = zeroArray[:]
+	}
+
+	return
+}
+
 // Top will return n elements from head of the slice
 // if the slice has less elements then n that'll return all elements
 // if n < 0 it'll return empty slice.
diff --git a/template.go b/template.go
index 9b944bb..12ca145 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
 }
@@ -531,24 +534,65 @@ func (ss SliceType) Max() (max ElementType) {
 // data sample.
 //
 // Zero is returned if there are no elements in the slice.
+//
+// If the number of elements is even, then the ElementType mean of the two "median values"
+// is returned.
 func (ss SliceType) Median() ElementType {
-	l := len(ss)
-
-	switch {
-	case l == 0:
+	n := len(ss)
+	if n == 0 {
 		return ElementZeroValue
-
-	case l == 1:
+	}
+	if n == 1 {
 		return ss[0]
 	}
 
-	sorted := ss.Sort()
+	// This implementation aims at linear time O(n) on average.
+	// It uses the same idea as QuickSort, but makes only 1 recursive
+	// call instead of 2. See also Quickselect.
 
-	if l%2 != 0 {
-		return sorted[l/2]
+	work := make(SliceType, len(ss))
+	copy(work, ss)
+
+	limit1, limit2 := n/2, n/2+1
+	if n%2 == 0 {
+		limit1, limit2 = n/2-1, n/2+1
+	}
+
+	var rec func(a, b int)
+	rec = func(a, b int) {
+		if b-a <= 1 {
+			return
+		}
+		ipivot := (a + b) / 2
+		pivot := work[ipivot]
+		work[a], work[ipivot] = work[ipivot], work[a]
+		j := a
+		k := b
+		for j+1 < k {
+			if work[j+1] < pivot {
+				work[j+1], work[j] = work[j], work[j+1]
+				j++
+			} else {
+				work[j+1], work[k-1] = work[k-1], work[j+1]
+				k--
+			}
+		}
+		// 1 or 0 recursive calls
+		if j > limit1 {
+			rec(a, j)
+		}
+		if j+1 < limit2 {
+			rec(j+1, b)
+		}
 	}
 
-	return (sorted[l/2-1] + sorted[l/2]) / 2
+	rec(0, len(work))
+
+	if n%2 == 1 {
+		return work[n/2]
+	} else {
+		return (work[n/2-1] + work[n/2]) / 2
+	}
 }
 `,
 	"Min": `package functions
@@ -568,6 +612,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
 
@@ -874,6 +950,39 @@ func (ss SliceType) Strings() pie.Strings {
 
 	return result
 }
+`,
+	"SubSlice": `package functions
+
+// SubSlice will return the subSlice from start to end(excluded)
+//
+// Condition 1: If start < 0 or end < 0, nil is returned.
+// Condition 2: If start >= end, nil is returned.
+// Condition 3: Return all elements that exist in the range provided,
+// if start or end is out of bounds, zero items will be placed.
+func (ss SliceType) SubSlice(start int, end int) (subSlice SliceType) {
+	if start < 0 || end < 0 {
+		return
+	}
+
+	if start >= end {
+		return
+	}
+
+	length := ss.Len()
+	if start < length {
+		if end <= length {
+			subSlice = ss[start:end]
+		} else {
+			zeroArray := make([]ElementType, end-length)
+			subSlice = ss[start:length].Append(zeroArray[:]...)
+		}
+	} else {
+		zeroArray := make([]ElementType, end-start)
+		subSlice = zeroArray[:]
+	}
+
+	return
+}
 `,
 	"Sum": `package functions
 


Test patch:
diff --git a/pie/carpointers_test.go b/pie/carpointers_test.go
index b7c55a5..3cfd466 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) {
@@ -1000,6 +1024,109 @@ func TestCarPointers_DropTop(t *testing.T) {
 	}
 }
 
+var carPointersSubSliceTests = []struct {
+	ss       carPointers
+	start    int
+	end      int
+	subSlice carPointers
+}{
+	{
+		// start:1 ,end: 2
+		nil,
+		1,
+		1,
+		nil,
+	},
+	{
+		// start:1 ,end: 2
+		nil,
+		1,
+		2,
+		carPointers{nil},
+	},
+	{
+		carPointers{},
+		1,
+		1,
+		nil,
+	},
+	{
+		carPointers{},
+		1,
+		2,
+		carPointers{nil},
+	},
+	{
+		carPointers{carPointerA, carPointerB},
+		-1,
+		-1,
+		nil,
+	},
+	{
+		carPointers{carPointerA, carPointerB},
+		-1,
+		1,
+		nil,
+	},
+	{
+		carPointers{carPointerA, carPointerB},
+		1,
+		-1,
+		nil,
+	},
+	{
+		carPointers{carPointerA, carPointerB},
+		2,
+		0,
+		nil,
+	},
+	{
+		carPointers{carPointerA, carPointerB},
+		1,
+		1,
+		nil,
+	},
+	{
+		carPointers{carPointerA, carPointerB},
+		1,
+		2,
+		carPointers{carPointerB},
+	},
+	{
+		carPointers{carPointerA, carPointerB},
+		1,
+		3,
+		carPointers{carPointerB, nil},
+	},
+	{
+		carPointers{carPointerA, carPointerB},
+		2,
+		2,
+		nil,
+	},
+	{
+		carPointers{carPointerA, carPointerB},
+		2,
+		3,
+		carPointers{nil},
+	},
+	{
+		carPointers{carPointerA, carPointerB, nil},
+		2,
+		3,
+		carPointers{nil},
+	},
+}
+
+func TestCarPointers_SubSlice(t *testing.T) {
+	for _, test := range carPointersSubSliceTests {
+		t.Run("", func(t *testing.T) {
+			defer assertImmutableCarPointers(t, &test.ss)()
+			assert.Equal(t, test.subSlice, test.ss.SubSlice(test.start, test.end))
+		})
+	}
+}
+
 var carsPointerFindFirstUsingTests = []struct {
 	ss         carPointers
 	expression func(value *car) bool
diff --git a/pie/cars_test.go b/pie/cars_test.go
index 3fc2542..9e783d4 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) {
@@ -974,6 +999,107 @@ func TestCars_DropTop(t *testing.T) {
 	}
 }
 
+var carsSubSliceTests = []struct {
+	ss       cars
+	start    int
+	end      int
+	subSlice cars
+}{
+	{
+		nil,
+		1,
+		1,
+		nil,
+	},
+	{
+		nil,
+		1,
+		2,
+		cars{car{}},
+	},
+	{
+		cars{},
+		1,
+		1,
+		nil,
+	},
+	{
+		cars{},
+		1,
+		2,
+		cars{car{}},
+	},
+	{
+		cars{car{"bar", "yellow"}, car{"Baz", "black"}},
+		-1,
+		-1,
+		nil,
+	},
+	{
+		cars{car{"bar", "yellow"}, car{"Baz", "black"}},
+		-1,
+		1,
+		nil,
+	},
+	{
+		cars{car{"bar", "yellow"}, car{"Baz", "black"}},
+		1,
+		-1,
+		nil,
+	},
+	{
+		cars{car{"bar", "yellow"}, car{"Baz", "black"}},
+		2,
+		0,
+		nil,
+	},
+	{
+		cars{car{"bar", "yellow"}, car{"Baz", "black"}},
+		1,
+		1,
+		nil,
+	},
+	{
+		cars{car{"bar", "yellow"}, car{"Baz", "black"}},
+		1,
+		2,
+		cars{car{"Baz", "black"}},
+	},
+	{
+		cars{car{"bar", "yellow"}, car{"Baz", "black"}},
+		1,
+		3,
+		cars{car{"Baz", "black"}, car{}},
+	},
+	{
+		cars{car{"bar", "yellow"}, car{"Baz", "black"}},
+		2,
+		2,
+		nil,
+	},
+	{
+		cars{car{"bar", "yellow"}, car{"Baz", "black"}},
+		2,
+		3,
+		cars{car{}},
+	},
+	{
+		cars{car{"bar", "yellow"}, car{"Baz", "black"}, car{}},
+		2,
+		3,
+		cars{car{}},
+	},
+}
+
+func TestCars_SubSlice(t *testing.T) {
+	for _, test := range carsSubSliceTests {
+		t.Run("", func(t *testing.T) {
+			defer assertImmutableCars(t, &test.ss)()
+			assert.Equal(t, test.subSlice, test.ss.SubSlice(test.start, test.end))
+		})
+	}
+}
+
 var carsFindFirstUsingTests = []struct {
 	ss         cars
 	expression func(value car) bool
diff --git a/pie/float64s_bench_test.go b/pie/float64s_bench_test.go
new file mode 100644
index 0000000..ff59edb
--- /dev/null
+++ b/pie/float64s_bench_test.go
@@ -0,0 +1,35 @@
+package pie
+
+import (
+	"math/rand"
+	"testing"
+)
+
+func BenchmarkFloatMedianSmall(b *testing.B) { benchmarkFloatMedian(b, 20) }
+
+func BenchmarkFloatMedianMedium(b *testing.B) { benchmarkFloatMedian(b, 800) }
+func BenchmarkFloatMedianLarge(b *testing.B)  { benchmarkFloatMedian(b, 1000000) }
+
+func benchmarkFloatMedian(b *testing.B, size int) {
+	// Make the random numbers below deterministic
+	rand.Seed(123)
+
+	a := make(Float64s, size)
+	for i := range a {
+		// As many possible values as slots in the slice.
+		// Positives and negatives.
+		// Variety, with some duplicates.
+		a[i] = -0.5 + rand.Float64()
+	}
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		// m := a.MedianOld()
+		// m := a.medianCheck()
+		m := a.Median()
+		sinkFloats += m
+	}
+}
+
+// Prevent compiler from agressively optimizing away the result
+var sinkFloats float64
diff --git a/pie/float64s_test.go b/pie/float64s_test.go
index e7b878d..d70a2fa 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) {
@@ -1162,6 +1187,108 @@ func TestFloat64s_DropTop(t *testing.T) {
 	}
 }
 
+var float64sSubSliceTests = []struct {
+	ss       Float64s
+	start    int
+	end      int
+	subSlice Float64s
+}{
+	{
+		nil,
+		1,
+		1,
+		nil,
+	},
+	{
+		nil,
+		1,
+		2,
+		Float64s{0},
+	},
+	{
+		Float64s{},
+		1,
+		1,
+		nil,
+	},
+	{
+		Float64s{},
+		1,
+		2,
+		Float64s{0},
+	},
+	{
+		Float64s{1.23, 2.34},
+		-1,
+		-1,
+		nil,
+	},
+	{
+		Float64s{1.23, 2.34},
+		-1,
+		1,
+		nil,
+	},
+	{
+		Float64s{1.23, 2.34},
+		1,
+		-1,
+		nil,
+	},
+	{
+		Float64s{1.23, 2.34},
+		2,
+		0,
+		nil,
+	},
+
+	{
+		Float64s{1.23, 2.34},
+		1,
+		1,
+		nil,
+	},
+	{
+		Float64s{1.23, 2.34},
+		1,
+		2,
+		Float64s{2.34},
+	},
+	{
+		Float64s{1.23, 2.34},
+		1,
+		3,
+		Float64s{2.34, 0},
+	},
+	{
+		Float64s{1.23, 2.34},
+		2,
+		2,
+		nil,
+	},
+	{
+		Float64s{1.23, 2.34},
+		2,
+		3,
+		Float64s{0},
+	},
+	{
+		Float64s{1.23, 2.34, 0},
+		2,
+		3,
+		Float64s{0},
+	},
+}
+
+func TestFloat64s_SubSlice(t *testing.T) {
+	for _, test := range float64sSubSliceTests {
+		t.Run("", func(t *testing.T) {
+			defer assertImmutableFloat64s(t, &test.ss)()
+			assert.Equal(t, test.subSlice, test.ss.SubSlice(test.start, test.end))
+		})
+	}
+}
+
 var floatFindFirstUsingTests = []struct {
 	ss         Float64s
 	expression func(value float64) bool
diff --git a/pie/ints_bench_test.go b/pie/ints_bench_test.go
new file mode 100644
index 0000000..09e106f
--- /dev/null
+++ b/pie/ints_bench_test.go
@@ -0,0 +1,33 @@
+package pie
+
+import (
+	"math/rand"
+	"testing"
+)
+
+func BenchmarkIntMedianSmall(b *testing.B) { benchmarkIntMedian(b, 20) }
+
+func BenchmarkIntMedianMedium(b *testing.B) { benchmarkIntMedian(b, 800) }
+func BenchmarkIntMedianLarge(b *testing.B)  { benchmarkIntMedian(b, 1000000) }
+
+func benchmarkIntMedian(b *testing.B, size int) {
+	// Make the random numbers below deterministic
+	rand.Seed(123)
+
+	a := make(Ints, size)
+	for i := range a {
+		// As many possible values as slots in the slice.
+		// Negatives and positives.
+		// Variety, with some duplicates.
+		a[i] = -size/2 + rand.Intn(size)
+	}
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		m := a.Median()
+		sinkInts += m
+	}
+}
+
+// Prevent compiler from agressively optimizing away the result
+var sinkInts int
diff --git a/pie/ints_test.go b/pie/ints_test.go
index 1a6aae4..322876d 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) {
@@ -1144,6 +1170,108 @@ func TestInts_DropTop(t *testing.T) {
 	}
 }
 
+var intsSubSliceTests = []struct {
+	ss       Ints
+	start    int
+	end      int
+	subSlice Ints
+}{
+	{
+		nil,
+		1,
+		1,
+		nil,
+	},
+	{
+		nil,
+		1,
+		2,
+		Ints{0},
+	},
+	{
+		Ints{},
+		1,
+		1,
+		nil,
+	},
+	{
+		Ints{},
+		1,
+		2,
+		Ints{0},
+	},
+	{
+		Ints{1, 2},
+		-1,
+		-1,
+		nil,
+	},
+	{
+		Ints{1, 2},
+		-1,
+		1,
+		nil,
+	},
+	{
+		Ints{1, 2},
+		1,
+		-1,
+		nil,
+	},
+	{
+		Ints{1, 2},
+		2,
+		0,
+		nil,
+	},
+
+	{
+		Ints{1, 2},
+		1,
+		1,
+		nil,
+	},
+	{
+		Ints{1, 2},
+		1,
+		2,
+		Ints{2},
+	},
+	{
+		Ints{1, 2},
+		1,
+		3,
+		Ints{2, 0},
+	},
+	{
+		Ints{1, 2},
+		2,
+		2,
+		nil,
+	},
+	{
+		Ints{1, 2},
+		2,
+		3,
+		Ints{0},
+	},
+	{
+		Ints{1, 2, 0},
+		2,
+		3,
+		Ints{0},
+	},
+}
+
+func TestInts_SubSlice(t *testing.T) {
+	for _, test := range intsSubSliceTests {
+		t.Run("", func(t *testing.T) {
+			defer assertImmutableInts(t, &test.ss)()
+			assert.Equal(t, test.subSlice, test.ss.SubSlice(test.start, test.end))
+		})
+	}
+}
+
 var intsFindFirstUsingTests = []struct {
 	ss         Ints
 	expression func(value int) bool
diff --git a/pie/strings_test.go b/pie/strings_test.go
index c616129..aa3bdb1 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) {
@@ -1156,6 +1181,133 @@ 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
+	end      int
+	subSlice Strings
+}{
+	{
+		nil,
+		1,
+		1,
+		nil,
+	},
+	{
+		nil,
+		1,
+		2,
+		Strings{""},
+	},
+	{
+		Strings{},
+		1,
+		1,
+		nil,
+	},
+	{
+		Strings{},
+		1,
+		2,
+		Strings{""},
+	},
+	{
+		Strings{"foo", "bar"},
+		-1,
+		-1,
+		nil,
+	},
+	{
+		Strings{"foo", "bar"},
+		-1,
+		1,
+		nil,
+	},
+	{
+		Strings{"foo", "bar"},
+		1,
+		-1,
+		nil,
+	},
+	{
+		Strings{"foo", "bar"},
+		2,
+		0,
+		nil,
+	},
+
+	{
+		Strings{"foo", "bar"},
+		1,
+		1,
+		nil,
+	},
+	{
+		Strings{"foo", "bar"},
+		1,
+		2,
+		Strings{"bar"},
+	},
+	{
+		Strings{"foo", "bar"},
+		1,
+		3,
+		Strings{"bar", ""},
+	},
+	{
+		Strings{"foo", "bar"},
+		2,
+		2,
+		nil,
+	},
+	{
+		Strings{"foo", "bar"},
+		2,
+		3,
+		Strings{""},
+	},
+	{
+		Strings{"foo", "bar", ""},
+		2,
+		3,
+		Strings{""},
+	},
+}
+
+func TestStrings_SubSlice(t *testing.T) {
+	for _, test := range stringsSubSliceTests {
+		t.Run("", func(t *testing.T) {
+			defer assertImmutableStrings(t, &test.ss)()
+			assert.Equal(t, test.subSlice, test.ss.SubSlice(test.start, test.end))
+		})
+	}
+}
+
 var stringsFindFirstUsingTests = []struct {
 	ss         Strings
 	expression func(value string) bool