Repository: elliotchance/pie
Pull request #31 (2019-04-11 09:07:50)

Added built-in types
Strings, Ints and Float64s are shipped with pie.

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



Patch:
diff --git a/.travis.yml b/.travis.yml
index 0439b1b..f369c43 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -12,8 +12,8 @@ script:
   - $(exit $(go fmt ./... | grep -v /vendor/ | wc -l))
 
   # Make sure go generate is in sync.
-  - go generate
+  - go generate ./...
   - $(exit $(git status --porcelain | wc -l))
 
   # Run tests.
-  - go test -race
+  - go test -race ./...
diff --git a/README.md b/README.md
index bf38eed..d9b3276 100644
--- a/README.md
+++ b/README.md
@@ -6,6 +6,8 @@
 focuses on type safety, performance and immutability.
 
 - [Quick Start](#quick-start)
+  * [Built-in Types](#built-in-types)
+  * [Custom Types And Structs](#custom-types-and-structs)
 - [Functions](#functions)
 - [FAQ](#faq)
   * [What are the requirements?](#what-are-the-requirements-)
@@ -15,13 +17,45 @@ focuses on type safety, performance and immutability.
 
 # Quick Start
 
-1. Install/update `pie`:
+Install/update `pie`:
 
 ```bash
 go get -u github.com/elliotchance/pie
 ```
 
-2. Annotate the types in your source code:
+## Built-in Types
+
+`pie` ships with some slice types ready to go (pun intended). These include:
+
+- `type [Strings](https://godoc.org/github.com/elliotchance/pie/pie#Strings) []string`
+- `type [Float64s](https://godoc.org/github.com/elliotchance/pie/pie#Float64s) []float64`
+- `type [Ints](https://godoc.org/github.com/elliotchance/pie/pie#Ints) []int`
+
+These can be used without needing `go generate`. For example:
+
+```go
+package main
+
+import (
+	"github.com/elliotchance/pie/pie"
+	"fmt"
+)
+
+func main() {
+	name := pie.Strings{"Bob", "Sally", "John", "Jane"}.
+        Without(func (name string) {
+            return strings.HasPrefix(name, "J")
+        }).
+        Transform(strings.ToUpper).
+        Last()
+
+    fmt.Println(name) // "SALLY"
+}
+```
+
+## Custom Types And Structs
+
+Annotate the slice type in your source code:
 
 ```go
 type Car struct {
@@ -32,11 +66,9 @@ type Car struct {
 type Cars []Car
 ```
 
-3. Run `go generate`. This will create a file called `cars_pie.go`. You
-should commit this with the rest of your code. Run `go generate` any time you
-need to add more types.
-
-4. Usage:
+Run `go generate`. This will create a file called `cars_pie.go`. You should
+commit this with the rest of your code. Run `go generate` any time you need to
+add more types.
 
 Now you can use the slices:
 
@@ -73,7 +105,7 @@ cars.Without(func (car Car) {
 
 # Functions
 
-| Function     | Description                                                                      | String | Number | Struct | O          |
+| Function     | Description                                                                      | String | Number | Struct | Big-O      |
 | ------------ | -------------------------------------------------------------------------------- | :----: | :----: | :----: | :--------: |
 | `AreSorted`  | Check if the slice is already sorted.                                            | ✓      | ✓      |        | n          |
 | `Average`    | The average (mean) value, or a zeroed value.                                     |        | ✓      |        | n          |
diff --git a/car.go b/car.go
deleted file mode 100644
index 523ff9b..0000000
--- a/car.go
+++ /dev/null
@@ -1,8 +0,0 @@
-package main
-
-//go:generate pie Cars
-type Cars []Car
-
-type Car struct {
-	Name, Color string
-}
diff --git a/pie/car.go b/pie/car.go
new file mode 100644
index 0000000..daedc2e
--- /dev/null
+++ b/pie/car.go
@@ -0,0 +1,8 @@
+package pie
+
+//go:generate pie cars
+type cars []car
+
+type car struct {
+	Name, Color string
+}
diff --git a/cars_pie.go b/pie/cars_pie.go
similarity index 77%
rename from cars_pie.go
rename to pie/cars_pie.go
index d10c794..7f7bd08 100755
--- a/cars_pie.go
+++ b/pie/cars_pie.go
@@ -1,4 +1,4 @@
-package main
+package pie
 
 import (
 	"encoding/json"
@@ -7,7 +7,7 @@ import (
 // The functions in this file work for all slices types.
 
 // Contains returns true if the element exists in the slice.
-func (ss Cars) Contains(lookingFor Car) bool {
+func (ss cars) Contains(lookingFor car) bool {
 	for _, s := range ss {
 		if s == lookingFor {
 			return true
@@ -20,8 +20,8 @@ func (ss Cars) Contains(lookingFor Car) bool {
 // Only will return a new slice containing only the elements that return
 // true from the condition. The returned slice may contain zero elements (nil).
 //
-// CarsWithout works in the opposite way as CarsOnly.
-func (ss Cars) Only(condition func(Car) bool) (ss2 Cars) {
+// carsWithout works in the opposite way as carsOnly.
+func (ss cars) Only(condition func(car) bool) (ss2 cars) {
 	for _, s := range ss {
 		if condition(s) {
 			ss2 = append(ss2, s)
@@ -34,7 +34,7 @@ func (ss Cars) Only(condition func(Car) bool) (ss2 Cars) {
 // Without works the same as Only, with a negated condition. That is, it will
 // return a new slice only containing the elements that returned false from the
 // condition. The returned slice may contain zero elements (nil).
-func (ss Cars) Without(condition func(Car) bool) (ss2 Cars) {
+func (ss cars) Without(condition func(car) bool) (ss2 cars) {
 	for _, s := range ss {
 		if !condition(s) {
 			ss2 = append(ss2, s)
@@ -46,12 +46,12 @@ func (ss Cars) Without(condition func(Car) bool) (ss2 Cars) {
 
 // Transform will return a new slice where each element has been transformed.
 // The number of element returned will always be the same as the input.
-func (ss Cars) Transform(fn func(Car) Car) (ss2 Cars) {
+func (ss cars) Transform(fn func(car) car) (ss2 cars) {
 	if ss == nil {
 		return nil
 	}
 
-	ss2 = make([]Car, len(ss))
+	ss2 = make([]car, len(ss))
 	for i, s := range ss {
 		ss2[i] = fn(s)
 	}
@@ -61,7 +61,7 @@ func (ss Cars) Transform(fn func(Car) Car) (ss2 Cars) {
 
 // FirstOr returns the first element or a default value if there are no
 // elements.
-func (ss Cars) FirstOr(defaultValue Car) Car {
+func (ss cars) FirstOr(defaultValue car) car {
 	if len(ss) == 0 {
 		return defaultValue
 	}
@@ -70,7 +70,7 @@ func (ss Cars) FirstOr(defaultValue Car) Car {
 }
 
 // LastOr returns the last element or a default value if there are no elements.
-func (ss Cars) LastOr(defaultValue Car) Car {
+func (ss cars) LastOr(defaultValue car) car {
 	if len(ss) == 0 {
 		return defaultValue
 	}
@@ -79,17 +79,17 @@ func (ss Cars) LastOr(defaultValue Car) Car {
 }
 
 // First returns the first element, or zero. Also see FirstOr().
-func (ss Cars) First() Car {
-	return ss.FirstOr(Car{})
+func (ss cars) First() car {
+	return ss.FirstOr(car{})
 }
 
 // Last returns the last element, or zero. Also see LastOr().
-func (ss Cars) Last() Car {
-	return ss.LastOr(Car{})
+func (ss cars) Last() car {
+	return ss.LastOr(car{})
 }
 
 // Len returns the number of elements.
-func (ss Cars) Len() int {
+func (ss cars) Len() int {
 	return len(ss)
 }
 
@@ -97,7 +97,7 @@ func (ss Cars) Len() int {
 //
 // 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.
-func (ss Cars) JSONString() string {
+func (ss cars) JSONString() string {
 	if ss == nil {
 		return "[]"
 	}
@@ -113,14 +113,14 @@ func (ss Cars) JSONString() string {
 //
 //   ss.Sort().Reverse()
 //
-func (ss Cars) Reverse() Cars {
+func (ss cars) Reverse() cars {
 	// Avoid the allocation. If there is one element or less it is already
 	// reversed.
 	if len(ss) < 2 {
 		return ss
 	}
 
-	sorted := make([]Car, len(ss))
+	sorted := make([]car, len(ss))
 	for i := 0; i < len(ss); i++ {
 		sorted[i] = ss[len(ss)-i-1]
 	}
diff --git a/pie/float64s.go b/pie/float64s.go
new file mode 100644
index 0000000..ee85eb0
--- /dev/null
+++ b/pie/float64s.go
@@ -0,0 +1,4 @@
+package pie
+
+//go:generate pie Float64s
+type Float64s []float64
diff --git a/pie/float64s_pie.go b/pie/float64s_pie.go
new file mode 100755
index 0000000..d8d496c
--- /dev/null
+++ b/pie/float64s_pie.go
@@ -0,0 +1,213 @@
+package pie
+
+import (
+	"encoding/json"
+	"sort"
+)
+
+// The functions in this file work for all slices types.
+
+// Contains returns true if the element exists in the slice.
+func (ss Float64s) Contains(lookingFor float64) bool {
+	for _, s := range ss {
+		if s == lookingFor {
+			return true
+		}
+	}
+
+	return false
+}
+
+// Only will return a new slice containing only the elements that return
+// true from the condition. The returned slice may contain zero elements (nil).
+//
+// Float64sWithout works in the opposite way as Float64sOnly.
+func (ss Float64s) Only(condition func(float64) bool) (ss2 Float64s) {
+	for _, s := range ss {
+		if condition(s) {
+			ss2 = append(ss2, s)
+		}
+	}
+
+	return
+}
+
+// Without works the same as Only, with a negated condition. That is, it will
+// return a new slice only containing the elements that returned false from the
+// condition. The returned slice may contain zero elements (nil).
+func (ss Float64s) Without(condition func(float64) bool) (ss2 Float64s) {
+	for _, s := range ss {
+		if !condition(s) {
+			ss2 = append(ss2, s)
+		}
+	}
+
+	return
+}
+
+// Transform will return a new slice where each element has been transformed.
+// The number of element returned will always be the same as the input.
+func (ss Float64s) Transform(fn func(float64) float64) (ss2 Float64s) {
+	if ss == nil {
+		return nil
+	}
+
+	ss2 = make([]float64, len(ss))
+	for i, s := range ss {
+		ss2[i] = fn(s)
+	}
+
+	return
+}
+
+// FirstOr returns the first element or a default value if there are no
+// elements.
+func (ss Float64s) FirstOr(defaultValue float64) float64 {
+	if len(ss) == 0 {
+		return defaultValue
+	}
+
+	return ss[0]
+}
+
+// LastOr returns the last element or a default value if there are no elements.
+func (ss Float64s) LastOr(defaultValue float64) float64 {
+	if len(ss) == 0 {
+		return defaultValue
+	}
+
+	return ss[len(ss)-1]
+}
+
+// First returns the first element, or zero. Also see FirstOr().
+func (ss Float64s) First() float64 {
+	return ss.FirstOr(0)
+}
+
+// Last returns the last element, or zero. Also see LastOr().
+func (ss Float64s) Last() float64 {
+	return ss.LastOr(0)
+}
+
+// Len returns the number of elements.
+func (ss Float64s) Len() int {
+	return len(ss)
+}
+
+// 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
+// slice to ensure that the JSON value return is always an array.
+func (ss Float64s) JSONString() string {
+	if ss == nil {
+		return "[]"
+	}
+
+	// An error should not be possible.
+	data, _ := json.Marshal(ss)
+
+	return string(data)
+}
+
+// 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:
+//
+//   ss.Sort().Reverse()
+//
+func (ss Float64s) Reverse() Float64s {
+	// Avoid the allocation. If there is one element or less it is already
+	// reversed.
+	if len(ss) < 2 {
+		return ss
+	}
+
+	sorted := make([]float64, len(ss))
+	for i := 0; i < len(ss); i++ {
+		sorted[i] = ss[len(ss)-i-1]
+	}
+
+	return sorted
+}
+
+// The functions in this file only work for string and numeric slices.
+
+// AreSorted will return true if the slice is already sorted. It is a wrapper
+// for sort.Float64sAreSorted.
+func (ss Float64s) AreSorted() bool {
+	return sort.SliceIsSorted(ss, func(i, j int) bool {
+		return ss[i] < ss[j]
+	})
+}
+
+// Sort works similar to sort.Float64s(). However, unlike sort.Float64s the
+// slice returned will be reallocated as to not modify the input slice.
+//
+// See Reverse() and AreSorted().
+func (ss Float64s) Sort() Float64s {
+	// Avoid the allocation. If there is one element or less it is already
+	// sorted.
+	if len(ss) < 2 {
+		return ss
+	}
+
+	sorted := make([]float64, len(ss))
+	copy(sorted, ss)
+	sort.Slice(sorted, func(i, j int) bool {
+		return sorted[i] < sorted[j]
+	})
+
+	return sorted
+}
+
+// Min is the minimum value, or zero.
+func (ss Float64s) Min() (min float64) {
+	if len(ss) == 0 {
+		return
+	}
+
+	min = ss[0]
+	for _, s := range ss {
+		if s < min {
+			min = s
+		}
+	}
+
+	return
+}
+
+// Max is the maximum value, or zero.
+func (ss Float64s) Max() (max float64) {
+	if len(ss) == 0 {
+		return
+	}
+
+	max = ss[0]
+	for _, s := range ss {
+		if s > max {
+			max = s
+		}
+	}
+
+	return
+}
+
+// The functions in this file only work on numeric slices.
+
+// Average is the average of all of the elements, or zero if there are no
+// elements.
+func (ss Float64s) Average() float64 {
+	if l := float64(len(ss)); l > 0 {
+		return float64(ss.Sum()) / float64(l)
+	}
+
+	return 0
+}
+
+// Sum is the sum of all of the elements.
+func (ss Float64s) Sum() (sum float64) {
+	for _, s := range ss {
+		sum += s
+	}
+
+	return
+}
diff --git a/ints.go b/pie/ints.go
similarity index 75%
rename from ints.go
rename to pie/ints.go
index 600c045..d38261d 100644
--- a/ints.go
+++ b/pie/ints.go
@@ -1,4 +1,4 @@
-package main
+package pie
 
 //go:generate pie Ints
 type Ints []int
diff --git a/ints_pie.go b/pie/ints_pie.go
similarity index 99%
rename from ints_pie.go
rename to pie/ints_pie.go
index c9ebf7f..f2c3773 100755
--- a/ints_pie.go
+++ b/pie/ints_pie.go
@@ -1,4 +1,4 @@
-package main
+package pie
 
 import (
 	"encoding/json"
diff --git a/strings.go b/pie/strings.go
similarity index 79%
rename from strings.go
rename to pie/strings.go
index 33e73aa..7cfff8f 100644
--- a/strings.go
+++ b/pie/strings.go
@@ -1,4 +1,4 @@
-package main
+package pie
 
 //go:generate pie Strings
 type Strings []string
diff --git a/strings_pie.go b/pie/strings_pie.go
similarity index 99%
rename from strings_pie.go
rename to pie/strings_pie.go
index 45f92e6..7d83d1e 100755
--- a/strings_pie.go
+++ b/pie/strings_pie.go
@@ -1,4 +1,4 @@
-package main
+package pie
 
 import (
 	"encoding/json"


Test patch:
diff --git a/cars_test.go b/pie/cars_test.go
similarity index 55%
rename from cars_test.go
rename to pie/cars_test.go
index dd856da..4f599d8 100644
--- a/cars_test.go
+++ b/pie/cars_test.go
@@ -1,4 +1,4 @@
-package main
+package pie
 
 import (
 	"github.com/stretchr/testify/assert"
@@ -7,19 +7,19 @@ import (
 )
 
 var carsContainsTests = []struct {
-	ss       Cars
-	contains Car
+	ss       cars
+	contains car
 	expected bool
 }{
-	{nil, Car{"a", "green"}, false},
-	{nil, Car{}, false},
-	{Cars{Car{"a", "green"}, Car{"b", "blue"}, Car{"c", "gray"}}, Car{"a", "green"}, true},
-	{Cars{Car{"a", "green"}, Car{"b", "blue"}, Car{"c", "gray"}}, Car{"b", "blue"}, true},
-	{Cars{Car{"a", "green"}, Car{"b", "blue"}, Car{"c", "gray"}}, Car{"c", "gray"}, true},
-	{Cars{Car{"a", "green"}, Car{"b", "blue"}, Car{"c", "gray"}}, Car{"A", ""}, false},
-	{Cars{Car{"a", "green"}, Car{"b", "blue"}, Car{"c", "gray"}}, Car{}, false},
-	{Cars{Car{"a", "green"}, Car{"b", "blue"}, Car{"c", "gray"}}, Car{"d", ""}, false},
-	{Cars{Car{"a", "green"}, Car{}, Car{"c", "gray"}}, Car{}, true},
+	{nil, car{"a", "green"}, false},
+	{nil, car{}, false},
+	{cars{car{"a", "green"}, car{"b", "blue"}, car{"c", "gray"}}, car{"a", "green"}, true},
+	{cars{car{"a", "green"}, car{"b", "blue"}, car{"c", "gray"}}, car{"b", "blue"}, true},
+	{cars{car{"a", "green"}, car{"b", "blue"}, car{"c", "gray"}}, car{"c", "gray"}, true},
+	{cars{car{"a", "green"}, car{"b", "blue"}, car{"c", "gray"}}, car{"A", ""}, false},
+	{cars{car{"a", "green"}, car{"b", "blue"}, car{"c", "gray"}}, car{}, false},
+	{cars{car{"a", "green"}, car{"b", "blue"}, car{"c", "gray"}}, car{"d", ""}, false},
+	{cars{car{"a", "green"}, car{}, car{"c", "gray"}}, car{}, true},
 }
 
 func TestCars_Contains(t *testing.T) {
@@ -32,15 +32,15 @@ func TestCars_Contains(t *testing.T) {
 }
 
 var carsOnlyAndWithoutTests = []struct {
-	ss                Cars
-	condition         func(Car) bool
-	expectedOnly      Cars
-	expectedWithout   Cars
-	expectedTransform Cars
+	ss                cars
+	condition         func(car) bool
+	expectedOnly      cars
+	expectedWithout   cars
+	expectedTransform cars
 }{
 	{
 		nil,
-		func(s Car) bool {
+		func(s car) bool {
 			return s.Name == ""
 		},
 		nil,
@@ -48,13 +48,13 @@ var carsOnlyAndWithoutTests = []struct {
 		nil,
 	},
 	{
-		Cars{Car{"a", "green"}, Car{"b", "blue"}, Car{"c", "gray"}},
-		func(s Car) bool {
+		cars{car{"a", "green"}, car{"b", "blue"}, car{"c", "gray"}},
+		func(s car) bool {
 			return s.Name != "b"
 		},
-		Cars{Car{"a", "green"}, Car{"c", "gray"}},
-		Cars{Car{"b", "blue"}},
-		Cars{Car{"A", "green"}, Car{"B", "blue"}, Car{"C", "gray"}},
+		cars{car{"a", "green"}, car{"c", "gray"}},
+		cars{car{"b", "blue"}},
+		cars{car{"A", "green"}, car{"B", "blue"}, car{"C", "gray"}},
 	},
 }
 
@@ -80,7 +80,7 @@ func TestCars_Transform(t *testing.T) {
 	for _, test := range carsOnlyAndWithoutTests {
 		t.Run("", func(t *testing.T) {
 			defer assertImmutableCars(t, &test.ss)()
-			assert.Equal(t, test.expectedTransform, test.ss.Transform(func(car Car) Car {
+			assert.Equal(t, test.expectedTransform, test.ss.Transform(func(car car) car {
 				car.Name = strings.ToUpper(car.Name)
 
 				return car
@@ -90,37 +90,37 @@ func TestCars_Transform(t *testing.T) {
 }
 
 var carsFirstAndLastTests = []struct {
-	ss             Cars
-	first, firstOr Car
-	last, lastOr   Car
+	ss             cars
+	first, firstOr car
+	last, lastOr   car
 }{
 	{
 		nil,
-		Car{},
-		Car{"default1", "unknown"},
-		Car{},
-		Car{"default2", "unknown"},
+		car{},
+		car{"default1", "unknown"},
+		car{},
+		car{"default2", "unknown"},
 	},
 	{
-		Cars{Car{"foo", "red"}},
-		Car{"foo", "red"},
-		Car{"foo", "red"},
-		Car{"foo", "red"},
-		Car{"foo", "red"},
+		cars{car{"foo", "red"}},
+		car{"foo", "red"},
+		car{"foo", "red"},
+		car{"foo", "red"},
+		car{"foo", "red"},
 	},
 	{
-		Cars{Car{"a", "green"}, Car{"b", "blue"}},
-		Car{"a", "green"},
-		Car{"a", "green"},
-		Car{"b", "blue"},
-		Car{"b", "blue"},
+		cars{car{"a", "green"}, car{"b", "blue"}},
+		car{"a", "green"},
+		car{"a", "green"},
+		car{"b", "blue"},
+		car{"b", "blue"},
 	},
 	{
-		Cars{Car{"a", "green"}, Car{"b", "blue"}, Car{"c", "gray"}},
-		Car{"a", "green"},
-		Car{"a", "green"},
-		Car{"c", "gray"},
-		Car{"c", "gray"},
+		cars{car{"a", "green"}, car{"b", "blue"}, car{"c", "gray"}},
+		car{"a", "green"},
+		car{"a", "green"},
+		car{"c", "gray"},
+		car{"c", "gray"},
 	},
 }
 
@@ -128,7 +128,7 @@ func TestCars_FirstOr(t *testing.T) {
 	for _, test := range carsFirstAndLastTests {
 		t.Run("", func(t *testing.T) {
 			defer assertImmutableCars(t, &test.ss)()
-			assert.Equal(t, test.firstOr, test.ss.FirstOr(Car{"default1", "unknown"}))
+			assert.Equal(t, test.firstOr, test.ss.FirstOr(car{"default1", "unknown"}))
 		})
 	}
 }
@@ -137,7 +137,7 @@ func TestCars_LastOr(t *testing.T) {
 	for _, test := range carsFirstAndLastTests {
 		t.Run("", func(t *testing.T) {
 			defer assertImmutableCars(t, &test.ss)()
-			assert.Equal(t, test.lastOr, test.ss.LastOr(Car{"default2", "unknown"}))
+			assert.Equal(t, test.lastOr, test.ss.LastOr(car{"default2", "unknown"}))
 		})
 	}
 }
@@ -161,32 +161,32 @@ func TestCars_Last(t *testing.T) {
 }
 
 var carsStatsTests = []struct {
-	ss       Cars
-	min, max Car
+	ss       cars
+	min, max car
 	len      int
 }{
 	{
 		nil,
-		Car{},
-		Car{},
+		car{},
+		car{},
 		0,
 	},
 	{
-		Cars{},
-		Car{},
-		Car{},
+		cars{},
+		car{},
+		car{},
 		0,
 	},
 	{
-		Cars{Car{"foo", "red"}},
-		Car{"foo", "red"},
-		Car{"foo", "red"},
+		cars{car{"foo", "red"}},
+		car{"foo", "red"},
+		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"}},
+		car{"Baz", "black"},
+		car{"qux", "cyan"},
 		4,
 	},
 }
@@ -195,13 +195,13 @@ func TestCars_Len(t *testing.T) {
 	for _, test := range carsStatsTests {
 		t.Run("", func(t *testing.T) {
 			defer assertImmutableCars(t, &test.ss)()
-			assert.Equal(t, test.len, Cars(test.ss).Len())
+			assert.Equal(t, test.len, cars(test.ss).Len())
 		})
 	}
 }
 
 var carsJSONTests = []struct {
-	ss         Cars
+	ss         cars
 	jsonString string
 }{
 	{
@@ -209,15 +209,15 @@ var carsJSONTests = []struct {
 		`[]`, // Instead of null.
 	},
 	{
-		Cars{},
+		cars{},
 		`[]`,
 	},
 	{
-		Cars{Car{"foo", "red"}},
+		cars{car{"foo", "red"}},
 		`[{"Name":"foo","Color":"red"}]`,
 	},
 	{
-		Cars{Car{"bar", "yellow"}, Car{"Baz", "black"}, Car{"qux", "cyan"}, Car{"foo", "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"}]`,
 	},
 }
@@ -232,9 +232,9 @@ func TestCars_JSONString(t *testing.T) {
 }
 
 var carsSortTests = []struct {
-	ss        Cars
-	sorted    Cars
-	reversed  Cars
+	ss        cars
+	sorted    cars
+	reversed  cars
 	areSorted bool
 }{
 	{
@@ -244,33 +244,33 @@ var carsSortTests = []struct {
 		true,
 	},
 	{
-		Cars{},
-		Cars{},
-		Cars{},
+		cars{},
+		cars{},
+		cars{},
 		true,
 	},
 	{
-		Cars{Car{"foo", "red"}},
-		Cars{Car{"foo", "red"}},
-		Cars{Car{"foo", "red"}},
+		cars{car{"foo", "red"}},
+		cars{car{"foo", "red"}},
+		cars{car{"foo", "red"}},
 		true,
 	},
 	{
-		Cars{Car{"bar", "yellow"}, Car{"Baz", "black"}, Car{"foo", "red"}},
-		Cars{Car{"Baz", "black"}, Car{"bar", "yellow"}, Car{"foo", "red"}},
-		Cars{Car{"foo", "red"}, Car{"Baz", "black"}, Car{"bar", "yellow"}},
+		cars{car{"bar", "yellow"}, car{"Baz", "black"}, car{"foo", "red"}},
+		cars{car{"Baz", "black"}, car{"bar", "yellow"}, car{"foo", "red"}},
+		cars{car{"foo", "red"}, car{"Baz", "black"}, car{"bar", "yellow"}},
 		false,
 	},
 	{
-		Cars{Car{"bar", "yellow"}, Car{"Baz", "black"}, Car{"qux", "cyan"}, Car{"foo", "red"}},
-		Cars{Car{"Baz", "black"}, Car{"bar", "yellow"}, Car{"foo", "red"}, Car{"qux", "cyan"}},
-		Cars{Car{"foo", "red"}, Car{"qux", "cyan"}, Car{"Baz", "black"}, Car{"bar", "yellow"}},
+		cars{car{"bar", "yellow"}, car{"Baz", "black"}, car{"qux", "cyan"}, car{"foo", "red"}},
+		cars{car{"Baz", "black"}, car{"bar", "yellow"}, car{"foo", "red"}, car{"qux", "cyan"}},
+		cars{car{"foo", "red"}, car{"qux", "cyan"}, car{"Baz", "black"}, car{"bar", "yellow"}},
 		false,
 	},
 	{
-		Cars{Car{"Baz", "black"}, Car{"bar", "yellow"}},
-		Cars{Car{"Baz", "black"}, Car{"bar", "yellow"}},
-		Cars{Car{"bar", "yellow"}, Car{"Baz", "black"}},
+		cars{car{"Baz", "black"}, car{"bar", "yellow"}},
+		cars{car{"Baz", "black"}, car{"bar", "yellow"}},
+		cars{car{"bar", "yellow"}, car{"Baz", "black"}},
 		true,
 	},
 }
diff --git a/pie/float64s_test.go b/pie/float64s_test.go
new file mode 100644
index 0000000..3e0fc30
--- /dev/null
+++ b/pie/float64s_test.go
@@ -0,0 +1,344 @@
+package pie
+
+import (
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+var float64sContainsTests = []struct {
+	ss       Float64s
+	contains float64
+	expected bool
+}{
+	{nil, 1, false},
+	{Float64s{1, 2, 3}, 1, true},
+	{Float64s{1, 2, 3}, 2, true},
+	{Float64s{1, 2, 3}, 3, true},
+	{Float64s{1, 2, 3}, 4, false},
+	{Float64s{1, 2, 3}, 5, false},
+	{Float64s{1, 2, 3}, 6, false},
+	{Float64s{1, 5, 3}, 5, true},
+}
+
+func TestFloat64s_Contains(t *testing.T) {
+	for _, test := range float64sContainsTests {
+		t.Run("", func(t *testing.T) {
+			defer assertImmutableFloat64s(t, &test.ss)()
+			assert.Equal(t, test.expected, test.ss.Contains(test.contains))
+		})
+	}
+}
+
+var float64sOnlyAndWithoutTests = []struct {
+	ss                Float64s
+	condition         func(float64) bool
+	expectedOnly      Float64s
+	expectedWithout   Float64s
+	expectedTransform Float64s
+}{
+	{
+		nil,
+		func(s float64) bool {
+			return s == 5
+		},
+		nil,
+		nil,
+		nil,
+	},
+	{
+		Float64s{1, 2, 3},
+		func(s float64) bool {
+			return s != 2
+		},
+		Float64s{1, 3},
+		Float64s{2},
+		Float64s{6.2, 7.2, 8.2},
+	},
+}
+
+func TestFloat64s_Only(t *testing.T) {
+	for _, test := range float64sOnlyAndWithoutTests {
+		t.Run("", func(t *testing.T) {
+			defer assertImmutableFloat64s(t, &test.ss)()
+			assert.Equal(t, test.expectedOnly, test.ss.Only(test.condition))
+		})
+	}
+}
+
+func TestFloat64s_Without(t *testing.T) {
+	for _, test := range float64sOnlyAndWithoutTests {
+		t.Run("", func(t *testing.T) {
+			defer assertImmutableFloat64s(t, &test.ss)()
+			assert.Equal(t, test.expectedWithout, test.ss.Without(test.condition))
+		})
+	}
+}
+
+func TestFloat64s_Transform(t *testing.T) {
+	for _, test := range float64sOnlyAndWithoutTests {
+		t.Run("", func(t *testing.T) {
+			defer assertImmutableFloat64s(t, &test.ss)()
+			assert.Equal(t, test.expectedTransform, test.ss.Transform(func(a float64) float64 {
+				return a + 5.2
+			}))
+		})
+	}
+}
+
+var float64sFirstAndLastTests = []struct {
+	ss             Float64s
+	first, firstOr float64
+	last, lastOr   float64
+}{
+	{
+		nil,
+		0,
+		102,
+		0,
+		202,
+	},
+	{
+		Float64s{100},
+		100,
+		100,
+		100,
+		100,
+	},
+	{
+		Float64s{1, 2},
+		1,
+		1,
+		2,
+		2,
+	},
+	{
+		Float64s{1, 2, 3},
+		1,
+		1,
+		3,
+		3,
+	},
+}
+
+func TestFloat64s_FirstOr(t *testing.T) {
+	for _, test := range float64sFirstAndLastTests {
+		t.Run("", func(t *testing.T) {
+			defer assertImmutableFloat64s(t, &test.ss)()
+			assert.Equal(t, test.firstOr, test.ss.FirstOr(102))
+		})
+	}
+}
+
+func TestFloat64s_LastOr(t *testing.T) {
+	for _, test := range float64sFirstAndLastTests {
+		t.Run("", func(t *testing.T) {
+			defer assertImmutableFloat64s(t, &test.ss)()
+			assert.Equal(t, test.lastOr, test.ss.LastOr(202))
+		})
+	}
+}
+
+func TestFloat64s_First(t *testing.T) {
+	for _, test := range float64sFirstAndLastTests {
+		t.Run("", func(t *testing.T) {
+			defer assertImmutableFloat64s(t, &test.ss)()
+			assert.Equal(t, test.first, test.ss.First())
+		})
+	}
+}
+
+func TestFloat64s_Last(t *testing.T) {
+	for _, test := range float64sFirstAndLastTests {
+		t.Run("", func(t *testing.T) {
+			defer assertImmutableFloat64s(t, &test.ss)()
+			assert.Equal(t, test.last, test.ss.Last())
+		})
+	}
+}
+
+var float64sStatsTests = []struct {
+	ss            Float64s
+	min, max, sum float64
+	len           int
+	average       float64
+}{
+	{
+		nil,
+		0,
+		0,
+		0,
+		0,
+		0,
+	},
+	{
+		[]float64{},
+		0,
+		0,
+		0,
+		0,
+		0,
+	},
+	{
+		[]float64{1.5},
+		1.5,
+		1.5,
+		1.5,
+		1,
+		1.5,
+	},
+	{
+		[]float64{2.2, 3.1, 5.1, 1.9},
+		1.9,
+		5.1,
+		12.3,
+		4,
+		3.075,
+	},
+}
+
+func TestFloat64s_Min(t *testing.T) {
+	for _, test := range float64sStatsTests {
+		t.Run("", func(t *testing.T) {
+			defer assertImmutableFloat64s(t, &test.ss)()
+			assert.Equal(t, test.min, Float64s(test.ss).Min())
+		})
+	}
+}
+
+func TestFloat64s_Max(t *testing.T) {
+	for _, test := range float64sStatsTests {
+		t.Run("", func(t *testing.T) {
+			defer assertImmutableFloat64s(t, &test.ss)()
+			assert.Equal(t, test.max, Float64s(test.ss).Max())
+		})
+	}
+}
+
+func TestFloat64s_Sum(t *testing.T) {
+	for _, test := range float64sStatsTests {
+		t.Run("", func(t *testing.T) {
+			defer assertImmutableFloat64s(t, &test.ss)()
+			assert.Equal(t, test.sum, Float64s(test.ss).Sum())
+		})
+	}
+}
+
+func TestFloat64s_Len(t *testing.T) {
+	for _, test := range float64sStatsTests {
+		t.Run("", func(t *testing.T) {
+			defer assertImmutableFloat64s(t, &test.ss)()
+			assert.Equal(t, test.len, Float64s(test.ss).Len())
+		})
+	}
+}
+
+func TestFloat64s_Average(t *testing.T) {
+	for _, test := range float64sStatsTests {
+		t.Run("", func(t *testing.T) {
+			defer assertImmutableFloat64s(t, &test.ss)()
+			assert.Equal(t, test.average, Float64s(test.ss).Average())
+		})
+	}
+}
+
+var float64sJSONTests = []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_JSONString(t *testing.T) {
+	for _, test := range float64sJSONTests {
+		t.Run("", func(t *testing.T) {
+			defer assertImmutableFloat64s(t, &test.ss)()
+			assert.Equal(t, test.jsonString, test.ss.JSONString())
+		})
+	}
+}
+
+var float64sSortTests = []struct {
+	ss        Float64s
+	sorted    Float64s
+	reversed  Float64s
+	areSorted bool
+}{
+	{
+		nil,
+		nil,
+		nil,
+		true,
+	},
+	{
+		Float64s{},
+		Float64s{},
+		Float64s{},
+		true,
+	},
+	{
+		Float64s{789},
+		Float64s{789},
+		Float64s{789},
+		true,
+	},
+	{
+		Float64s{12.789, -13.2, 789},
+		Float64s{-13.2, 12.789, 789},
+		Float64s{789, -13.2, 12.789},
+		false,
+	},
+	{
+		Float64s{12.789, -13.2, 1.234e6, 789},
+		Float64s{-13.2, 12.789, 789, 1.234e6},
+		Float64s{789, 1.234e6, -13.2, 12.789},
+		false,
+	},
+	{
+		Float64s{-13.2, 12.789},
+		Float64s{-13.2, 12.789},
+		Float64s{12.789, -13.2},
+		true,
+	},
+}
+
+func TestFloat64s_Sort(t *testing.T) {
+	for _, test := range float64sSortTests {
+		t.Run("", func(t *testing.T) {
+			defer assertImmutableFloat64s(t, &test.ss)()
+			assert.Equal(t, test.sorted, test.ss.Sort())
+		})
+	}
+}
+
+func TestFloat64s_Reverse(t *testing.T) {
+	for _, test := range float64sSortTests {
+		t.Run("", func(t *testing.T) {
+			defer assertImmutableFloat64s(t, &test.ss)()
+			assert.Equal(t, test.reversed, test.ss.Reverse())
+		})
+	}
+}
+
+func TestFloat64s_AreSorted(t *testing.T) {
+	for _, test := range float64sSortTests {
+		t.Run("", func(t *testing.T) {
+			defer assertImmutableFloat64s(t, &test.ss)()
+			assert.Equal(t, test.areSorted, test.ss.AreSorted())
+		})
+	}
+}
diff --git a/ints_test.go b/pie/ints_test.go
similarity index 99%
rename from ints_test.go
rename to pie/ints_test.go
index 8bb6d5e..2aaac91 100644
--- a/ints_test.go
+++ b/pie/ints_test.go
@@ -1,4 +1,4 @@
-package main
+package pie
 
 import (
 	"github.com/stretchr/testify/assert"
diff --git a/main_test.go b/pie/main_test.go
similarity index 68%
rename from main_test.go
rename to pie/main_test.go
index 2d36ecc..0f6dff0 100644
--- a/main_test.go
+++ b/pie/main_test.go
@@ -1,4 +1,4 @@
-package main
+package pie
 
 import (
 	"github.com/stretchr/testify/assert"
@@ -25,7 +25,17 @@ func assertImmutableInts(t *testing.T, ss *Ints) func() {
 	}
 }
 
-func assertImmutableCars(t *testing.T, ss *Cars) func() {
+func assertImmutableFloat64s(t *testing.T, ss *Float64s) func() {
+	before := (*ss).JSONString()
+
+	return func() {
+		after := (*ss).JSONString()
+		assert.Equal(t, before, after)
+		assert.True(t, before == after)
+	}
+}
+
+func assertImmutableCars(t *testing.T, ss *cars) func() {
 	before := (*ss).JSONString()
 
 	return func() {
diff --git a/strings_test.go b/pie/strings_test.go
similarity index 99%
rename from strings_test.go
rename to pie/strings_test.go
index 251895f..19db6ee 100644
--- a/strings_test.go
+++ b/pie/strings_test.go
@@ -1,4 +1,4 @@
-package main
+package pie
 
 import (
 	"github.com/stretchr/testify/assert"