Giter Site home page Giter Site logo

go-funk's Introduction

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!

https://media.giphy.com/media/3oEjHQKtDXpeGN9rW0/giphy.gif

<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

go-funk's People

Contributors

adammw avatar arnovanliere avatar cengsin avatar dovahyol avatar erezrokah avatar ferhatelmas avatar getogrand avatar guyarb avatar haraldnordgren avatar harrywang29 avatar hitjackma avatar kkhaike avatar marcinc avatar miudit avatar ninghejun avatar nohponex avatar poporul avatar reetuparna avatar roneli avatar samber avatar spoonscen avatar thoas avatar vellotis avatar whilei avatar winggao avatar xunleii avatar yangtau avatar yansal avatar youyuanwu avatar zeeshantamboli 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  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

go-funk's Issues

Panic with empty array during Get(...) call

Hi,

When using funk.Get(make([]*Xxxxx, 0), "Aaaa.Bbbbb") I get this panic:

panic: reflect: call of reflect.Value.Type on zero Value
goroutine 1 [running]:
reflect.Value.Type(0x0, 0x0, 0x0, 0x0, 0x9f34c0)
	/usr/local/go/src/reflect/value.go:1743 +0x16c
github.com/thoas/go-funk.get(0xa0e300, 0xc000474cc0, 0x197, 0xb66d75, 0xb, 0x197, 0x2, 0xae7d00)
	/home/aaaaaaa/vendor/github.com/thoas/go-funk/retrieve.go:46 +0x289
github.com/thoas/go-funk.get(0x9f34c0, 0xc000474cc0, 0x16, 0xb66d75, 0xe, 0xa37000, 0xb8d968, 0x13)
	/home/aaaaaaa/vendor/github.com/thoas/go-funk/retrieve.go:69 +0x4b2
github.com/thoas/go-funk.Get(0x9f34c0, 0xc000474cc0, 0xb66d75, 0xe, 0xc0002d1700, 0x76e4da)
	/home/aaaaaaa/vendor/github.com/thoas/go-funk/retrieve.go:10 +0xbe

It's related to:
https://github.com/thoas/go-funk/blob/master/retrieve.go#L45-L48

I don't understand exactly why this is happening. I can for sure make a statement my array is not empty before using funk.Get, but maybe funk should return me an empty array?

Thanks,

Tests sometimes fail

It seems that TestChainShuffle and TestSubtract sometimes fail. This can be reproduced with go test . -count=100 and such.

--- FAIL: TestChainShuffle (0.00s)
    --- FAIL: TestChainShuffle/test_case_#1 (0.00s)
        chain_builder_test.go:326:
                Error Trace:    chain_builder_test.go:326
                Error:          Should not be: []int{1, 3, 2, 4, 0}
                Test:           TestChainShuffle/test_case_#1
--- FAIL: TestSubtract (0.00s)
    subtraction_test.go:16:
                Error Trace:    subtraction_test.go:16
                Error:          Not equal:
                                expected: []string{"hello", "hi"}
                                actual  : []string{"hi", "hello"}

                                Diff:
                                --- Expected
                                +++ Actual
                                @@ -1,4 +1,4 @@
                                 ([]string) (len=2) {
                                - (string) (len=5) "hello",
                                - (string) (len=2) "hi"
                                + (string) (len=2) "hi",
                                + (string) (len=5) "hello"
                                 }
                Test:           TestSubtract

Let's discuss performance

I was curious what the performance impact is of your functional implementation vs the classic implementations. I haven't gone in depth, but the results are interesting (in particular the memory allocations for Contains.)

➜  funk-bench git:(master) ✗ go test -benchmem -bench .
testing: warning: no tests to run
BenchmarkContains/Idiomatic-8               1000           1946239 ns/op               0 B/op          0 allocs/op
BenchmarkContains/funk-8                       2         645429814 ns/op        28916552 B/op    3614564 allocs/op
PASS
ok      github.com/christian-blades-cb/funk-bench       4.417s

Implement #Any and #All

Hi @thoas
have time to add #Any and #All functions?
it would be great if supports structs too.
like find out if a user in a slice has a admin field true.
thanks

Contributing

Please clarify what is meant by pinging the author on Twitter.

funk.Get can't handle struct fields with interface{} type

Hello,

When trying following codes:

package main

import (
	"github.com/davecgh/go-spew/spew"
	"github.com/thoas/go-funk"
)

type Foo struct {
	Sth interface{}
}

type User struct {
	Phone struct {
		CountryCode string
		Number      int
	}
	Age int
}

func main() {
	e := Foo{
		Sth: User{
			Phone: struct {
				CountryCode string
				Number      int
			}{CountryCode: "+1", Number: 123},
			Age: 10,
		},
	}
	spew.Dump(funk.Get(e, "Sth.Age"))
	spew.Dump(funk.Get(e, "Sth.Phone.Number"))
	spew.Dump(funk.Get(e, "Sth.Phone.CountryCode"))
}

What I expect to get is:

(int) 10
(int) 123
(string) (len=2) "+1"

But actually I get 3 repeated

(main.User) {
Phone: (struct { CountryCode string; Number int }) {
CountryCode: (string) (len=2) "+1",
Number: (int) 123
},
Age: (int) 10
}

The reason looks like, Foo.Sth is interface type and redirectValue doesn't handle this, I try to fix it with this change:

-       if !value.IsValid() || value.Kind() != reflect.Ptr {
+       if !value.IsValid() || (value.Kind() != reflect.Ptr && value.Kind() != reflect.Interface) {
            return value
        }
 
-       res := reflect.Indirect(value)
+       res := value.Elem()

It works and all tests pass, but not sure if other things will be broken. If the change do work correctly, could you please apply it(or me send a PR)? Thanks.

Typesafe / zero allocs implementation

  • ContainsInt
  • ContainsInt64
  • ContainsFloat64
  • ContainsString
  • ContainsStrings
  • ContainsFloat32
  • ReverseString
  • ReverseInt
  • SumInt
  • SumInt64
  • SumFloat64
  • SumFloat32
  • UniqInt
  • UniqString
  • UniqInt64
  • UniqFloat32
  • UniqFloat64
  • ShuffleInt
  • ShuffleString
  • ShuffleInt64
  • ShuffleFloat32
  • ShuffleFloat64
  • IndexOfInt
  • IndexOfString
  • IndexOfInt64
  • IndexOfFloat64
  • LastIndexOfInt
  • LastIndexOfString
  • LastIndexOfInt64
  • LastIndexOfFloat64
  • FilterInt
  • FilterInt64
  • FilterString
  • FilterFloat32
  • FilterFloat64
  • FindFloat32
  • FindFloat64
  • FindString
  • FindInt64
  • FindInt

Possible clarification needed for `Uniq` function?

Hi! I was browsing through the code, and for the Uniq function I noticed this line:

value.Index(j).Set(val)

Now, the code value.Index(j).Set(val) is actively editing the input argument. Could you help me understand why this line exists/does what it does? I'm failing to grasp the logic behind it 😅

Edit: A coffee helped. I understand the algorithm itself, it edits the input argument and a copy is therefore not needed, using j as a secondary index. Though, there is no mention in the comment/function doc (unless I missed it somewhere), that it actually alters the original array! Maybe this issue is more related to this, I was not understanding why the input argument was being altered.

Thanks a lot,
Alberto

Reduce must return an interface to support multiple type

Hi I would like to use Reduce function but it seems that output result must be always float64, so no Map or Array or whatever else is supported? I am completely new to Go maybe I am missing something. Thank you for your help.

Implements Union

Merge 2 map:

a := map[string]string{
   "hello": "world",
   "foo": "bar",
}

b := map[string]string{
   "key": "value",
   "foo": "BAR",
}

funk.Union(a, b)

Returning:

map[string]string{
   "hello": "world",
   "foo": "BAR",
   "key": "value",
}

Accept pointers to iteratees

It would be great if everywhere an iteratee was needed (i.e. IsIteratee(i) must be true) it also accepted a pointer to such iteratee. It's a less frequent but very useful use case.

Example use case: input is a reflect type and JSON which encodes a slice of the given type. Create a pointer to a slice of the given type dynamically, use json.Unmarshal to populate it. At this point it would be great to be able to use funk.Map directly instead of having to use reflect to dereference the pointer like this reflect.ValueOf(slicePtr).Elem().Interface(), after all that's the whole point of funk, to simplify the code that deals with various Go data structures.

Code example https://goplay.space/#0GUn6UbxvJR (see locally defined Map).

Not sure what's the best way to implement this within funk, maybe GetIteratee instead of IsIteratee to hide the code dealing with dereferencing the pointer?

If there is an agreed upon way to do it I would probably be able to submit PR.

Get: Should return typed nil when out is nil/empty slice

I'm running into an issue when using Get() with a zero-value or empty slice of structs.

Here's an example:

package main

import (
	"fmt"
	"log"

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

type BigThing struct {
	Littles []LittleThing
}

type LittleThing struct {
	SomeKey string
}

func main() {
	// Using a zero-value slice (unexpected output)
	var x BigThing
	iKeys := funk.Get(x.Littles, "SomeKey")
	if _, ok := iKeys.([]string); ok {
		log.Fatalf("Uh oh...")
	}
	fmt.Printf("iKeys is %T, not []string\n", iKeys)

	// Using an empty slice (unexpected output)
	x.Littles = []LittleThing{}
	iKeys = funk.Get(x.Littles, "SomeKey")
	if _, ok := iKeys.([]string); ok {
		log.Fatalf("Uh oh...")
	}
	fmt.Printf("iKeys is %T, not []string\n", iKeys)

	// Using a non-empty slice (expected output)
	x.Littles = []LittleThing{{SomeKey: "hello"}}
	iKeys = funk.Get(x.Littles, "SomeKey")
	keys, ok := iKeys.([]string)
	if !ok {
		log.Fatalf("Uh oh...")
	}
	fmt.Println(keys)
}

I expected all three cases to return a zero-value []string (i.e., a typed nil), however the first two return an untyped nil, which causes these type conversions to fail.

Support FindIndex function

If I use Find function, I'll get a object. If I use IndexOf function, the predicate function will not work.

So I want the lib support FindIndex or something like that would help me to find a object with its index. Thank you so much

Make indirect function public

The indirect function becomes handy whenever I worked with reflection.

func indirect(a interface{}) interface{} {

Support for go modules and more go compliant versioning

This is a minor feature request. I love the library and would love to see support for go modules as well as well better versioning.

This is how the library loads within my project when I use go modules. This makes it harder to depend on stable versions of the library.

module github.com/syncaide/gate

require (
        ...
	github.com/thoas/go-funk v0.0.0-20190407194523-c43409e2d5de
        ...
)

With go.mod being added and a more golang compliant versioning scheme the dependency would end up looking as follows. This is much more dependable if in the future there are breaking changes introduced into the application.

module github.com/syncaide/gate

require (
        ...
	github.com/thoas/go-funk v0.4.0
        ...
)

use external param inside Filter method

Hi,

Inside the Filter method, I need to compare with a param defined outside anonymous method funk.Filter.

for _, repartition := range repartitions {
		operationsGroupByRepartition := funk.Filter(operations, func(operation models.OperationFromMetadata) bool {
			return operation.Repartition == repartition.Name()
		}).([]models.OperationFromMetadata)
}

so repartition is nil.

Is there anything I could do about it ?

Implement funk.Chain / funk.Builder

I think it can be great to have the possibility to easily chains several funk methods through a builder, like Lodash.
I can try to implement this features if you want.

Good to have arrayToObj with id keys method

func ArrayToObj(field string, arr interface{}) map[string]interface{} {
	var result = map[string]interface{}{}
	var arrValue  = reflect.ValueOf(arr) // convert to reflect.Value
	var arrFirstValField = arrValue.Index(0).FieldByName(field)

	if arrValue.Len() == 0 {
		return result
	}

	if arrFirstValField.Kind() == reflect.String {
		for i := 0; i < arrValue.Len(); i++ {
			var item  = arrValue.Index(i).Interface()
			var id = arrValue.Index(i).FieldByName(field).Interface().(string)
			result[id] =  item
		}
	} 

	if arrFirstValField.Kind() == reflect.Int {
		for i := 0; i < arrValue.Len(); i++ {
			var item  = arrValue.Index(i).Interface()
			id   := strconv.Itoa(arrValue.Index(i).FieldByName(field).Interface().(int) )
			result[id] =  item
		}
	}

	if arrFirstValField.Kind() == reflect.Int64 {
		for i := 0; i < arrValue.Len(); i++ {
			var item  = arrValue.Index(i).Interface()
			var idn   = arrValue.Index(i).FieldByName(field).Interface().(int64)
			id        :=  strconv.FormatInt(idn, 10)
			result[id] =  item
		}
	}

	return result
}

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.