Giter Club home page Giter Club logo

pie's Introduction

πŸ• github.com/elliotchance/pie

GoDoc Build Status codecov

Enjoy a slice! pie is a library of utility functions for common operations on slices and maps.

Quick Start

If you are using (or require) Go 1.17 or below, you will have to use v1.

pie can be used in two ways, the first is to use the regular parameterized functions:

Run this program

package main

import (
    "fmt"
    "strings"

    "github.com/elliotchance/pie/v2"
)

func main() {
    names := pie.FilterNot([]string{"Bob", "Sally", "John", "Jane"},
        func(name string) bool {
            return strings.HasPrefix(name, "J")
        })

    fmt.Println(names) // "[Bob Sally]"
}

Or, if you need to chain multiple operations you can use one of:

  • pie.Of - works with any element type, but functions are limited.
  • pie.OfOrdered - only works with numbers and strings, but has more functions.
  • pie.OfNumeric - only works with numbers, but has all functions.

Run this program

package main

import (
    "fmt"
    "strings"

    "github.com/elliotchance/pie/v2"
)

func main() {
    name := pie.Of([]string{"Bob", "Sally", "John", "Jane"}).
        FilterNot(func(name string) bool {
            return strings.HasPrefix(name, "J")
        }).
        Map(strings.ToUpper).
        Last()

    fmt.Println(name) // "SALLY"
}

You can find the full documentation here.

FAQ

What are the requirements?

pie v2 only supports Go 1.18+. If you have an older version you can use v1.

What are the goals of pie?

  1. Type safety. I never want to hit runtime bugs because I could pass in the wrong type, or perform an invalid type case out the other end.

  2. Performance. The functions need to be as fast as native Go implementations otherwise there's no point in this library existing.

  3. Nil-safe. All of the functions will happily accept nil and treat them as empty slices. Apart from less possible panics, it makes it easier to chain.

  4. Immutable. Functions never modify inputs (except in cases where it would be illogical), unlike some built-ins such as sort.Strings.

How do I contribute a function?

Pull requests are always welcome.

Here is a comprehensive list of steps to follow to add a new function:

  1. Create a new file for your function (tip: copy an existing file can be quicker). Add your implmentation and comment.

  2. Create appropriate tests.

  3. If your function accepts a slice, it should also be added to the OfSlice API (see of.go).

Why is the emoji a slice of pizza instead of a pie?

I wanted to pick a name for the project that was short and had an associated emoji. I liked pie, but then I found out that the pie emoji is not fully supported everywhere. I didn't want to change the name of the project to cake, but pizza pie still made sense. I'm not sure if I will change it back to a pie later.

pie's People

Contributors

alessandrolorenzi avatar chocolacula avatar danielpsf avatar deleplace avatar dylanmeeus avatar electrofocus avatar elliotchance avatar istudko avatar jerome-laforge avatar jhuliano avatar kirillmorozov avatar kochurovro avatar kylemarek avatar leilei3167 avatar luoxin avatar misikdmytro avatar morrisxyang avatar orisano avatar os-m avatar ougwen1235 avatar rfyiamcool avatar samdfonseca avatar samlitowitz avatar slothninja avatar testwill avatar unique1o1 avatar vehsamrak avatar westrious avatar zhiburt avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

pie's Issues

Any() and All()

These would work in a similar way as Python. They return true if any or all of the items are truthy.

Union()

Unlike Append() and Extend(), Union() always considered the slice empty:

pie.Strings{}.Union(pie.Strings{"baz", "qux"}, pie.Strings{"quux"})
// pie.Strings{"baz", "qux", "quux"}

pie.Strings{"foo", "bar"}.Union(pie.Strings{"baz", "qux"}, pie.Strings{"quux"})
// pie.Strings{"baz", "qux", "quux"}

This syntax is useful when unioning multiple slices without separating the first one from the rest:

// This is easier:
pie.Strings{}.Union(zeroOrMoreSlices...)

// Than handling the nil case:
if len(zeroOrMoreSlices) == 0 {
    return nil;
}

return zeroOrMoreSlices[0].Extend(zeroOrMoreSlices[1:]...)

Sequence()

Generates a sequence of numbers between min and max inclusive:

pie.Ints{}.Sequence(5, 8)
// pie.Ints{5, 6, 7, 8}

There could also be an optional third argument for the step size:

pie.Ints{}.Sequence(5, 8, 2)
// pie.Ints{5, 7}

Discussion about immutability

It's great that Pie methods are "safe" because they never alter the receiver, and always allocate new data instead.

The adjective "immutable" is maybe not the best to describe the slice types, as they are always mutable.

One solution would be to change the wording and make it extra-explicit that the objects are ot immutable.

Another solution would be to enforce immutability by generating struct types with private fields :

type Cars {
    cars []Car
}

func (ss Cars) Len() int {
    return len(ss.cars)
}

func (ss Cars) Index(i int) Car {
    return ss.cars[i]
}

This would add some code and some complexity in the Pie project. But also, users would lose the flexibility and convenience of using a Cars directly everywhere a []Caris expected.

Send()

Copy all items to a channel.

IntersectUsing()

Logically works the same way as Intersect() but allows you to use a callback that passes in two elements for custom equality check.

Undefined method

Hi @elliotchance,
Anyone could tell me what step am I missing when run go generate ./... && go install && go generate ./...?
List error attached below
Screen Shot 2019-05-07 at 4 39 05 PM
Thanks in advance!

Add Count()

Returns the number of elements that match a value. This would be a cleaner way of using Only(Equals()).Len()

SequenceUsing for all types

Generates sequence like in #70 for all types:

It seems to me, It'd be fine for many reasons such as testing and convenience.
I present some examples.

For Structure.

Cars{}.SequenceWith(3, func(i int) Car {
    return Car{Number: i}
})
// Cars{Car{0}, Car{1}, Car{2}}

For strings.

pie.Strings{}.SequenceWith(3, func(i int) string {
    prefix := "id_"
    return prefix + i
})
// pie.Strings{"id_0", "id_1", "id_2"}

It can be important to make a special sequence of numbers, and it could help.

pie.Ints{}.SequenceWith(3, func(i int) int {
    return i * 4
})
// pie.Ints{0, 4, 8}

Append() and Extend()

They do the same thing, but take different the items in different forms. Like Python.

Reduce()

Add a function to reduce elements in a collection to a scalar value. Think of something like this:

nums := []int{1,2,3,4,5}
res := reduce(nums, func(a, b int) int{
      return a + b
}

Similar to how a reduce function would work in e.g: Haskell, Java, Python, ...

  • I'll try to add this function to the lib, but wanted to communicate my intention beforehand.

Unique is O(n)

The README says O(n log n), but I trust the go implementation detail that maps are hash maps with constant access time (read/write) to an element.

Limit code generation to specific functions

There are already many functions, and there will be more so it seems wasteful to generate every possible function for every type. The default behaviour will be to generate all functions, but specific functions can be specified with chained dot notation:

//go:generate pie MyInts.Sum.Average
type MyInts []int

DeepCopy()

Return a new slice with each element deep copied.

Functions table in README

The type Maps stands out, as it is a bit special. I initially thought it was for types in the form []map[KeyType][ElementType] like the 3 previous columns on the left.

Capture d’écran 2019-05-05 aΜ€ 17 56 14

It is however for generating methods Keys and Values to a type in the form map[KeyType][ElementType], thus the receiver is actually not a slice type.

I suggest we split this table into 2 distinct tables, for clarity.

Except()

Except() is the opposite of Intersect(). It will return any values that only exist one slice but not the other.

func Subslice(int, int) SliceType

Subslice returns a new slice that starts an index and contains len items. The parameters must both be zero or positive and be in a valid range.

Random()

Returns a single random element.

Add Group()

Returns a map of the value with an individual count.

generate for custom types

This is all about type safety, it would make sense then to be able to generate all these functions (and more) for any given type. If this makes sense, I might try to contribute a PR with this

Append can be destructive

One important goal of Pie is "Functions never modify inputs".

However, as long as Append is a wrapper of the built-in append, the result will often share its underlying array with the input receiver, which leads to erratic behavior. The test below fails:

package pie

import (
	"testing"
)

func TestAppendNonDestructive(t *testing.T) {
	ab := Strings{"A", "B"}
	if x, expected := ab.Join(""), "AB"; x != expected {
		t.Errorf("Expected %q, got %q", expected, x)
	}

	abc := ab.Append("C")
	aby := ab.Append("Y")
	if x, expected := abc.Join(""), "ABC"; x != expected {
		t.Errorf("Expected %q, got %q", expected, x)
	}
	if x, expected := aby.Join(""), "ABY"; x != expected {
		t.Errorf("Expected %q, got %q", expected, x)
	}

	abcd := abc.Append("D")
	abcz := abc.Append("Z")
	if x, expected := abcd.Join(""), "ABCD"; x != expected {
		t.Errorf("Expected %q, got %q", expected, x)
	}
	if x, expected := abcz.Join(""), "ABCZ"; x != expected {
		t.Errorf("Expected %q, got %q", expected, x)
	}
}

I suggest a small modification to Append, to make sure a new slice a always allocated.

Stable Unique

Unique can be modified at no extra cost to keep the original ordering of the elements. This would seem a sensible default to me.

Shall I write the code and make a PR?

func Mode() SliceType

Mode returns the most frequent value. If there are multiple modes then one of them is returned. However, this is not intended to be deterministic. It is best to only use this when you know there to be a single most frequent value, or multiple modes are permitted.

func Strings() pie.Strings

Strings transform each element into a string. The number of elements returned will be the same as the original slice.

Abs()

Convert all values to positive using math.Abs().

Remove circular dependency in pie command

The file main.go, used to build the pie command and in turn to generate pie/*_pie.go files, references pie/strings_pie.go function Contains. Because there is only one use and the use case is simple, removing the circular dependency allows the generate/re-generation of pie/*_pie.go functions to be generated without already existing. This also covers the edge case of generating pie/strings_pie.go without the Contains function.

SortWith and SortStableWith, for Structs

It would be nice to have wrappers to sort.Slice and sort.SliceStable.

2 advantages:

  • create a new slice to be sorted and returned, instead of sorting in-place in a destructive manner;
  • instead of parameter less of type func(i, j int) bool, we can choose the more convenient signature less func(a, b ElementType) bool.

A combined Filter and Map?

I'm finding there are cases where I need to transform elements and skip/ignore some. It can be done with .Transform().Select() but it seems a little verbose for me.

Add pie.Comparator

type Comparator interface {
    Equals(ss2 ElementType) bool
}

If an ElementType implements this interface it should be used in any function that would otherwise use the = operator.

Diff()

Diff() describes a patch of how to get the left slice to be the right slice. To do this it returns two slices; the items that are new on the right side, and the items that need to be removed from the left side.

Select vs Filter

Hey,

This lib looks really neat, thank you & good job! πŸ‘

Just one thing that kind of surprised me. The Filter method is called Select here. There are quite a few languages that have the terminology for Filter. I don't really know Select from other languages, but that might be my ignorance πŸ˜„.

I believe that a lot of people will ook for a Filter method (I tried before finding out it's Select).What do you think of renaming it to Filter? (It does break the symmetry between Select and Unselect. But I also think Unselect is not a common name).

For comparison, here is a list of names for the "Filter" function in different languages: https://en.wikipedia.org/wiki/Filter_(higher-order_function).
Only mathematica seems to be using the Select terminology.

So, in short. I'd say Select -> Filter to make it easier for people to find/understand which method they need. What do you think?

I'd want to contribute this change. But I first want to know your opinion. Maybe my concern is unfounded πŸ˜… Not to mention this will break some dependencies. Unless we can introduce a synonym and have both Filter/Select. (They can point to the same implementation, so it'd just be kind of like a "typedef").

Rename Only -> Select

Select is more inline with other implementations. Also, the opposite function Without should be Unselect?

Product()

Product is the result of multiplying all of the elements.

Median in O(n) average performance

Hi, Median currently does alloc + Sort, for a total cost O(n log n).

I think we can achieve alloc + custom code, for a total cost O(n) on average.

Shall I do impl, tests, benchmarks, and PR? This would be mergeable if the large case is improved, and the small case not degraded.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.