Enjoy a slice! pie
is a code generator for dealing with slices that
focuses on type safety, performance and immutability.
go get -u github.com/elliotchance/pie
pie
ships with some slice types ready to go (pun intended). These include:
These can be used without needing go generate
. For example:
package main
import (
"fmt"
"strings"
"github.com/elliotchance/pie/pie"
)
func main() {
name := pie.Strings{"Bob", "Sally", "John", "Jane"}.
FilterNot(func (name string) bool {
return strings.HasPrefix(name, "J")
}).
Map(strings.ToUpper).
Last()
fmt.Println(name) // "SALLY"
}
Annotate the slice type in your source code:
type Car struct {
Name, Color string
}
//go:generate pie Cars.*
type Cars []Car
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:
cars := Cars{
{"Bob", "blue"},
{"Sally", "green"},
{"John", "red"},
{"Jane", "red"},
}
redCars := cars.Filter(func(car Car) bool {
return car.Color == "red"
})
// redCars = Cars{{"John", "red"}, {"Jane", "red"}}
Or, more complex operations can be chained:
cars.FilterNot(func (car Car) {
return strings.HasPrefix(car.Name, "J")
}).
Map(func (car Car) Car {
car.Name = strings.ToUpper(car.Name)
return car
}).
Last()
// Car{"SALLY", "green"}
Some functions that compare elements, such as Contains will use the following method if it is available on the element type:
func (a ElementType) Equals(b ElementType) bool
The ElementType
must be the same for the receiver and argument and it must
return a bool. Be careful to create the function on the pointer or non-pointer
type that is used by the slice.
Here is a minimal example:
type Car struct {
Name, Color string
}
type Cars []*Car // ElementType is *Car
func (c *Car) Equals(c2 *Car) bool {
return c.Name == c2.Name
}
Some functions that need elements to be represented as strings, such as
Strings()
will try to use the fmt.Stringer
interface. If it's not available
then it will fallback to:
fmt.Sprintf("%v", element)
The .*
can be used to generate all functions. This is easy to get going but
creates a lot of unused code. You can limit the functions generated by chaining
the function names with a dot syntax, like:
//go:generate pie myInts.Average.Sum myStrings.Filter
This will only generate myInts.Average
, myInts.Sum
and myStrings.Filter
.
Below is a summary of the available functions.
The letters in brackets indicate:
-
E: The function will use the
Equals
method if it is available. See Custom Equality. -
S: The function will use the
String
method if it is available. See Custom Stringer.
Function | String | Number | Struct | Maps | Big-O | Description |
---|---|---|---|---|---|---|
Abs |
β | n | Abs is a function which returns the absolute value of all the elements in the slice. | |||
All |
β | β | β | n | All will return true if all callbacks return true. It follows the same logic as the all() function in Python. | |
Any |
β | β | β | n | Any will return true if any callbacks return true. It follows the same logic as the any() function in Python. | |
Append |
β | β | β | n | Append will return a new slice with the elements appended to the end. | |
AreSorted |
β | β | n | AreSorted will return true if the slice is already sorted. It is a wrapper for sort.SliceTypeAreSorted. | ||
AreUnique |
β | β | n | AreUnique will return true if the slice contains elements that are all different (unique) from each other. | ||
Average |
β | n | Average is the average of all of the elements, or zero if there are no elements. | |||
Bottom |
β | β | β | n | Bottom will return n elements from bottom | |
Contains (E) |
β | β | β | n | Contains returns true if the element exists in the slice. | |
Diff (E) |
β | β | β | nΒ² | Diff returns the elements that needs to be added or removed from the first slice to have the same elements in the second slice. | |
DropTop |
β | β | β | n | DropTop will return the rest slice after dropping the top n elements if the slice has less elements then n that'll return empty slice if n < 0 it'll return empty slice. | |
Each |
β | β | β | n | Each is more condensed version of Transform that allows an action to happen on each elements and pass the original slice on. | |
Equals (E) |
β | β | β | n | Equals compare elements from the start to the end, | |
Extend |
β | β | β | n | Extend will return a new slice with the slices of elements appended to the end. | |
Filter |
β | β | β | n | Filter will return a new slice containing only the elements that return true from the condition. The returned slice may contain zero elements (nil). | |
FilterNot |
β | β | β | n | FilterNot works the same as Filter, 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). | |
FindFirstUsing |
β | β | β | n | FindFirstUsing will return the index of the first element when the callback returns true or -1 if no element is found. It follows the same logic as the findIndex() function in Javascript. | |
First |
β | β | β | 1 | First returns the first element, or zero. Also see FirstOr(). | |
FirstOr |
β | β | β | 1 | FirstOr returns the first element or a default value if there are no elements. | |
Float64s (S) |
β | β | β | n | Float64s transforms each element to a float64. | |
Intersect |
β | β | n | Intersect returns items that exist in all lists. | ||
Ints (S) |
β | β | β | n | Ints transforms each element to an integer. | |
Join |
β | n | Join returns a string from joining each of the elements. | |||
JSONBytes |
β | β | β | n | JSONBytes returns the JSON encoded array as bytes. | |
JSONBytesIndent |
β | β | β | n | JSONBytesIndent returns the JSON encoded array as bytes with indent applied. | |
JSONString |
β | β | β | n | JSONString returns the JSON encoded array as a string. | |
JSONStringIndent |
β | β | β | n | JSONStringIndent returns the JSON encoded array as a string with indent applied. | |
Keys |
β | n | Keys returns the keys in the map. All of the items will be unique. | |||
Last |
β | β | β | 1 | Last returns the last element, or zero. Also see LastOr(). | |
LastOr |
β | β | β | 1 | LastOr returns the last element or a default value if there are no elements. | |
Len |
β | β | β | 1 | Len returns the number of elements. | |
Map |
β | β | β | n | Map will return a new slice where each element has been mapped (transformed). The number of elements returned will always be the same as the input. | |
Max |
β | β | n | Max is the maximum value, or zero. | ||
Median |
β | n | Median returns the value separating the higher half from the lower half of a data sample. | |||
Min |
β | β | n | Min is the minimum value, or zero. | ||
Mode |
β | β | β | n | Mode returns a new slice containing the most frequently occuring values. | |
Product |
β | n | Product is the product of all of the elements. | |||
Random |
β | β | β | 1 | Random returns a random element by your rand.Source, or zero | |
Reduce |
β | β | n | Reduce continually applies the provided function over the slice. Reducing the elements to a single value. | ||
Reverse |
β | β | β | n | 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: | |
Send |
β | β | β | n | Send sends elements to channel in normal act it sends all elements but if func canceled it can be less | |
Sequence |
β | n | Sequence generates all numbers in range or returns nil if params invalid | |||
SequenceUsing |
β | β | β | n | SequenceUsing generates slice in range using creator function | |
Shuffle |
β | β | β | n | Shuffle returns shuffled slice by your rand.Source | |
Sort |
β | β | nβ log(n) | Sort works similar to sort.SliceType(). However, unlike sort.SliceType the slice returned will be reallocated as to not modify the input slice. | ||
SortStableUsing |
β | β | nβ log(n) | SortStableUsing works similar to sort.SliceStable. However, unlike sort.SliceStable the slice returned will be reallocated as to not modify the input slice. | ||
SortUsing |
β | β | nβ log(n) | SortUsing works similar to sort.Slice. However, unlike sort.Slice the slice returned will be reallocated as to not modify the input slice. | ||
Strings (S) |
β | β | β | n | Strings transforms each element to a string. | |
SubSlice |
β | β | β | n | SubSlice will return the subSlice from start to end(excluded) | |
Sum |
β | n | Sum is the sum of all of the elements. | |||
Top |
β | β | β | n | 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. | |
StringsUsing |
β | β | β | n | StringsUsing transforms each element to a string. | |
Unique |
β | β | n | Unique returns a new slice with all of the unique values. | ||
Values |
β | n | Values returns the values in the map. |
Abs is a function which returns the absolute value of all the elements in the slice.
All will return true if all callbacks return true. It follows the same logic as the all() function in Python.
If the list is empty then true is always returned.
Any will return true if any callbacks return true. It follows the same logic as the any() function in Python.
If the list is empty then false is always returned.
Append will return a new slice with the elements appended to the end.
It is acceptable to provide zero arguments.
AreSorted will return true if the slice is already sorted. It is a wrapper for sort.SliceTypeAreSorted.
AreUnique will return true if the slice contains elements that are all different (unique) from each other.
Average is the average of all of the elements, or zero if there are no elements.
Bottom will return n elements from bottom
that means that elements is taken from the end of the slice for this [1,2,3] slice with n == 2 will be returned [3,2] if the slice has less elements then n that'll return all elements if n < 0 it'll return empty slice.
Contains returns true if the element exists in the slice.
When using slices of pointers it will only compare by address, not value.
Diff returns the elements that needs to be added or removed from the first slice to have the same elements in the second slice.
The order of elements is not taken into consideration, so the slices are treated sets that allow duplicate items.
The added and removed returned may be blank respectively, or contain upto as many elements that exists in the largest slice.
DropTop will return the rest slice after dropping the top n elements if the slice has less elements then n that'll return empty slice if n < 0 it'll return empty slice.
Each is more condensed version of Transform that allows an action to happen on each elements and pass the original slice on.
cars.Each(func (car *Car) {
fmt.Printf("Car color is: %s\n", car.Color)
})
Pie will not ensure immutability on items passed in so they can be manipulated, if you choose to do it this way, for example:
// Set all car colors to Red.
cars.Each(func (car *Car) {
car.Color = "Red"
})
Equals compare elements from the start to the end,
if they are the same is considered the slices are equal if all elements are the same is considered the slices are equal if each slice == nil is considered that they're equal
if element realizes Equals interface it uses that method, in other way uses default compare
Extend will return a new slice with the slices of elements appended to the end.
It is acceptable to provide zero arguments.
Filter will return a new slice containing only the elements that return true from the condition. The returned slice may contain zero elements (nil).
FilterNot works in the opposite way of Filter.
FilterNot works the same as Filter, 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).
FindFirstUsing will return the index of the first element when the callback returns true or -1 if no element is found. It follows the same logic as the findIndex() function in Javascript.
If the list is empty then -1 is always returned.
First returns the first element, or zero. Also see FirstOr().
FirstOr returns the first element or a default value if there are no elements.
Float64s transforms each element to a float64.
Intersect returns items that exist in all lists.
It returns slice without any duplicates. If zero slice arguments are provided, then nil is returned.
Ints transforms each element to an integer.
Join returns a string from joining each of the elements.
JSONBytes returns the JSON encoded array as bytes.
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.
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.
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.
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.
Keys returns the keys in the map. All of the items will be unique.
Due to Go's randomization of iterating maps the order is not deterministic.
Last returns the last element, or zero. Also see LastOr().
LastOr returns the last element or a default value if there are no elements.
Len returns the number of elements.
Map will return a new slice where each element has been mapped (transformed). The number of elements returned will always be the same as the input.
Be careful when using this with slices of pointers. If you modify the input value it will affect the original slice. Be sure to return a new allocated object or deep copy the existing one.
Max is the maximum value, or zero.
Median returns the value separating the higher half from the lower half of a 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.
Min is the minimum value, or zero.
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.
Product is the product of all of the elements.
Random returns a random element by your rand.Source, or zero
Reduce continually applies the provided function over the slice. Reducing the elements to a single value.
Returns a zero value of ElementType if there are no elements in the slice. It will panic if the reducer is nil and the slice has more than one element (required to invoke reduce). Otherwise returns result of applying reducer from left to right.
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()
Send sends elements to channel in normal act it sends all elements but if func canceled it can be less
it locks execution of gorutine it doesn't close channel after work returns sended elements if len(this) != len(old) considered func was canceled
Sequence generates all numbers in range or returns nil if params invalid
There are 3 variations to generate: 1. [0, n). 2. [min, max). 3. [min, max) with step.
if len(params) == 1 considered that will be returned slice between 0 and n, where n is the first param, [0, n). if len(params) == 2 considered that will be returned slice between min and max, where min is the first param, max is the second, [min, max). if len(params) > 2 considered that will be returned slice between min and max with step, where min is the first param, max is the second, step is the third one, [min, max) with step, others params will be ignored
SequenceUsing generates slice in range using creator function
There are 3 variations to generate: 1. [0, n). 2. [min, max). 3. [min, max) with step.
if len(params) == 1 considered that will be returned slice between 0 and n, where n is the first param, [0, n). if len(params) == 2 considered that will be returned slice between min and max, where min is the first param, max is the second, [min, max). if len(params) > 2 considered that will be returned slice between min and max with step, where min is the first param, max is the second, step is the third one, [min, max) with step, others params will be ignored
Shuffle returns shuffled slice by your rand.Source
Sort works similar to sort.SliceType(). However, unlike sort.SliceType the slice returned will be reallocated as to not modify the input slice.
See Reverse() and AreSorted().
SortStableUsing works similar to sort.SliceStable. However, unlike sort.SliceStable the slice returned will be reallocated as to not modify the input slice.
SortUsing works similar to sort.Slice. However, unlike sort.Slice the slice returned will be reallocated as to not modify the input slice.
Strings transforms each element to a string.
If the element type implements fmt.Stringer it will be used. Otherwise it will fallback to the result of:
fmt.Sprintf("%v")
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.
Sum is the sum of all of the elements.
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.
StringsUsing transforms each element to a string.
Unique returns a new slice with all of the unique values.
The items will be returned in a randomized order, even with the same input.
The number of items returned may be the same as the input or less. It will never return zero items unless then input slice has zero items.
A slice with zero elements is considered to be unique.
See AreUnique().
Values returns the values in the map.
Due to Go's randomization of iterating maps the order is not deterministic.
pie
supports many Go versions, all the way back to Go 1.8.
-
Type safety. I never want to hit runtime bugs because I could pass in the wrong type, or perform an invalid type case out the other end.
-
Performance. The functions need to be as fast as native Go implementations otherwise there's no point in this library existing.
-
Nil-safe. All of the functions will happily accept nil and treat them as empty slices. Apart from less possible panics, it makes it easier to chain.
-
Immutable. Functions never modify inputs, unlike some built-ins such as
sort.Strings
.
Pull requests are always welcome.
Here is a comprehensive list of steps to follow to add a new function:
-
Create a new file in the
functions/
directory. The file should be named the same as the function. You must include documentation for your function. -
Update
functions/main.go
to register the new function by adding an entry toFunctions
. Make sure you choose the correctFor
value that is appropriate for your function. -
Run
go generate ./... && go install && go generate ./...
. The firstgenerate
is to create the pie templates,install
will update your binary for the annotations and the secondgenerate
will use the newly created templates to update the generated code for the internal types. If you encounter errors with your code you can safely rerun the command above. -
If you chose
ForAll
orForStructs
, then you must add unit tests topie/carpointers_test.go
andpie/cars_test.go
. -
If you chose
ForAll
,ForNumbersAndStrings
orForNumbers
, then you must add unit tests topie/float64s_test.go
andpie/ints_test.go
. -
If you chose
ForAll
orForStrings
, then you must add unit tests topie/strings_test.go
. -
If you chose
ForMaps
, then you must add unit tests topie/currencies.go
.
I wanted to pick a name for the project that was short and had an associated emoji. I liked pie, but then I found out that the pie emoji is not fully supported everywhere. I didn't want to change the name of the project to cake, but pizza pie still made sense. I'm not sure if I will change it back to a pie later.
Go does not provide a way to exclude code coverage from specific files or lines
with comments. However, you can remove the _pie.go
files from the code
coverage report before it is published:
go test -race -coverprofile=coverage.txt -covermode=atomic
sed -i '/_pie\.go/d' ./coverage.txt
If you are running on macOS, you will need a slightly different syntax for
sed
:
sed -i '' /_pie\.go/d' ./coverage.txt