├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── functions ├── abs.go ├── all.go ├── any.go ├── append.go ├── are_sorted.go ├── are_unique.go ├── average.go ├── bottom.go ├── contains.go ├── diff.go ├── drop_top.go ├── drop_while.go ├── each.go ├── equals.go ├── extend.go ├── filter.go ├── filter_not.go ├── find_first_using.go ├── first.go ├── first_or.go ├── float64s.go ├── group.go ├── insert.go ├── intersect.go ├── ints.go ├── join.go ├── json_bytes.go ├── json_bytes_indent.go ├── json_string.go ├── json_string_indent.go ├── keys.go ├── last.go ├── last_or.go ├── len.go ├── main.go ├── map.go ├── max.go ├── median.go ├── min.go ├── mode.go ├── pop.go ├── product.go ├── random.go ├── reduce.go ├── reverse.go ├── send.go ├── sequence.go ├── sequence_using.go ├── shift.go ├── shuffle.go ├── sort.go ├── sort_stable_using.go ├── sort_using.go ├── stddev.go ├── strings.go ├── strings_using.go ├── sub_slice.go ├── sum.go ├── top.go ├── unique.go ├── unshift.go └── values.go ├── generate.go ├── go.mod ├── go.sum ├── main.go ├── pie ├── car.go ├── carpointers_pie.go ├── carpointers_test.go ├── cars_pie.go ├── cars_test.go ├── currencies.go ├── currencies_pie.go ├── currencies_test.go ├── float64s.go ├── float64s_bench_test.go ├── float64s_pie.go ├── float64s_test.go ├── ints.go ├── ints_bench_test.go ├── ints_pie.go ├── ints_test.go ├── main_test.go ├── myints_pie.go ├── myints_test.go ├── strings.go ├── strings_bench_test.go ├── strings_pie.go ├── strings_test.go └── util │ ├── floor.go │ └── rand.go ├── template.go ├── type_explorer.go ├── v1 └── README.md └── v2 ├── abs.go ├── abs_test.go ├── all.go ├── all_test.go ├── any.go ├── any_test.go ├── are_sorted.go ├── are_sorted_test.go ├── are_unique.go ├── are_unique_test.go ├── average.go ├── average_test.go ├── bottom.go ├── bottom_test.go ├── chunk.go ├── chunk_test.go ├── contains.go ├── contains_test.go ├── delete.go ├── delete_test.go ├── diff.go ├── diff_test.go ├── drop_top.go ├── drop_top_test.go ├── drop_while.go ├── drop_while_test.go ├── each.go ├── each_test.go ├── equals.go ├── equals_test.go ├── filter.go ├── filter_not.go ├── filter_not_test.go ├── filter_test.go ├── find_first_using.go ├── find_first_using_test.go ├── first.go ├── first_or.go ├── first_or_test.go ├── first_test.go ├── flat.go ├── flat_test.go ├── float64.go ├── float64_test.go ├── float64s.go ├── float64s_test.go ├── go.mod ├── go.sum ├── group.go ├── group_test.go ├── groupby.go ├── groupby_test.go ├── insert.go ├── insert_test.go ├── int.go ├── int_test.go ├── intersect.go ├── intersect_test.go ├── ints.go ├── ints_test.go ├── join.go ├── join_test.go ├── json_bytes.go ├── json_bytes_indent.go ├── json_bytes_indent_test.go ├── json_bytes_test.go ├── json_string.go ├── json_string_indent.go ├── json_string_indent_test.go ├── json_string_test.go ├── keys.go ├── keys_test.go ├── last.go ├── last_or.go ├── last_or_test.go ├── last_test.go ├── map.go ├── map_test.go ├── max.go ├── max_test.go ├── median.go ├── median_test.go ├── min.go ├── min_test.go ├── mode.go ├── mode_test.go ├── of.go ├── of_numeric.go ├── of_numeric_test.go ├── of_ordered.go ├── of_ordered_test.go ├── of_test.go ├── pop.go ├── pop_test.go ├── product.go ├── product_test.go ├── random.go ├── random_test.go ├── reduce.go ├── reduce_test.go ├── reverse.go ├── reverse_test.go ├── rotate.go ├── rotate_test.go ├── send.go ├── send_test.go ├── sequence.go ├── sequence_test.go ├── sequence_using.go ├── sequence_using_test.go ├── shift.go ├── shift_test.go ├── shuffle.go ├── shuffle_test.go ├── sort.go ├── sort_stable_using.go ├── sort_stable_using_test.go ├── sort_test.go ├── sort_using.go ├── sort_using_test.go ├── stddev.go ├── stddev_test.go ├── string.go ├── string_test.go ├── strings.go ├── strings_test.go ├── strings_using.go ├── strings_using_test.go ├── sub_slice.go ├── sub_slice_test.go ├── sum.go ├── sum_test.go ├── top.go ├── top_test.go ├── unique.go ├── unique_stable.go ├── unique_stable_test.go ├── unique_test.go ├── unshift.go ├── unshift_test.go ├── values.go ├── values_test.go ├── zip.go ├── zip_longest.go ├── zip_longest_test.go └── zip_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | # Binaries for programs and plugins 4 | *.exe 5 | *.exe~ 6 | *.dll 7 | *.so 8 | *.dylib 9 | 10 | # Test binary, build with `go test -c` 11 | *.test 12 | 13 | # Output of the go coverage tool, specifically when used with LiteIDE 14 | *.out 15 | /.idea 16 | 17 | 18 | # Vim specific 19 | 20 | # Swap 21 | [._]*.s[a-v][a-z] 22 | [._]*.sw[a-p] 23 | [._]s[a-rt-v][a-z] 24 | [._]ss[a-gi-z] 25 | [._]sw[a-p] 26 | 27 | # # Session 28 | Session.vim 29 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - "1.13" 5 | - "1.14" 6 | - "1.15" 7 | 8 | script: 9 | # Make sure everything is formatted correctly. 10 | - $(exit $(go fmt ./... | wc -l)) 11 | 12 | # Record all assertions for stats. 13 | - $(exit $(grep -r '"github.com/stretchr/testify"' . --include \*.go | wc -l)) 14 | 15 | # Make sure go generate is in sync. 16 | - go generate ./... && go install && go generate ./... 17 | - $(exit $(git status --porcelain | wc -l)) 18 | 19 | # Run tests. 20 | - go test -race ./pie -coverprofile=coverage.txt -covermode=atomic 21 | 22 | after_success: 23 | - bash <(curl -s https://codecov.io/bash) 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Elliot Chance 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🍕 `github.com/elliotchance/pie` 2 | [![GoDoc](https://godoc.org/github.com/elliotchance/pie?status.svg)](https://godoc.org/github.com/elliotchance/pie) 3 | [![Build Status](https://travis-ci.org/elliotchance/pie.svg?branch=master)](https://travis-ci.org/elliotchance/pie) 4 | [![codecov](https://codecov.io/gh/elliotchance/pie/branch/master/graph/badge.svg)](https://codecov.io/gh/elliotchance/pie) 5 | 6 | **Enjoy a slice!** `pie` is a library of utility functions for common operations 7 | on slices and maps. 8 | 9 | - [Quick Start](#quick-start) 10 | - [FAQ](#faq) 11 | * [What are the requirements?](#what-are-the-requirements) 12 | * [What are the goals of `pie`?](#what-are-the-goals-of-pie) 13 | * [How do I contribute a function?](#how-do-i-contribute-a-function) 14 | * [Why is the emoji a slice of pizza instead of a pie?](#why-is-the-emoji-a-slice-of-pizza-instead-of-a-pie) 15 | 16 | # Quick Start 17 | 18 | If you are using (or require) Go 1.17 or below, you will have to 19 | [use v1](https://github.com/elliotchance/pie/v1). 20 | 21 | `pie` can be used in two ways, the first is to use the regular 22 | [parameterized functions](https://go.googlesource.com/proposal/+/master/design/15292/2013-12-type-params.md): 23 | 24 | [Run this program](https://go.dev/play/p/qYaBXPRs3Nk) 25 | 26 | ```go 27 | package main 28 | 29 | import ( 30 | "fmt" 31 | "strings" 32 | 33 | "github.com/elliotchance/pie/v2" 34 | ) 35 | 36 | func main() { 37 | names := pie.FilterNot([]string{"Bob", "Sally", "John", "Jane"}, 38 | func(name string) bool { 39 | return strings.HasPrefix(name, "J") 40 | }) 41 | 42 | fmt.Println(names) // "[Bob Sally]" 43 | } 44 | ``` 45 | 46 | Or, if you need to chain multiple operations you can use one of: 47 | 48 | - [`pie.Of`](https://pkg.go.dev/github.com/elliotchance/pie/v2#Of) - works with any element type, but functions are limited. 49 | - [`pie.OfOrdered`](https://pkg.go.dev/github.com/elliotchance/pie/v2#OfOrdered) - only works with numbers and strings, but has more functions. 50 | - [`pie.OfNumeric`](https://pkg.go.dev/github.com/elliotchance/pie/v2#OfNumeric) - only works with numbers, but has all functions. 51 | 52 | [Run this program](https://go.dev/play/p/4IhVbw0koxg) 53 | 54 | ```go 55 | package main 56 | 57 | import ( 58 | "fmt" 59 | "strings" 60 | 61 | "github.com/elliotchance/pie/v2" 62 | ) 63 | 64 | func main() { 65 | name := pie.Of([]string{"Bob", "Sally", "John", "Jane"}). 66 | FilterNot(func(name string) bool { 67 | return strings.HasPrefix(name, "J") 68 | }). 69 | Map(strings.ToUpper). 70 | Last() 71 | 72 | fmt.Println(name) // "SALLY" 73 | } 74 | ``` 75 | 76 | You can find the 77 | [full documentation here](https://pkg.go.dev/github.com/elliotchance/pie/v2). 78 | 79 | # FAQ 80 | 81 | ## What are the requirements? 82 | 83 | `pie` v2 only supports Go 1.18+. If you have an older version you can 84 | [use v1](https://github.com/elliotchance/pie/v1). 85 | 86 | ## What are the goals of `pie`? 87 | 88 | 1. **Type safety.** I never want to hit runtime bugs because I could pass in the 89 | wrong type, or perform an invalid type case out the other end. 90 | 91 | 2. **Performance.** The functions need to be as fast as native Go 92 | implementations otherwise there's no point in this library existing. 93 | 94 | 3. **Nil-safe.** All of the functions will happily accept nil and treat them as 95 | empty slices. Apart from less possible panics, it makes it easier to chain. 96 | 97 | 4. **Immutable.** Functions never modify inputs (except in cases where it would 98 | be illogical), unlike some built-ins such as `sort.Strings`. 99 | 100 | ## How do I contribute a function? 101 | 102 | Pull requests are always welcome. 103 | 104 | Here is a comprehensive list of steps to follow to add a new function: 105 | 106 | 1. Create a new file for your function (tip: copy an existing file can be 107 | quicker). Add your implmentation and comment. 108 | 109 | 2. Create appropriate tests. 110 | 111 | 3. If your function accepts a slice, it should also be added to the `OfSlice` 112 | API (see `of.go`). 113 | 114 | ## Why is the emoji a slice of pizza instead of a pie? 115 | 116 | I wanted to pick a name for the project that was short and had an associated 117 | emoji. I liked pie, but then I found out that the pie emoji is not fully 118 | supported everywhere. I didn't want to change the name of the project to cake, 119 | but pizza pie still made sense. I'm not sure if I will change it back to a pie 120 | later. 121 | -------------------------------------------------------------------------------- /functions/abs.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | // Abs is a function which returns the absolute value of all the 4 | // elements in the slice. 5 | func (ss SliceType) Abs() SliceType { 6 | result := make(SliceType, len(ss)) 7 | for i, val := range ss { 8 | if val < 0 { 9 | result[i] = -val 10 | } else { 11 | result[i] = val 12 | } 13 | } 14 | return result 15 | } 16 | -------------------------------------------------------------------------------- /functions/all.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | // All will return true if all callbacks return true. It follows the same logic 4 | // as the all() function in Python. 5 | // 6 | // If the list is empty then true is always returned. 7 | func (ss SliceType) All(fn func(value ElementType) bool) bool { 8 | for _, value := range ss { 9 | if !fn(value) { 10 | return false 11 | } 12 | } 13 | 14 | return true 15 | } 16 | -------------------------------------------------------------------------------- /functions/any.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | // Any will return true if any callbacks return true. It follows the same logic 4 | // as the any() function in Python. 5 | // 6 | // If the list is empty then false is always returned. 7 | func (ss SliceType) Any(fn func(value ElementType) bool) bool { 8 | for _, value := range ss { 9 | if fn(value) { 10 | return true 11 | } 12 | } 13 | 14 | return false 15 | } 16 | -------------------------------------------------------------------------------- /functions/append.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | // Append will return a new slice with the elements appended to the end. 4 | // 5 | // It is acceptable to provide zero arguments. 6 | func (ss SliceType) Append(elements ...ElementType) SliceType { 7 | // Copy ss, to make sure no memory is overlapping between input and 8 | // output. See issue #97. 9 | result := append(SliceType{}, ss...) 10 | 11 | result = append(result, elements...) 12 | return result 13 | } 14 | -------------------------------------------------------------------------------- /functions/are_sorted.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | import ( 4 | "sort" 5 | ) 6 | 7 | // AreSorted will return true if the slice is already sorted. It is a wrapper 8 | // for sort.SliceTypeAreSorted. 9 | func (ss SliceType) AreSorted() bool { 10 | return sort.SliceIsSorted(ss, func(i, j int) bool { 11 | return ss[i] < ss[j] 12 | }) 13 | } 14 | -------------------------------------------------------------------------------- /functions/are_unique.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | // AreUnique will return true if the slice contains elements that are all 4 | // different (unique) from each other. 5 | func (ss SliceType) AreUnique() bool { 6 | return ss.Unique().Len() == ss.Len() 7 | } 8 | -------------------------------------------------------------------------------- /functions/average.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | // Average is the average of all of the elements, or zero if there are no 4 | // elements. 5 | func (ss SliceType) Average() float64 { 6 | if l := ElementType(len(ss)); l > 0 { 7 | return float64(ss.Sum()) / float64(l) 8 | } 9 | 10 | return 0 11 | } 12 | -------------------------------------------------------------------------------- /functions/bottom.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | // Bottom will return n elements from bottom 4 | // 5 | // that means that elements is taken from the end of the slice 6 | // for this [1,2,3] slice with n == 2 will be returned [3,2] 7 | // if the slice has less elements then n that'll return all elements 8 | // if n < 0 it'll return empty slice. 9 | func (ss SliceType) Bottom(n int) (top SliceType) { 10 | var lastIndex = len(ss) - 1 11 | for i := lastIndex; i > -1 && n > 0; i-- { 12 | top = append(top, ss[i]) 13 | n-- 14 | } 15 | 16 | return 17 | } 18 | -------------------------------------------------------------------------------- /functions/contains.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | // Contains returns true if the element exists in the slice. 4 | // 5 | // When using slices of pointers it will only compare by address, not value. 6 | func (ss SliceType) Contains(lookingFor ElementType) bool { 7 | for _, s := range ss { 8 | if lookingFor.Equals(s) { 9 | return true 10 | } 11 | } 12 | 13 | return false 14 | } 15 | -------------------------------------------------------------------------------- /functions/diff.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | // Diff returns the elements that needs to be added or removed from the first 4 | // slice to have the same elements in the second slice. 5 | // 6 | // The order of elements is not taken into consideration, so the slices are 7 | // treated sets that allow duplicate items. 8 | // 9 | // The added and removed returned may be blank respectively, or contain upto as 10 | // many elements that exists in the largest slice. 11 | func (ss SliceType) Diff(against SliceType) (added, removed SliceType) { 12 | // This is probably not the best way to do it. We do an O(n^2) between the 13 | // slices to see which items are missing in each direction. 14 | 15 | diffOneWay := func(ss1, ss2raw SliceType) (result SliceType) { 16 | ss2 := make(SliceType, len(ss2raw)) 17 | copy(ss2, ss2raw) 18 | 19 | for _, s := range ss1 { 20 | found := false 21 | 22 | for i, element := range ss2 { 23 | if s.Equals(element) { 24 | ss2 = append(ss2[:i], ss2[i+1:]...) 25 | found = true 26 | break 27 | } 28 | } 29 | 30 | if !found { 31 | result = append(result, s) 32 | } 33 | } 34 | 35 | return 36 | } 37 | 38 | removed = diffOneWay(ss, against) 39 | added = diffOneWay(against, ss) 40 | 41 | return 42 | } 43 | -------------------------------------------------------------------------------- /functions/drop_top.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | // DropTop will return the rest slice after dropping the top n elements 4 | // if the slice has less elements then n that'll return empty slice 5 | // if n < 0 it'll return empty slice. 6 | func (ss SliceType) DropTop(n int) (drop SliceType) { 7 | if n < 0 || n >= len(ss) { 8 | return 9 | } 10 | 11 | // Copy ss, to make sure no memory is overlapping between input and 12 | // output. See issue #145. 13 | drop = make([]ElementType, len(ss)-n) 14 | copy(drop, ss[n:]) 15 | 16 | return 17 | } 18 | -------------------------------------------------------------------------------- /functions/drop_while.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | // Drop items from the slice while f(item) is true. 4 | // Afterwards, return every element until the slice is empty. It follows the same logic as the dropwhile() function from itertools in Python. 5 | func (ss SliceType) DropWhile(f func(s ElementType) bool) (ss2 SliceType) { 6 | ss2 = make([]ElementType, len(ss)) 7 | copy(ss2, ss) 8 | for i, value := range ss2 { 9 | if !f(value) { 10 | return ss2[i:] 11 | } 12 | } 13 | return SliceType{} 14 | } 15 | -------------------------------------------------------------------------------- /functions/each.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | // Each is more condensed version of Transform that allows an action to happen 4 | // on each elements and pass the original slice on. 5 | // 6 | // cars.Each(func (car *Car) { 7 | // fmt.Printf("Car color is: %s\n", car.Color) 8 | // }) 9 | // 10 | // Pie will not ensure immutability on items passed in so they can be 11 | // manipulated, if you choose to do it this way, for example: 12 | // 13 | // // Set all car colors to Red. 14 | // cars.Each(func (car *Car) { 15 | // car.Color = "Red" 16 | // }) 17 | // 18 | func (ss SliceType) Each(fn func(ElementType)) SliceType { 19 | for _, s := range ss { 20 | fn(s) 21 | } 22 | 23 | return ss 24 | } 25 | -------------------------------------------------------------------------------- /functions/equals.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | // Equals compare elements from the start to the end, 4 | // 5 | // if they are the same is considered the slices are equal if all elements are the same is considered the slices are equal 6 | // if each slice == nil is considered that they're equal 7 | // 8 | // if element realizes Equals interface it uses that method, in other way uses default compare 9 | func (ss SliceType) Equals(rhs SliceType) bool { 10 | if len(ss) != len(rhs) { 11 | return false 12 | } 13 | 14 | for i := range ss { 15 | if !ss[i].Equals(rhs[i]) { 16 | return false 17 | } 18 | } 19 | 20 | return true 21 | } 22 | -------------------------------------------------------------------------------- /functions/extend.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | // Extend will return a new slice with the slices of elements appended to the 4 | // end. 5 | // 6 | // It is acceptable to provide zero arguments. 7 | func (ss SliceType) Extend(slices ...SliceType) (ss2 SliceType) { 8 | ss2 = ss 9 | 10 | for _, slice := range slices { 11 | ss2 = ss2.Append(slice...) 12 | } 13 | 14 | return ss2 15 | } 16 | -------------------------------------------------------------------------------- /functions/filter.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | // Filter will return a new slice containing only the elements that return 4 | // true from the condition. The returned slice may contain zero elements (nil). 5 | // 6 | // FilterNot works in the opposite way of Filter. 7 | func (ss SliceType) Filter(condition func(ElementType) bool) (ss2 SliceType) { 8 | for _, s := range ss { 9 | if condition(s) { 10 | ss2 = append(ss2, s) 11 | } 12 | } 13 | return 14 | } 15 | -------------------------------------------------------------------------------- /functions/filter_not.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | // FilterNot works the same as Filter, with a negated condition. That is, it will 4 | // return a new slice only containing the elements that returned false from the 5 | // condition. The returned slice may contain zero elements (nil). 6 | func (ss SliceType) FilterNot(condition func(ElementType) bool) (ss2 SliceType) { 7 | for _, s := range ss { 8 | if !condition(s) { 9 | ss2 = append(ss2, s) 10 | } 11 | } 12 | 13 | return 14 | } 15 | -------------------------------------------------------------------------------- /functions/find_first_using.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | // FindFirstUsing will return the index of the first element when the callback returns true or -1 if no element is found. 4 | // It follows the same logic as the findIndex() function in Javascript. 5 | // 6 | // If the list is empty then -1 is always returned. 7 | func (ss SliceType) FindFirstUsing(fn func(value ElementType) bool) int { 8 | for idx, value := range ss { 9 | if fn(value) { 10 | return idx 11 | } 12 | } 13 | 14 | return -1 15 | } 16 | -------------------------------------------------------------------------------- /functions/first.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | // First returns the first element, or zero. Also see FirstOr(). 4 | func (ss SliceType) First() ElementType { 5 | return ss.FirstOr(ElementZeroValue) 6 | } 7 | -------------------------------------------------------------------------------- /functions/first_or.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | // FirstOr returns the first element or a default value if there are no 4 | // elements. 5 | func (ss SliceType) FirstOr(defaultValue ElementType) ElementType { 6 | if len(ss) == 0 { 7 | return defaultValue 8 | } 9 | 10 | return ss[0] 11 | } 12 | -------------------------------------------------------------------------------- /functions/float64s.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | import ( 4 | "github.com/elliotchance/pie/pie" 5 | "strconv" 6 | ) 7 | 8 | // Float64s transforms each element to a float64. 9 | func (ss SliceType) Float64s() pie.Float64s { 10 | l := len(ss) 11 | 12 | // Avoid the allocation. 13 | if l == 0 { 14 | return nil 15 | } 16 | 17 | result := make(pie.Float64s, l) 18 | for i := 0; i < l; i++ { 19 | mightBeString := ss[i] 20 | result[i], _ = strconv.ParseFloat(mightBeString.String(), 64) 21 | } 22 | 23 | return result 24 | } 25 | -------------------------------------------------------------------------------- /functions/group.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | // Group returns a map of the value with an individual count. 4 | // 5 | func (ss SliceType) Group() map[ElementType]int { 6 | group := map[ElementType]int{} 7 | for _, n := range ss { 8 | group[n]++ 9 | } 10 | return group 11 | } 12 | -------------------------------------------------------------------------------- /functions/insert.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | // Insert a value at an index 4 | func (ss SliceType) Insert(index int, values ...ElementType) SliceType { 5 | if index >= ss.Len() { 6 | return SliceType.Extend(ss, SliceType(values)) 7 | } 8 | 9 | return SliceType.Extend(ss[:index], SliceType(values), ss[index:]) 10 | } 11 | -------------------------------------------------------------------------------- /functions/intersect.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | // Intersect returns items that exist in all lists. 4 | // 5 | // It returns slice without any duplicates. 6 | // If zero slice arguments are provided, then nil is returned. 7 | func (ss SliceType) Intersect(slices ...SliceType) (ss2 SliceType) { 8 | if slices == nil { 9 | return nil 10 | } 11 | 12 | var uniqs = make([]map[ElementType]struct{}, len(slices)) 13 | for i := 0; i < len(slices); i++ { 14 | m := make(map[ElementType]struct{}) 15 | for _, el := range slices[i] { 16 | m[el] = struct{}{} 17 | } 18 | uniqs[i] = m 19 | } 20 | 21 | var containsInAll = false 22 | for _, el := range ss.Unique() { 23 | for _, u := range uniqs { 24 | if _, exists := u[el]; !exists { 25 | containsInAll = false 26 | break 27 | } 28 | containsInAll = true 29 | } 30 | if containsInAll { 31 | ss2 = append(ss2, el) 32 | } 33 | } 34 | 35 | return 36 | } 37 | -------------------------------------------------------------------------------- /functions/ints.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | import ( 4 | "github.com/elliotchance/pie/pie" 5 | "strconv" 6 | ) 7 | 8 | // Ints transforms each element to an integer. 9 | func (ss SliceType) Ints() pie.Ints { 10 | l := len(ss) 11 | 12 | // Avoid the allocation. 13 | if l == 0 { 14 | return nil 15 | } 16 | 17 | result := make(pie.Ints, l) 18 | for i := 0; i < l; i++ { 19 | mightBeString := ss[i] 20 | f, _ := strconv.ParseFloat(mightBeString.String(), 64) 21 | result[i] = int(f) 22 | } 23 | 24 | return result 25 | } 26 | -------------------------------------------------------------------------------- /functions/join.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | import "strings" 4 | 5 | // Join returns a string from joining each of the elements. 6 | func (ss SliceType) Join(glue string) (s string) { 7 | var slice interface{} = []ElementType(ss) 8 | 9 | if y, ok := slice.([]string); ok { 10 | // The stdlib is efficient for type []string 11 | return strings.Join(y, glue) 12 | } else { 13 | // General case 14 | parts := make([]string, len(ss)) 15 | for i, element := range ss { 16 | mightBeString := element 17 | parts[i] = mightBeString.String() 18 | } 19 | return strings.Join(parts, glue) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /functions/json_bytes.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | // JSONBytes returns the JSON encoded array as bytes. 8 | // 9 | // One important thing to note is that it will treat a nil slice as an empty 10 | // slice to ensure that the JSON value return is always an array. 11 | func (ss SliceType) JSONBytes() []byte { 12 | if ss == nil { 13 | return []byte("[]") 14 | } 15 | 16 | // An error should not be possible. 17 | data, _ := json.Marshal(ss) 18 | 19 | return data 20 | } 21 | -------------------------------------------------------------------------------- /functions/json_bytes_indent.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | // JSONBytesIndent returns the JSON encoded array as bytes with indent applied. 8 | // 9 | // One important thing to note is that it will treat a nil slice as an empty 10 | // slice to ensure that the JSON value return is always an array. See 11 | // json.MarshalIndent for details. 12 | func (ss SliceType) JSONBytesIndent(prefix, indent string) []byte { 13 | if ss == nil { 14 | return []byte("[]") 15 | } 16 | 17 | // An error should not be possible. 18 | data, _ := json.MarshalIndent(ss, prefix, indent) 19 | 20 | return data 21 | } 22 | -------------------------------------------------------------------------------- /functions/json_string.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | // JSONString returns the JSON encoded array as a string. 8 | // 9 | // One important thing to note is that it will treat a nil slice as an empty 10 | // slice to ensure that the JSON value return is always an array. 11 | func (ss SliceType) JSONString() string { 12 | if ss == nil { 13 | return "[]" 14 | } 15 | 16 | // An error should not be possible. 17 | data, _ := json.Marshal(ss) 18 | 19 | return string(data) 20 | } 21 | -------------------------------------------------------------------------------- /functions/json_string_indent.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | // JSONStringIndent returns the JSON encoded array as a string with indent applied. 8 | // 9 | // One important thing to note is that it will treat a nil slice as an empty 10 | // slice to ensure that the JSON value return is always an array. See 11 | // json.MarshalIndent for details. 12 | func (ss SliceType) JSONStringIndent(prefix, indent string) string { 13 | if ss == nil { 14 | return "[]" 15 | } 16 | 17 | // An error should not be possible. 18 | data, _ := json.MarshalIndent(ss, prefix, indent) 19 | 20 | return string(data) 21 | } 22 | -------------------------------------------------------------------------------- /functions/keys.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | // Keys returns the keys in the map. All of the items will be unique. 4 | // 5 | // Due to Go's randomization of iterating maps the order is not deterministic. 6 | func (m MapType) Keys() KeySliceType { 7 | // Avoid allocation 8 | l := len(m) 9 | if l == 0 { 10 | return nil 11 | } 12 | 13 | i := 0 14 | keys := make(KeySliceType, len(m)) 15 | for key := range m { 16 | keys[i] = key 17 | i++ 18 | } 19 | 20 | return keys 21 | } 22 | -------------------------------------------------------------------------------- /functions/last.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | // Last returns the last element, or zero. Also see LastOr(). 4 | func (ss SliceType) Last() ElementType { 5 | return ss.LastOr(ElementZeroValue) 6 | } 7 | -------------------------------------------------------------------------------- /functions/last_or.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | // LastOr returns the last element or a default value if there are no elements. 4 | func (ss SliceType) LastOr(defaultValue ElementType) ElementType { 5 | if len(ss) == 0 { 6 | return defaultValue 7 | } 8 | 9 | return ss[len(ss)-1] 10 | } 11 | -------------------------------------------------------------------------------- /functions/len.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | // Len returns the number of elements. 4 | func (ss SliceType) Len() int { 5 | return len(ss) 6 | } 7 | -------------------------------------------------------------------------------- /functions/main.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | import "fmt" 4 | 5 | const ( 6 | ForNumbers = 1 << iota 7 | ForStrings 8 | ForStructs 9 | ForMaps 10 | 11 | ForAll = ForNumbers | ForStrings | ForStructs 12 | ForNumbersAndStrings = ForNumbers | ForStrings 13 | ) 14 | 15 | // Function is a list of functions and which types they are available to. It is 16 | // a slice instead of a may to make sure we iterate in a predicable order so 17 | // regenerating files is deterministic. 18 | var Functions = []struct { 19 | Name string 20 | File string 21 | For int 22 | BigO string 23 | }{ 24 | {"Abs", "abs.go", ForNumbers, "n"}, 25 | {"All", "all.go", ForAll, "n"}, 26 | {"Any", "any.go", ForAll, "n"}, 27 | {"Append", "append.go", ForAll, "n"}, 28 | {"AreSorted", "are_sorted.go", ForNumbersAndStrings, "n"}, 29 | {"AreUnique", "are_unique.go", ForNumbersAndStrings, "n"}, 30 | {"Average", "average.go", ForNumbers, "n"}, 31 | {"Bottom", "bottom.go", ForAll, "n"}, 32 | {"Contains", "contains.go", ForAll, "n"}, 33 | {"Diff", "diff.go", ForAll, "n²"}, 34 | {"DropTop", "drop_top.go", ForAll, "n"}, 35 | {"DropWhile", "drop_while.go", ForAll, "n"}, 36 | {"Each", "each.go", ForAll, "n"}, 37 | {"Equals", "equals.go", ForAll, "n"}, 38 | {"Extend", "extend.go", ForAll, "n"}, 39 | {"Filter", "filter.go", ForAll, "n"}, 40 | {"FilterNot", "filter_not.go", ForAll, "n"}, 41 | {"FindFirstUsing", "find_first_using.go", ForAll, "n"}, 42 | {"First", "first.go", ForAll, "1"}, 43 | {"FirstOr", "first_or.go", ForAll, "1"}, 44 | {"Float64s", "float64s.go", ForAll, "n"}, 45 | {"Group", "group.go", ForNumbersAndStrings, "n"}, 46 | {"Intersect", "intersect.go", ForNumbersAndStrings, "n"}, 47 | {"Insert", "insert.go", ForAll, "n"}, 48 | {"Ints", "ints.go", ForAll, "n"}, 49 | {"Join", "join.go", ForAll, "n"}, 50 | {"JSONBytes", "json_bytes.go", ForAll, "n"}, 51 | {"JSONBytesIndent", "json_bytes_indent.go", ForAll, "n"}, 52 | {"JSONString", "json_string.go", ForAll, "n"}, 53 | {"JSONStringIndent", "json_string_indent.go", ForAll, "n"}, 54 | {"Keys", "keys.go", ForMaps, "n"}, 55 | {"Last", "last.go", ForAll, "1"}, 56 | {"LastOr", "last_or.go", ForAll, "1"}, 57 | {"Len", "len.go", ForAll, "1"}, 58 | {"Map", "map.go", ForAll, "n"}, 59 | {"Max", "max.go", ForNumbersAndStrings, "n"}, 60 | {"Median", "median.go", ForNumbers, "n"}, 61 | {"Min", "min.go", ForNumbersAndStrings, "n"}, 62 | {"Mode", "mode.go", ForAll, "n"}, 63 | {"Pop", "pop.go", ForAll, "n"}, 64 | {"Product", "product.go", ForNumbers, "n"}, 65 | {"Random", "random.go", ForAll, "1"}, 66 | {"Reduce", "reduce.go", ForNumbersAndStrings, "n"}, 67 | {"Reverse", "reverse.go", ForAll, "n"}, 68 | {"Send", "send.go", ForAll, "n"}, 69 | {"Sequence", "sequence.go", ForNumbers, "n"}, 70 | {"SequenceUsing", "sequence_using.go", ForAll, "n"}, 71 | {"Shift", "shift.go", ForAll, "n"}, 72 | {"Shuffle", "shuffle.go", ForAll, "n"}, 73 | {"Sort", "sort.go", ForNumbersAndStrings, "n⋅log(n)"}, 74 | {"SortStableUsing", "sort_stable_using.go", ForStrings | ForStructs, "n⋅log(n)"}, 75 | {"SortUsing", "sort_using.go", ForStrings | ForStructs, "n⋅log(n)"}, 76 | {"Stddev", "stddev.go", ForNumbers, "n"}, 77 | {"Strings", "strings.go", ForAll, "n"}, 78 | {"SubSlice", "sub_slice.go", ForAll, "n"}, 79 | {"Sum", "sum.go", ForNumbers, "n"}, 80 | {"Top", "top.go", ForAll, "n"}, 81 | {"StringsUsing", "strings_using.go", ForAll, "n"}, 82 | {"Unique", "unique.go", ForNumbersAndStrings, "n"}, 83 | {"Unshift", "unshift.go", ForAll, "n"}, 84 | {"Values", "values.go", ForMaps, "n"}, 85 | } 86 | 87 | type ElementType float64 88 | type SliceType []ElementType 89 | type StringElementType string 90 | type StringSliceType []StringElementType 91 | type KeyType string 92 | type KeySliceType []KeyType 93 | type MapType map[KeyType]ElementType 94 | 95 | var ElementZeroValue ElementType 96 | 97 | func (a ElementType) Equals(b ElementType) bool { 98 | return a == b 99 | } 100 | 101 | func (a ElementType) String() string { 102 | return fmt.Sprintf("%f", a) 103 | } 104 | -------------------------------------------------------------------------------- /functions/map.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | // Map will return a new slice where each element has been mapped (transformed). 4 | // The number of elements returned will always be the same as the input. 5 | // 6 | // Be careful when using this with slices of pointers. If you modify the input 7 | // value it will affect the original slice. Be sure to return a new allocated 8 | // object or deep copy the existing one. 9 | func (ss SliceType) Map(fn func(ElementType) ElementType) (ss2 SliceType) { 10 | if ss == nil { 11 | return nil 12 | } 13 | 14 | ss2 = make([]ElementType, len(ss)) 15 | for i, s := range ss { 16 | ss2[i] = fn(s) 17 | } 18 | 19 | return 20 | } 21 | -------------------------------------------------------------------------------- /functions/max.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | // Max is the maximum value, or zero. 4 | func (ss SliceType) Max() (max ElementType) { 5 | if len(ss) == 0 { 6 | return 7 | } 8 | 9 | max = ss[0] 10 | for _, s := range ss { 11 | if s > max { 12 | max = s 13 | } 14 | } 15 | 16 | return 17 | } 18 | -------------------------------------------------------------------------------- /functions/median.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | // Median returns the value separating the higher half from the lower half of a 4 | // data sample. 5 | // 6 | // Zero is returned if there are no elements in the slice. 7 | // 8 | // If the number of elements is even, then the ElementType mean of the two "median values" 9 | // is returned. 10 | func (ss SliceType) Median() ElementType { 11 | n := len(ss) 12 | if n == 0 { 13 | return ElementZeroValue 14 | } 15 | if n == 1 { 16 | return ss[0] 17 | } 18 | 19 | // This implementation aims at linear time O(n) on average. 20 | // It uses the same idea as QuickSort, but makes only 1 recursive 21 | // call instead of 2. See also Quickselect. 22 | 23 | work := make(SliceType, len(ss)) 24 | copy(work, ss) 25 | 26 | limit1, limit2 := n/2, n/2+1 27 | if n%2 == 0 { 28 | limit1, limit2 = n/2-1, n/2+1 29 | } 30 | 31 | var rec func(a, b int) 32 | rec = func(a, b int) { 33 | if b-a <= 1 { 34 | return 35 | } 36 | ipivot := (a + b) / 2 37 | pivot := work[ipivot] 38 | work[a], work[ipivot] = work[ipivot], work[a] 39 | j := a 40 | k := b 41 | for j+1 < k { 42 | if work[j+1] < pivot { 43 | work[j+1], work[j] = work[j], work[j+1] 44 | j++ 45 | } else { 46 | work[j+1], work[k-1] = work[k-1], work[j+1] 47 | k-- 48 | } 49 | } 50 | // 1 or 0 recursive calls 51 | if j > limit1 { 52 | rec(a, j) 53 | } 54 | if j+1 < limit2 { 55 | rec(j+1, b) 56 | } 57 | } 58 | 59 | rec(0, len(work)) 60 | 61 | if n%2 == 1 { 62 | return work[n/2] 63 | } else { 64 | return (work[n/2-1] + work[n/2]) / 2 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /functions/min.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | // Min is the minimum value, or zero. 4 | func (ss SliceType) Min() (min ElementType) { 5 | if len(ss) == 0 { 6 | return 7 | } 8 | 9 | min = ss[0] 10 | for _, s := range ss { 11 | if s < min { 12 | min = s 13 | } 14 | } 15 | 16 | return 17 | } 18 | -------------------------------------------------------------------------------- /functions/mode.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | // Mode returns a new slice containing the most frequently occuring values. 4 | // 5 | // The number of items returned may be the same as the input or less. It will 6 | // never return zero items unless the input slice has zero items. 7 | func (ss SliceType) Mode() SliceType { 8 | if len(ss) == 0 { 9 | return nil 10 | } 11 | values := make(map[ElementType]int) 12 | for _, s := range ss { 13 | values[s]++ 14 | } 15 | 16 | var maxFrequency int 17 | for _, v := range values { 18 | if v > maxFrequency { 19 | maxFrequency = v 20 | } 21 | } 22 | 23 | var maxValues SliceType 24 | for k, v := range values { 25 | if v == maxFrequency { 26 | maxValues = append(maxValues, k) 27 | } 28 | } 29 | 30 | return maxValues 31 | } 32 | -------------------------------------------------------------------------------- /functions/pop.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | // Pop the first element of the slice 4 | // 5 | // Usage Example: 6 | // 7 | // type knownGreetings []string 8 | // greetings := knownGreetings{"ciao", "hello", "hola"} 9 | // for greeting := greetings.Pop(); greeting != nil; greeting = greetings.Pop() { 10 | // fmt.Println(*greeting) 11 | // } 12 | func (ss *SliceType) Pop() (popped *ElementType) { 13 | 14 | if len(*ss) == 0 { 15 | return 16 | } 17 | 18 | popped = &(*ss)[0] 19 | *ss = (*ss)[1:] 20 | return 21 | } 22 | -------------------------------------------------------------------------------- /functions/product.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | // Product is the product of all of the elements. 4 | func (ss SliceType) Product() (product ElementType) { 5 | if len(ss) == 0 { 6 | return 7 | } 8 | product = ss[0] 9 | for _, s := range ss[1:] { 10 | product *= s 11 | } 12 | 13 | return 14 | } 15 | -------------------------------------------------------------------------------- /functions/random.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | import ( 4 | "math/rand" 5 | ) 6 | 7 | // Random returns a random element by your rand.Source, or zero 8 | func (ss SliceType) Random(source rand.Source) ElementType { 9 | n := len(ss) 10 | 11 | // Avoid the extra allocation. 12 | if n < 1 { 13 | return ElementZeroValue 14 | } 15 | if n < 2 { 16 | return ss[0] 17 | } 18 | rnd := rand.New(source) 19 | i := rnd.Intn(n) 20 | return ss[i] 21 | } 22 | -------------------------------------------------------------------------------- /functions/reduce.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | // Reduce continually applies the provided function 4 | // over the slice. Reducing the elements to a single value. 5 | // 6 | // 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). 7 | // Otherwise returns result of applying reducer from left to right. 8 | func (ss SliceType) Reduce(reducer func(ElementType, ElementType) ElementType) (el ElementType) { 9 | if len(ss) == 0 { 10 | return 11 | } 12 | el = ss[0] 13 | for _, s := range ss[1:] { 14 | el = reducer(el, s) 15 | } 16 | return 17 | } 18 | -------------------------------------------------------------------------------- /functions/reverse.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | // Reverse returns a new copy of the slice with the elements ordered in reverse. 4 | // This is useful when combined with Sort to get a descending sort order: 5 | // 6 | // ss.Sort().Reverse() 7 | // 8 | func (ss SliceType) Reverse() SliceType { 9 | // Avoid the allocation. If there is one element or less it is already 10 | // reversed. 11 | if len(ss) < 2 { 12 | return ss 13 | } 14 | 15 | sorted := make([]ElementType, len(ss)) 16 | for i := 0; i < len(ss); i++ { 17 | sorted[i] = ss[len(ss)-i-1] 18 | } 19 | 20 | return sorted 21 | } 22 | -------------------------------------------------------------------------------- /functions/send.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | // Send sends elements to channel 8 | // in normal act it sends all elements but if func canceled it can be less 9 | // 10 | // it locks execution of gorutine 11 | // it doesn't close channel after work 12 | // returns sended elements if len(this) != len(old) considered func was canceled 13 | func (ss SliceType) Send(ctx context.Context, ch chan<- ElementType) SliceType { 14 | for i, s := range ss { 15 | select { 16 | case <-ctx.Done(): 17 | return ss[:i] 18 | default: 19 | ch <- s 20 | } 21 | } 22 | 23 | return ss 24 | } 25 | -------------------------------------------------------------------------------- /functions/sequence.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | // Sequence generates all numbers in range or returns nil if params invalid 4 | // 5 | // There are 3 variations to generate: 6 | // 1. [0, n). 7 | // 2. [min, max). 8 | // 3. [min, max) with step. 9 | // 10 | // if len(params) == 1 considered that will be returned slice between 0 and n, 11 | // where n is the first param, [0, n). 12 | // if len(params) == 2 considered that will be returned slice between min and max, 13 | // where min is the first param, max is the second, [min, max). 14 | // if len(params) > 2 considered that will be returned slice between min and max with step, 15 | // where min is the first param, max is the second, step is the third one, [min, max) with step, 16 | // others params will be ignored 17 | func (ss SliceType) Sequence(params ...int) SliceType { 18 | var creator = func(i int) ElementType { 19 | return ElementType(i) 20 | } 21 | 22 | return ss.SequenceUsing(creator, params...) 23 | } 24 | -------------------------------------------------------------------------------- /functions/sequence_using.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | import "github.com/elliotchance/pie/pie/util" 4 | 5 | // SequenceUsing generates slice in range using creator function 6 | // 7 | // There are 3 variations to generate: 8 | // 1. [0, n). 9 | // 2. [min, max). 10 | // 3. [min, max) with step. 11 | // 12 | // if len(params) == 1 considered that will be returned slice between 0 and n, 13 | // where n is the first param, [0, n). 14 | // if len(params) == 2 considered that will be returned slice between min and max, 15 | // where min is the first param, max is the second, [min, max). 16 | // if len(params) > 2 considered that will be returned slice between min and max with step, 17 | // where min is the first param, max is the second, step is the third one, [min, max) with step, 18 | // others params will be ignored 19 | func (ss SliceType) SequenceUsing(creator func(int) ElementType, params ...int) SliceType { 20 | var seq = func(min, max, step int) (seq SliceType) { 21 | length := int(util.Round(float64(max-min) / float64(step))) 22 | if length < 1 { 23 | return 24 | } 25 | 26 | seq = make(SliceType, length) 27 | for i := 0; i < length; min += step { 28 | seq[i] = creator(min) 29 | i++ 30 | } 31 | 32 | return seq 33 | } 34 | 35 | if len(params) > 2 { 36 | return seq(params[0], params[1], params[2]) 37 | } else if len(params) == 2 { 38 | return seq(params[0], params[1], 1) 39 | } else if len(params) == 1 { 40 | return seq(0, params[0], 1) 41 | } else { 42 | return nil 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /functions/shift.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | // Shift will return two values: the shifted value and the rest slice. 4 | func (ss SliceType) Shift() (ElementType, SliceType) { 5 | return ss.First(), ss.DropTop(1) 6 | } 7 | -------------------------------------------------------------------------------- /functions/shuffle.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | import ( 4 | "github.com/elliotchance/pie/pie/util" 5 | "math/rand" 6 | ) 7 | 8 | // Shuffle returns shuffled slice by your rand.Source 9 | func (ss SliceType) Shuffle(source rand.Source) SliceType { 10 | n := len(ss) 11 | 12 | // Avoid the extra allocation. 13 | if n < 2 { 14 | return ss 15 | } 16 | 17 | // go 1.10+ provides rnd.Shuffle. However, to support older versions we copy 18 | // the algorithm directly from the go source: src/math/rand/rand.go below, 19 | // with some adjustments: 20 | shuffled := make([]ElementType, n) 21 | copy(shuffled, ss) 22 | 23 | rnd := rand.New(source) 24 | 25 | util.Shuffle(rnd, n, func(i, j int) { 26 | shuffled[i], shuffled[j] = shuffled[j], shuffled[i] 27 | }) 28 | 29 | return shuffled 30 | } 31 | -------------------------------------------------------------------------------- /functions/sort.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | import ( 4 | "sort" 5 | ) 6 | 7 | // Sort works similar to sort.SliceType(). However, unlike sort.SliceType the 8 | // slice returned will be reallocated as to not modify the input slice. 9 | // 10 | // See Reverse() and AreSorted(). 11 | func (ss SliceType) Sort() SliceType { 12 | // Avoid the allocation. If there is one element or less it is already 13 | // sorted. 14 | if len(ss) < 2 { 15 | return ss 16 | } 17 | 18 | sorted := make(SliceType, len(ss)) 19 | copy(sorted, ss) 20 | sort.Slice(sorted, func(i, j int) bool { 21 | return sorted[i] < sorted[j] 22 | }) 23 | 24 | return sorted 25 | } 26 | -------------------------------------------------------------------------------- /functions/sort_stable_using.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | import ( 4 | "sort" 5 | ) 6 | 7 | // SortStableUsing works similar to sort.SliceStable. However, unlike sort.SliceStable the 8 | // slice returned will be reallocated as to not modify the input slice. 9 | func (ss SliceType) SortStableUsing(less func(a, b ElementType) bool) SliceType { 10 | // Avoid the allocation. If there is one element or less it is already 11 | // sorted. 12 | if len(ss) < 2 { 13 | return ss 14 | } 15 | 16 | sorted := make(SliceType, len(ss)) 17 | copy(sorted, ss) 18 | sort.SliceStable(sorted, func(i, j int) bool { 19 | return less(sorted[i], sorted[j]) 20 | }) 21 | 22 | return sorted 23 | } 24 | -------------------------------------------------------------------------------- /functions/sort_using.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | import ( 4 | "sort" 5 | ) 6 | 7 | // SortUsing works similar to sort.Slice. However, unlike sort.Slice the 8 | // slice returned will be reallocated as to not modify the input slice. 9 | func (ss SliceType) SortUsing(less func(a, b ElementType) bool) SliceType { 10 | // Avoid the allocation. If there is one element or less it is already 11 | // sorted. 12 | if len(ss) < 2 { 13 | return ss 14 | } 15 | 16 | sorted := make(SliceType, len(ss)) 17 | copy(sorted, ss) 18 | sort.Slice(sorted, func(i, j int) bool { 19 | return less(sorted[i], sorted[j]) 20 | }) 21 | 22 | return sorted 23 | } 24 | -------------------------------------------------------------------------------- /functions/stddev.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | import "math" 4 | 5 | // Stddev is the standard deviation 6 | func (ss SliceType) Stddev() float64 { 7 | if len(ss) == 0 { 8 | return 0.0 9 | } 10 | 11 | avg := ss.Average() 12 | 13 | var sd float64 14 | for i := range ss { 15 | sd += math.Pow(float64(ss[i])-avg, 2) 16 | } 17 | sd = math.Sqrt(sd / float64(len(ss))) 18 | 19 | return sd 20 | } 21 | -------------------------------------------------------------------------------- /functions/strings.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | import ( 4 | "github.com/elliotchance/pie/pie" 5 | ) 6 | 7 | // Strings transforms each element to a string. 8 | // 9 | // If the element type implements fmt.Stringer it will be used. Otherwise it 10 | // will fallback to the result of: 11 | // 12 | // fmt.Sprintf("%v") 13 | // 14 | func (ss SliceType) Strings() pie.Strings { 15 | l := len(ss) 16 | 17 | // Avoid the allocation. 18 | if l == 0 { 19 | return nil 20 | } 21 | 22 | result := make(pie.Strings, l) 23 | for i := 0; i < l; i++ { 24 | mightBeString := ss[i] 25 | result[i] = mightBeString.String() 26 | } 27 | 28 | return result 29 | } 30 | -------------------------------------------------------------------------------- /functions/strings_using.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | import ( 4 | "github.com/elliotchance/pie/pie" 5 | ) 6 | 7 | // StringsUsing transforms each element to a string. 8 | func (ss SliceType) StringsUsing(transform func(ElementType) string) pie.Strings { 9 | l := len(ss) 10 | 11 | // Avoid the allocation. 12 | if l == 0 { 13 | return nil 14 | } 15 | 16 | result := make(pie.Strings, l) 17 | for i := 0; i < l; i++ { 18 | result[i] = transform(ss[i]) 19 | } 20 | 21 | return result 22 | } 23 | -------------------------------------------------------------------------------- /functions/sub_slice.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | // SubSlice will return the subSlice from start to end(excluded) 4 | // 5 | // Condition 1: If start < 0 or end < 0, nil is returned. 6 | // Condition 2: If start >= end, nil is returned. 7 | // Condition 3: Return all elements that exist in the range provided, 8 | // if start or end is out of bounds, zero items will be placed. 9 | func (ss SliceType) SubSlice(start int, end int) (subSlice SliceType) { 10 | if start < 0 || end < 0 { 11 | return 12 | } 13 | 14 | if start >= end { 15 | return 16 | } 17 | 18 | length := ss.Len() 19 | if start < length { 20 | if end <= length { 21 | subSlice = ss[start:end] 22 | } else { 23 | zeroArray := make([]ElementType, end-length) 24 | subSlice = ss[start:length].Append(zeroArray[:]...) 25 | } 26 | } else { 27 | zeroArray := make([]ElementType, end-start) 28 | subSlice = zeroArray[:] 29 | } 30 | 31 | return 32 | } 33 | -------------------------------------------------------------------------------- /functions/sum.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | // Sum is the sum of all of the elements. 4 | func (ss SliceType) Sum() (sum ElementType) { 5 | for _, s := range ss { 6 | sum += s 7 | } 8 | 9 | return 10 | } 11 | -------------------------------------------------------------------------------- /functions/top.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | // Top will return n elements from head of the slice 4 | // if the slice has less elements then n that'll return all elements 5 | // if n < 0 it'll return empty slice. 6 | func (ss SliceType) Top(n int) (top SliceType) { 7 | for i := 0; i < len(ss) && n > 0; i++ { 8 | top = append(top, ss[i]) 9 | n-- 10 | } 11 | 12 | return 13 | } 14 | -------------------------------------------------------------------------------- /functions/unique.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | // Unique returns a new slice with all of the unique values. 4 | // 5 | // The items will be returned in a randomized order, even with the same input. 6 | // 7 | // The number of items returned may be the same as the input or less. It will 8 | // never return zero items unless then input slice has zero items. 9 | // 10 | // A slice with zero elements is considered to be unique. 11 | // 12 | // See AreUnique(). 13 | func (ss SliceType) Unique() SliceType { 14 | // Avoid the allocation. If there is one element or less it is already 15 | // unique. 16 | if len(ss) < 2 { 17 | return ss 18 | } 19 | 20 | values := map[ElementType]struct{}{} 21 | 22 | for _, value := range ss { 23 | values[value] = struct{}{} 24 | } 25 | 26 | var uniqueValues SliceType 27 | for value := range values { 28 | uniqueValues = append(uniqueValues, value) 29 | } 30 | 31 | return uniqueValues 32 | } 33 | -------------------------------------------------------------------------------- /functions/unshift.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | // Unshift adds one or more elements to the beginning of the slice 4 | // and returns the new slice. 5 | func (ss SliceType) Unshift(elements ...ElementType) (unshift SliceType) { 6 | unshift = append(SliceType{}, elements...) 7 | unshift = append(unshift, ss...) 8 | 9 | return 10 | } 11 | -------------------------------------------------------------------------------- /functions/values.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | // Values returns the values in the map. 4 | // 5 | // Due to Go's randomization of iterating maps the order is not deterministic. 6 | func (m MapType) Values() []ElementType { 7 | // Avoid allocation 8 | l := len(m) 9 | if l == 0 { 10 | return nil 11 | } 12 | 13 | i := 0 14 | keys := make([]ElementType, len(m)) 15 | for _, value := range m { 16 | keys[i] = value 17 | i++ 18 | } 19 | 20 | return keys 21 | } 22 | -------------------------------------------------------------------------------- /generate.go: -------------------------------------------------------------------------------- 1 | // +build ignore 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "go/ast" 8 | "go/parser" 9 | "go/token" 10 | "io/ioutil" 11 | "os" 12 | "regexp" 13 | "strings" 14 | "text/template" 15 | "unicode/utf8" 16 | 17 | "github.com/elliotchance/pie/functions" 18 | ) 19 | 20 | var packageTemplate = template.Must(template.New(""). 21 | Parse("// Code generated by go generate; DO NOT EDIT.\n" + 22 | "package main\n" + 23 | "\n" + 24 | "var pieTemplates = map[string]string{\n" + 25 | "{{ range $fn, $file := . }}" + 26 | "\t\"{{ $fn }}\": `{{ $file }}`,\n" + 27 | "{{ end }}" + 28 | "}\n")) 29 | 30 | type Function struct { 31 | Name string 32 | DescriptiveName string 33 | For int 34 | Doc string 35 | BigO string 36 | } 37 | 38 | func (f Function) BriefDoc() (brief string) { 39 | for _, line := range strings.Split(f.Doc, "\n") { 40 | if line == "" { 41 | break 42 | } 43 | 44 | brief += line + " " 45 | } 46 | 47 | return 48 | } 49 | 50 | func updateREADME() { 51 | var funcs []Function 52 | 53 | for _, function := range functions.Functions { 54 | file, err := ioutil.ReadFile("functions/" + function.File) 55 | if err != nil { 56 | panic(err) 57 | } 58 | 59 | fset := token.NewFileSet() 60 | f, err := parser.ParseFile(fset, "", file, parser.ParseComments) 61 | if err != nil { 62 | panic(err) 63 | } 64 | 65 | var qualifiers []string 66 | 67 | if strings.Contains(string(file), ".Equals(") { 68 | qualifiers = append(qualifiers, "E") 69 | } 70 | 71 | if strings.Contains(string(file), ".String(") { 72 | qualifiers = append(qualifiers, "S") 73 | } 74 | 75 | descriptiveName := fmt.Sprintf("[`%s`](#%s)", function.Name, strings.ToLower(function.Name)) 76 | 77 | if len(qualifiers) > 0 { 78 | descriptiveName += fmt.Sprintf(" (%s)", strings.Join(qualifiers, "")) 79 | } 80 | 81 | funcs = append(funcs, Function{ 82 | Name: function.Name, 83 | DescriptiveName: descriptiveName, 84 | For: function.For, 85 | Doc: getDoc(f.Decls), 86 | BigO: function.BigO, 87 | }) 88 | } 89 | 90 | longestFunctionName := 0 91 | for _, function := range funcs { 92 | if newLen := len(function.DescriptiveName); newLen > longestFunctionName { 93 | longestFunctionName = newLen 94 | } 95 | } 96 | 97 | newDocs := fmt.Sprintf("| Function%s | String | Number | Struct | Maps | Big-O | Description |\n", strings.Repeat(" ", longestFunctionName-8)) 98 | newDocs += fmt.Sprintf("| %s | :----: | :----: | :----: | :--: | :------: | ----------- |\n", strings.Repeat("-", longestFunctionName)) 99 | 100 | for _, function := range funcs { 101 | newDocs += fmt.Sprintf("| %s%s | %s | %s | %s | %s | %s%s | %s |\n", 102 | function.DescriptiveName, 103 | strings.Repeat(" ", longestFunctionName-len(function.DescriptiveName)), 104 | tick(function.For&functions.ForStrings), 105 | tick(function.For&functions.ForNumbers), 106 | tick(function.For&functions.ForStructs), 107 | tick(function.For&functions.ForMaps), 108 | function.BigO, 109 | strings.Repeat(" ", 8-utf8.RuneCountInString(function.BigO)), 110 | function.BriefDoc()) 111 | } 112 | 113 | newDocs += "\n" 114 | 115 | for _, function := range funcs { 116 | newDocs += fmt.Sprintf("## %s\n\n%s\n", 117 | function.Name, function.Doc) 118 | } 119 | 120 | readme, err := ioutil.ReadFile("README.md") 121 | if err != nil { 122 | panic(err) 123 | } 124 | 125 | newReadme := regexp.MustCompile(`(?s)\| Function.*# FAQ`). 126 | ReplaceAllString(string(readme), newDocs+"# FAQ") 127 | 128 | err = ioutil.WriteFile("README.md", []byte(newReadme), 0644) 129 | if err != nil { 130 | panic(err) 131 | } 132 | } 133 | 134 | func tick(x int) string { 135 | if x != 0 { 136 | return "✓" 137 | } 138 | 139 | return " " 140 | } 141 | 142 | func getDoc(decls []ast.Decl) (doc string) { 143 | for _, decl := range decls { 144 | if f, ok := decl.(*ast.FuncDecl); ok && f.Doc != nil { 145 | for _, comment := range f.Doc.List { 146 | if len(comment.Text) < 3 { 147 | doc += "\n" 148 | } else { 149 | doc += comment.Text[3:] + "\n" 150 | } 151 | } 152 | } 153 | } 154 | 155 | // Fix code snippets 156 | var newLines []string 157 | inCodeBlock := false 158 | 159 | for _, line := range strings.Split(doc, "\n") { 160 | if line == "" { 161 | newLines = append(newLines, "") 162 | } 163 | 164 | if strings.HasPrefix(line, " ") { 165 | if inCodeBlock { 166 | newLines = append(newLines, line[2:]) 167 | } else { 168 | inCodeBlock = true 169 | newLines = append(newLines, "```go", line[2:]) 170 | } 171 | } else { 172 | if inCodeBlock { 173 | inCodeBlock = false 174 | newLines = append(newLines, "```", line) 175 | } else { 176 | newLines = append(newLines, line) 177 | } 178 | } 179 | } 180 | 181 | return strings.Join(newLines, "\n") 182 | } 183 | 184 | func main() { 185 | data := map[string]string{} 186 | 187 | for _, function := range functions.Functions { 188 | tmpl, err := ioutil.ReadFile("functions/" + function.File) 189 | if err != nil { 190 | panic(err) 191 | } 192 | 193 | data[function.Name] = string(tmpl) 194 | } 195 | 196 | f, err := os.Create("template.go") 197 | if err != nil { 198 | panic(err) 199 | } 200 | defer f.Close() 201 | 202 | packageTemplate.Execute(f, data) 203 | 204 | updateREADME() 205 | } 206 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/elliotchance/pie 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/davecgh/go-spew v1.1.1 // indirect 7 | github.com/elliotchance/testify-stats v1.0.0 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 4 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/elliotchance/testify-stats v1.0.0 h1:CMcRBfQIB0WwT1+aY38MM4ShFqhPyP6jkHRytSvXLzI= 6 | github.com/elliotchance/testify-stats v1.0.0/go.mod h1:Mc25k7L4E65uf6CfW+s/pY04XcoiqQBrfIRsWQcgweA= 7 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 8 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 9 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 10 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 11 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 12 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | //go:generate go run generate.go 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "go/ast" 8 | "go/parser" 9 | "go/token" 10 | "io/ioutil" 11 | "os" 12 | "regexp" 13 | "sort" 14 | "strings" 15 | 16 | "github.com/elliotchance/pie/functions" 17 | ) 18 | 19 | func check(err error) { 20 | if err != nil { 21 | panic(err) 22 | } 23 | } 24 | 25 | func getIdentName(e ast.Expr) string { 26 | switch v := e.(type) { 27 | case *ast.Ident: 28 | return v.Name 29 | 30 | case *ast.StarExpr: 31 | return "*" + getIdentName(v.X) 32 | 33 | default: 34 | panic(fmt.Sprintf("cannot decode %T", e)) 35 | } 36 | } 37 | 38 | func getKeyAndElementType(pkg *ast.Package, name string, typeSpec *ast.TypeSpec) (string, string, string, *TypeExplorer) { 39 | pkgName := pkg.Name 40 | 41 | if t, ok := typeSpec.Type.(*ast.ArrayType); ok { 42 | explorer := NewTypeExplorer(pkg, getIdentName(t.Elt)) 43 | 44 | switch v := t.Elt.(type) { 45 | case *ast.Ident: 46 | if v == nil || v.Obj == nil { 47 | break 48 | } 49 | 50 | if ts, ok := v.Obj.Decl.(*ast.TypeSpec); ok { 51 | if _, ok := ts.Type.(*ast.InterfaceType); ok { 52 | explorer.IsInterface = true 53 | } 54 | } 55 | 56 | } 57 | 58 | return pkgName, "", getIdentName(t.Elt), explorer 59 | } 60 | 61 | if t, ok := typeSpec.Type.(*ast.MapType); ok { 62 | explorer := NewTypeExplorer(pkg, getIdentName(t.Value)) 63 | 64 | return pkgName, getIdentName(t.Key), getIdentName(t.Value), explorer 65 | } 66 | 67 | panic(fmt.Sprintf("type %s must be a slice or map", name)) 68 | } 69 | 70 | func findType(pkgs map[string]*ast.Package, name string) (packageName, keyType, elementType string, explorer *TypeExplorer) { 71 | for _, pkg := range pkgs { 72 | for _, file := range pkg.Files { 73 | for _, decl := range file.Decls { 74 | if genDecl, ok := decl.(*ast.GenDecl); ok { 75 | for _, spec := range genDecl.Specs { 76 | if typeSpec, ok := spec.(*ast.TypeSpec); ok { 77 | if typeSpec.Name.String() == name { 78 | return getKeyAndElementType(pkg, name, typeSpec) 79 | } 80 | } 81 | } 82 | } 83 | } 84 | } 85 | } 86 | 87 | panic(fmt.Sprintf("type %s does not exist", name)) 88 | } 89 | 90 | func getType(keyType, elementType string) int { 91 | if keyType != "" { 92 | return functions.ForMaps 93 | } 94 | 95 | switch elementType { 96 | case "int8", "uint8", "byte", "int16", "uint16", "int32", "rune", "uint32", 97 | "int64", "uint64", "int", "uint", "uintptr", "float32", "float64", 98 | "complex64", "complex128": 99 | return functions.ForNumbers 100 | 101 | case "string": 102 | return functions.ForStrings 103 | } 104 | 105 | return functions.ForStructs 106 | } 107 | 108 | func getImports(packageName, s string) (imports []string) { 109 | fset := token.NewFileSet() 110 | f, err := parser.ParseFile(fset, "", s, parser.ImportsOnly) 111 | if err != nil { 112 | panic(err) 113 | } 114 | 115 | for _, s := range f.Imports { 116 | importName := s.Path.Value 117 | 118 | if importName == `"github.com/elliotchance/pie/pie"` && 119 | isSelfPackage(packageName) { 120 | continue 121 | } 122 | 123 | imports = append(imports, importName) 124 | } 125 | 126 | return 127 | } 128 | 129 | func getAllImports(packageName string, files []string, explorer *TypeExplorer) (imports []string) { 130 | mapImports := map[string]struct{}{} 131 | 132 | for _, file := range files { 133 | if !explorer.HasString() && strings.Contains(file, "mightBeString") { 134 | mapImports[`"fmt"`] = struct{}{} 135 | } 136 | 137 | for _, imp := range getImports(packageName, file) { 138 | mapImports[imp] = struct{}{} 139 | } 140 | } 141 | 142 | for imp := range mapImports { 143 | imports = append(imports, imp) 144 | } 145 | 146 | sort.Strings(imports) 147 | 148 | return 149 | } 150 | 151 | // We have to generate imports slightly differently when we are building code 152 | // that will go into its own packages vs an external package. 153 | func isSelfPackage(packageName string) bool { 154 | return packageName == "pie" 155 | } 156 | 157 | func main() { 158 | fset := token.NewFileSet() 159 | pkgs, err := parser.ParseDir(fset, ".", nil, 0) 160 | check(err) 161 | 162 | for _, arg := range os.Args[1:] { 163 | mapOrSliceType, fns := getFunctionsFromArg(arg) 164 | packageName, keyType, elementType, explorer := findType(pkgs, mapOrSliceType) 165 | kind := getType(keyType, elementType) 166 | 167 | var templates []string 168 | for _, function := range functions.Functions { 169 | if fns[0] != "*" && !stringSliceContains(fns, function.Name) { 170 | continue 171 | } 172 | 173 | if function.For&kind != 0 { 174 | templates = append(templates, pieTemplates[function.Name]) 175 | } 176 | } 177 | 178 | // Aggregate imports. 179 | t := fmt.Sprintf("package %s\n\n", packageName) 180 | 181 | imports := getAllImports(packageName, templates, explorer) 182 | if len(imports) > 0 { 183 | t += "import (" 184 | for _, imp := range imports { 185 | t += fmt.Sprintf("\n\t%s", imp) 186 | } 187 | t += "\n)\n\n" 188 | } 189 | 190 | for _, tmpl := range templates { 191 | i := strings.Index(tmpl, "//") 192 | t += tmpl[i:] + "\n" 193 | } 194 | 195 | t = strings.Replace(t, "StringSliceType", mapOrSliceType, -1) 196 | t = strings.Replace(t, "StringElementType", elementType, -1) 197 | t = strings.Replace(t, "ElementType", elementType, -1) 198 | t = strings.Replace(t, "MapType", mapOrSliceType, -1) 199 | t = strings.Replace(t, "KeyType", elementType, -1) 200 | t = strings.Replace(t, "KeySliceType", "[]"+keyType, -1) 201 | t = strings.Replace(t, "SliceType", mapOrSliceType, -1) 202 | 203 | if !explorer.HasEquals() { 204 | re := regexp.MustCompile(`([\w_]+|[\w_]+\[\w+\])\.Equals\(([^)]+)\)`) 205 | t = ReplaceAllStringSubmatchFunc(re, t, func(groups []string) string { 206 | return fmt.Sprintf("%s == %s", groups[1], groups[2]) 207 | }) 208 | } 209 | 210 | if !explorer.HasString() { 211 | t = strings.Replace(t, "mightBeString.String()", `fmt.Sprintf("%v", mightBeString)`, -1) 212 | } 213 | 214 | switch kind { 215 | case functions.ForNumbers: 216 | t = strings.Replace(t, "ElementZeroValue", "0", -1) 217 | 218 | case functions.ForStrings: 219 | t = strings.Replace(t, "ElementZeroValue", `""`, -1) 220 | 221 | case functions.ForStructs: 222 | zeroValue := fmt.Sprintf("%s{}", elementType) 223 | 224 | // If its a pointer we need to replace '*' -> '&' when 225 | // instantiating. 226 | if elementType[0] == '*' || explorer.IsInterface { 227 | zeroValue = "nil" 228 | } 229 | 230 | t = strings.Replace(t, "ElementZeroValue", zeroValue, -1) 231 | } 232 | 233 | if isSelfPackage(packageName) { 234 | t = strings.Replace(t, "pie.Strings", "Strings", -1) 235 | t = strings.Replace(t, "pie.Ints", "Ints", -1) 236 | t = strings.Replace(t, "pie.Float64s", "Float64s", -1) 237 | } 238 | 239 | // The TrimRight is important to remove an extra new line that conflicts 240 | // with go fmt. 241 | t = strings.TrimRight(t, "\n") + "\n" 242 | 243 | err := ioutil.WriteFile(strings.ToLower(mapOrSliceType)+"_pie.go", []byte(t), 0755) 244 | check(err) 245 | } 246 | } 247 | 248 | func getFunctionsFromArg(arg string) (mapOrSliceType string, fns []string) { 249 | parts := strings.Split(arg, ".") 250 | 251 | if len(parts) < 2 { 252 | panic("must specify at least one function or *: " + arg) 253 | } 254 | 255 | return parts[0], parts[1:] 256 | } 257 | 258 | func stringSliceContains(haystack []string, needle string) bool { 259 | if haystack == nil { 260 | return false 261 | } 262 | 263 | for _, w := range haystack { 264 | if w == needle { 265 | return true 266 | } 267 | } 268 | return false 269 | } 270 | 271 | // http://elliot.land/post/go-replace-string-with-regular-expression-callback 272 | func ReplaceAllStringSubmatchFunc(re *regexp.Regexp, str string, repl func([]string) string) string { 273 | result := "" 274 | lastIndex := 0 275 | 276 | for _, v := range re.FindAllSubmatchIndex([]byte(str), -1) { 277 | groups := []string{} 278 | for i := 0; i < len(v); i += 2 { 279 | groups = append(groups, str[v[i]:v[i+1]]) 280 | } 281 | 282 | if isNegative(str[v[0]-1]) { 283 | result += str[lastIndex:v[0]] + addBrackets(repl(groups)) 284 | } else { 285 | result += str[lastIndex:v[0]] + repl(groups) 286 | } 287 | lastIndex = v[1] 288 | } 289 | 290 | return result + str[lastIndex:] 291 | } 292 | 293 | func isNegative(b byte) bool { 294 | return b == '!' 295 | } 296 | 297 | func addBrackets(str string) string { 298 | return `(` + str + `)` 299 | } 300 | -------------------------------------------------------------------------------- /pie/car.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | import "fmt" 4 | 5 | //go:generate pie cars.* carPointers.* 6 | type cars []car 7 | type carPointers []*car 8 | 9 | type car struct { 10 | Name, Color string 11 | } 12 | 13 | func (c *car) Equals(c2 *car) bool { 14 | if c == nil && c2 == nil { 15 | return true 16 | } 17 | 18 | if c == nil || c2 == nil { 19 | return false 20 | } 21 | 22 | return c.Name == c2.Name 23 | } 24 | 25 | func (c *car) String() string { 26 | return fmt.Sprintf("%s is %s", c.Name, c.Color) 27 | } 28 | -------------------------------------------------------------------------------- /pie/currencies.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | type currency struct { 4 | NumericCode, Exponent int 5 | } 6 | 7 | //go:generate pie currencies.* 8 | type currencies map[string]currency 9 | 10 | var isoCurrencies = currencies{ 11 | "AUD": {36, -2}, 12 | "USD": {840, -2}, 13 | } 14 | -------------------------------------------------------------------------------- /pie/currencies_pie.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | // Keys returns the keys in the map. All of the items will be unique. 4 | // 5 | // Due to Go's randomization of iterating maps the order is not deterministic. 6 | func (m currencies) Keys() []string { 7 | // Avoid allocation 8 | l := len(m) 9 | if l == 0 { 10 | return nil 11 | } 12 | 13 | i := 0 14 | keys := make([]string, len(m)) 15 | for key := range m { 16 | keys[i] = key 17 | i++ 18 | } 19 | 20 | return keys 21 | } 22 | 23 | // Values returns the values in the map. 24 | // 25 | // Due to Go's randomization of iterating maps the order is not deterministic. 26 | func (m currencies) Values() []currency { 27 | // Avoid allocation 28 | l := len(m) 29 | if l == 0 { 30 | return nil 31 | } 32 | 33 | i := 0 34 | keys := make([]currency, len(m)) 35 | for _, value := range m { 36 | keys[i] = value 37 | i++ 38 | } 39 | 40 | return keys 41 | } 42 | -------------------------------------------------------------------------------- /pie/currencies_test.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | import ( 4 | "github.com/elliotchance/testify-stats/assert" 5 | "sort" 6 | "testing" 7 | ) 8 | 9 | func TestCurrencies_Keys(t *testing.T) { 10 | assert.Equal(t, []string(nil), currencies(nil).Keys()) 11 | 12 | assert.Equal(t, []string(nil), currencies{}.Keys()) 13 | 14 | keys := isoCurrencies.Keys() 15 | sort.Strings(keys) 16 | assert.Equal(t, []string{"AUD", "USD"}, keys) 17 | } 18 | 19 | func TestCurrencies_Values(t *testing.T) { 20 | assert.Equal(t, []currency(nil), currencies(nil).Values()) 21 | 22 | assert.Equal(t, []currency(nil), currencies{}.Values()) 23 | 24 | values := isoCurrencies.Values() 25 | sort.Slice(values, func(i, j int) bool { 26 | return values[i].NumericCode < values[j].NumericCode 27 | }) 28 | 29 | assert.Equal(t, []currency{{36, -2}, {840, -2}}, values) 30 | } 31 | -------------------------------------------------------------------------------- /pie/float64s.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | //go:generate pie Float64s.* 4 | type Float64s []float64 5 | -------------------------------------------------------------------------------- /pie/float64s_bench_test.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | import ( 4 | "math/rand" 5 | "testing" 6 | ) 7 | 8 | func BenchmarkFloatMedianSmall(b *testing.B) { benchmarkFloatMedian(b, 20) } 9 | 10 | func BenchmarkFloatMedianMedium(b *testing.B) { benchmarkFloatMedian(b, 800) } 11 | func BenchmarkFloatMedianLarge(b *testing.B) { benchmarkFloatMedian(b, 1000000) } 12 | 13 | func benchmarkFloatMedian(b *testing.B, size int) { 14 | // Make the random numbers below deterministic 15 | rand.Seed(123) 16 | 17 | a := make(Float64s, size) 18 | for i := range a { 19 | // As many possible values as slots in the slice. 20 | // Positives and negatives. 21 | // Variety, with some duplicates. 22 | a[i] = -0.5 + rand.Float64() 23 | } 24 | 25 | b.ResetTimer() 26 | for i := 0; i < b.N; i++ { 27 | // m := a.MedianOld() 28 | // m := a.medianCheck() 29 | m := a.Median() 30 | sinkFloats += m 31 | } 32 | } 33 | 34 | // Prevent compiler from agressively optimizing away the result 35 | var sinkFloats float64 36 | -------------------------------------------------------------------------------- /pie/ints.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | //go:generate pie Ints.* 4 | type Ints []int 5 | 6 | //go:generate pie myInts.Sum.Average 7 | type myInts []int 8 | -------------------------------------------------------------------------------- /pie/ints_bench_test.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | import ( 4 | "math/rand" 5 | "testing" 6 | ) 7 | 8 | func BenchmarkIntMedianSmall(b *testing.B) { benchmarkIntMedian(b, 20) } 9 | 10 | func BenchmarkIntMedianMedium(b *testing.B) { benchmarkIntMedian(b, 800) } 11 | func BenchmarkIntMedianLarge(b *testing.B) { benchmarkIntMedian(b, 1000000) } 12 | 13 | func benchmarkIntMedian(b *testing.B, size int) { 14 | // Make the random numbers below deterministic 15 | rand.Seed(123) 16 | 17 | a := make(Ints, size) 18 | for i := range a { 19 | // As many possible values as slots in the slice. 20 | // Negatives and positives. 21 | // Variety, with some duplicates. 22 | a[i] = -size/2 + rand.Intn(size) 23 | } 24 | 25 | b.ResetTimer() 26 | for i := 0; i < b.N; i++ { 27 | m := a.Median() 28 | sinkInts += m 29 | } 30 | } 31 | 32 | // Prevent compiler from agressively optimizing away the result 33 | var sinkInts int 34 | -------------------------------------------------------------------------------- /pie/main_test.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "testing" 7 | "time" 8 | 9 | testify_stats "github.com/elliotchance/testify-stats" 10 | "github.com/elliotchance/testify-stats/assert" 11 | ) 12 | 13 | func TestMain(m *testing.M) { 14 | os.Exit(testify_stats.Run(m)) 15 | } 16 | 17 | func assertImmutableStrings(t *testing.T, ss *Strings) func() { 18 | before := (*ss).JSONString() 19 | 20 | return func() { 21 | after := (*ss).JSONString() 22 | assert.Equal(t, before, after) 23 | assert.True(t, before == after) 24 | } 25 | } 26 | 27 | func assertImmutableInts(t *testing.T, ss *Ints) func() { 28 | before := (*ss).JSONString() 29 | 30 | return func() { 31 | after := (*ss).JSONString() 32 | assert.Equal(t, before, after) 33 | assert.True(t, before == after) 34 | } 35 | } 36 | 37 | func assertImmutableFloat64s(t *testing.T, ss *Float64s) func() { 38 | before := (*ss).JSONString() 39 | 40 | return func() { 41 | after := (*ss).JSONString() 42 | assert.Equal(t, before, after) 43 | assert.True(t, before == after) 44 | } 45 | } 46 | 47 | func assertImmutableCars(t *testing.T, ss *cars) func() { 48 | before := (*ss).JSONString() 49 | 50 | return func() { 51 | after := (*ss).JSONString() 52 | assert.Equal(t, before, after) 53 | assert.True(t, before == after) 54 | } 55 | } 56 | 57 | func assertImmutableCarPointers(t *testing.T, ss *carPointers) func() { 58 | before := (*ss).JSONString() 59 | 60 | return func() { 61 | after := (*ss).JSONString() 62 | assert.Equal(t, before, after) 63 | assert.True(t, before == after) 64 | } 65 | } 66 | 67 | func createContextByDelay(t time.Duration) context.Context { 68 | ctx := context.Background() 69 | if t > 0 { 70 | ctx, _ = context.WithTimeout(ctx, t) 71 | } 72 | 73 | return ctx 74 | } 75 | 76 | func getCarPointersFromChan(ch chan *car, t time.Duration) func() carPointers { 77 | done := make(chan struct{}) 78 | var cars carPointers 79 | if t > 0 { 80 | go func() { 81 | ticker := time.NewTicker(t) 82 | defer ticker.Stop() 83 | for range ticker.C { 84 | val, ok := <-ch 85 | if !ok { 86 | break 87 | } else { 88 | cars = append(cars, val) 89 | } 90 | } 91 | done <- struct{}{} 92 | 93 | }() 94 | } else { 95 | go func() { 96 | for val := range ch { 97 | cars = append(cars, val) 98 | } 99 | done <- struct{}{} 100 | }() 101 | } 102 | 103 | return func() carPointers { 104 | <-done 105 | return cars 106 | } 107 | } 108 | 109 | func getCarsFromChan(ch chan car, t time.Duration) func() cars { 110 | done := make(chan struct{}) 111 | var c cars 112 | if t > 0 { 113 | go func() { 114 | ticker := time.NewTicker(t) 115 | defer ticker.Stop() 116 | for range ticker.C { 117 | val, ok := <-ch 118 | if !ok { 119 | break 120 | } else { 121 | c = append(c, val) 122 | } 123 | } 124 | done <- struct{}{} 125 | 126 | }() 127 | } else { 128 | go func() { 129 | for val := range ch { 130 | c = append(c, val) 131 | } 132 | done <- struct{}{} 133 | }() 134 | } 135 | 136 | return func() cars { 137 | <-done 138 | return c 139 | } 140 | } 141 | 142 | func getFloat64sFromChan(ch chan float64, t time.Duration) func() Float64s { 143 | done := make(chan struct{}) 144 | var c Float64s 145 | if t > 0 { 146 | go func() { 147 | ticker := time.NewTicker(t) 148 | defer ticker.Stop() 149 | for range ticker.C { 150 | val, ok := <-ch 151 | if !ok { 152 | break 153 | } else { 154 | c = append(c, val) 155 | } 156 | } 157 | done <- struct{}{} 158 | 159 | }() 160 | } else { 161 | go func() { 162 | for val := range ch { 163 | c = append(c, val) 164 | } 165 | done <- struct{}{} 166 | }() 167 | } 168 | 169 | return func() Float64s { 170 | <-done 171 | return c 172 | } 173 | } 174 | 175 | func getIntsFromChan(ch chan int, t time.Duration) func() Ints { 176 | done := make(chan struct{}) 177 | var c Ints 178 | if t > 0 { 179 | go func() { 180 | ticker := time.NewTicker(t) 181 | defer ticker.Stop() 182 | for range ticker.C { 183 | val, ok := <-ch 184 | if !ok { 185 | break 186 | } else { 187 | c = append(c, val) 188 | } 189 | } 190 | done <- struct{}{} 191 | 192 | }() 193 | } else { 194 | go func() { 195 | for val := range ch { 196 | c = append(c, val) 197 | } 198 | done <- struct{}{} 199 | }() 200 | } 201 | 202 | return func() Ints { 203 | <-done 204 | return c 205 | } 206 | } 207 | 208 | func getStringsFromChan(ch chan string, t time.Duration) func() Strings { 209 | done := make(chan struct{}) 210 | var c Strings 211 | if t > 0 { 212 | go func() { 213 | ticker := time.NewTicker(t) 214 | defer ticker.Stop() 215 | for range ticker.C { 216 | val, ok := <-ch 217 | if !ok { 218 | break 219 | } else { 220 | c = append(c, val) 221 | } 222 | } 223 | done <- struct{}{} 224 | 225 | }() 226 | } else { 227 | go func() { 228 | for val := range ch { 229 | c = append(c, val) 230 | } 231 | done <- struct{}{} 232 | }() 233 | } 234 | 235 | return func() Strings { 236 | <-done 237 | return c 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /pie/myints_pie.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | // Average is the average of all of the elements, or zero if there are no 4 | // elements. 5 | func (ss myInts) Average() float64 { 6 | if l := int(len(ss)); l > 0 { 7 | return float64(ss.Sum()) / float64(l) 8 | } 9 | 10 | return 0 11 | } 12 | 13 | // Sum is the sum of all of the elements. 14 | func (ss myInts) Sum() (sum int) { 15 | for _, s := range ss { 16 | sum += s 17 | } 18 | 19 | return 20 | } 21 | -------------------------------------------------------------------------------- /pie/myints_test.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | import ( 4 | "github.com/elliotchance/testify-stats/assert" 5 | "testing" 6 | ) 7 | 8 | // There tests are just to make sure that the select functions for myInts are 9 | // generated. The more extensive tests for these functions are in ints_test.go 10 | 11 | func TestMyInts_Average(t *testing.T) { 12 | assert.Equal(t, 0.0, myInts(nil).Average()) 13 | assert.Equal(t, 4.333333333333333, myInts{1, 5, 7}.Average()) 14 | } 15 | 16 | func TestMyInts_Sum(t *testing.T) { 17 | assert.Equal(t, 13, myInts{1, 5, 7}.Sum()) 18 | } 19 | -------------------------------------------------------------------------------- /pie/strings.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | //go:generate pie Strings.* 4 | type Strings []string 5 | -------------------------------------------------------------------------------- /pie/strings_bench_test.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | ) 7 | 8 | func BenchmarkStringsJoin(b *testing.B) { 9 | ss := Strings{"A", "utility", "library", "for", "dealing", "with", "slices", "and", "maps", "that", "focuses", "on", "type", "safety", "and", "performance"} 10 | for i := 0; i < b.N; i++ { 11 | result := ss.Join(" ") 12 | sinkString = result 13 | } 14 | } 15 | 16 | func BenchmarkStdlibStringsJoin(b *testing.B) { 17 | ss := Strings{"A", "utility", "library", "for", "dealing", "with", "slices", "and", "maps", "that", "focuses", "on", "type", "safety", "and", "performance"} 18 | for i := 0; i < b.N; i++ { 19 | result := strings.Join(ss, " ") 20 | sinkString = result 21 | } 22 | } 23 | 24 | // Prevent compiler from agressively optimizing away the result 25 | var sinkString string 26 | -------------------------------------------------------------------------------- /pie/util/floor.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import "math" 4 | 5 | // These constants was copied from src/math/bits.go to support Round in go 6 | // versions before 1.10. 7 | const ( 8 | shift = 64 - 11 - 1 9 | mask = 0x7FF 10 | bias = 1023 11 | signMask = 1 << 63 12 | uvone = 0x3FF0000000000000 13 | fracMask = 1<= 0.5 { 24 | // return t + Copysign(1, x) 25 | // } 26 | // return t 27 | // } 28 | bits := math.Float64bits(x) 29 | e := uint(bits>>shift) & mask 30 | if e < bias { 31 | // Round abs(x) < 1 including denormals. 32 | bits &= signMask // +-0 33 | if e == bias-1 { 34 | bits |= uvone // +-1 35 | } 36 | } else if e < bias+shift { 37 | // Round any abs(x) >= 1 containing a fractional component [0,1). 38 | // 39 | // Numbers with larger exponents are returned unchanged since they 40 | // must be either an integer, infinity, or NaN. 41 | const half = 1 << (shift - 1) 42 | e -= bias 43 | bits += half >> e 44 | bits &^= fracMask >> e 45 | } 46 | return math.Float64frombits(bits) 47 | } 48 | -------------------------------------------------------------------------------- /pie/util/rand.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import "math/rand" 4 | 5 | // Int31n was copied from src/math/rand/rand.go to support Shuffle in go 6 | // versions before 1.10. 7 | func Int31n(r *rand.Rand, n int32) int32 { 8 | v := r.Uint32() 9 | prod := uint64(v) * uint64(n) 10 | low := uint32(prod) 11 | if low < uint32(n) { 12 | thresh := uint32(-n) % uint32(n) 13 | for low < thresh { 14 | v = r.Uint32() 15 | prod = uint64(v) * uint64(n) 16 | low = uint32(prod) 17 | } 18 | } 19 | return int32(prod >> 32) 20 | } 21 | 22 | // Shuffle was copied from src/math/rand/rand.go to support Shuffle in go 23 | // versions before 1.10. 24 | func Shuffle(r *rand.Rand, n int, swap func(i, j int)) { 25 | if n < 0 { 26 | panic("invalid argument to Shuffle") 27 | } 28 | 29 | // Fisher-Yates shuffle: https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle 30 | // Shuffle really ought not be called with n that doesn't fit in 32 bits. 31 | // Not only will it take a very long time, but with 2³¹! possible permutations, 32 | // there's no way that any PRNG can have a big enough internal state to 33 | // generate even a minuscule percentage of the possible permutations. 34 | // Nevertheless, the right API signature accepts an int n, so handle it as best we can. 35 | i := n - 1 36 | for ; i > 1<<31-1-1; i-- { 37 | j := int(r.Int63n(int64(i + 1))) 38 | swap(i, j) 39 | } 40 | for ; i > 0; i-- { 41 | j := int(Int31n(r, int32(i+1))) 42 | swap(i, j) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /type_explorer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "go/ast" 6 | ) 7 | 8 | type TypeExplorer struct { 9 | TypeName string 10 | Methods []string 11 | IsInterface bool 12 | } 13 | 14 | func NewTypeExplorer(pkg *ast.Package, typeName string) *TypeExplorer { 15 | explorer := &TypeExplorer{ 16 | TypeName: typeName, 17 | } 18 | 19 | ast.Walk(explorer, pkg) 20 | 21 | return explorer 22 | } 23 | 24 | func (explorer *TypeExplorer) Visit(node ast.Node) ast.Visitor { 25 | if n, ok := node.(*ast.FuncDecl); ok && n.Recv != nil { 26 | receiver := getIdentName(n.Recv.List[0].Type) 27 | if receiver == explorer.TypeName { 28 | method := fmt.Sprintf("%s(%v)", getIdentName(n.Name), getIdentName(n.Recv.List[0].Type)) 29 | explorer.Methods = append(explorer.Methods, method) 30 | } 31 | } 32 | 33 | return explorer 34 | } 35 | 36 | func (explorer *TypeExplorer) HasEquals() bool { 37 | return explorer.HasMethod(fmt.Sprintf("Equals(%s)", explorer.TypeName)) 38 | } 39 | 40 | func (explorer *TypeExplorer) HasString() bool { 41 | return explorer.HasMethod("String()") 42 | } 43 | 44 | func (explorer *TypeExplorer) HasMethod(lookingFor string) bool { 45 | for _, method := range explorer.Methods { 46 | if method == lookingFor { 47 | return true 48 | } 49 | } 50 | 51 | return false 52 | } 53 | -------------------------------------------------------------------------------- /v2/abs.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | import "golang.org/x/exp/constraints" 4 | 5 | // Abs returns the absolute value. 6 | func Abs[T constraints.Integer | constraints.Float](val T) T { 7 | if val < 0 { 8 | return -val 9 | } 10 | 11 | return val 12 | } 13 | -------------------------------------------------------------------------------- /v2/abs_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestAbs(t *testing.T) { 10 | assert.Equal(t, 584.2727, pie.Abs(-584.2727)) 11 | assert.Equal(t, 5, pie.Abs(-5)) 12 | assert.Equal(t, 584.2727, pie.Abs(584.2727)) 13 | } 14 | -------------------------------------------------------------------------------- /v2/all.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | // All will return true if all callbacks return true. It follows the same logic 4 | // as the all() function in Python. 5 | // 6 | // If the list is empty then true is always returned. 7 | func All[T any](ss []T, fn func(value T) bool) bool { 8 | for _, value := range ss { 9 | if !fn(value) { 10 | return false 11 | } 12 | } 13 | 14 | return true 15 | } 16 | -------------------------------------------------------------------------------- /v2/all_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestAll(t *testing.T) { 10 | assert.True(t, 11 | pie.All([]float64{}, func(value float64) bool { 12 | return false 13 | }), 14 | ) 15 | 16 | // None 17 | assert.False(t, 18 | pie.All([]float64{12.3, 4.56}, func(value float64) bool { 19 | return value == 1 20 | }), 21 | ) 22 | 23 | // Some 24 | assert.False(t, 25 | pie.All([]float64{12.3, 4.56}, func(value float64) bool { 26 | return value == 12.3 27 | }), 28 | ) 29 | 30 | // All 31 | assert.True(t, 32 | pie.All([]float64{12.3, 4.56}, func(value float64) bool { 33 | return value > 0 34 | }), 35 | ) 36 | } 37 | -------------------------------------------------------------------------------- /v2/any.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | // Any will return true if any callbacks return true. It follows the same logic 4 | // as the any() function in Python. 5 | // 6 | // If the list is empty then false is always returned. 7 | func Any[T any](ss []T, fn func(value T) bool) bool { 8 | for _, value := range ss { 9 | if fn(value) { 10 | return true 11 | } 12 | } 13 | 14 | return false 15 | } 16 | -------------------------------------------------------------------------------- /v2/any_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestAny(t *testing.T) { 10 | assert.False(t, 11 | pie.Any([]float64{}, func(value float64) bool { 12 | return true 13 | }), 14 | ) 15 | 16 | // None 17 | assert.False(t, 18 | pie.Any([]float64{12.3, 4.56}, func(value float64) bool { 19 | return value == 1 20 | }), 21 | ) 22 | 23 | // Some 24 | assert.True(t, 25 | pie.Any([]float64{12.3, 4.56}, func(value float64) bool { 26 | return value == 12.3 27 | }), 28 | ) 29 | 30 | // All 31 | assert.True(t, 32 | pie.Any([]float64{12.3, 4.56}, func(value float64) bool { 33 | return value > 0 34 | }), 35 | ) 36 | } 37 | -------------------------------------------------------------------------------- /v2/are_sorted.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | import ( 4 | "golang.org/x/exp/constraints" 5 | "sort" 6 | ) 7 | 8 | // AreSorted will return true if the slice is already sorted. It is a wrapper 9 | // for sort.SliceIsSorted. 10 | func AreSorted[T constraints.Ordered](ss []T) bool { 11 | return sort.SliceIsSorted(ss, func(i, j int) bool { 12 | return ss[i] < ss[j] 13 | }) 14 | } 15 | -------------------------------------------------------------------------------- /v2/are_sorted_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | var sortTests = []struct { 10 | ss []float64 11 | sorted []float64 12 | reversed []float64 13 | areSorted bool 14 | }{ 15 | { 16 | nil, 17 | nil, 18 | nil, 19 | true, 20 | }, 21 | { 22 | []float64{}, 23 | []float64{}, 24 | []float64{}, 25 | true, 26 | }, 27 | { 28 | []float64{789}, 29 | []float64{789}, 30 | []float64{789}, 31 | true, 32 | }, 33 | { 34 | []float64{12.789, -13.2, 789}, 35 | []float64{-13.2, 12.789, 789}, 36 | []float64{789, -13.2, 12.789}, 37 | false, 38 | }, 39 | { 40 | []float64{12.789, -13.2, 1.234e6, 789}, 41 | []float64{-13.2, 12.789, 789, 1.234e6}, 42 | []float64{789, 1.234e6, -13.2, 12.789}, 43 | false, 44 | }, 45 | { 46 | []float64{-13.2, 12.789}, 47 | []float64{-13.2, 12.789}, 48 | []float64{12.789, -13.2}, 49 | true, 50 | }, 51 | } 52 | 53 | func TestAreSorted(t *testing.T) { 54 | for _, test := range sortTests { 55 | t.Run("", func(t *testing.T) { 56 | assert.Equal(t, test.areSorted, pie.AreSorted(test.ss)) 57 | }) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /v2/are_unique.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | // AreUnique will return true if the slice contains elements that are all 4 | // different (unique) from each other. 5 | func AreUnique[T comparable](ss []T) bool { 6 | return len(Unique(ss)) == len(ss) 7 | } 8 | -------------------------------------------------------------------------------- /v2/are_unique_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | var uniqueTests = []struct { 10 | ss []float64 11 | unique []float64 12 | areUnique bool 13 | }{ 14 | { 15 | nil, 16 | nil, 17 | true, 18 | }, 19 | { 20 | []float64{}, 21 | []float64{}, 22 | true, 23 | }, 24 | { 25 | []float64{789}, 26 | []float64{789}, 27 | true, 28 | }, 29 | { 30 | []float64{12.789, -13.2, 12.789}, 31 | []float64{-13.2, 12.789}, 32 | false, 33 | }, 34 | { 35 | []float64{12.789, -13.2, 1.234e6, 789}, 36 | []float64{-13.2, 12.789, 789, 1.234e6}, 37 | true, 38 | }, 39 | } 40 | 41 | func TestAreUnique(t *testing.T) { 42 | for _, test := range uniqueTests { 43 | t.Run("", func(t *testing.T) { 44 | assert.Equal(t, test.areUnique, pie.AreUnique(test.ss)) 45 | }) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /v2/average.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | import "golang.org/x/exp/constraints" 4 | 5 | // Average is the average of all of the elements, or zero if there are no 6 | // elements. 7 | func Average[T constraints.Integer | constraints.Float](ss []T) float64 { 8 | if l := len(ss); l > 0 { 9 | return float64(Sum(ss)) / float64(l) 10 | } 11 | 12 | return 0 13 | } 14 | -------------------------------------------------------------------------------- /v2/average_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | var statsTests = []struct { 10 | ss []float64 11 | min, max, sum, product float64 12 | len int 13 | mode []float64 14 | average float64 15 | }{ 16 | { 17 | nil, 18 | 0, 19 | 0, 20 | 0, 21 | 0, 22 | 0, 23 | nil, 24 | 0, 25 | }, 26 | { 27 | []float64{}, 28 | 0, 29 | 0, 30 | 0, 31 | 0, 32 | 0, 33 | []float64{}, 34 | 0, 35 | }, 36 | { 37 | []float64{1.5}, 38 | 1.5, 39 | 1.5, 40 | 1.5, 41 | 1.5, 42 | 1, 43 | []float64{1.5}, 44 | 1.5, 45 | }, 46 | { 47 | []float64{2.2, 3.1, 5.1, 1.9}, 48 | 1.9, 49 | 5.1, 50 | 12.3, 51 | 66.0858, 52 | 4, 53 | []float64{2.2, 3.1, 5.1, 1.9}, 54 | 3.075, 55 | }, 56 | } 57 | 58 | func TestAverage(t *testing.T) { 59 | for _, test := range statsTests { 60 | t.Run("", func(t *testing.T) { 61 | assert.Equal(t, test.average, pie.Average(test.ss)) 62 | }) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /v2/bottom.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | // Bottom will return n elements from bottom 4 | // 5 | // that means that elements is taken from the end of the slice 6 | // for this [1,2,3] slice with n == 2 will be returned [3,2] 7 | // if the slice has less elements then n that'll return all elements 8 | // if n < 0 it'll return empty slice. 9 | func Bottom[T any](ss []T, n int) (top []T) { 10 | var lastIndex = len(ss) - 1 11 | for i := lastIndex; i > -1 && n > 0; i-- { 12 | top = append(top, ss[i]) 13 | n-- 14 | } 15 | 16 | return 17 | } 18 | -------------------------------------------------------------------------------- /v2/bottom_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | var topAndBottomTests = []struct { 10 | ss []float64 11 | n int 12 | top []float64 13 | bottom []float64 14 | }{ 15 | { 16 | nil, 17 | 1, 18 | nil, 19 | nil, 20 | }, 21 | { 22 | []float64{}, 23 | 1, 24 | nil, 25 | nil, 26 | }, 27 | { 28 | []float64{1.23, 2.34}, 29 | 1, 30 | []float64{1.23}, 31 | []float64{2.34}, 32 | }, 33 | { 34 | []float64{1.23, 2.34}, 35 | 3, 36 | []float64{1.23, 2.34}, 37 | []float64{2.34, 1.23}, 38 | }, 39 | { 40 | []float64{1.23, 2.34}, 41 | 0, 42 | nil, 43 | nil, 44 | }, 45 | { 46 | []float64{1.23, 2.34}, 47 | -1, 48 | nil, 49 | nil, 50 | }, 51 | } 52 | 53 | func TestBottom(t *testing.T) { 54 | for _, test := range topAndBottomTests { 55 | t.Run("", func(t *testing.T) { 56 | assert.Equal(t, test.bottom, pie.Bottom(test.ss, test.n)) 57 | }) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /v2/chunk.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | // Chunk splits the input and returns multi slices whose length equals chunkLength, 4 | // except for the last slice which may contain fewer elements. 5 | // 6 | // Examples: 7 | // 8 | // Chunk([1, 2, 3], 4) => [ [1, 2, 3] ] 9 | // Chunk([1, 2, 3], 3) => [ [1, 2, 3] ] 10 | // Chunk([1, 2, 3], 2) => [ [1, 2], [3] ] 11 | // Chunk([1, 2, 3], 1) => [ [1], [2], [3] ] 12 | // Chunk([], 1) => [ [] ] 13 | // Chunk([1, 2, 3], 0) => panic: chunkLength should be greater than 0 14 | func Chunk[T any](ss []T, chunkLength int) [][]T { 15 | if chunkLength <= 0 { 16 | panic("chunkLength should be greater than 0") 17 | } 18 | 19 | result := make([][]T, 0) 20 | l := len(ss) 21 | if l == 0 { 22 | return result 23 | } 24 | 25 | var step = l / chunkLength 26 | if step == 0 { 27 | result = append(result, ss) 28 | return result 29 | } 30 | var remain = l % chunkLength 31 | for i := 0; i < step; i++ { 32 | result = append(result, ss[i*chunkLength:(i+1)*chunkLength]) 33 | } 34 | if remain != 0 { 35 | result = append(result, ss[step*chunkLength:l]) 36 | } 37 | 38 | return result 39 | } 40 | -------------------------------------------------------------------------------- /v2/chunk_test.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | var chunkTests = []struct { 10 | ss []int 11 | chunkLength int 12 | expected [][]int 13 | }{ 14 | {nil, 1, [][]int{}}, 15 | {[]int{}, 1, [][]int{}}, 16 | {[]int{1, 2, 3}, 4, [][]int{{1, 2, 3}}}, 17 | {[]int{1, 2, 3}, 3, [][]int{{1, 2, 3}}}, 18 | {[]int{1, 2, 3}, 2, [][]int{{1, 2}, {3}}}, 19 | {[]int{1, 2, 3}, 1, [][]int{{1}, {2}, {3}}}, 20 | } 21 | 22 | func TestChunk(t *testing.T) { 23 | for _, test := range chunkTests { 24 | t.Run("", func(t *testing.T) { 25 | assert.Equal(t, test.expected, Chunk(test.ss, test.chunkLength)) 26 | }) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /v2/contains.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | // Contains returns true if the element exists in the slice. 4 | // 5 | // When using slices of pointers it will only compare by address, not value. 6 | func Contains[T comparable](ss []T, lookingFor T) bool { 7 | for _, s := range ss { 8 | if lookingFor == s { 9 | return true 10 | } 11 | } 12 | 13 | return false 14 | } 15 | -------------------------------------------------------------------------------- /v2/contains_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | var containsTests = []struct { 10 | ss []float64 11 | contains float64 12 | expected bool 13 | }{ 14 | {nil, 1, false}, 15 | {[]float64{1, 2, 3}, 1, true}, 16 | {[]float64{1, 2, 3}, 2, true}, 17 | {[]float64{1, 2, 3}, 3, true}, 18 | {[]float64{1, 2, 3}, 4, false}, 19 | {[]float64{1, 2, 3}, 5, false}, 20 | {[]float64{1, 2, 3}, 6, false}, 21 | {[]float64{1, 5, 3}, 5, true}, 22 | } 23 | 24 | func TestContains(t *testing.T) { 25 | for _, test := range containsTests { 26 | t.Run("", func(t *testing.T) { 27 | assert.Equal(t, test.expected, pie.Contains(test.ss, test.contains)) 28 | }) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /v2/delete.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | import "sort" 4 | 5 | // Removes elements at indices in idx from input slice, returns resulting slice. 6 | // If an index is out of bounds, skip it. 7 | func Delete[T any](ss []T, idx ...int) []T { 8 | // short path O(n) inplace 9 | if len(idx) == 1 { 10 | i := idx[0] 11 | 12 | if i < 0 || i >= len(ss) { 13 | return ss 14 | } 15 | return append(ss[:i], ss[i+1:]...) 16 | } 17 | 18 | // long path O(mLog(m) + n) 19 | sort.Ints(idx) 20 | 21 | ss2 := make([]T, 0, len(ss)) 22 | 23 | prev := 0 24 | for _, i := range idx { 25 | if i < 0 || i >= len(ss) { 26 | continue 27 | } 28 | // Copy by consecutive chunks instead of one by one 29 | ss2 = append(ss2, ss[prev:i]...) 30 | prev = i + 1 31 | } 32 | ss2 = append(ss2, ss[prev:]...) 33 | 34 | return ss2 35 | } 36 | -------------------------------------------------------------------------------- /v2/delete_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | var deleteTests = []struct { 10 | ss []int 11 | idx []int 12 | expected []int 13 | }{ 14 | // idx out of bounds 15 | { 16 | []int{1, 2}, 17 | []int{-1}, 18 | []int{1, 2}, 19 | }, 20 | { 21 | []int{1, 2}, 22 | []int{2}, 23 | []int{1, 2}, 24 | }, 25 | // remove from empty slice 26 | { 27 | []int{}, 28 | []int{0}, 29 | []int{}, 30 | }, 31 | { 32 | []int{1}, 33 | []int{0}, 34 | []int{}, 35 | }, 36 | { 37 | []int{1, 2, 3, 4, 5}, 38 | []int{2}, 39 | []int{1, 2, 4, 5}, 40 | }, 41 | { 42 | []int{1, 2, 3, 4, 5}, 43 | []int{1, 3}, 44 | []int{1, 3, 5}, 45 | }, 46 | // mixed indices 47 | { 48 | []int{1, 2, 3, 4, 5}, 49 | []int{1, -1, 5, 3}, 50 | []int{1, 3, 5}, 51 | }, 52 | } 53 | 54 | func TestDelete(t *testing.T) { 55 | for _, test := range deleteTests { 56 | 57 | t.Run("", func(t *testing.T) { 58 | assert.Equal(t, test.expected, pie.Delete(test.ss, test.idx...)) 59 | }) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /v2/diff.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | // Diff returns the elements that needs to be added or removed from the first 4 | // slice to have the same elements in the second slice. 5 | // 6 | // The order of elements is not taken into consideration, so the slices are 7 | // treated sets that allow duplicate items. 8 | // 9 | // The added and removed returned may be blank respectively, or contain upto as 10 | // many elements that exists in the largest slice. 11 | func Diff[T comparable](ss []T, against []T) (added, removed []T) { 12 | diffOneWay := func(ss1, ss2raw []T) (result []T) { 13 | set := make(map[T]struct{}, len(ss1)) 14 | 15 | for _, s := range ss1 { 16 | set[s] = struct{}{} 17 | } 18 | 19 | for _, s := range ss2raw { 20 | if _, ok := set[s]; ok { 21 | delete(set, s) // remove duplicates 22 | } else { 23 | result = append(result, s) 24 | } 25 | } 26 | return 27 | } 28 | 29 | added = diffOneWay(ss, against) 30 | removed = diffOneWay(against, ss) 31 | 32 | return 33 | } 34 | -------------------------------------------------------------------------------- /v2/diff_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | var diffTests = map[string]struct { 10 | ss1 []float64 11 | ss2 []float64 12 | added []float64 13 | removed []float64 14 | }{ 15 | "BothEmpty": { 16 | nil, 17 | nil, 18 | nil, 19 | nil, 20 | }, 21 | "OnlyRemovedUnique": { 22 | []float64{4334.5435, 879.123}, 23 | nil, 24 | nil, 25 | []float64{4334.5435, 879.123}, 26 | }, 27 | "OnlyRemovedDuplicates": { 28 | []float64{4334.5435, 92.384, 4334.5435}, 29 | nil, 30 | nil, 31 | []float64{4334.5435, 92.384, 4334.5435}, 32 | }, 33 | "OnlyAddedUnique": { 34 | nil, 35 | []float64{879.123, 92.384}, 36 | []float64{879.123, 92.384}, 37 | nil, 38 | }, 39 | "OnlyAddedDuplicates": { 40 | nil, 41 | []float64{879.123, 92.384, 92.384, 879.123}, 42 | []float64{879.123, 92.384, 92.384, 879.123}, 43 | nil, 44 | }, 45 | "AddedAndRemovedUnique": { 46 | []float64{4334.5435, 879.123, 92.384, 823.324}, 47 | []float64{92.384, 823.324, 453, 3.345}, 48 | []float64{453, 3.345}, 49 | []float64{4334.5435, 879.123}, 50 | }, 51 | "AddedAndRemovedDuplicates": { 52 | []float64{4334.5435, 879.123, 92.384, 92.384, 823.324}, 53 | []float64{92.384, 823.324, 453, 823.324, 3.345}, 54 | []float64{453, 823.324, 3.345}, 55 | []float64{4334.5435, 879.123, 92.384}, 56 | }, 57 | } 58 | 59 | func TestDiff(t *testing.T) { 60 | for testName, test := range diffTests { 61 | t.Run(testName, func(t *testing.T) { 62 | added, removed := pie.Diff(test.ss1, test.ss2) 63 | assert.Equal(t, test.added, added) 64 | assert.Equal(t, test.removed, removed) 65 | }) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /v2/drop_top.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | // DropTop will return the rest slice after dropping the top n elements 4 | // if the slice has less elements then n that'll return empty slice 5 | // if n < 0 it'll return empty slice. 6 | func DropTop[T any](ss []T, n int) (drop []T) { 7 | if n < 0 || n >= len(ss) { 8 | return 9 | } 10 | 11 | // Copy ss, to make sure no memory is overlapping between input and 12 | // output. See issue #145. 13 | drop = make([]T, len(ss)-n) 14 | copy(drop, ss[n:]) 15 | 16 | return 17 | } 18 | -------------------------------------------------------------------------------- /v2/drop_top_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | var dropTopTests = []struct { 10 | ss []float64 11 | n int 12 | dropTop []float64 13 | }{ 14 | { 15 | nil, 16 | 1, 17 | nil, 18 | }, 19 | { 20 | []float64{}, 21 | 1, 22 | nil, 23 | }, 24 | { 25 | []float64{1.23, 2.34}, 26 | -1, 27 | nil, 28 | }, 29 | { 30 | []float64{1.23, 2.34}, 31 | 0, 32 | []float64{1.23, 2.34}, 33 | }, 34 | 35 | { 36 | []float64{1.23, 2.34}, 37 | 1, 38 | []float64{2.34}, 39 | }, 40 | { 41 | []float64{1.23, 2.34}, 42 | 2, 43 | nil, 44 | }, 45 | { 46 | []float64{1.23, 2.34}, 47 | 3, 48 | nil, 49 | }, 50 | } 51 | 52 | func TestDropTop(t *testing.T) { 53 | for _, test := range dropTopTests { 54 | t.Run("", func(t *testing.T) { 55 | assert.Equal(t, test.dropTop, pie.DropTop(test.ss, test.n)) 56 | }) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /v2/drop_while.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | // Drop items from the slice while f(item) is true. 4 | // Afterwards, return every element until the slice is empty. It follows the 5 | // same logic as the dropwhile() function from itertools in Python. 6 | func DropWhile[T comparable](ss []T, f func(s T) bool) (ss2 []T) { 7 | ss2 = make([]T, len(ss)) 8 | copy(ss2, ss) 9 | for i, value := range ss2 { 10 | if !f(value) { 11 | return ss2[i:] 12 | } 13 | } 14 | 15 | return []T{} 16 | } 17 | -------------------------------------------------------------------------------- /v2/drop_while_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | var dropWhileTests = []struct { 10 | ss []float64 11 | f func(s float64) bool 12 | dropWhile []float64 13 | }{ 14 | { 15 | ss: nil, 16 | f: func(s float64) bool { return s == 0.1 }, 17 | dropWhile: []float64{}, 18 | }, 19 | { 20 | ss: []float64{2.1, 2.1, 2.1, 7.2, 8.1}, 21 | f: func(s float64) bool { return s == 2.1 }, 22 | dropWhile: []float64{7.2, 8.1}, 23 | }, 24 | { 25 | ss: []float64{2.1, 4.1, 5.1, 7.2, 8.1}, 26 | f: func(s float64) bool { return s == 0.2 }, 27 | dropWhile: []float64{2.1, 4.1, 5.1, 7.2, 8.1}, 28 | }, 29 | { 30 | ss: []float64{2.1, 2.1, 2.1, 2.1, 2.1}, 31 | f: func(s float64) bool { return s == 2.1 }, 32 | dropWhile: []float64{}, 33 | }, 34 | } 35 | 36 | func TestDropWhile(t *testing.T) { 37 | for _, test := range dropWhileTests { 38 | t.Run("", func(t *testing.T) { 39 | assert.Equal(t, test.dropWhile, pie.DropWhile(test.ss, test.f)) 40 | }) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /v2/each.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | // Each is more condensed version of Transform that allows an action to happen 4 | // on each elements and pass the original slice on. 5 | // 6 | // pie.Each(cars, func (car *Car) { 7 | // fmt.Printf("Car color is: %s\n", car.Color) 8 | // }) 9 | // 10 | // Pie will not ensure immutability on items passed in so they can be 11 | // manipulated, if you choose to do it this way, for example: 12 | // 13 | // // Set all car colors to Red. 14 | // pie.Each(cars, func (car *Car) { 15 | // car.Color = "Red" 16 | // }) 17 | // 18 | func Each[T any](ss []T, fn func(T)) []T { 19 | for _, s := range ss { 20 | fn(s) 21 | } 22 | 23 | return ss 24 | } 25 | -------------------------------------------------------------------------------- /v2/each_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestEach(t *testing.T) { 10 | var values []float64 11 | 12 | values = []float64{} 13 | pie.Each([]float64{}, func(value float64) { 14 | values = append(values, value) 15 | }) 16 | assert.Equal(t, []float64{}, values) 17 | 18 | values = []float64{} 19 | pie.Each([]float64{435.34, 8923.1}, func(value float64) { 20 | values = append(values, value) 21 | }) 22 | assert.Equal(t, []float64{435.34, 8923.1}, values) 23 | } 24 | -------------------------------------------------------------------------------- /v2/equals.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | // Equals compare elements from the start to the end, 4 | // 5 | // if they are the same is considered the slices are equal if all elements are 6 | // the same is considered the slices are equal 7 | // if each slice == nil is considered that they're equal 8 | // 9 | // if element realizes Equals interface it uses that method, in other way uses 10 | // default compare 11 | func Equals[T comparable](ss []T, rhs []T) bool { 12 | if len(ss) != len(rhs) { 13 | return false 14 | } 15 | 16 | for i := range ss { 17 | if ss[i] != rhs[i] { 18 | return false 19 | } 20 | } 21 | 22 | return true 23 | } 24 | -------------------------------------------------------------------------------- /v2/equals_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | var equalsTests = []struct { 10 | ss []float64 11 | rhs []float64 12 | expected bool 13 | }{ 14 | {nil, nil, true}, 15 | {[]float64{}, []float64{}, true}, 16 | {nil, []float64{}, true}, 17 | {[]float64{1.0, 2.0}, []float64{1.0, 2.0}, true}, 18 | {[]float64{1.0, 2.0}, []float64{1.0, 5.0}, false}, 19 | {[]float64{1.0, 2.0}, []float64{1.0}, false}, 20 | {[]float64{1.0}, []float64{2.0}, false}, 21 | {[]float64{1.0}, nil, false}, 22 | } 23 | 24 | func TestEquals(t *testing.T) { 25 | for _, test := range equalsTests { 26 | t.Run("", func(t *testing.T) { 27 | assert.Equal(t, test.expected, pie.Equals(test.ss, test.rhs)) 28 | }) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /v2/filter.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | // Filter will return a new slice containing only the elements that return 4 | // true from the condition. The returned slice may contain zero elements (nil). 5 | // 6 | // FilterNot works in the opposite way of Filter. 7 | func Filter[T any](ss []T, condition func(T) bool) (ss2 []T) { 8 | for _, s := range ss { 9 | if condition(s) { 10 | ss2 = append(ss2, s) 11 | } 12 | } 13 | 14 | return 15 | } 16 | -------------------------------------------------------------------------------- /v2/filter_not.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | // FilterNot works the same as Filter, with a negated condition. That is, it will 4 | // return a new slice only containing the elements that returned false from the 5 | // condition. The returned slice may contain zero elements (nil). 6 | func FilterNot[T any](ss []T, condition func(T) bool) (ss2 []T) { 7 | for _, s := range ss { 8 | if !condition(s) { 9 | ss2 = append(ss2, s) 10 | } 11 | } 12 | 13 | return 14 | } 15 | -------------------------------------------------------------------------------- /v2/filter_not_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestFilterNot(t *testing.T) { 10 | for _, test := range selectTests { 11 | t.Run("", func(t *testing.T) { 12 | assert.Equal(t, test.expectedFilterNot, pie.FilterNot(test.ss, test.condition)) 13 | }) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /v2/filter_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | var selectTests = []struct { 10 | ss []float64 11 | condition func(float64) bool 12 | expectedFilter []float64 13 | expectedFilterNot []float64 14 | expectedMap []float64 15 | }{ 16 | { 17 | nil, 18 | func(s float64) bool { 19 | return s == 5 20 | }, 21 | nil, 22 | nil, 23 | nil, 24 | }, 25 | { 26 | []float64{1, 2, 3}, 27 | func(s float64) bool { 28 | return s != 2 29 | }, 30 | []float64{1, 3}, 31 | []float64{2}, 32 | []float64{6.2, 7.2, 8.2}, 33 | }, 34 | } 35 | 36 | func TestFilter(t *testing.T) { 37 | for _, test := range selectTests { 38 | t.Run("", func(t *testing.T) { 39 | assert.Equal(t, test.expectedFilter, pie.Filter(test.ss, test.condition)) 40 | }) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /v2/find_first_using.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | // FindFirstUsing will return the index of the first element when the callback 4 | // returns true or -1 if no element is found. 5 | // It follows the same logic as the findIndex() function in Javascript. 6 | // 7 | // If the list is empty then -1 is always returned. 8 | func FindFirstUsing[T any](ss []T, fn func(value T) bool) int { 9 | for idx, value := range ss { 10 | if fn(value) { 11 | return idx 12 | } 13 | } 14 | 15 | return -1 16 | } 17 | -------------------------------------------------------------------------------- /v2/find_first_using_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | var findFirstUsingTests = []struct { 10 | ss []float64 11 | expression func(value float64) bool 12 | expected int 13 | }{ 14 | { 15 | nil, 16 | func(value float64) bool { return value == 1.5 }, 17 | -1, 18 | }, 19 | { 20 | []float64{}, 21 | func(value float64) bool { return value == 0.1 }, 22 | -1, 23 | }, 24 | { 25 | []float64{0.0, 1.5, 3.2}, 26 | func(value float64) bool { return value == 9.99 }, 27 | -1, 28 | }, 29 | { 30 | []float64{5.4, 6.98, 4.987, 33.04}, 31 | func(value float64) bool { return value == 33.04 }, 32 | 3, 33 | }, 34 | { 35 | []float64{9.0, 0.11, 150.44, 33.04}, 36 | func(value float64) bool { return value == 0.11 }, 37 | 1, 38 | }, 39 | } 40 | 41 | func TestFindFirstUsing(t *testing.T) { 42 | for _, test := range findFirstUsingTests { 43 | t.Run("", func(t *testing.T) { 44 | assert.Equal(t, test.expected, pie.FindFirstUsing(test.ss, test.expression)) 45 | }) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /v2/first.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | // First returns the first element or a zero value if there are no elements. 4 | func First[T any](ss []T) T { 5 | if len(ss) == 0 { 6 | var zeroValue T 7 | 8 | return zeroValue 9 | } 10 | 11 | return ss[0] 12 | } 13 | -------------------------------------------------------------------------------- /v2/first_or.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | // FirstOr returns the first element or a default value if there are no 4 | // elements. 5 | func FirstOr[T any](ss []T, defaultValue T) T { 6 | if len(ss) == 0 { 7 | return defaultValue 8 | } 9 | 10 | return ss[0] 11 | } 12 | -------------------------------------------------------------------------------- /v2/first_or_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | var firstOrAndLastOrTests = []struct { 10 | ss []float64 11 | firstOr float64 12 | lastOr float64 13 | }{ 14 | { 15 | nil, 16 | 102, 17 | 202, 18 | }, 19 | { 20 | []float64{100}, 21 | 100, 22 | 100, 23 | }, 24 | { 25 | []float64{1, 2}, 26 | 1, 27 | 2, 28 | }, 29 | { 30 | []float64{1, 2, 3}, 31 | 1, 32 | 3, 33 | }, 34 | } 35 | 36 | func TestFirstOr(t *testing.T) { 37 | for _, test := range firstOrAndLastOrTests { 38 | t.Run("", func(t *testing.T) { 39 | assert.Equal(t, test.firstOr, pie.FirstOr(test.ss, 102)) 40 | }) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /v2/first_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | var firstAndLastTests = []struct { 10 | ss []float64 11 | first float64 12 | last float64 13 | }{ 14 | { 15 | nil, 16 | 0, 17 | 0, 18 | }, 19 | { 20 | []float64{100}, 21 | 100, 22 | 100, 23 | }, 24 | { 25 | []float64{1, 2}, 26 | 1, 27 | 2, 28 | }, 29 | { 30 | []float64{1, 2, 3}, 31 | 1, 32 | 3, 33 | }, 34 | } 35 | 36 | func TestFirst(t *testing.T) { 37 | for _, test := range firstAndLastTests { 38 | t.Run("", func(t *testing.T) { 39 | assert.Equal(t, test.first, pie.First(test.ss)) 40 | }) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /v2/flat.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | // Flat flattens the two-dimensional slice into one-dimensional slice. 4 | // Slices of zero-length are ignored. 5 | // 6 | // Examples: 7 | // 8 | // Flat([[100], [101, 102], [102, 103]]) => [100, 101, 102, 102, 103] 9 | // Flat([nil, [101, 102], []]) => [101, 102] 10 | func Flat[T any](ss [][]T) (ss2 []T) { 11 | for _, s := range ss { 12 | ss2 = append(ss2, s...) 13 | } 14 | 15 | return ss2 16 | } 17 | -------------------------------------------------------------------------------- /v2/flat_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/elliotchance/pie/v2" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | var flatTests = []struct { 11 | ss [][]float64 12 | flat []float64 13 | }{ 14 | { 15 | nil, 16 | nil, 17 | }, 18 | { 19 | [][]float64{{100}}, 20 | []float64{100}, 21 | }, 22 | { 23 | [][]float64{{100}, {101, 102}, {102, 103}}, 24 | []float64{100, 101, 102, 102, 103}, 25 | }, 26 | { 27 | [][]float64{nil, {101, 102}, {}}, 28 | []float64{101, 102}, 29 | }, 30 | { 31 | [][]float64{nil, nil}, 32 | nil, 33 | }, 34 | } 35 | 36 | func TestFlat(t *testing.T) { 37 | for _, test := range flatTests { 38 | t.Run("", func(t *testing.T) { 39 | assert.Equal(t, test.flat, pie.Flat(test.ss)) 40 | }) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /v2/float64.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | import ( 4 | "golang.org/x/exp/constraints" 5 | "strconv" 6 | ) 7 | 8 | // Float64 transforms a value into a float64. This should only be used on slices 9 | // that resolve to strings that represent numbers. An invalid value will use 10 | // zero. 11 | func Float64[T constraints.Ordered](x T) float64 { 12 | f, _ := strconv.ParseFloat(String(x), 64) 13 | 14 | return f 15 | } 16 | -------------------------------------------------------------------------------- /v2/float64_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestFloat64(t *testing.T) { 10 | assert.Equal(t, 123.0, pie.Float64(123)) 11 | assert.Equal(t, 1.89, pie.Float64(1.89)) 12 | assert.Equal(t, 1.89, pie.Float64("1.89")) 13 | } 14 | -------------------------------------------------------------------------------- /v2/float64s.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | import ( 4 | "golang.org/x/exp/constraints" 5 | ) 6 | 7 | // Float64s transforms each element to a float64. 8 | func Float64s[T constraints.Ordered](ss []T) []float64 { 9 | return Map(ss, Float64[T]) 10 | } 11 | -------------------------------------------------------------------------------- /v2/float64s_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestFloat64s(t *testing.T) { 10 | assert.Equal(t, []float64(nil), pie.Float64s([]float64(nil))) 11 | 12 | assert.Equal(t, 13 | []float64{92.384, 823.324, 453}, 14 | pie.Float64s([]float64{92.384, 823.324, 453})) 15 | } 16 | -------------------------------------------------------------------------------- /v2/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/elliotchance/pie/v2 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/stretchr/testify v1.7.1 7 | golang.org/x/exp v0.0.0-20220321173239-a90fa8a75705 8 | ) 9 | 10 | require ( 11 | github.com/davecgh/go-spew v1.1.1 // indirect 12 | github.com/pmezard/go-difflib v1.0.0 // indirect 13 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect 14 | ) 15 | -------------------------------------------------------------------------------- /v2/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 5 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 6 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 7 | github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= 8 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 9 | golang.org/x/exp v0.0.0-20220321173239-a90fa8a75705 h1:ba9YlqfDGTTQ5aZ2fwOoQ1hf32QySyQkR6ODGDzHlnE= 10 | golang.org/x/exp v0.0.0-20220321173239-a90fa8a75705/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= 11 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 12 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 13 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 14 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 15 | -------------------------------------------------------------------------------- /v2/group.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | // Group returns a map of the value with an individual count. 4 | func Group[T comparable](ss []T) map[T]int { 5 | group := map[T]int{} 6 | for _, n := range ss { 7 | group[n]++ 8 | } 9 | 10 | return group 11 | } 12 | -------------------------------------------------------------------------------- /v2/group_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestGroup(t *testing.T) { 10 | assert.Equal(t, map[float64]int{}, pie.Group([]float64{})) 11 | 12 | assert.Equal(t, map[float64]int{ 13 | 1: 1, 14 | }, pie.Group([]float64{1})) 15 | 16 | assert.Equal(t, map[float64]int{ 17 | 1: 1, 18 | 2: 2, 19 | 3: 3, 20 | }, pie.Group([]float64{1, 2, 2, 3, 3, 3})) 21 | } 22 | -------------------------------------------------------------------------------- /v2/groupby.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | // GroupBy groups slice elements by key returned by getKey function for each 4 | // slice element. 5 | // 6 | // It returns a map in which slices of elements of original slice are matched 7 | // to keys defined by getKey function. It returns non-nil map, if empty or nil 8 | // slice is passed. 9 | // 10 | // For example, if you want to group integers by their remainder from division 11 | // by 5, you can use this function as follows: 12 | // 13 | // _ = pie.GroupBy( 14 | // []int{23, 76, 37, 11, 23, 47}, 15 | // func(num int) int { 16 | // return num % 5 17 | // }, 18 | // ) 19 | // 20 | // In above case map {1:[76, 11], 2:[37, 47], 3:[23, 23]} is returned. 21 | func GroupBy[T comparable, U any](values []U, getKey func(U) T) map[T][]U { 22 | groups := make(map[T][]U) 23 | 24 | for _, val := range values { 25 | key := getKey(val) 26 | groups[key] = append(groups[key], val) 27 | } 28 | 29 | return groups 30 | } 31 | -------------------------------------------------------------------------------- /v2/groupby_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/elliotchance/pie/v2" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestGroupBy(t *testing.T) { 11 | type Room int 12 | 13 | const ( 14 | Kitchen Room = iota + 1 15 | Bedroom 16 | Lounge 17 | ) 18 | 19 | type Item struct { 20 | room Room 21 | name string 22 | } 23 | 24 | var ( 25 | bed = Item{room: Bedroom, name: "bed"} 26 | table = Item{room: Kitchen, name: "table"} 27 | toaster = Item{room: Kitchen, name: "toaster"} 28 | pillow = Item{room: Bedroom, name: "pillow"} 29 | sofa = Item{room: Lounge, name: "sofa"} 30 | ) 31 | 32 | groups := pie.GroupBy( 33 | []Item{ 34 | bed, 35 | table, 36 | toaster, 37 | pillow, 38 | sofa, 39 | }, 40 | func(item Item) Room { 41 | return item.room 42 | }, 43 | ) 44 | 45 | assert.Equal( 46 | t, 47 | map[Room][]Item{ 48 | Kitchen: {table, toaster}, 49 | Bedroom: {bed, pillow}, 50 | Lounge: {sofa}, 51 | }, 52 | groups, 53 | ) 54 | 55 | groups = pie.GroupBy(nil, func(item Item) Room { return item.room }) 56 | assert.Equal(t, map[Room][]Item{}, groups) 57 | } 58 | -------------------------------------------------------------------------------- /v2/insert.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | // Insert a value at an index. 4 | func Insert[T any](ss []T, index int, values ...T) []T { 5 | if index >= len(ss) { 6 | return append(ss, values...) 7 | } 8 | 9 | return append(ss[:index], append(values, ss[index:]...)...) 10 | } 11 | -------------------------------------------------------------------------------- /v2/insert_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestInsert(t *testing.T) { 10 | assert.Equal(t, []float64(nil), pie.Insert([]float64(nil), 0)) 11 | assert.Equal(t, []float64{2.0, 1.0}, pie.Insert([]float64{1.0}, 0, 2.0)) 12 | assert.Equal(t, []float64{1.0, 2.0}, pie.Insert([]float64{1.0}, 1, 2.0)) 13 | assert.Equal(t, []float64{1.0, 2.0, 3.3}, pie.Insert([]float64{1.0, 3.3}, 1, 2.0)) 14 | } 15 | -------------------------------------------------------------------------------- /v2/int.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | import ( 4 | "golang.org/x/exp/constraints" 5 | ) 6 | 7 | // Int transforms a value into an int. This should only be used on slices 8 | // that resolve to strings that represent numbers. An invalid value will use 9 | // zero and fractional values will be truncated. 10 | func Int[T constraints.Ordered](x T) int { 11 | return int(Float64(x)) 12 | } 13 | -------------------------------------------------------------------------------- /v2/int_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestInt(t *testing.T) { 10 | assert.Equal(t, 123, pie.Int(123)) 11 | assert.Equal(t, 1, pie.Int(1.89)) 12 | assert.Equal(t, 1, pie.Int("1.89")) 13 | } 14 | -------------------------------------------------------------------------------- /v2/intersect.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | // Intersect returns items that exist in all lists. 4 | // 5 | // It returns slice without any duplicates. 6 | // If zero slice arguments are provided, then nil is returned. 7 | func Intersect[T comparable](ss []T, slices ...[]T) (ss2 []T) { 8 | if slices == nil { 9 | return nil 10 | } 11 | 12 | var uniqs = make([]map[T]struct{}, len(slices)) 13 | for i := 0; i < len(slices); i++ { 14 | m := make(map[T]struct{}) 15 | for _, el := range slices[i] { 16 | m[el] = struct{}{} 17 | } 18 | uniqs[i] = m 19 | } 20 | 21 | var containsInAll = false 22 | for _, el := range Unique(ss) { 23 | for _, u := range uniqs { 24 | if _, exists := u[el]; !exists { 25 | containsInAll = false 26 | break 27 | } 28 | containsInAll = true 29 | } 30 | if containsInAll { 31 | ss2 = append(ss2, el) 32 | } 33 | } 34 | 35 | return 36 | } 37 | -------------------------------------------------------------------------------- /v2/intersect_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | var intersectTests = []struct { 10 | ss []float64 11 | params [][]float64 12 | expected []float64 13 | }{ 14 | { 15 | nil, 16 | nil, 17 | nil, 18 | }, 19 | { 20 | []float64{1.2, 3.2}, 21 | nil, 22 | nil, 23 | }, 24 | { 25 | nil, 26 | [][]float64{{1.2, 3.2, 5.5}, {5.5, 1.2}}, 27 | nil, 28 | }, 29 | { 30 | []float64{1.2, 3.2}, 31 | [][]float64{{1.2}, {3.2}}, 32 | nil, 33 | }, 34 | { 35 | []float64{1.2, 3.2}, 36 | [][]float64{{1.2}}, 37 | []float64{1.2}, 38 | }, 39 | { 40 | []float64{1.2, 3.2}, 41 | [][]float64{{1.2, 3.2, 5.5}, {5.5, 1.2}}, 42 | []float64{1.2}, 43 | }, 44 | } 45 | 46 | func TestIntersect(t *testing.T) { 47 | for _, test := range intersectTests { 48 | t.Run("", func(t *testing.T) { 49 | assert.Equal(t, test.expected, pie.Intersect(test.ss, test.params...)) 50 | }) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /v2/ints.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | import ( 4 | "golang.org/x/exp/constraints" 5 | ) 6 | 7 | // Ints transforms each element to an integer. 8 | func Ints[T constraints.Ordered](ss []T) []int { 9 | return Map(ss, Int[T]) 10 | } 11 | -------------------------------------------------------------------------------- /v2/ints_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestInts(t *testing.T) { 10 | assert.Equal(t, []int(nil), pie.Ints([]int(nil))) 11 | 12 | assert.Equal(t, 13 | []int{92, 823, 453}, 14 | pie.Ints([]float64{92.384, 823.324, 453})) 15 | } 16 | -------------------------------------------------------------------------------- /v2/join.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | import ( 4 | "golang.org/x/exp/constraints" 5 | "strings" 6 | ) 7 | 8 | // Join returns a string from joining each of the elements. 9 | func Join[T constraints.Ordered](ss []T, glue string) (s string) { 10 | parts := make([]string, len(ss)) 11 | for i, element := range ss { 12 | parts[i] = String(element) 13 | } 14 | 15 | return strings.Join(parts, glue) 16 | } 17 | -------------------------------------------------------------------------------- /v2/join_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestJoin(t *testing.T) { 10 | assert.Equal(t, "", pie.Join([]float64(nil), "a")) 11 | assert.Equal(t, "", pie.Join([]float64{}, "a")) 12 | var f1, f2 float64 = 0.1, 20000000000000000 13 | assert.Equal(t, "0.1;2e+16", pie.Join([]float64{f1, f2}, ";")) 14 | } 15 | -------------------------------------------------------------------------------- /v2/json_bytes.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | import ( 4 | "encoding/json" 5 | "golang.org/x/exp/constraints" 6 | ) 7 | 8 | // JSONBytes returns the JSON encoded array as bytes. 9 | // 10 | // One important thing to note is that it will treat a nil slice as an empty 11 | // slice to ensure that the JSON value return is always an array. 12 | func JSONBytes[T constraints.Ordered](ss []T) []byte { 13 | if ss == nil { 14 | return []byte("[]") 15 | } 16 | 17 | // An error should not be possible. 18 | data, _ := json.Marshal(ss) 19 | 20 | return data 21 | } 22 | -------------------------------------------------------------------------------- /v2/json_bytes_indent.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | import ( 4 | "encoding/json" 5 | "golang.org/x/exp/constraints" 6 | ) 7 | 8 | // JSONBytesIndent returns the JSON encoded array as bytes with indent applied. 9 | // 10 | // One important thing to note is that it will treat a nil slice as an empty 11 | // slice to ensure that the JSON value return is always an array. See 12 | // json.MarshalIndent for details. 13 | func JSONBytesIndent[T constraints.Ordered](ss []T, prefix, indent string) []byte { 14 | if ss == nil { 15 | return []byte("[]") 16 | } 17 | 18 | // An error should not be possible. 19 | data, _ := json.MarshalIndent(ss, prefix, indent) 20 | 21 | return data 22 | } 23 | -------------------------------------------------------------------------------- /v2/json_bytes_indent_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | var jsonIndentTests = []struct { 10 | ss []float64 11 | jsonString string 12 | }{ 13 | { 14 | nil, 15 | `[]`, // Instead of null. 16 | }, 17 | { 18 | []float64{}, 19 | `[]`, 20 | }, 21 | { 22 | []float64{12.3}, 23 | `[ 24 | 12.3 25 | ]`, 26 | }, 27 | { 28 | []float64{23, -2.5, 3424, 12.3}, 29 | `[ 30 | 23, 31 | -2.5, 32 | 3424, 33 | 12.3 34 | ]`, 35 | }, 36 | } 37 | 38 | func TestJSONBytesIndent(t *testing.T) { 39 | for _, test := range jsonIndentTests { 40 | t.Run("", func(t *testing.T) { 41 | assert.Equal(t, []byte(test.jsonString), pie.JSONBytesIndent(test.ss, "", " ")) 42 | }) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /v2/json_bytes_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | var jsonTests = []struct { 10 | ss []float64 11 | jsonString string 12 | }{ 13 | { 14 | nil, 15 | `[]`, // Instead of null. 16 | }, 17 | { 18 | []float64{}, 19 | `[]`, 20 | }, 21 | { 22 | []float64{12.3}, 23 | `[12.3]`, 24 | }, 25 | { 26 | []float64{23, -2.5, 3424, 12.3}, 27 | `[23,-2.5,3424,12.3]`, 28 | }, 29 | } 30 | 31 | func TestJSONBytes(t *testing.T) { 32 | for _, test := range jsonTests { 33 | t.Run("", func(t *testing.T) { 34 | assert.Equal(t, []byte(test.jsonString), pie.JSONBytes(test.ss)) 35 | }) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /v2/json_string.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | import ( 4 | "encoding/json" 5 | "golang.org/x/exp/constraints" 6 | ) 7 | 8 | // JSONString returns the JSON encoded array as a string. 9 | // 10 | // One important thing to note is that it will treat a nil slice as an empty 11 | // slice to ensure that the JSON value return is always an array. 12 | func JSONString[T constraints.Ordered](ss []T) string { 13 | if ss == nil { 14 | return "[]" 15 | } 16 | 17 | // An error should not be possible. 18 | data, _ := json.Marshal(ss) 19 | 20 | return string(data) 21 | } 22 | -------------------------------------------------------------------------------- /v2/json_string_indent.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | import ( 4 | "encoding/json" 5 | "golang.org/x/exp/constraints" 6 | ) 7 | 8 | // JSONStringIndent returns the JSON encoded array as a string with indent applied. 9 | // 10 | // One important thing to note is that it will treat a nil slice as an empty 11 | // slice to ensure that the JSON value return is always an array. See 12 | // json.MarshalIndent for details. 13 | func JSONStringIndent[T constraints.Ordered](ss []T, prefix, indent string) string { 14 | if ss == nil { 15 | return "[]" 16 | } 17 | 18 | // An error should not be possible. 19 | data, _ := json.MarshalIndent(ss, prefix, indent) 20 | 21 | return string(data) 22 | } 23 | -------------------------------------------------------------------------------- /v2/json_string_indent_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestJSONStringIndent(t *testing.T) { 10 | for _, test := range jsonIndentTests { 11 | t.Run("", func(t *testing.T) { 12 | assert.Equal(t, test.jsonString, pie.JSONStringIndent(test.ss, "", " ")) 13 | }) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /v2/json_string_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestJSONString(t *testing.T) { 10 | for _, test := range jsonTests { 11 | t.Run("", func(t *testing.T) { 12 | assert.Equal(t, test.jsonString, pie.JSONString(test.ss)) 13 | }) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /v2/keys.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | // Keys returns the keys in the map. All of the items will be unique. 4 | // 5 | // Due to Go's randomization of iterating maps the order is not deterministic. 6 | func Keys[K comparable, V any](m map[K]V) []K { 7 | // Avoid allocation 8 | l := len(m) 9 | if l == 0 { 10 | return nil 11 | } 12 | 13 | i := 0 14 | keys := make([]K, len(m)) 15 | for key := range m { 16 | keys[i] = key 17 | i++ 18 | } 19 | 20 | return keys 21 | } 22 | -------------------------------------------------------------------------------- /v2/keys_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "sort" 7 | "testing" 8 | ) 9 | 10 | type currency struct { 11 | NumericCode, Exponent int 12 | } 13 | 14 | type currencies map[string]currency 15 | 16 | var isoCurrencies = currencies{ 17 | "AUD": {36, -2}, 18 | "USD": {840, -2}, 19 | } 20 | 21 | func TestKeys(t *testing.T) { 22 | assert.Equal(t, []string(nil), pie.Keys(currencies(nil))) 23 | 24 | assert.Equal(t, []string(nil), pie.Keys(currencies{})) 25 | 26 | keys := pie.Keys(isoCurrencies) 27 | sort.Strings(keys) 28 | assert.Equal(t, []string{"AUD", "USD"}, keys) 29 | } 30 | -------------------------------------------------------------------------------- /v2/last.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | // Last returns the last element or a zero value if there are no elements. 4 | func Last[T any](ss []T) T { 5 | if len(ss) == 0 { 6 | var zeroValue T 7 | 8 | return zeroValue 9 | } 10 | 11 | return ss[len(ss)-1] 12 | } 13 | -------------------------------------------------------------------------------- /v2/last_or.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | // LastOr returns the last element or a default value if there are no elements. 4 | func LastOr[T any](ss []T, defaultValue T) T { 5 | if len(ss) == 0 { 6 | return defaultValue 7 | } 8 | 9 | return ss[len(ss)-1] 10 | } 11 | -------------------------------------------------------------------------------- /v2/last_or_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestLastOr(t *testing.T) { 10 | for _, test := range firstOrAndLastOrTests { 11 | t.Run("", func(t *testing.T) { 12 | assert.Equal(t, test.lastOr, pie.LastOr(test.ss, 202)) 13 | }) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /v2/last_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestLast(t *testing.T) { 10 | for _, test := range firstAndLastTests { 11 | t.Run("", func(t *testing.T) { 12 | assert.Equal(t, test.last, pie.Last(test.ss)) 13 | }) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /v2/map.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | // Map will return a new slice where each element has been mapped (transformed). 4 | // The number of elements returned will always be the same as the input. 5 | // 6 | // Be careful when using this with slices of pointers. If you modify the input 7 | // value it will affect the original slice. Be sure to return a new allocated 8 | // object or deep copy the existing one. 9 | func Map[T any, U any](ss []T, fn func(T) U) (ss2 []U) { 10 | if ss == nil { 11 | return nil 12 | } 13 | 14 | ss2 = make([]U, len(ss)) 15 | for i, s := range ss { 16 | ss2[i] = fn(s) 17 | } 18 | 19 | return 20 | } 21 | -------------------------------------------------------------------------------- /v2/map_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestMap(t *testing.T) { 10 | for _, test := range selectTests { 11 | t.Run("", func(t *testing.T) { 12 | assert.Equal(t, test.expectedMap, pie.Map(test.ss, func(a float64) float64 { 13 | return a + 5.2 14 | })) 15 | }) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /v2/max.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | import ( 4 | "golang.org/x/exp/constraints" 5 | ) 6 | 7 | // Max is the maximum value, or zero. 8 | func Max[T constraints.Ordered](ss []T) (max T) { 9 | if len(ss) == 0 { 10 | return 11 | } 12 | 13 | max = ss[0] 14 | for _, s := range ss { 15 | if s > max { 16 | max = s 17 | } 18 | } 19 | 20 | return 21 | } 22 | -------------------------------------------------------------------------------- /v2/max_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestMax(t *testing.T) { 10 | for _, test := range statsTests { 11 | t.Run("", func(t *testing.T) { 12 | assert.Equal(t, test.max, pie.Max(test.ss)) 13 | }) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /v2/median.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | import "golang.org/x/exp/constraints" 4 | 5 | // Median returns the value separating the higher half from the lower half of a 6 | // data sample. 7 | // 8 | // Zero is returned if there are no elements in the slice. 9 | // 10 | // If the number of elements is even, then the ElementType mean of the two 11 | // "median values" is returned. 12 | func Median[T constraints.Integer | constraints.Float](ss []T) T { 13 | n := len(ss) 14 | if n == 0 { 15 | return 0 16 | } 17 | 18 | if n == 1 { 19 | return ss[0] 20 | } 21 | 22 | // This implementation aims at linear time O(n) on average. 23 | // It uses the same idea as QuickSort, but makes only 1 recursive 24 | // call instead of 2. See also Quickselect. 25 | 26 | work := make([]T, len(ss)) 27 | copy(work, ss) 28 | 29 | limit1, limit2 := n/2, n/2+1 30 | if n%2 == 0 { 31 | limit1, limit2 = n/2-1, n/2+1 32 | } 33 | 34 | var rec func(a, b int) 35 | rec = func(a, b int) { 36 | if b-a <= 1 { 37 | return 38 | } 39 | ipivot := (a + b) / 2 40 | pivot := work[ipivot] 41 | work[a], work[ipivot] = work[ipivot], work[a] 42 | j := a 43 | k := b 44 | for j+1 < k { 45 | if work[j+1] < pivot { 46 | work[j+1], work[j] = work[j], work[j+1] 47 | j++ 48 | } else { 49 | work[j+1], work[k-1] = work[k-1], work[j+1] 50 | k-- 51 | } 52 | } 53 | // 1 or 0 recursive calls 54 | if j > limit1 { 55 | rec(a, j) 56 | } 57 | if j+1 < limit2 { 58 | rec(j+1, b) 59 | } 60 | } 61 | 62 | rec(0, len(work)) 63 | 64 | if n%2 == 1 { 65 | return work[n/2] 66 | } else { 67 | return (work[n/2-1] + work[n/2]) / 2 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /v2/median_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestMedian(t *testing.T) { 10 | assert.Equal(t, 0.0, pie.Median([]float64{})) 11 | assert.Equal(t, 12.3, pie.Median([]float64{12.3})) 12 | assert.Equal(t, 8.4, pie.Median([]float64{12.3, 4.5})) 13 | assert.Equal(t, 4.5, pie.Median([]float64{2.1, 12.3, 4.5})) 14 | } 15 | -------------------------------------------------------------------------------- /v2/min.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | import ( 4 | "golang.org/x/exp/constraints" 5 | ) 6 | 7 | // Min is the minimum value, or zero. 8 | func Min[T constraints.Ordered](ss []T) (min T) { 9 | if len(ss) == 0 { 10 | return 11 | } 12 | 13 | min = ss[0] 14 | for _, s := range ss { 15 | if s < min { 16 | min = s 17 | } 18 | } 19 | 20 | return 21 | } 22 | -------------------------------------------------------------------------------- /v2/min_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestMin(t *testing.T) { 10 | for _, test := range statsTests { 11 | t.Run("", func(t *testing.T) { 12 | assert.Equal(t, test.min, pie.Min(test.ss)) 13 | }) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /v2/mode.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | // Mode returns a new slice containing the most frequently occuring values. 4 | // 5 | // The number of items returned may be the same as the input or less. It will 6 | // never return zero items unless the input slice has zero items. 7 | func Mode[T comparable](ss []T) []T { 8 | if len(ss) == 0 { 9 | return nil 10 | } 11 | 12 | values := make(map[T]int) 13 | for _, s := range ss { 14 | values[s]++ 15 | } 16 | 17 | var maxFrequency int 18 | for _, v := range values { 19 | if v > maxFrequency { 20 | maxFrequency = v 21 | } 22 | } 23 | 24 | var maxValues []T 25 | for k, v := range values { 26 | if v == maxFrequency { 27 | maxValues = append(maxValues, k) 28 | } 29 | } 30 | 31 | return maxValues 32 | } 33 | -------------------------------------------------------------------------------- /v2/mode_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestMode(t *testing.T) { 10 | cmp := func(a, b []float64) bool { 11 | m := make(map[float64]struct{}) 12 | for _, i := range a { 13 | m[i] = struct{}{} 14 | } 15 | 16 | for _, i := range b { 17 | if _, ok := m[i]; !ok { 18 | return false 19 | } 20 | } 21 | 22 | return true 23 | } 24 | 25 | for _, test := range statsTests { 26 | t.Run("", func(t *testing.T) { 27 | assert.True(t, cmp(test.mode, pie.Mode(test.ss))) 28 | }) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /v2/of.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | import ( 4 | "context" 5 | "math/rand" 6 | ) 7 | 8 | // Of encapsulates a slice to be used in multiple chained operations. 9 | func Of[T any](ss []T) OfSlice[T] { 10 | return OfSlice[T]{ss} 11 | } 12 | 13 | // OfSlice provides the proxy methods that operate on slices. If the last method 14 | // in the chain does not return a single value, you can access the Result to get 15 | // final slice. 16 | type OfSlice[T any] struct { 17 | Result []T 18 | } 19 | 20 | // All will return true if all callbacks return true. It follows the same logic 21 | // as the all() function in Python. 22 | // 23 | // If the list is empty then true is always returned. 24 | func (o OfSlice[T]) All(fn func(value T) bool) bool { 25 | return All(o.Result, fn) 26 | } 27 | 28 | // Any will return true if any callbacks return true. It follows the same logic 29 | // as the any() function in Python. 30 | // 31 | // If the list is empty then false is always returned. 32 | func (o OfSlice[T]) Any(fn func(value T) bool) bool { 33 | return Any(o.Result, fn) 34 | } 35 | 36 | // Bottom will return n elements from bottom 37 | // 38 | // that means that elements is taken from the end of the slice 39 | // for this [1,2,3] slice with n == 2 will be returned [3,2] 40 | // if the slice has less elements then n that'll return all elements 41 | // if n < 0 it'll return empty slice. 42 | func (o OfSlice[T]) Bottom(n int) OfSlice[T] { 43 | return OfSlice[T]{Bottom(o.Result, n)} 44 | } 45 | 46 | // DropTop will return the rest slice after dropping the top n elements 47 | // if the slice has less elements then n that'll return empty slice 48 | // if n < 0 it'll return empty slice. 49 | func (o OfSlice[T]) DropTop(n int) OfSlice[T] { 50 | return OfSlice[T]{DropTop(o.Result, n)} 51 | } 52 | 53 | // Each is more condensed version of Transform that allows an action to happen 54 | // on each elements and pass the original slice on. 55 | // 56 | // pie.Each(cars, func (car *Car) { 57 | // fmt.Printf("Car color is: %s\n", car.Color) 58 | // }) 59 | // 60 | // Pie will not ensure immutability on items passed in so they can be 61 | // manipulated, if you choose to do it this way, for example: 62 | // 63 | // // Set all car colors to Red. 64 | // pie.Each(cars, func (car *Car) { 65 | // car.Color = "Red" 66 | // }) 67 | func (o OfSlice[T]) Each(fn func(T)) OfSlice[T] { 68 | return OfSlice[T]{Each(o.Result, fn)} 69 | } 70 | 71 | // Filter will return a new slice containing only the elements that return 72 | // true from the condition. The returned slice may contain zero elements (nil). 73 | // 74 | // FilterNot works in the opposite way of Filter. 75 | func (o OfSlice[T]) Filter(condition func(T) bool) OfSlice[T] { 76 | return OfSlice[T]{Filter(o.Result, condition)} 77 | } 78 | 79 | // FilterNot works the same as Filter, with a negated condition. That is, it will 80 | // return a new slice only containing the elements that returned false from the 81 | // condition. The returned slice may contain zero elements (nil). 82 | func (o OfSlice[T]) FilterNot(condition func(T) bool) OfSlice[T] { 83 | return OfSlice[T]{FilterNot(o.Result, condition)} 84 | } 85 | 86 | // FindFirstUsing will return the index of the first element when the callback 87 | // returns true or -1 if no element is found. 88 | // It follows the same logic as the findIndex() function in Javascript. 89 | // 90 | // If the list is empty then -1 is always returned. 91 | func (o OfSlice[T]) FindFirstUsing(fn func(value T) bool) int { 92 | return FindFirstUsing(o.Result, fn) 93 | } 94 | 95 | // First returns the first element or a zero value if there are no elements. 96 | func (o OfSlice[T]) First() T { 97 | return First(o.Result) 98 | } 99 | 100 | // FirstOr returns the first element or a default value if there are no 101 | // elements. 102 | func (o OfSlice[T]) FirstOr(defaultValue T) T { 103 | return FirstOr(o.Result, defaultValue) 104 | } 105 | 106 | // Insert a value at an index. 107 | func (o OfSlice[T]) Insert(index int, values ...T) OfSlice[T] { 108 | return OfSlice[T]{Insert(o.Result, index, values...)} 109 | } 110 | 111 | // Last returns the last element or a zero value if there are no elements. 112 | func (o OfSlice[T]) Last() T { 113 | return Last(o.Result) 114 | } 115 | 116 | // LastOr returns the last element or a default value if there are no elements. 117 | func (o OfSlice[T]) LastOr(defaultValue T) T { 118 | return LastOr(o.Result, defaultValue) 119 | } 120 | 121 | // Map will return a new slice where each element has been mapped (transformed). 122 | // The number of elements returned will always be the same as the input. 123 | // 124 | // Be careful when using this with slices of pointers. If you modify the input 125 | // value it will affect the original slice. Be sure to return a new allocated 126 | // object or deep copy the existing one. 127 | func (o OfSlice[T]) Map(fn func(T) T) OfSlice[T] { 128 | return OfSlice[T]{Map(o.Result, fn)} 129 | } 130 | 131 | // Reverse returns a new copy of the slice with the elements ordered in reverse. 132 | // This is useful when combined with Sort to get a descending sort order: 133 | // 134 | // ss.Sort().Reverse() 135 | func (o OfSlice[T]) Reverse() OfSlice[T] { 136 | return OfSlice[T]{Reverse(o.Result)} 137 | } 138 | 139 | // Rotate returns slice circularly rotated by a number of positions n. 140 | // If n is positive, the slice is rotated right. 141 | // If n is negative, the slice is rotated left. 142 | func (o OfSlice[T]) Rotate(n int) OfSlice[T] { 143 | return OfSlice[T]{Rotate(o.Result, n)} 144 | } 145 | 146 | // Send sends elements to channel 147 | // in normal act it sends all elements but if func canceled it can be less 148 | // 149 | // it locks execution of gorutine 150 | // it doesn't close channel after work 151 | // returns sent elements if len(this) != len(old) considered func was canceled 152 | func (o OfSlice[T]) Send(ctx context.Context, ch chan<- T) OfSlice[T] { 153 | return OfSlice[T]{Send(ctx, o.Result, ch)} 154 | } 155 | 156 | // SequenceUsing generates slice in range using creator function 157 | // 158 | // There are 3 variations to generate: 159 | // 1. [0, n). 160 | // 2. [min, max). 161 | // 3. [min, max) with step. 162 | // 163 | // if len(params) == 1 considered that will be returned slice between 0 and n, 164 | // where n is the first param, [0, n). 165 | // if len(params) == 2 considered that will be returned slice between min and max, 166 | // where min is the first param, max is the second, [min, max). 167 | // if len(params) > 2 considered that will be returned slice between min and max with step, 168 | // where min is the first param, max is the second, step is the third one, [min, max) with step, 169 | // others params will be ignored 170 | func (o OfSlice[T]) SequenceUsing(creator func(int) T, params ...int) OfSlice[T] { 171 | return OfSlice[T]{SequenceUsing(o.Result, creator, params...)} 172 | } 173 | 174 | // Shuffle returns a new shuffled slice by your rand.Source. The original slice 175 | // is not modified. 176 | func (o OfSlice[T]) Shuffle(source rand.Source) OfSlice[T] { 177 | return OfSlice[T]{Shuffle(o.Result, source)} 178 | } 179 | 180 | // SortUsing works similar to sort.Slice. However, unlike sort.Slice the 181 | // slice returned will be reallocated as to not modify the input slice. 182 | func (o OfSlice[T]) SortUsing(less func(a, b T) bool) OfSlice[T] { 183 | return OfSlice[T]{SortUsing(o.Result, less)} 184 | } 185 | 186 | // StringsUsing transforms each element to a string. 187 | func (o OfSlice[T]) StringsUsing(transform func(T) string) []string { 188 | return StringsUsing(o.Result, transform) 189 | } 190 | 191 | // SubSlice will return the subSlice from start to end(excluded) 192 | // 193 | // Condition 1: If start < 0 or end < 0, nil is returned. 194 | // Condition 2: If start >= end, nil is returned. 195 | // Condition 3: Return all elements that exist in the range provided, 196 | // if start or end is out of bounds, zero items will be placed. 197 | func (o OfSlice[T]) SubSlice(start int, end int) OfSlice[T] { 198 | return OfSlice[T]{SubSlice(o.Result, start, end)} 199 | } 200 | 201 | // Top will return n elements from head of the slice 202 | // if the slice has less elements then n that'll return all elements 203 | // if n < 0 it'll return empty slice. 204 | func (o OfSlice[T]) Top(n int) OfSlice[T] { 205 | return OfSlice[T]{Top(o.Result, n)} 206 | } 207 | 208 | // Unshift adds one or more elements to the beginning of the slice 209 | // and returns the new slice. 210 | func (o OfSlice[T]) Unshift(elements ...T) OfSlice[T] { 211 | return OfSlice[T]{Unshift(o.Result, elements...)} 212 | } 213 | 214 | // Removes element at index in idx from input slice, returns resulting slice. 215 | // If an index in idx out of bounds, skip it. 216 | func (o OfSlice[T]) Delete(idx ...int) OfSlice[T] { 217 | return OfSlice[T]{Delete(o.Result, idx...)} 218 | } 219 | -------------------------------------------------------------------------------- /v2/of_numeric.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | import ( 4 | "context" 5 | "math/rand" 6 | 7 | "golang.org/x/exp/constraints" 8 | ) 9 | 10 | // OfNumeric encapsulates a slice to be used in multiple chained operations. 11 | // OfNumeric requires that elements be numerical for certain operations to be 12 | // performed. 13 | func OfNumeric[T constraints.Integer | constraints.Float](ss []T) OfNumericSlice[T] { 14 | return OfNumericSlice[T]{ss} 15 | } 16 | 17 | // OfNumericSlice provides the proxy methods that operate on slices. If the last 18 | // method in the chain does not return a single value, you can access the Result 19 | // to get final slice. 20 | type OfNumericSlice[T constraints.Integer | constraints.Float] struct { 21 | Result []T 22 | } 23 | 24 | func (o OfNumericSlice[T]) All(fn func(value T) bool) bool { 25 | return All(o.Result, fn) 26 | } 27 | 28 | func (o OfNumericSlice[T]) Any(fn func(value T) bool) bool { 29 | return Any(o.Result, fn) 30 | } 31 | 32 | func (o OfNumericSlice[T]) AreSorted() bool { 33 | return AreSorted(o.Result) 34 | } 35 | 36 | func (o OfNumericSlice[T]) AreUnique() bool { 37 | return AreUnique(o.Result) 38 | } 39 | 40 | func (o OfNumericSlice[T]) Average() float64 { 41 | return Average(o.Result) 42 | } 43 | 44 | func (o OfNumericSlice[T]) Bottom(n int) OfNumericSlice[T] { 45 | return OfNumericSlice[T]{Bottom(o.Result, n)} 46 | } 47 | 48 | func (o OfNumericSlice[T]) Contains(lookingFor T) bool { 49 | return Contains(o.Result, lookingFor) 50 | } 51 | 52 | func (o OfNumericSlice[T]) Diff(against []T) ([]T, []T) { 53 | return Diff(o.Result, against) 54 | } 55 | 56 | func (o OfNumericSlice[T]) DropTop(n int) OfNumericSlice[T] { 57 | return OfNumericSlice[T]{DropTop(o.Result, n)} 58 | } 59 | 60 | func (o OfNumericSlice[T]) DropWhile(f func(s T) bool) OfNumericSlice[T] { 61 | return OfNumericSlice[T]{DropWhile(o.Result, f)} 62 | } 63 | 64 | func (o OfNumericSlice[T]) Each(fn func(T)) OfNumericSlice[T] { 65 | return OfNumericSlice[T]{Each(o.Result, fn)} 66 | } 67 | 68 | func (o OfNumericSlice[T]) Equals(rhs []T) bool { 69 | return Equals(o.Result, rhs) 70 | } 71 | 72 | func (o OfNumericSlice[T]) Filter(condition func(T) bool) OfNumericSlice[T] { 73 | return OfNumericSlice[T]{Filter(o.Result, condition)} 74 | } 75 | 76 | func (o OfNumericSlice[T]) FilterNot(condition func(T) bool) OfNumericSlice[T] { 77 | return OfNumericSlice[T]{FilterNot(o.Result, condition)} 78 | } 79 | 80 | func (o OfNumericSlice[T]) FindFirstUsing(fn func(value T) bool) int { 81 | return FindFirstUsing(o.Result, fn) 82 | } 83 | 84 | func (o OfNumericSlice[T]) First() T { 85 | return First(o.Result) 86 | } 87 | 88 | func (o OfNumericSlice[T]) FirstOr(defaultValue T) T { 89 | return FirstOr(o.Result, defaultValue) 90 | } 91 | 92 | func (o OfNumericSlice[T]) Float64s() []float64 { 93 | return Float64s(o.Result) 94 | } 95 | 96 | func (o OfNumericSlice[T]) Group() map[T]int { 97 | return Group(o.Result) 98 | } 99 | 100 | func (o OfNumericSlice[T]) Insert(index int, values ...T) OfNumericSlice[T] { 101 | return OfNumericSlice[T]{Insert(o.Result, index, values...)} 102 | } 103 | 104 | func (o OfNumericSlice[T]) Intersect(slices ...[]T) OfNumericSlice[T] { 105 | return OfNumericSlice[T]{Intersect(o.Result, slices...)} 106 | } 107 | 108 | func (o OfNumericSlice[T]) Ints() []int { 109 | return Ints(o.Result) 110 | } 111 | 112 | func (o OfNumericSlice[T]) Join(glue string) string { 113 | return Join(o.Result, glue) 114 | } 115 | 116 | func (o OfNumericSlice[T]) JSONBytes() []byte { 117 | return JSONBytes(o.Result) 118 | } 119 | 120 | func (o OfNumericSlice[T]) JSONBytesIndent(prefix, indent string) []byte { 121 | return JSONBytesIndent(o.Result, prefix, indent) 122 | } 123 | 124 | func (o OfNumericSlice[T]) JSONString() string { 125 | return JSONString(o.Result) 126 | } 127 | 128 | func (o OfNumericSlice[T]) JSONStringIndent(prefix, indent string) string { 129 | return JSONStringIndent(o.Result, prefix, indent) 130 | } 131 | 132 | func (o OfNumericSlice[T]) Last() T { 133 | return Last(o.Result) 134 | } 135 | 136 | func (o OfNumericSlice[T]) LastOr(defaultValue T) T { 137 | return LastOr(o.Result, defaultValue) 138 | } 139 | 140 | func (o OfNumericSlice[T]) Map(fn func(T) T) OfNumericSlice[T] { 141 | return OfNumericSlice[T]{Map(o.Result, fn)} 142 | } 143 | 144 | func (o OfNumericSlice[T]) Max() T { 145 | return Max(o.Result) 146 | } 147 | 148 | func (o OfNumericSlice[T]) Median() T { 149 | return Median(o.Result) 150 | } 151 | 152 | func (o OfNumericSlice[T]) Min() T { 153 | return Min(o.Result) 154 | } 155 | 156 | func (o OfNumericSlice[T]) Mode() OfNumericSlice[T] { 157 | return OfNumericSlice[T]{Mode(o.Result)} 158 | } 159 | 160 | func (o OfNumericSlice[T]) Product() T { 161 | return Product(o.Result) 162 | } 163 | 164 | func (o OfNumericSlice[T]) Random(source rand.Source) T { 165 | return Random(o.Result, source) 166 | } 167 | 168 | func (o OfNumericSlice[T]) Reduce(reducer func(T, T) T) T { 169 | return Reduce(o.Result, reducer) 170 | } 171 | 172 | func (o OfNumericSlice[T]) Reverse() OfNumericSlice[T] { 173 | return OfNumericSlice[T]{Reverse(o.Result)} 174 | } 175 | 176 | func (o OfNumericSlice[T]) Send(ctx context.Context, ch chan<- T) OfNumericSlice[T] { 177 | return OfNumericSlice[T]{Send(ctx, o.Result, ch)} 178 | } 179 | 180 | func (o OfNumericSlice[T]) Sequence(params ...int) OfNumericSlice[T] { 181 | return OfNumericSlice[T]{Sequence(o.Result, params...)} 182 | } 183 | 184 | func (o OfNumericSlice[T]) SequenceUsing(creator func(int) T, params ...int) OfNumericSlice[T] { 185 | return OfNumericSlice[T]{SequenceUsing(o.Result, creator, params...)} 186 | } 187 | 188 | func (o OfNumericSlice[T]) Shuffle(source rand.Source) OfNumericSlice[T] { 189 | return OfNumericSlice[T]{Shuffle(o.Result, source)} 190 | } 191 | 192 | func (o OfNumericSlice[T]) Sort() OfNumericSlice[T] { 193 | return OfNumericSlice[T]{Sort(o.Result)} 194 | } 195 | 196 | func (o OfNumericSlice[T]) SortStableUsing(less func(a, b T) bool) OfNumericSlice[T] { 197 | return OfNumericSlice[T]{SortStableUsing(o.Result, less)} 198 | } 199 | 200 | func (o OfNumericSlice[T]) SortUsing(less func(a, b T) bool) OfNumericSlice[T] { 201 | return OfNumericSlice[T]{SortUsing(o.Result, less)} 202 | } 203 | 204 | func (o OfNumericSlice[T]) Stddev() float64 { 205 | return Stddev(o.Result) 206 | } 207 | 208 | func (o OfNumericSlice[T]) Strings() []string { 209 | return Strings(o.Result) 210 | } 211 | 212 | func (o OfNumericSlice[T]) StringsUsing(transform func(T) string) []string { 213 | return StringsUsing(o.Result, transform) 214 | } 215 | 216 | func (o OfNumericSlice[T]) SubSlice(start int, end int) OfNumericSlice[T] { 217 | return OfNumericSlice[T]{SubSlice(o.Result, start, end)} 218 | } 219 | 220 | func (o OfNumericSlice[T]) Sum() T { 221 | return Sum(o.Result) 222 | } 223 | 224 | func (o OfNumericSlice[T]) Top(n int) OfNumericSlice[T] { 225 | return OfNumericSlice[T]{Top(o.Result, n)} 226 | } 227 | 228 | func (o OfNumericSlice[T]) Unique() OfNumericSlice[T] { 229 | return OfNumericSlice[T]{Unique(o.Result)} 230 | } 231 | 232 | func (o OfNumericSlice[T]) UniqueStable() OfNumericSlice[T] { 233 | return OfNumericSlice[T]{UniqueStable(o.Result)} 234 | } 235 | 236 | func (o OfNumericSlice[T]) Unshift(elements ...T) OfNumericSlice[T] { 237 | return OfNumericSlice[T]{Unshift(o.Result, elements...)} 238 | } 239 | 240 | func (o OfNumericSlice[T]) Delete(idx ...int) OfNumericSlice[T] { 241 | return OfNumericSlice[T]{Delete(o.Result, idx...)} 242 | } 243 | -------------------------------------------------------------------------------- /v2/of_numeric_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/elliotchance/pie/v2" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestOfONumeric(t *testing.T) { 11 | t.Run("chaining", func(t *testing.T) { 12 | total := pie.OfNumeric([]float64{123, 456}). 13 | Sum() 14 | 15 | assert.Equal(t, 579.0, total) 16 | }) 17 | 18 | t.Run("result", func(t *testing.T) { 19 | names := pie.OfNumeric([]float64{1.23, 4.56}). 20 | Filter(func(x float64) bool { 21 | return x < 4 22 | }). 23 | Result 24 | 25 | assert.Equal(t, []float64{1.23}, names) 26 | }) 27 | 28 | t.Run("delete", func(t *testing.T) { 29 | names := pie.OfNumeric([]float64{1.23, 4.56}). 30 | Delete(1). 31 | Result 32 | 33 | assert.Equal(t, []float64{1.23}, names) 34 | }) 35 | 36 | t.Run("unique_stable", func(t *testing.T) { 37 | names := pie.OfNumeric([]float64{-4.56, 1.23, -4.56}). 38 | UniqueStable(). 39 | Result 40 | 41 | assert.Equal(t, []float64{-4.56, 1.23}, names) 42 | }) 43 | } 44 | -------------------------------------------------------------------------------- /v2/of_ordered.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | import ( 4 | "context" 5 | "math/rand" 6 | 7 | "golang.org/x/exp/constraints" 8 | ) 9 | 10 | // OfOrdered encapsulates a slice to be used in multiple chained operations. 11 | // OfOrdered requires that elements be numerical or a string for certain 12 | // operations to be performed. 13 | func OfOrdered[T constraints.Ordered](ss []T) OfOrderedSlice[T] { 14 | return OfOrderedSlice[T]{ss} 15 | } 16 | 17 | // OfOrderedSlice provides the proxy methods that operate on slices. If the last 18 | // method in the chain does not return a single value, you can access the Result 19 | // to get final slice. 20 | type OfOrderedSlice[T constraints.Ordered] struct { 21 | Result []T 22 | } 23 | 24 | func (o OfOrderedSlice[T]) All(fn func(value T) bool) bool { 25 | return All(o.Result, fn) 26 | } 27 | 28 | func (o OfOrderedSlice[T]) Any(fn func(value T) bool) bool { 29 | return Any(o.Result, fn) 30 | } 31 | 32 | func (o OfOrderedSlice[T]) AreSorted() bool { 33 | return AreSorted(o.Result) 34 | } 35 | 36 | func (o OfOrderedSlice[T]) AreUnique() bool { 37 | return AreUnique(o.Result) 38 | } 39 | 40 | func (o OfOrderedSlice[T]) Bottom(n int) OfOrderedSlice[T] { 41 | return OfOrderedSlice[T]{Bottom(o.Result, n)} 42 | } 43 | 44 | func (o OfOrderedSlice[T]) Contains(lookingFor T) bool { 45 | return Contains(o.Result, lookingFor) 46 | } 47 | 48 | func (o OfOrderedSlice[T]) Diff(against []T) ([]T, []T) { 49 | return Diff(o.Result, against) 50 | } 51 | 52 | func (o OfOrderedSlice[T]) DropTop(n int) OfOrderedSlice[T] { 53 | return OfOrderedSlice[T]{DropTop(o.Result, n)} 54 | } 55 | 56 | func (o OfOrderedSlice[T]) DropWhile(f func(s T) bool) OfOrderedSlice[T] { 57 | return OfOrderedSlice[T]{DropWhile(o.Result, f)} 58 | } 59 | 60 | func (o OfOrderedSlice[T]) Each(fn func(T)) OfOrderedSlice[T] { 61 | return OfOrderedSlice[T]{Each(o.Result, fn)} 62 | } 63 | 64 | func (o OfOrderedSlice[T]) Equals(rhs []T) bool { 65 | return Equals(o.Result, rhs) 66 | } 67 | 68 | func (o OfOrderedSlice[T]) Filter(condition func(T) bool) OfOrderedSlice[T] { 69 | return OfOrderedSlice[T]{Filter(o.Result, condition)} 70 | } 71 | 72 | func (o OfOrderedSlice[T]) FilterNot(condition func(T) bool) OfOrderedSlice[T] { 73 | return OfOrderedSlice[T]{FilterNot(o.Result, condition)} 74 | } 75 | 76 | func (o OfOrderedSlice[T]) FindFirstUsing(fn func(value T) bool) int { 77 | return FindFirstUsing(o.Result, fn) 78 | } 79 | 80 | func (o OfOrderedSlice[T]) First() T { 81 | return First(o.Result) 82 | } 83 | 84 | func (o OfOrderedSlice[T]) FirstOr(defaultValue T) T { 85 | return FirstOr(o.Result, defaultValue) 86 | } 87 | 88 | func (o OfOrderedSlice[T]) Float64s() []float64 { 89 | return Float64s(o.Result) 90 | } 91 | 92 | func (o OfOrderedSlice[T]) Group() map[T]int { 93 | return Group(o.Result) 94 | } 95 | 96 | func (o OfOrderedSlice[T]) Insert(index int, values ...T) OfOrderedSlice[T] { 97 | return OfOrderedSlice[T]{Insert(o.Result, index, values...)} 98 | } 99 | 100 | func (o OfOrderedSlice[T]) Intersect(slices ...[]T) OfOrderedSlice[T] { 101 | return OfOrderedSlice[T]{Intersect(o.Result, slices...)} 102 | } 103 | 104 | func (o OfOrderedSlice[T]) Ints() []int { 105 | return Ints(o.Result) 106 | } 107 | 108 | func (o OfOrderedSlice[T]) Join(glue string) string { 109 | return Join(o.Result, glue) 110 | } 111 | 112 | func (o OfOrderedSlice[T]) JSONBytes() []byte { 113 | return JSONBytes(o.Result) 114 | } 115 | 116 | func (o OfOrderedSlice[T]) JSONBytesIndent(prefix, indent string) []byte { 117 | return JSONBytesIndent(o.Result, prefix, indent) 118 | } 119 | 120 | func (o OfOrderedSlice[T]) JSONString() string { 121 | return JSONString(o.Result) 122 | } 123 | 124 | func (o OfOrderedSlice[T]) JSONStringIndent(prefix, indent string) string { 125 | return JSONStringIndent(o.Result, prefix, indent) 126 | } 127 | 128 | func (o OfOrderedSlice[T]) Last() T { 129 | return Last(o.Result) 130 | } 131 | 132 | func (o OfOrderedSlice[T]) LastOr(defaultValue T) T { 133 | return LastOr(o.Result, defaultValue) 134 | } 135 | 136 | func (o OfOrderedSlice[T]) Map(fn func(T) T) OfOrderedSlice[T] { 137 | return OfOrderedSlice[T]{Map(o.Result, fn)} 138 | } 139 | 140 | func (o OfOrderedSlice[T]) Max() T { 141 | return Max(o.Result) 142 | } 143 | 144 | func (o OfOrderedSlice[T]) Min() T { 145 | return Min(o.Result) 146 | } 147 | 148 | func (o OfOrderedSlice[T]) Mode() OfOrderedSlice[T] { 149 | return OfOrderedSlice[T]{Mode(o.Result)} 150 | } 151 | 152 | func (o OfOrderedSlice[T]) Reverse() OfOrderedSlice[T] { 153 | return OfOrderedSlice[T]{Reverse(o.Result)} 154 | } 155 | 156 | func (o OfOrderedSlice[T]) Send(ctx context.Context, ch chan<- T) OfOrderedSlice[T] { 157 | return OfOrderedSlice[T]{Send(ctx, o.Result, ch)} 158 | } 159 | 160 | func (o OfOrderedSlice[T]) SequenceUsing(creator func(int) T, params ...int) OfOrderedSlice[T] { 161 | return OfOrderedSlice[T]{SequenceUsing(o.Result, creator, params...)} 162 | } 163 | 164 | func (o OfOrderedSlice[T]) Shuffle(source rand.Source) OfOrderedSlice[T] { 165 | return OfOrderedSlice[T]{Shuffle(o.Result, source)} 166 | } 167 | 168 | func (o OfOrderedSlice[T]) Sort() OfOrderedSlice[T] { 169 | return OfOrderedSlice[T]{Sort(o.Result)} 170 | } 171 | 172 | func (o OfOrderedSlice[T]) SortStableUsing(less func(a, b T) bool) OfOrderedSlice[T] { 173 | return OfOrderedSlice[T]{SortStableUsing(o.Result, less)} 174 | } 175 | 176 | func (o OfOrderedSlice[T]) SortUsing(less func(a, b T) bool) OfOrderedSlice[T] { 177 | return OfOrderedSlice[T]{SortUsing(o.Result, less)} 178 | } 179 | 180 | func (o OfOrderedSlice[T]) Strings() []string { 181 | return Strings(o.Result) 182 | } 183 | 184 | func (o OfOrderedSlice[T]) StringsUsing(transform func(T) string) []string { 185 | return StringsUsing(o.Result, transform) 186 | } 187 | 188 | func (o OfOrderedSlice[T]) SubSlice(start int, end int) OfOrderedSlice[T] { 189 | return OfOrderedSlice[T]{SubSlice(o.Result, start, end)} 190 | } 191 | 192 | func (o OfOrderedSlice[T]) Top(n int) OfOrderedSlice[T] { 193 | return OfOrderedSlice[T]{Top(o.Result, n)} 194 | } 195 | 196 | func (o OfOrderedSlice[T]) Unique() OfOrderedSlice[T] { 197 | return OfOrderedSlice[T]{Unique(o.Result)} 198 | } 199 | 200 | func (o OfOrderedSlice[T]) UniqueStable() OfOrderedSlice[T] { 201 | return OfOrderedSlice[T]{UniqueStable(o.Result)} 202 | } 203 | 204 | func (o OfOrderedSlice[T]) Unshift(elements ...T) OfOrderedSlice[T] { 205 | return OfOrderedSlice[T]{Unshift(o.Result, elements...)} 206 | } 207 | 208 | func (o OfOrderedSlice[T]) Delete(idx ...int) OfOrderedSlice[T] { 209 | return OfOrderedSlice[T]{Delete(o.Result, idx...)} 210 | } 211 | -------------------------------------------------------------------------------- /v2/of_ordered_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/elliotchance/pie/v2" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestOfOrdered(t *testing.T) { 11 | t.Run("chaining", func(t *testing.T) { 12 | name := pie.OfOrdered([]string{"Bob", "Sally", "John", "Jane"}). 13 | Join("+") 14 | 15 | assert.Equal(t, "Bob+Sally+John+Jane", name) 16 | }) 17 | 18 | t.Run("result", func(t *testing.T) { 19 | names := pie.OfOrdered([]string{"Bob", "Sally", "John", "Jane"}). 20 | Filter(func(s string) bool { 21 | return len(s) < 4 22 | }). 23 | Result 24 | 25 | assert.Equal(t, []string{"Bob"}, names) 26 | }) 27 | 28 | t.Run("delete", func(t *testing.T) { 29 | names := pie.Of([]string{"Bob", "Sally", "John", "Jane"}). 30 | Delete(2, 3). 31 | Result 32 | 33 | assert.Equal(t, []string{"Bob", "Sally"}, names) 34 | }) 35 | 36 | t.Run("unique_stable", func(t *testing.T) { 37 | names := pie.OfNumeric([]float64{-4.56, 1.23, -4.56}). 38 | UniqueStable(). 39 | Result 40 | 41 | assert.Equal(t, []float64{-4.56, 1.23}, names) 42 | }) 43 | } 44 | -------------------------------------------------------------------------------- /v2/of_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/elliotchance/pie/v2" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestOf(t *testing.T) { 12 | t.Run("chaining", func(t *testing.T) { 13 | name := pie.Of([]string{"Bob", "Sally", "John", "Jane"}). 14 | FilterNot(func(name string) bool { 15 | return strings.HasPrefix(name, "J") 16 | }). 17 | Map(strings.ToUpper). 18 | LastOr("") 19 | 20 | assert.Equal(t, "SALLY", name) 21 | }) 22 | 23 | t.Run("result", func(t *testing.T) { 24 | names := pie.Of([]string{"Bob", "Sally", "John", "Jane"}). 25 | FilterNot(func(name string) bool { 26 | return strings.HasPrefix(name, "J") 27 | }). 28 | Result 29 | 30 | assert.Equal(t, []string{"Bob", "Sally"}, names) 31 | }) 32 | 33 | t.Run("delete", func(t *testing.T) { 34 | names := pie.Of([]string{"Bob", "Sally", "John", "Jane"}). 35 | Delete(2, 3). 36 | Result 37 | 38 | assert.Equal(t, []string{"Bob", "Sally"}, names) 39 | }) 40 | 41 | t.Run("rotate", func(t *testing.T) { 42 | names := pie.Of([]string{"Bob", "Sally", "John", "Jane"}). 43 | Rotate(1). 44 | Result 45 | 46 | assert.Equal(t, []string{"Jane", "Bob", "Sally", "John"}, names) 47 | }) 48 | } 49 | -------------------------------------------------------------------------------- /v2/pop.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | // Pop the first element of the slice 4 | // 5 | // Usage Example: 6 | // 7 | // type knownGreetings []string 8 | // greetings := knownGreetings{"ciao", "hello", "hola"} 9 | // for greeting := greetings.Pop(); greeting != nil; greeting = greetings.Pop() { 10 | // fmt.Println(*greeting) 11 | // } 12 | // 13 | func Pop[T any](ss *[]T) (popped *T) { 14 | if len(*ss) == 0 { 15 | return 16 | } 17 | 18 | popped = &(*ss)[0] 19 | *ss = (*ss)[1:] 20 | 21 | return 22 | } 23 | -------------------------------------------------------------------------------- /v2/pop_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestPop(t *testing.T) { 10 | numbers := []float64{42.0, 4.2} 11 | 12 | assert.Equal(t, 42.0, *pie.Pop(&numbers)) 13 | assert.Equal(t, []float64{4.2}, numbers) 14 | 15 | assert.Equal(t, 4.2, *pie.Pop(&numbers)) 16 | assert.Equal(t, []float64{}, numbers) 17 | } 18 | -------------------------------------------------------------------------------- /v2/product.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | import "golang.org/x/exp/constraints" 4 | 5 | // Product is the product of all of the elements. 6 | func Product[T constraints.Integer | constraints.Float](ss []T) (product T) { 7 | if len(ss) == 0 { 8 | return 9 | } 10 | 11 | product = ss[0] 12 | for _, s := range ss[1:] { 13 | product *= s 14 | } 15 | 16 | return 17 | } 18 | -------------------------------------------------------------------------------- /v2/product_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestProduct(t *testing.T) { 10 | for _, test := range statsTests { 11 | t.Run("", func(t *testing.T) { 12 | assert.Equal(t, test.product, pie.Product(test.ss)) 13 | }) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /v2/random.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | import ( 4 | "golang.org/x/exp/constraints" 5 | "math/rand" 6 | ) 7 | 8 | // Random returns a random element by your rand.Source, or zero. 9 | func Random[T constraints.Integer | constraints.Float](ss []T, source rand.Source) T { 10 | n := len(ss) 11 | 12 | // Avoid the extra allocation. 13 | if n < 1 { 14 | return 0 15 | } 16 | 17 | if n < 2 { 18 | return ss[0] 19 | } 20 | 21 | rnd := rand.New(source) 22 | i := rnd.Intn(n) 23 | 24 | return ss[i] 25 | } 26 | -------------------------------------------------------------------------------- /v2/random_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "math/rand" 7 | "testing" 8 | ) 9 | 10 | var randomTests = []struct { 11 | ss []float64 12 | expected float64 13 | source rand.Source 14 | }{ 15 | { 16 | nil, 17 | 0.0, 18 | nil, 19 | }, 20 | { 21 | nil, 22 | 0.0, 23 | rand.NewSource(0), 24 | }, 25 | { 26 | []float64{}, 27 | 0.0, 28 | rand.NewSource(0), 29 | }, 30 | { 31 | []float64{12.3, 2.34, 4.56}, 32 | 12.3, 33 | rand.NewSource(0), 34 | }, 35 | { 36 | []float64{12.3, 2.34, 4.56}, 37 | 4.56, 38 | rand.NewSource(1), 39 | }, 40 | { 41 | []float64{12.3}, 42 | 12.3, 43 | rand.NewSource(0), 44 | }, 45 | } 46 | 47 | func TestRandom(t *testing.T) { 48 | for _, test := range randomTests { 49 | t.Run("", func(t *testing.T) { 50 | assert.Equal(t, test.expected, pie.Random(test.ss, test.source)) 51 | }) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /v2/reduce.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | // Reduce continually applies the provided function 4 | // over the slice. Reducing the elements to a single value. 5 | // 6 | // Returns a zero value of T if there are no elements in the slice. It will 7 | // panic if the reducer is nil and the slice has more than one element (required 8 | // to invoke reduce). Otherwise returns result of applying reducer from left to 9 | // right. 10 | func Reduce[T any](ss []T, reducer func(T, T) T) (el T) { 11 | if len(ss) == 0 { 12 | return 13 | } 14 | 15 | el = ss[0] 16 | for _, s := range ss[1:] { 17 | el = reducer(el, s) 18 | } 19 | 20 | return 21 | } 22 | -------------------------------------------------------------------------------- /v2/reduce_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | var reduceTests = []struct { 10 | ss []float64 11 | expected float64 12 | reducer func(a, b float64) float64 13 | }{ 14 | { 15 | []float64{1, 2, 3}, 16 | 6, 17 | func(a, b float64) float64 { return a + b }, 18 | }, 19 | { 20 | []float64{1, 2, 3}, 21 | -4, 22 | func(a, b float64) float64 { return a - b }, 23 | }, 24 | { 25 | []float64{}, 26 | 0, 27 | func(a, b float64) float64 { return a - b }, 28 | }, 29 | { 30 | []float64{1}, 31 | 1, 32 | func(a, b float64) float64 { return a - b }, 33 | }, 34 | } 35 | 36 | func TestReduce(t *testing.T) { 37 | for _, test := range reduceTests { 38 | t.Run("", func(t *testing.T) { 39 | assert.Equal(t, test.expected, pie.Reduce(test.ss, test.reducer)) 40 | }) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /v2/reverse.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | // Reverse returns a new copy of the slice with the elements ordered in reverse. 4 | // This is useful when combined with Sort to get a descending sort order: 5 | // 6 | // ss.Sort().Reverse() 7 | // 8 | func Reverse[T any](ss []T) []T { 9 | // Avoid the allocation. If there is one element or less it is already 10 | // reversed. 11 | if len(ss) < 2 { 12 | return ss 13 | } 14 | 15 | sorted := make([]T, len(ss)) 16 | for i := 0; i < len(ss); i++ { 17 | sorted[i] = ss[len(ss)-i-1] 18 | } 19 | 20 | return sorted 21 | } 22 | -------------------------------------------------------------------------------- /v2/reverse_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestReverse(t *testing.T) { 10 | for _, test := range sortTests { 11 | t.Run("", func(t *testing.T) { 12 | assert.Equal(t, test.reversed, pie.Reverse(test.ss)) 13 | }) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /v2/rotate.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | // Rotate return slice circularly rotated by a number of positions n. 4 | // If n is positive, the slice is rotated right. 5 | // If n is negative, the slice is rotated left. 6 | func Rotate[T any](ss []T, n int) []T { 7 | 8 | length := len(ss) 9 | 10 | // Avoid the allocation. 11 | // If there is one element or less, then already rotated. 12 | if length < 2 { 13 | return ss 14 | } 15 | 16 | // Normalize shift 17 | // no div by 0 since length >= 2 18 | shift := -n % length 19 | if shift < 0 { 20 | shift = length + shift 21 | } 22 | 23 | // Avoid the allocation. 24 | // If normalized shift is 0, then already rotated. 25 | if shift == 0 { 26 | return ss 27 | } 28 | 29 | return append(DropTop(ss, shift), Top(ss, shift)...) 30 | } 31 | -------------------------------------------------------------------------------- /v2/rotate_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/elliotchance/pie/v2" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | var rotateTests = []struct { 11 | ss []float64 12 | rotated []float64 13 | n int 14 | }{ 15 | { 16 | nil, 17 | nil, 18 | 0, 19 | }, 20 | { 21 | []float64{1.23, 2.34}, 22 | []float64{1.23, 2.34}, 23 | 0, 24 | }, 25 | { 26 | []float64{}, 27 | []float64{}, 28 | 0, 29 | }, 30 | { 31 | []float64{}, 32 | []float64{}, 33 | 3, 34 | }, 35 | { 36 | []float64{1.23, 2.34}, 37 | []float64{2.34, 1.23}, 38 | 1, 39 | }, 40 | { 41 | []float64{1.23, 2.34}, 42 | []float64{2.34, 1.23}, 43 | -1, 44 | }, 45 | { 46 | []float64{1.23}, 47 | []float64{1.23}, 48 | 1000, 49 | }, 50 | { 51 | []float64{1.23, 2.34, 3.45}, 52 | []float64{1.23, 2.34, 3.45}, 53 | 3, 54 | }, 55 | { 56 | []float64{1.23, 2.34, 3.45}, 57 | []float64{1.23, 2.34, 3.45}, 58 | -3, 59 | }, 60 | { 61 | []float64{1.23, 2.34, 3.45}, 62 | []float64{1.23, 2.34, 3.45}, 63 | 6, 64 | }, 65 | { 66 | []float64{1.23, 2.34, 3.45}, 67 | []float64{1.23, 2.34, 3.45}, 68 | -6, 69 | }, 70 | { 71 | []float64{1.23, 2.34, 3.45}, 72 | []float64{2.34, 3.45, 1.23}, 73 | -1, 74 | }, 75 | { 76 | []float64{1.23, 2.34, 3.45}, 77 | []float64{3.45, 1.23, 2.34}, 78 | 1, 79 | }, 80 | } 81 | 82 | func TestRotate(t *testing.T) { 83 | for _, test := range rotateTests { 84 | t.Run("", func(t *testing.T) { 85 | rotated := pie.Rotate(test.ss, test.n) 86 | assert.Equal(t, test.rotated, rotated) 87 | }) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /v2/send.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | // Send sends elements to channel 8 | // in normal act it sends all elements but if func canceled it can be less 9 | // 10 | // it locks execution of gorutine 11 | // it doesn't close channel after work 12 | // returns sent elements if len(this) != len(old) considered func was canceled 13 | func Send[T any](ctx context.Context, ss []T, ch chan<- T) []T { 14 | for i, s := range ss { 15 | select { 16 | case <-ctx.Done(): 17 | return ss[:i] 18 | default: 19 | ch <- s 20 | } 21 | } 22 | 23 | return ss 24 | } 25 | -------------------------------------------------------------------------------- /v2/send_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "context" 5 | "github.com/elliotchance/pie/v2" 6 | "github.com/stretchr/testify/assert" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | var sendTests = []struct { 12 | ss []float64 13 | recieveDelay time.Duration 14 | canceledDelay time.Duration 15 | expected []float64 16 | }{ 17 | { 18 | nil, 19 | 0, 20 | 0, 21 | nil, 22 | }, 23 | { 24 | []float64{1.2, 3.2}, 25 | 0, 26 | 0, 27 | []float64{1.2, 3.2}, 28 | }, 29 | { 30 | []float64{1.2, 3.2}, 31 | time.Millisecond * 30, 32 | time.Millisecond * 10, 33 | []float64{1.2}, 34 | }, 35 | { 36 | []float64{1.2, 3.2}, 37 | time.Millisecond * 3, 38 | time.Millisecond * 10, 39 | []float64{1.2, 3.2}, 40 | }, 41 | } 42 | 43 | func TestSend(t *testing.T) { 44 | for _, test := range sendTests { 45 | t.Run("", func(t *testing.T) { 46 | ch := make(chan float64) 47 | 48 | actual := getFloat64sFromChan(ch, test.recieveDelay) 49 | ctx := createContextByDelay(test.canceledDelay) 50 | 51 | actualSent := pie.Send(ctx, test.ss, ch) 52 | close(ch) 53 | 54 | assert.Equal(t, test.expected, actualSent) 55 | assert.Equal(t, test.expected, actual()) 56 | }) 57 | } 58 | } 59 | 60 | func getFloat64sFromChan(ch chan float64, t time.Duration) func() []float64 { 61 | done := make(chan struct{}) 62 | var c []float64 63 | if t > 0 { 64 | go func() { 65 | ticker := time.NewTicker(t) 66 | defer ticker.Stop() 67 | for range ticker.C { 68 | val, ok := <-ch 69 | if !ok { 70 | break 71 | } else { 72 | c = append(c, val) 73 | } 74 | } 75 | done <- struct{}{} 76 | 77 | }() 78 | } else { 79 | go func() { 80 | for val := range ch { 81 | c = append(c, val) 82 | } 83 | done <- struct{}{} 84 | }() 85 | } 86 | 87 | return func() []float64 { 88 | <-done 89 | return c 90 | } 91 | } 92 | 93 | func createContextByDelay(t time.Duration) context.Context { 94 | ctx := context.Background() 95 | if t > 0 { 96 | ctx, _ = context.WithTimeout(ctx, t) 97 | } 98 | 99 | return ctx 100 | } 101 | -------------------------------------------------------------------------------- /v2/sequence.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | import "golang.org/x/exp/constraints" 4 | 5 | // Sequence generates all numbers in range or returns nil if params invalid 6 | // 7 | // There are 3 variations to generate: 8 | // 1. [0, n). 9 | // 2. [min, max). 10 | // 3. [min, max) with step. 11 | // 12 | // if len(params) == 1 considered that will be returned slice between 0 and n, 13 | // where n is the first param, [0, n). 14 | // if len(params) == 2 considered that will be returned slice between min and max, 15 | // where min is the first param, max is the second, [min, max). 16 | // if len(params) > 2 considered that will be returned slice between min and max with step, 17 | // where min is the first param, max is the second, step is the third one, [min, max) with step, 18 | // others params will be ignored 19 | func Sequence[T constraints.Integer | constraints.Float](ss []T, params ...int) []T { 20 | var creator = func(i int) T { 21 | return T(i) 22 | } 23 | 24 | return SequenceUsing(ss, creator, params...) 25 | } 26 | -------------------------------------------------------------------------------- /v2/sequence_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | var sequenceAndSequenceUsingTests = []struct { 10 | ss []float64 11 | params []int 12 | expected []float64 13 | }{ 14 | // n 15 | { 16 | nil, 17 | nil, 18 | nil, 19 | }, 20 | { 21 | nil, 22 | []int{-1}, 23 | nil, 24 | }, 25 | { 26 | nil, 27 | []int{0}, 28 | nil, 29 | }, 30 | { 31 | nil, 32 | []int{3}, 33 | []float64{0, 1, 2}, 34 | }, 35 | { 36 | []float64{}, 37 | []int{3}, 38 | []float64{0, 1, 2}, 39 | }, 40 | // range 41 | { 42 | nil, 43 | []int{2, 2}, 44 | nil, 45 | }, 46 | { 47 | []float64{}, 48 | []int{3, 2}, 49 | nil, 50 | }, 51 | { 52 | nil, 53 | []int{0, 3}, 54 | []float64{0, 1, 2}, 55 | }, 56 | { 57 | []float64{}, 58 | []int{3, 6}, 59 | []float64{3, 4, 5}, 60 | }, 61 | { 62 | []float64{}, 63 | []int{-5, 0}, 64 | []float64{-5, -4, -3, -2, -1}, 65 | }, 66 | { 67 | []float64{}, 68 | []int{-5, -10}, 69 | nil, 70 | }, 71 | // range with step 72 | { 73 | nil, 74 | []int{3, 3, 1}, 75 | nil, 76 | }, 77 | { 78 | []float64{}, 79 | []int{3, 6, 2}, 80 | []float64{3, 5}, 81 | }, 82 | { 83 | []float64{}, 84 | []int{3, 7, 2}, 85 | []float64{3, 5}, 86 | }, 87 | { 88 | []float64{}, 89 | []int{-10, -6, 1}, 90 | []float64{-10, -9, -8, -7}, 91 | }, 92 | { 93 | []float64{}, 94 | []int{-6, -10, -1}, 95 | []float64{-6, -7, -8, -9}, 96 | }, 97 | { 98 | []float64{}, 99 | []int{-6, -10, 1}, 100 | nil, 101 | }, 102 | } 103 | 104 | func TestSequence(t *testing.T) { 105 | for _, test := range sequenceAndSequenceUsingTests { 106 | t.Run("", func(t *testing.T) { 107 | assert.Equal(t, test.expected, pie.Sequence(test.ss, test.params...)) 108 | }) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /v2/sequence_using.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | import "math" 4 | 5 | // SequenceUsing generates slice in range using creator function 6 | // 7 | // There are 3 variations to generate: 8 | // 1. [0, n). 9 | // 2. [min, max). 10 | // 3. [min, max) with step. 11 | // 12 | // if len(params) == 1 considered that will be returned slice between 0 and n, 13 | // where n is the first param, [0, n). 14 | // if len(params) == 2 considered that will be returned slice between min and max, 15 | // where min is the first param, max is the second, [min, max). 16 | // if len(params) > 2 considered that will be returned slice between min and max with step, 17 | // where min is the first param, max is the second, step is the third one, [min, max) with step, 18 | // others params will be ignored 19 | func SequenceUsing[T any](ss []T, creator func(int) T, params ...int) []T { 20 | var seq = func(min, max, step int) (seq []T) { 21 | length := int(math.Round(float64(max-min) / float64(step))) 22 | if length < 1 { 23 | return 24 | } 25 | 26 | seq = make([]T, length) 27 | for i := 0; i < length; min += step { 28 | seq[i] = creator(min) 29 | i++ 30 | } 31 | 32 | return seq 33 | } 34 | 35 | if len(params) > 2 { 36 | return seq(params[0], params[1], params[2]) 37 | } else if len(params) == 2 { 38 | return seq(params[0], params[1], 1) 39 | } else if len(params) == 1 { 40 | return seq(0, params[0], 1) 41 | } 42 | 43 | return nil 44 | } 45 | -------------------------------------------------------------------------------- /v2/sequence_using_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestSequenceUsing(t *testing.T) { 10 | for _, test := range sequenceAndSequenceUsingTests { 11 | t.Run("", func(t *testing.T) { 12 | assert.Equal(t, test.expected, pie.SequenceUsing(test.ss, 13 | func(i int) float64 { return float64(i) }, test.params...)) 14 | }) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /v2/shift.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | // Shift will return two values: the shifted value and the rest slice. 4 | // if the slice is empty then returned shifted value is the zero value of the slice elements and the rest slice is empty slice 5 | func Shift[T any](ss []T) (T, []T) { 6 | var zeroValue T 7 | return FirstOr(ss, zeroValue), DropTop(ss, 1) 8 | } 9 | -------------------------------------------------------------------------------- /v2/shift_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | var shiftAndUnshiftTests = []struct { 10 | ss []float64 11 | shifted float64 12 | shift []float64 13 | params []float64 14 | unshift []float64 15 | }{ 16 | { 17 | nil, 18 | 0, 19 | nil, 20 | nil, 21 | []float64{}, 22 | }, 23 | { 24 | nil, 25 | 0, 26 | nil, 27 | []float64{}, 28 | []float64{}, 29 | }, 30 | { 31 | nil, 32 | 0, 33 | nil, 34 | []float64{1.23, 2.34}, 35 | []float64{1.23, 2.34}, 36 | }, 37 | { 38 | []float64{}, 39 | 0, 40 | nil, 41 | nil, 42 | []float64{}, 43 | }, 44 | { 45 | []float64{}, 46 | 0, 47 | nil, 48 | []float64{}, 49 | []float64{}, 50 | }, 51 | { 52 | []float64{}, 53 | 0, 54 | nil, 55 | []float64{1.23, 2.34}, 56 | []float64{1.23, 2.34}, 57 | }, 58 | { 59 | []float64{1.23}, 60 | 1.23, 61 | nil, 62 | []float64{2.34}, 63 | []float64{2.34, 1.23}, 64 | }, 65 | { 66 | []float64{1.23, 2.34}, 67 | 1.23, 68 | []float64{2.34}, 69 | []float64{3.45}, 70 | []float64{3.45, 1.23, 2.34}, 71 | }, 72 | { 73 | []float64{1.23, 2.34}, 74 | 1.23, 75 | []float64{2.34}, 76 | []float64{3.45, 4.56}, 77 | []float64{3.45, 4.56, 1.23, 2.34}, 78 | }, 79 | } 80 | 81 | func TestShiftAndUnshift(t *testing.T) { 82 | for _, test := range shiftAndUnshiftTests { 83 | t.Run("", func(t *testing.T) { 84 | shifted, shift := pie.Shift(test.ss) 85 | assert.Equal(t, test.shifted, shifted) 86 | assert.Equal(t, test.shift, shift) 87 | assert.Equal(t, test.unshift, pie.Unshift(test.ss, test.params...)) 88 | }) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /v2/shuffle.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | import ( 4 | "math/rand" 5 | ) 6 | 7 | // Shuffle returns a new shuffled slice by your rand.Source. The original slice 8 | // is not modified. 9 | func Shuffle[T any](ss []T, source rand.Source) []T { 10 | n := len(ss) 11 | 12 | // Avoid the extra allocation. 13 | if n < 2 { 14 | return ss 15 | } 16 | 17 | shuffled := make([]T, n) 18 | copy(shuffled, ss) 19 | 20 | rnd := rand.New(source) 21 | rnd.Shuffle(n, func(i, j int) { 22 | shuffled[i], shuffled[j] = shuffled[j], shuffled[i] 23 | }) 24 | 25 | return shuffled 26 | } 27 | -------------------------------------------------------------------------------- /v2/shuffle_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "math/rand" 7 | "testing" 8 | ) 9 | 10 | var shuffleTests = []struct { 11 | ss []float64 12 | expected []float64 13 | source rand.Source 14 | }{ 15 | { 16 | nil, 17 | nil, 18 | nil, 19 | }, 20 | { 21 | nil, 22 | nil, 23 | rand.NewSource(0), 24 | }, 25 | { 26 | []float64{}, 27 | []float64{}, 28 | rand.NewSource(0), 29 | }, 30 | { 31 | []float64{12.3, 2.34, 4.56}, 32 | []float64{2.34, 12.3, 4.56}, 33 | rand.NewSource(0), 34 | }, 35 | { 36 | []float64{12.3}, 37 | []float64{12.3}, 38 | rand.NewSource(0), 39 | }, 40 | } 41 | 42 | func TestShuffle(t *testing.T) { 43 | for _, test := range shuffleTests { 44 | t.Run("", func(t *testing.T) { 45 | assert.Equal(t, test.expected, pie.Shuffle(test.ss, test.source)) 46 | }) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /v2/sort.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | import ( 4 | "golang.org/x/exp/constraints" 5 | "sort" 6 | ) 7 | 8 | // Sort works similar to sort.SliceType(). However, unlike sort.SliceType the 9 | // slice returned will be reallocated as to not modify the input slice. 10 | // 11 | // See Reverse() and AreSorted(). 12 | func Sort[T constraints.Ordered](ss []T) []T { 13 | // Avoid the allocation. If there is one element or less it is already 14 | // sorted. 15 | if len(ss) < 2 { 16 | return ss 17 | } 18 | 19 | sorted := make([]T, len(ss)) 20 | copy(sorted, ss) 21 | sort.Slice(sorted, func(i, j int) bool { 22 | return sorted[i] < sorted[j] 23 | }) 24 | 25 | return sorted 26 | } 27 | -------------------------------------------------------------------------------- /v2/sort_stable_using.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | import ( 4 | "sort" 5 | ) 6 | 7 | // SortStableUsing works similar to sort.SliceStable. However, unlike sort.SliceStable the 8 | // slice returned will be reallocated as to not modify the input slice. 9 | func SortStableUsing[T comparable](ss []T, less func(a, b T) bool) []T { 10 | // Avoid the allocation. If there is one element or less it is already 11 | // sorted. 12 | if len(ss) < 2 { 13 | return ss 14 | } 15 | 16 | sorted := make([]T, len(ss)) 17 | copy(sorted, ss) 18 | sort.SliceStable(sorted, func(i, j int) bool { 19 | return less(sorted[i], sorted[j]) 20 | }) 21 | 22 | return sorted 23 | } 24 | -------------------------------------------------------------------------------- /v2/sort_stable_using_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func stringShorter(a, b string) bool { 10 | return len(a) < len(b) 11 | } 12 | 13 | var sortByLengthTests = []struct { 14 | ss []string 15 | sortedStable []string 16 | }{ 17 | { 18 | nil, 19 | nil, 20 | }, 21 | { 22 | []string{}, 23 | []string{}, 24 | }, 25 | { 26 | []string{"foo"}, 27 | []string{"foo"}, 28 | }, 29 | { 30 | []string{"aaa", "b", "cc"}, 31 | []string{"b", "cc", "aaa"}, 32 | }, 33 | { 34 | []string{"zz", "aaa", "b", "cc"}, 35 | []string{"b", "zz", "cc", "aaa"}, 36 | }, 37 | } 38 | 39 | func TestSortStableUsing(t *testing.T) { 40 | less := stringShorter 41 | for _, test := range sortByLengthTests { 42 | t.Run("", func(t *testing.T) { 43 | assert.Equal(t, test.sortedStable, pie.SortStableUsing(test.ss, less)) 44 | }) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /v2/sort_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestSort(t *testing.T) { 10 | for _, test := range sortTests { 11 | t.Run("", func(t *testing.T) { 12 | assert.Equal(t, test.sorted, pie.Sort(test.ss)) 13 | }) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /v2/sort_using.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | import ( 4 | "sort" 5 | ) 6 | 7 | // SortUsing works similar to sort.Slice. However, unlike sort.Slice the 8 | // slice returned will be reallocated as to not modify the input slice. 9 | func SortUsing[T any](ss []T, less func(a, b T) bool) []T { 10 | // Avoid the allocation. If there is one element or less it is already 11 | // sorted. 12 | if len(ss) < 2 { 13 | return ss 14 | } 15 | 16 | sorted := make([]T, len(ss)) 17 | copy(sorted, ss) 18 | sort.Slice(sorted, func(i, j int) bool { 19 | return less(sorted[i], sorted[j]) 20 | }) 21 | 22 | return sorted 23 | } 24 | -------------------------------------------------------------------------------- /v2/sort_using_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | type carPointers []*car 10 | 11 | type car struct { 12 | Name, Color string 13 | } 14 | 15 | func carPointerNameLess(a, b *car) bool { 16 | return a.Name < b.Name 17 | } 18 | 19 | func carPointerColorLess(a, b *car) bool { 20 | return a.Color < b.Color 21 | } 22 | 23 | var carPointersSortCustomTests = []struct { 24 | ss carPointers 25 | sortedStableByName carPointers 26 | sortedStableByColor carPointers 27 | }{ 28 | { 29 | nil, 30 | nil, 31 | nil, 32 | }, 33 | { 34 | carPointers{}, 35 | carPointers{}, 36 | carPointers{}, 37 | }, 38 | { 39 | carPointers{&car{"foo", "red"}}, 40 | carPointers{&car{"foo", "red"}}, 41 | carPointers{&car{"foo", "red"}}, 42 | }, 43 | { 44 | carPointers{&car{"bar", "yellow"}, &car{"Baz", "black"}, &car{"foo", "red"}}, 45 | carPointers{&car{"Baz", "black"}, &car{"bar", "yellow"}, &car{"foo", "red"}}, 46 | carPointers{&car{"Baz", "black"}, &car{"foo", "red"}, &car{"bar", "yellow"}}, 47 | }, 48 | { 49 | carPointers{&car{"bar", "yellow"}, &car{"Baz", "black"}, &car{"qux", "cyan"}, &car{"foo", "red"}}, 50 | carPointers{&car{"Baz", "black"}, &car{"bar", "yellow"}, &car{"foo", "red"}, &car{"qux", "cyan"}}, 51 | carPointers{&car{"Baz", "black"}, &car{"qux", "cyan"}, &car{"foo", "red"}, &car{"bar", "yellow"}}, 52 | }, 53 | { 54 | carPointers{&car{"aaa", "yellow"}, &car{"aaa", "black"}, &car{"bbb", "yellow"}, &car{"bbb", "black"}}, 55 | carPointers{&car{"aaa", "yellow"}, &car{"aaa", "black"}, &car{"bbb", "yellow"}, &car{"bbb", "black"}}, 56 | carPointers{&car{"aaa", "black"}, &car{"bbb", "black"}, &car{"aaa", "yellow"}, &car{"bbb", "yellow"}}, 57 | }, 58 | } 59 | 60 | func TestSortUsing(t *testing.T) { 61 | isSortedUsing := func(ss carPointers, less func(a, b *car) bool) bool { 62 | for i := 1; i < len(ss); i++ { 63 | if less(ss[i], ss[i-1]) { 64 | return false 65 | } 66 | } 67 | return true 68 | } 69 | 70 | for _, test := range carPointersSortCustomTests { 71 | t.Run("", func(t *testing.T) { 72 | sortedByName := pie.SortUsing(test.ss, carPointerNameLess) 73 | assert.True(t, isSortedUsing(sortedByName, carPointerNameLess)) 74 | sortedStableByName := pie.SortStableUsing(test.ss, carPointerNameLess) 75 | assert.Equal(t, test.sortedStableByName, carPointers(sortedStableByName)) 76 | 77 | sortedByColor := pie.SortUsing(test.ss, carPointerColorLess) 78 | assert.True(t, isSortedUsing(sortedByColor, carPointerColorLess)) 79 | sortedStableByColor := pie.SortStableUsing(test.ss, carPointerColorLess) 80 | assert.Equal(t, test.sortedStableByColor, carPointers(sortedStableByColor)) 81 | }) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /v2/stddev.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | import ( 4 | "golang.org/x/exp/constraints" 5 | "math" 6 | ) 7 | 8 | // Stddev is the standard deviation 9 | func Stddev[T constraints.Integer | constraints.Float](ss []T) float64 { 10 | if len(ss) == 0 { 11 | return 0.0 12 | } 13 | 14 | avg := Average(ss) 15 | 16 | var sd float64 17 | for i := range ss { 18 | sd += math.Pow(float64(ss[i])-avg, 2) 19 | } 20 | sd = math.Sqrt(sd / float64(len(ss))) 21 | 22 | return sd 23 | } 24 | -------------------------------------------------------------------------------- /v2/stddev_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestStddev(t *testing.T) { 10 | assert.Equal(t, 0.0, pie.Stddev([]float64{})) 11 | assert.Equal(t, 0.0, pie.Stddev([]float64{1})) 12 | assert.Equal(t, 4.8587389053127765, pie.Stddev([]float64{10.0, 12.5, 23.3, 23.1, 16.5, 23.1, 21.2, 16.4})) 13 | } 14 | -------------------------------------------------------------------------------- /v2/string.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | import ( 4 | "fmt" 5 | "golang.org/x/exp/constraints" 6 | ) 7 | 8 | // String transforms a value into a string. Nil values will be treated as empty 9 | // strings. 10 | // 11 | // If the element type implements fmt.Stringer it will be used. Otherwise it 12 | // will fallback to the result of: 13 | // 14 | // fmt.Sprintf("%v") 15 | // 16 | func String[T constraints.Ordered](s T) string { 17 | return fmt.Sprintf("%v", s) 18 | } 19 | -------------------------------------------------------------------------------- /v2/string_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestString(t *testing.T) { 10 | assert.Equal(t, "123", pie.String(123)) 11 | assert.Equal(t, "1.89", pie.String(1.89)) 12 | assert.Equal(t, "1.89", pie.String("1.89")) 13 | } 14 | -------------------------------------------------------------------------------- /v2/strings.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | import ( 4 | "golang.org/x/exp/constraints" 5 | ) 6 | 7 | // Strings transforms each element to a string. 8 | // 9 | // If the element type implements fmt.Stringer it will be used. Otherwise it 10 | // will fallback to the result of: 11 | // 12 | // fmt.Sprintf("%v") 13 | // 14 | func Strings[T constraints.Ordered](ss []T) []string { 15 | return Map(ss, String[T]) 16 | } 17 | -------------------------------------------------------------------------------- /v2/strings_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestStrings(t *testing.T) { 10 | assert.Equal(t, []string{}, pie.Strings([]float64{})) 11 | 12 | assert.Equal(t, 13 | []string{"92.384", "823.324", "453"}, 14 | pie.Strings([]float64{92.384, 823.324, 453})) 15 | } 16 | -------------------------------------------------------------------------------- /v2/strings_using.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | // StringsUsing transforms each element to a string. 4 | func StringsUsing[T any](ss []T, transform func(T) string) []string { 5 | return Map(ss, transform) 6 | } 7 | -------------------------------------------------------------------------------- /v2/strings_using_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "fmt" 5 | "github.com/elliotchance/pie/v2" 6 | "github.com/stretchr/testify/assert" 7 | "testing" 8 | ) 9 | 10 | var stringsUsingTests = []struct { 11 | ss []float64 12 | transform func(float64) string 13 | expected []string 14 | }{ 15 | { 16 | nil, 17 | func(s float64) string { 18 | return "foo" 19 | }, 20 | nil, 21 | }, 22 | { 23 | []float64{}, 24 | func(s float64) string { 25 | return fmt.Sprintf("%f!", s) 26 | }, 27 | []string{}, 28 | }, 29 | { 30 | []float64{6.2, 7.2, 8.2}, 31 | func(s float64) string { 32 | return fmt.Sprintf("%.2f!", s) 33 | }, 34 | []string{"6.20!", "7.20!", "8.20!"}, 35 | }, 36 | } 37 | 38 | func TestStringsUsing(t *testing.T) { 39 | for _, test := range stringsUsingTests { 40 | t.Run("", func(t *testing.T) { 41 | assert.Equal(t, test.expected, pie.StringsUsing(test.ss, test.transform)) 42 | }) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /v2/sub_slice.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | // SubSlice will return the subSlice from start to end(excluded) 4 | // 5 | // Condition 1: If start < 0 or end < 0, nil is returned. 6 | // Condition 2: If start >= end, nil is returned. 7 | // Condition 3: Return all elements that exist in the range provided, 8 | // if start or end is out of bounds, zero items will be placed. 9 | func SubSlice[T any](ss []T, start int, end int) (subSlice []T) { 10 | if start < 0 || end < 0 { 11 | return 12 | } 13 | 14 | if start >= end { 15 | return 16 | } 17 | 18 | length := len(ss) 19 | if start < length { 20 | if end <= length { 21 | subSlice = ss[start:end] 22 | } else { 23 | zeroArray := make([]T, end-length) 24 | subSlice = append(ss[start:length], zeroArray[:]...) 25 | } 26 | } else { 27 | zeroArray := make([]T, end-start) 28 | subSlice = zeroArray[:] 29 | } 30 | 31 | return 32 | } 33 | -------------------------------------------------------------------------------- /v2/sub_slice_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | var subSliceTests = []struct { 10 | ss []float64 11 | start int 12 | end int 13 | subSlice []float64 14 | }{ 15 | { 16 | nil, 17 | 1, 18 | 1, 19 | nil, 20 | }, 21 | { 22 | nil, 23 | 1, 24 | 2, 25 | []float64{0}, 26 | }, 27 | { 28 | []float64{}, 29 | 1, 30 | 1, 31 | nil, 32 | }, 33 | { 34 | []float64{}, 35 | 1, 36 | 2, 37 | []float64{0}, 38 | }, 39 | { 40 | []float64{1.23, 2.34}, 41 | -1, 42 | -1, 43 | nil, 44 | }, 45 | { 46 | []float64{1.23, 2.34}, 47 | -1, 48 | 1, 49 | nil, 50 | }, 51 | { 52 | []float64{1.23, 2.34}, 53 | 1, 54 | -1, 55 | nil, 56 | }, 57 | { 58 | []float64{1.23, 2.34}, 59 | 2, 60 | 0, 61 | nil, 62 | }, 63 | 64 | { 65 | []float64{1.23, 2.34}, 66 | 1, 67 | 1, 68 | nil, 69 | }, 70 | { 71 | []float64{1.23, 2.34}, 72 | 1, 73 | 2, 74 | []float64{2.34}, 75 | }, 76 | { 77 | []float64{1.23, 2.34}, 78 | 1, 79 | 3, 80 | []float64{2.34, 0}, 81 | }, 82 | { 83 | []float64{1.23, 2.34}, 84 | 2, 85 | 2, 86 | nil, 87 | }, 88 | { 89 | []float64{1.23, 2.34}, 90 | 2, 91 | 3, 92 | []float64{0}, 93 | }, 94 | { 95 | []float64{1.23, 2.34, 0}, 96 | 2, 97 | 3, 98 | []float64{0}, 99 | }, 100 | } 101 | 102 | func TestSubSlice(t *testing.T) { 103 | for _, test := range subSliceTests { 104 | t.Run("", func(t *testing.T) { 105 | assert.Equal(t, test.subSlice, pie.SubSlice(test.ss, test.start, test.end)) 106 | }) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /v2/sum.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | import "golang.org/x/exp/constraints" 4 | 5 | // Sum is the sum of all of the elements. 6 | func Sum[T constraints.Integer | constraints.Float](ss []T) (sum T) { 7 | for _, s := range ss { 8 | sum += s 9 | } 10 | 11 | return 12 | } 13 | -------------------------------------------------------------------------------- /v2/sum_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestSum(t *testing.T) { 10 | for _, test := range statsTests { 11 | t.Run("", func(t *testing.T) { 12 | assert.Equal(t, test.sum, pie.Sum(test.ss)) 13 | }) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /v2/top.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | // Top will return n elements from head of the slice 4 | // if the slice has less elements then n that'll return all elements 5 | // if n < 0 it'll return empty slice. 6 | func Top[T any](ss []T, n int) (top []T) { 7 | for i := 0; i < len(ss) && n > 0; i++ { 8 | top = append(top, ss[i]) 9 | n-- 10 | } 11 | 12 | return 13 | } 14 | -------------------------------------------------------------------------------- /v2/top_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestTop(t *testing.T) { 10 | for _, test := range topAndBottomTests { 11 | t.Run("", func(t *testing.T) { 12 | assert.Equal(t, test.top, pie.Top(test.ss, test.n)) 13 | }) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /v2/unique.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | // Unique returns a new slice with all of the unique values. 4 | // 5 | // The items will be returned in a randomized order, even with the same input. 6 | // 7 | // The number of items returned may be the same as the input or less. It will 8 | // never return zero items unless then input slice has zero items. 9 | // 10 | // A slice with zero elements is considered to be unique. 11 | // 12 | // See AreUnique(). 13 | func Unique[T comparable](ss []T) []T { 14 | // Avoid the allocation. If there is one element or less it is already 15 | // unique. 16 | if len(ss) < 2 { 17 | return ss 18 | } 19 | 20 | values := map[T]struct{}{} 21 | 22 | for _, value := range ss { 23 | values[value] = struct{}{} 24 | } 25 | 26 | return Keys(values) 27 | } 28 | -------------------------------------------------------------------------------- /v2/unique_stable.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | // UniqueStable works similar to Unique. However, unlike Unique 4 | // the slice returned will be in previous relative order 5 | func UniqueStable[T comparable](ss []T) []T { 6 | // Avoid the allocation. If there is one element or less it is already 7 | // unique. 8 | if len(ss) < 2 { 9 | return ss 10 | } 11 | 12 | seen := map[T]struct{}{} 13 | ret := make([]T, 0) 14 | 15 | for _, value := range ss { 16 | if _, ok := seen[value]; ok { 17 | continue 18 | } 19 | seen[value] = struct{}{} 20 | ret = append(ret, value) 21 | } 22 | 23 | return ret 24 | } 25 | -------------------------------------------------------------------------------- /v2/unique_stable_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/elliotchance/pie/v2" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | var uniqueStableTests = []struct { 11 | ss []float64 12 | uniqueStable []float64 13 | }{ 14 | { 15 | nil, 16 | nil, 17 | }, 18 | { 19 | []float64{}, 20 | []float64{}, 21 | }, 22 | { 23 | []float64{789}, 24 | []float64{789}, 25 | }, 26 | { 27 | []float64{12.789, -13.2, 12.789}, 28 | []float64{12.789, -13.2}, 29 | }, 30 | { 31 | []float64{12.789, -13.2, 1.234e6, 789}, 32 | []float64{12.789, -13.2, 1.234e6, 789}, 33 | }, 34 | } 35 | 36 | func TestUniqueStable(t *testing.T) { 37 | for _, test := range uniqueStableTests { 38 | t.Run("", func(t *testing.T) { 39 | assert.Equal(t, test.uniqueStable, pie.UniqueStable(test.ss)) 40 | }) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /v2/unique_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestUnique(t *testing.T) { 10 | for _, test := range uniqueTests { 11 | t.Run("", func(t *testing.T) { 12 | // We have to sort the unique slice because it is always returned in 13 | // random order. 14 | assert.Equal(t, test.unique, pie.Sort(pie.Unique(test.ss))) 15 | }) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /v2/unshift.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | // Unshift adds one or more elements to the beginning of the slice 4 | // and returns the new slice. 5 | func Unshift[T any](ss []T, elements ...T) (unshift []T) { 6 | unshift = append([]T{}, elements...) 7 | unshift = append(unshift, ss...) 8 | 9 | return 10 | } 11 | -------------------------------------------------------------------------------- /v2/unshift_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | // See shift_test.go 4 | -------------------------------------------------------------------------------- /v2/values.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | // Values returns the values in the map. 4 | // 5 | // Due to Go's randomization of iterating maps the order is not deterministic. 6 | func Values[K comparable, V any](m map[K]V) []V { 7 | // Avoid allocation 8 | l := len(m) 9 | if l == 0 { 10 | return nil 11 | } 12 | 13 | i := 0 14 | keys := make([]V, len(m)) 15 | for _, value := range m { 16 | keys[i] = value 17 | i++ 18 | } 19 | 20 | return keys 21 | } 22 | -------------------------------------------------------------------------------- /v2/values_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "sort" 7 | "testing" 8 | ) 9 | 10 | func TestValues(t *testing.T) { 11 | assert.Equal(t, []currency(nil), pie.Values(currencies(nil))) 12 | 13 | assert.Equal(t, []currency(nil), pie.Values(currencies{})) 14 | 15 | values := pie.Values(isoCurrencies) 16 | sort.Slice(values, func(i, j int) bool { 17 | return values[i].NumericCode < values[j].NumericCode 18 | }) 19 | 20 | assert.Equal(t, []currency{{36, -2}, {840, -2}}, values) 21 | } 22 | -------------------------------------------------------------------------------- /v2/zip.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | // A pair struct containing two zipped values. 4 | type Zipped[T1, T2 any] struct { 5 | A T1 6 | B T2 7 | } 8 | 9 | // Zip will return a new slice containing pairs with elements from input slices. 10 | // If input slices have diffrent length, the output slice will be truncated to 11 | // the length of the smallest input slice. 12 | func Zip[T1, T2 any](ss1 []T1, ss2 []T2) []Zipped[T1, T2] { 13 | var minLen int 14 | 15 | if len(ss1) <= len(ss2) { 16 | minLen = len(ss1) 17 | } else { 18 | minLen = len(ss2) 19 | } 20 | 21 | ss3 := []Zipped[T1, T2]{} 22 | for i := 0; i < minLen; i++ { 23 | ss3 = append(ss3, Zipped[T1, T2]{ss1[i], ss2[i]}) 24 | } 25 | 26 | return ss3 27 | } 28 | -------------------------------------------------------------------------------- /v2/zip_longest.go: -------------------------------------------------------------------------------- 1 | package pie 2 | 3 | // ZipLongest will return a new slice containing pairs with elements from input slices. 4 | // If input slices have different length, missing elements will be padded with default values. 5 | func ZipLongest[T1, T2 any](ss1 []T1, ss2 []T2) []Zipped[T1, T2] { 6 | ss3 := make([]Zipped[T1, T2], Max([]int{len(ss1), len(ss2)})) 7 | for i := range ss3 { 8 | ss3[i] = Zipped[T1, T2]{ 9 | A: First(ss1), 10 | B: First(ss2), 11 | } 12 | Pop(&ss1) 13 | Pop(&ss2) 14 | } 15 | 16 | return ss3 17 | } 18 | -------------------------------------------------------------------------------- /v2/zip_longest_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestZipLongest(t *testing.T) { 10 | for _, test := range zipTests { 11 | 12 | t.Run("", func(t *testing.T) { 13 | c := pie.ZipLongest(test.ss1, test.ss2) 14 | 15 | assert.Equal(t, c, test.expectedLong) 16 | }) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /v2/zip_test.go: -------------------------------------------------------------------------------- 1 | package pie_test 2 | 3 | import ( 4 | "github.com/elliotchance/pie/v2" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | var zipTests = []struct { 10 | ss1 []int 11 | ss2 []float32 12 | expectedShort []pie.Zipped[int, float32] 13 | expectedLong []pie.Zipped[int, float32] 14 | }{ 15 | { 16 | []int{}, 17 | []float32{}, 18 | []pie.Zipped[int, float32]{}, 19 | []pie.Zipped[int, float32]{}, 20 | }, 21 | { 22 | []int{1, 2, 3, 4, 5}, 23 | []float32{}, 24 | []pie.Zipped[int, float32]{}, 25 | []pie.Zipped[int, float32]{{1, 0}, {2, 0}, {3, 0}, {4, 0}, {5, 0}}, 26 | }, 27 | { 28 | []int{}, 29 | []float32{1.0, 2.0, 3.0, 4.0, 5.0}, 30 | []pie.Zipped[int, float32]{}, 31 | []pie.Zipped[int, float32]{{0, 1.0}, {0, 2.0}, {0, 3.0}, {0, 4.0}, {0, 5.0}}, 32 | }, 33 | { 34 | []int{1, 2, 3, 4, 5}, 35 | []float32{1.0, 2.0, 3.0, 4.0, 5.0}, 36 | []pie.Zipped[int, float32]{{1, 1.0}, {2, 2.0}, {3, 3.0}, {4, 4.0}, {5, 5.0}}, 37 | []pie.Zipped[int, float32]{{1, 1.0}, {2, 2.0}, {3, 3.0}, {4, 4.0}, {5, 5.0}}, 38 | }, 39 | { 40 | []int{1, 2, 3, 4, 5, 6, 7, 8}, 41 | []float32{1.0, 2.0, 3.0, 4.0, 5.0}, 42 | []pie.Zipped[int, float32]{{1, 1.0}, {2, 2.0}, {3, 3.0}, {4, 4.0}, {5, 5.0}}, 43 | []pie.Zipped[int, float32]{{1, 1.0}, {2, 2.0}, {3, 3.0}, {4, 4.0}, {5, 5.0}, {6, 0}, {7, 0}, {8, 0}}, 44 | }, 45 | { 46 | []int{1, 2, 3}, 47 | []float32{1.0, 2.0, 3.0, 4.0, 5.0}, 48 | []pie.Zipped[int, float32]{{1, 1.0}, {2, 2.0}, {3, 3.0}}, 49 | []pie.Zipped[int, float32]{{1, 1.0}, {2, 2.0}, {3, 3.0}, {0, 4.0}, {0, 5.0}}, 50 | }, 51 | } 52 | 53 | func TestZip(t *testing.T) { 54 | for _, test := range zipTests { 55 | 56 | t.Run("", func(t *testing.T) { 57 | c := pie.Zip(test.ss1, test.ss2) 58 | 59 | assert.Equal(t, c, test.expectedShort) 60 | }) 61 | } 62 | } 63 | --------------------------------------------------------------------------------