• Stars
    star
    4,577
  • Rank 9,289 (Top 0.2 %)
  • Language
    Go
  • License
    MIT License
  • Created almost 8 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A modern Go utility library which provides helpers (map, find, contains, filter, ...)

go-funk

Build Status

GoDoc

Go report

go-funk is a modern Go library based on reflect.

Generic helpers rely on reflect, be careful this code runs exclusively on runtime so you must have a good test suite.

These helpers have started as an experiment to learn reflect. It may look like lodash in some aspects but it will have its own roadmap. lodash is an awesome library with a lot of work behind it, all features included in go-funk come from internal use cases.

You can also find typesafe implementation in the godoc.

Why this name?

Long story, short answer because func is a reserved word in Go, I wanted something similar.

Initially this project was named fn I don't need to explain why that was a bad idea for french speakers :)

Let's funk!

image

<3

Installation

go get github.com/thoas/go-funk

Usage

import "github.com/thoas/go-funk"

These examples will be based on the following data model:

type Foo struct {
    ID        int
    FirstName string `tag_name:"tag 1"`
    LastName  string `tag_name:"tag 2"`
    Age       int    `tag_name:"tag 3"`
}

func (f Foo) TableName() string {
    return "foo"
}

With fixtures:

f := &Foo{
    ID:        1,
    FirstName: "Foo",
    LastName:  "Bar",
    Age:       30,
}

You can import go-funk using a basic statement:

import "github.com/thoas/go-funk"

funk.Contains

Returns true if an element is present in a iteratee (slice, map, string).

One frustrating thing in Go is to implement contains methods for each type, for example:

func ContainsInt(s []int, e int) bool {
    for _, a := range s {
        if a == e {
            return true
        }
    }
    return false
}

this can be replaced by funk.Contains:

// slice of string
funk.Contains([]string{"foo", "bar"}, "bar") // true

// slice of Foo ptr
funk.Contains([]*Foo{f}, f) // true
funk.Contains([]*Foo{f}, func (foo *Foo) bool {
    return foo.ID == f.ID
}) // true
funk.Contains([]*Foo{f}, nil) // false

b := &Foo{
    ID:        2,
    FirstName: "Florent",
    LastName:  "Messa",
    Age:       28,
}

funk.Contains([]*Foo{f}, b) // false

// string
funk.Contains("florent", "rent") // true
funk.Contains("florent", "foo") // false

// even map
funk.Contains(map[int]string{1: "Florent"}, 1) // true
funk.Contains(map[int]string{1: "Florent"}, func(key int, name string) bool {
    return key == 1 // or `name == "Florent"` for the value type
}) // true

see also, typesafe implementations: ContainsInt, ContainsInt64, ContainsFloat32, ContainsFloat64, ContainsString

funk.Intersect

Returns the intersection between two collections.

funk.Intersect([]int{1, 2, 3, 4}, []int{2, 4, 6})  // []int{2, 4}
funk.Intersect([]string{"foo", "bar", "hello", "bar"}, []string{"foo", "bar"})  // []string{"foo", "bar"}

see also, typesafe implementations: IntersectString

funk.Difference ..............

Returns the difference between two collections.

funk.Difference([]int{1, 2, 3, 4}, []int{2, 4, 6})  // []int{1, 3}, []int{6}
funk.Difference([]string{"foo", "bar", "hello", "bar"}, []string{"foo", "bar"})  // []string{"hello"}, []string{}

see also, typesafe implementations: DifferenceString

funk.IndexOf

Gets the index at which the first occurrence of a value is found in an array or return -1 if the value cannot be found.

// slice of string
funk.IndexOf([]string{"foo", "bar"}, "bar") // 1
funk.IndexOf([]string{"foo", "bar"}, func(value string) bool {
    return value == "bar"
}) // 1
funk.IndexOf([]string{"foo", "bar"}, "gilles") // -1

see also, typesafe implementations: IndexOfInt, IndexOfInt64, IndexOfFloat32, IndexOfFloat64, IndexOfString

funk.LastIndexOf

Gets the index at which the last occurrence of a value is found in an array or return -1 if the value cannot be found.

// slice of string
funk.LastIndexOf([]string{"foo", "bar", "bar"}, "bar") // 2
funk.LastIndexOf([]string{"foo", "bar", "bar"}, func(value string) bool {
    return value == "bar"
}) // 2
funk.LastIndexOf([]string{"foo", "bar"}, "gilles") // -1

see also, typesafe implementations: LastIndexOfInt, LastIndexOfInt64, LastIndexOfFloat32, LastIndexOfFloat64, LastIndexOfString

funk.ToMap

Transforms a slice or an array of structs to a map based on a pivot field.

f := &Foo{
    ID:        1,
    FirstName: "Gilles",
    LastName:  "Fabio",
    Age:       70,
}

b := &Foo{
    ID:        2,
    FirstName: "Florent",
    LastName:  "Messa",
    Age:       80,
}

results := []*Foo{f, b}

mapping := funk.ToMap(results, "ID") // map[int]*Foo{1: f, 2: b}

funk.ToSet

Transforms an array or a slice to a set (a map with zero-size values).

f := Foo{
    ID:        1,
    FirstName: "Gilles",
    LastName:  "Fabio",
    Age:       70,
}

b := Foo{
    ID:        2,
    FirstName: "Florent",
    LastName:  "Messa",
    Age:       80,
}

mapping := funk.ToSet([]Foo{f, b}) // map[Foo]stuct{}{f: struct{}{}, b: struct{}{}}

mapping := funk.ToSet([4]int{1, 1, 2, 2}) // map[int]struct{}{1: struct{}{}, 2: struct{}{}}

funk.Filter

Filters a slice based on a predicate.

r := funk.Filter([]int{1, 2, 3, 4}, func(x int) bool {
    return x%2 == 0
}) // []int{2, 4}

see also, typesafe implementations: FilterInt, FilterInt64, FilterFloat32, FilterFloat64, FilterString

funk.Reduce

Reduces an iteratee based on an accumulator function or operation rune for numbers.

// Using operation runes. '+' and '*' only supported.
r := funk.Reduce([]int{1, 2, 3, 4}, '+', float64(0)) // 10
r := funk.Reduce([]int{1, 2, 3, 4}, '*', 1) // 24

// Using accumulator function
r := funk.Reduce([]int{1, 2, 3, 4}, func(acc float64, num int) float64 {
    return acc + float64(num)
}, float64(0)) // 10

r := funk.Reduce([]int{1, 2, 3, 4}, func(acc string, num int) string {
    return acc + fmt.Sprint(num)
}, "") // "1234"

funk.Find

Finds an element in a slice based on a predicate.

r := funk.Find([]int{1, 2, 3, 4}, func(x int) bool {
    return x%2 == 0
}) // 2

see also, typesafe implementations: FindInt, FindInt64, FindFloat32, FindFloat64, FindString

funk.Map

Manipulates an iteratee (map, slice) and transforms it to another type:

  • map -> slice
  • map -> map
  • slice -> map
  • slice -> slice
r := funk.Map([]int{1, 2, 3, 4}, func(x int) int {
    return x * 2
}) // []int{2, 4, 6, 8}

r := funk.Map([]int{1, 2, 3, 4}, func(x int) string {
    return "Hello"
}) // []string{"Hello", "Hello", "Hello", "Hello"}

r = funk.Map([]int{1, 2, 3, 4}, func(x int) (int, int) {
    return x, x
}) // map[int]int{1: 1, 2: 2, 3: 3, 4: 4}

mapping := map[int]string{
    1: "Florent",
    2: "Gilles",
}

r = funk.Map(mapping, func(k int, v string) int {
    return k
}) // []int{1, 2}

r = funk.Map(mapping, func(k int, v string) (string, string) {
    return fmt.Sprintf("%d", k), v
}) // map[string]string{"1": "Florent", "2": "Gilles"}

funk.FlatMap

Manipulates an iteratee (map, slice) and transforms it to to a flattened collection of another type:

  • map -> slice
  • slice -> slice
r := funk.FlatMap([][]int{{1, 2}, {3, 4}}, func(x []int) []int {
    return append(x, 0)
}) // []int{1, 2, 0, 3, 4, 0}

mapping := map[string][]int{
    "Florent": {1, 2},
    "Gilles": {3, 4},
}

r = funk.FlatMap(mapping, func(k string, v []int) []int {
    return v
}) // []int{1, 2, 3, 4}

funk.Get

Retrieves the value at path of struct(s) or map(s).

var bar *Bar = &Bar{
    Name: "Test",
    Bars: []*Bar{
        &Bar{
            Name: "Level1-1",
            Bar: &Bar{
                Name: "Level2-1",
            },
        },
        &Bar{
            Name: "Level1-2",
            Bar: &Bar{
                Name: "Level2-2",
            },
        },
    },
}

var foo *Foo = &Foo{
    ID:        1,
    FirstName: "Dark",
    LastName:  "Vador",
    Age:       30,
    Bar:       bar,
    Bars: []*Bar{
        bar,
        bar,
    },
}

funk.Get([]*Foo{foo}, "Bar.Bars.Bar.Name") // []string{"Level2-1", "Level2-2"}
funk.Get(foo, "Bar.Bars.Bar.Name") // []string{"Level2-1", "Level2-2"}
funk.Get(foo, "Bar.Name") // Test

funk.Get also support map values:

bar := map[string]interface{}{
    "Name": "Test",
}

foo1 := map[string]interface{}{
    "ID":        1,
    "FirstName": "Dark",
    "LastName":  "Vador",
    "Age":       30,
    "Bar":       bar,
}

foo2 := &map[string]interface{}{
    "ID":        1,
    "FirstName": "Dark",
    "LastName":  "Vador",
    "Age":       30,
    "Labels": map[string]interface{} {
        "example.com/hello": "world",
    },
} // foo2.Bar is nil

funk.Get(bar, "Name") // "Test"
funk.Get([]map[string]interface{}{foo1, foo2}, "Bar.Name") // []string{"Test"}
funk.Get(foo2, "Bar.Name") // nil
funk.Get(foo2, `Labels."example.com/hello"`) // world

funk.Get also handles nil values:

bar := &Bar{
    Name: "Test",
}

foo1 := &Foo{
    ID:        1,
    FirstName: "Dark",
    LastName:  "Vador",
    Age:       30,
    Bar:       bar,
}

foo2 := &Foo{
    ID:        1,
    FirstName: "Dark",
    LastName:  "Vador",
    Age:       30,
} // foo2.Bar is nil

funk.Get([]*Foo{foo1, foo2}, "Bar.Name") // []string{"Test"}
funk.Get(foo2, "Bar.Name") // nil

funk.GetOrElse

Retrieves the value of the pointer or default.

str := "hello world"
GetOrElse(&str, "foobar")   // string{"hello world"}
GetOrElse(str, "foobar")    // string{"hello world"}
GetOrElse(nil, "foobar")    // string{"foobar"}

funk.Set

Set value at a path of a struct

var bar Bar = Bar{
    Name: "level-0",
    Bar: &Bar{
        Name: "level-1",
        Bars: []*Bar{
            {Name: "level2-1"},
            {Name: "level2-2"},
        },
    },
}

_ = Set(&bar, "level-0-new", "Name")
fmt.Println(bar.Name) // "level-0-new"

MustSet(&bar, "level-1-new", "Bar.Name")
fmt.Println(bar.Bar.Name) // "level-1-new"

Set(&bar, "level-2-new", "Bar.Bars.Name")
fmt.Println(bar.Bar.Bars[0].Name) // "level-2-new"
fmt.Println(bar.Bar.Bars[1].Name) // "level-2-new"

funk.MustSet

Short hand for funk.Set if struct does not contain interface{} field type to discard errors.

funk.Prune

Copy a struct with only selected fields. Slice is handled by pruning all elements.

bar := &Bar{
    Name: "Test",
}

foo1 := &Foo{
    ID:        1,
    FirstName: "Dark",
    LastName:  "Vador",
    Bar:       bar,
}

pruned, _ := Prune(foo1, []string{"FirstName", "Bar.Name"})
// *Foo{
//    ID:        0,
//    FirstName: "Dark",
//    LastName:  "",
//    Bar:       &Bar{Name: "Test},
// }

funk.PruneByTag .......... Same functionality as funk.Prune, but uses struct tags instead of struct field names.

funk.Keys

Creates an array of the own enumerable map keys or struct field names.

funk.Keys(map[string]int{"one": 1, "two": 2}) // []string{"one", "two"} (iteration order is not guaranteed)

foo := &Foo{
    ID:        1,
    FirstName: "Dark",
    LastName:  "Vador",
    Age:       30,
}

funk.Keys(foo) // []string{"ID", "FirstName", "LastName", "Age"} (iteration order is not guaranteed)

funk.Values

Creates an array of the own enumerable map values or struct field values.

funk.Values(map[string]int{"one": 1, "two": 2}) // []int{1, 2} (iteration order is not guaranteed)

foo := &Foo{
    ID:        1,
    FirstName: "Dark",
    LastName:  "Vador",
    Age:       30,
}

funk.Values(foo) // []interface{}{1, "Dark", "Vador", 30} (iteration order is not guaranteed)

funk.ForEach

Range over an iteratee (map, slice).

Or update element in slice(Not map, reflect#Value#MapIndex#CanSet is false).

funk.ForEach([]int{1, 2, 3, 4}, func(x int) {
    fmt.Println(x)
})

foo := []int{1,2,3}
funk.ForEach(foo, func(x *int){ *x = *x * 2})
fmt.Println(foo) // []int{2, 4, 6}

funk.ForEachRight ............

Range over an iteratee (map, slice) from the right.

results := []int{}

funk.ForEachRight([]int{1, 2, 3, 4}, func(x int) {
    results = append(results, x)
})

fmt.Println(results) // []int{4, 3, 2, 1}

funk.Chunk

Creates an array of elements split into groups with the length of the size. If array can't be split evenly, the final chunk will be the remaining element.

funk.Chunk([]int{1, 2, 3, 4, 5}, 2) // [][]int{[]int{1, 2}, []int{3, 4}, []int{5}}

funk.FlattenDeep

Recursively flattens an array.

funk.FlattenDeep([][]int{[]int{1, 2}, []int{3, 4}}) // []int{1, 2, 3, 4}

funk.Uniq

Creates an array with unique values.

funk.Uniq([]int{0, 1, 1, 2, 3, 0, 0, 12}) // []int{0, 1, 2, 3, 12}

see also, typesafe implementations: UniqInt, UniqInt64, UniqFloat32, UniqFloat64, UniqString

funk.UniqBy .........

Creates an array with unique values returned by a callback.

funk.UniqBy([]int{0, 1, 1, 2, 3, 0, 0, 12}, func(nbr int) int {
    return nbr % 3
}) // []int{0, 1, 2}

foo1 := Foo{
    ID: 42,
    FirstName: "Bob",
}
foo2 := Foo{
    ID: 42,
    FirstName: "Bob",
}
funk.UniqBy([]Foo{foo1, foo2}, func(f Foo) int {
    return f.ID
}) // []Foo{ Foo{ID: 42, Firstname: "Bob"} }

funk.Drop

Creates an array/slice with n elements dropped from the beginning.

funk.Drop([]int{0, 0, 0, 0}, 3) // []int{0}

see also, typesafe implementations: DropInt, DropInt32, DropInt64, DropFloat32, DropFloat64, DropString

funk.Initial

Gets all but the last element of array.

funk.Initial([]int{0, 1, 2, 3, 4}) // []int{0, 1, 2, 3}

funk.Tail

Gets all but the first element of array.

funk.Tail([]int{0, 1, 2, 3, 4}) // []int{1, 2, 3, 4}

funk.Shuffle

Creates an array of shuffled values.

funk.Shuffle([]int{0, 1, 2, 3, 4}) // []int{2, 1, 3, 4, 0}

see also, typesafe implementations: ShuffleInt, ShuffleInt64, ShuffleFloat32, ShuffleFloat64, ShuffleString

funk.Subtract

Returns the subtraction between two collections. It preserve order.

funk.Subtract([]int{0, 1, 2, 3, 4}, []int{0, 4}) // []int{1, 2, 3}
funk.Subtract([]int{0, 3, 2, 3, 4}, []int{0, 4}) // []int{3, 2, 3}

see also, typesafe implementations: SubtractString

funk.Sum

Computes the sum of the values in an array.

funk.Sum([]int{0, 1, 2, 3, 4}) // 10.0
funk.Sum([]interface{}{0.5, 1, 2, 3, 4}) // 10.5

see also, typesafe implementations: SumInt, SumInt64, SumFloat32, SumFloat64

funk.Reverse

Transforms an array such that the first element will become the last, the second element will become the second to last, etc.

funk.Reverse([]int{0, 1, 2, 3, 4}) // []int{4, 3, 2, 1, 0}

see also, typesafe implementations: ReverseInt, ReverseInt64, ReverseFloat32, ReverseFloat64, ReverseString, ReverseStrings

funk.SliceOf

Returns a slice based on an element.

funk.SliceOf(f) // will return a []*Foo{f}

funk.RandomInt

Generates a random int, based on a min and max values.

funk.RandomInt(0, 100) // will be between 0 and 100

funk.RandomString

Generates a random string with a fixed length.

funk.RandomString(4) // will be a string of 4 random characters

funk.Shard

Generates a sharded string with a fixed length and depth.

funk.Shard("e89d66bdfdd4dd26b682cc77e23a86eb", 1, 2, false) // []string{"e", "8", "e89d66bdfdd4dd26b682cc77e23a86eb"}

funk.Shard("e89d66bdfdd4dd26b682cc77e23a86eb", 2, 2, false) // []string{"e8", "9d", "e89d66bdfdd4dd26b682cc77e23a86eb"}

funk.Shard("e89d66bdfdd4dd26b682cc77e23a86eb", 2, 3, true) // []string{"e8", "9d", "66", "bdfdd4dd26b682cc77e23a86eb"}

funk.Subset

Returns true if a collection is a subset of another

funk.Subset([]int{1, 2, 4}, []int{1, 2, 3, 4, 5}) // true
funk.Subset([]string{"foo", "bar"},[]string{"foo", "bar", "hello", "bar", "hi"}) //true

Performance

go-funk currently has an open issue about performance, don't hesitate to participate in the discussion to enhance the generic helpers implementations.

Let's stop beating around the bush, a typesafe implementation in pure Go of funk.Contains, let's say for example:

func ContainsInt(s []int, e int) bool {
    for _, a := range s {
        if a == e {
            return true
        }
    }
    return false
}

will always outperform an implementation based on reflect in terms of speed and allocs because of how it's implemented in the language.

If you want a similarity, gorm will always be slower than sqlx (which is very low level btw) and will use more allocs.

You must not think generic helpers of go-funk as a replacement when you are dealing with performance in your codebase, you should use typesafe implementations instead.

Contributing

Don't hesitate ;)

Authors

  • Florent Messa
  • Gilles Fabio
  • Alexey Pokhozhaev
  • Alexandre Nicolaie

More Repositories

1

picfit

An image resizing server written in Go
Go
1,990
star
2

stats

A Go middleware that stores various information about your web application (response time, status code count, etc.)
Go
594
star
3

bokchoy

Simple job queues for Go backed by Redis
Go
263
star
4

django-sequere

A Django application to implement a follow system and a timeline using multiple backends (db, redis, etc.)
Python
58
star
5

django-backward

A Django application to store your previous history and action in your session engine
Python
30
star
6

django-metadata

Attach metadata to any Django models using redis
Python
27
star
7

django-rq-mail

Store mails with waiting and active queues and send them asynchronously
Python
20
star
8

django-sluggable

Manage your slugs and redirect old slugs to the new one
Python
19
star
9

djangogo

Utilities to integrate Go with Django web framework (users management, password encryption, etc.)
Go
10
star
10

observr

A microservice to store analytics about your visitors
Go
9
star
11

django-online

Standalone application to know if a django user is online or not
Python
8
star
12

python-leetchi

A client library written in python to work with leetchi api
Python
7
star
13

django-events-watcher

A basic django application to log information with a data model
Python
7
star
14

finder

Find files and directories in Go
Go
6
star
15

django-fairepart

A generic application to import your contact from facebook, google, etc.
Python
5
star
16

django-data-exporter

[ABANDONED] Export asynchronously your data from your models
Python
5
star
17

vimconfig

My personal vimconfig
Vim Script
4
star
18

letitcrash

A middleware to display debug information when your Go application is panicking
Go
3
star
19

django-eros

Standalone application to like any content types you want and transform link
JavaScript
2
star
20

forj

Michel berard personal project
Jinja
2
star
21

django-gravatar

Templatetag to get Gravatar URL's from email addresses
Python
2
star
22

vimagneto

Tutoriels gratuits en vidéo en français autour de l'éditeur de code vim
Python
1
star
23

isolation

A simple nodejs project with MongoDB
JavaScript
1
star
24

may

JavaScript
1
star
25

muxer

Basic extension to build http server on top of gorilla mux
Go
1
star