douzhao2047
2016-06-01 08:05
浏览 372
已采纳

在Golang中从数组中选择元素的最惯用方式?

I have an array of strings, and I'd like to exclude values that start in foo_ OR are longer than 7 characters.

I can loop through each element, run the if statement, and add it to a slice along the way. But I was curious if there was an idiomatic or more golang-like way of accomplishing that.

Just for example, the same thing might be done in Ruby as

my_array.select! { |val| val !~ /^foo_/ && val.length <= 7 }
  • 写回答
  • 好问题 提建议
  • 关注问题
  • 收藏
  • 邀请回答

5条回答 默认 最新

  • douyao1856 2016-06-01 08:18
    已采纳

    There is no one-liner as you have it in Ruby, but with a helper function you can make it almost as short.

    Here's our helper function that loops over a slice, and selects and returns only the elements that meet a criteria captured by a function value:

    func filter(ss []string, test func(string) bool) (ret []string) {
        for _, s := range ss {
            if test(s) {
                ret = append(ret, s)
            }
        }
        return
    }
    

    Using this helper function your task:

    ss := []string{"foo_1", "asdf", "loooooooong", "nfoo_1", "foo_2"}
    
    mytest := func(s string) bool { return !strings.HasPrefix(s, "foo_") && len(s) <= 7 }
    s2 := filter(ss, mytest)
    
    fmt.Println(s2)
    

    Output (try it on the Go Playground):

    [asdf nfoo_1]
    

    Note:

    If it is expected that many elements will be selected, it might be profitable to allocate a "big" ret slice beforehand, and use simple assignment instead of the append(). And before returning, slice the ret to have a length equal to the number of selected elements.

    Note #2:

    In my example I chose a test() function which tells if an element is to be returned. So I had to invert your "exclusion" condition. Obviously you may write the helper function to expect a tester function which tells what to exclude (and not what to include).

    已采纳该答案
    评论
    解决 无用
    打赏 举报
  • dtv8189 2016-06-01 08:24

    There isn't an idiomatic way you can achieve the same expected result in Go in one single line as in Ruby, but with a helper function you can obtain the same expressiveness as in Ruby.

    You can call this helper function as:

    Filter(strs, func(v string) bool {
        return strings.HasPrefix(v, "foo_") // return foo_testfor
    }))
    

    Here is the whole code:

    package main
    
    import "strings"
    import "fmt"
    
    // Returns a new slice containing all strings in the
    // slice that satisfy the predicate `f`.
    func Filter(vs []string, f func(string) bool) []string {
        vsf := make([]string, 0)
        for _, v := range vs {
            if f(v) && len(v) > 7 {
                vsf = append(vsf, v)
            }
        }
        return vsf
    }
    
    func main() {
    
        var strs = []string{"foo1", "foo2", "foo3", "foo3", "foo_testfor", "_foo"}
    
        fmt.Println(Filter(strs, func(v string) bool {
            return strings.HasPrefix(v, "foo_") // return foo_testfor
        }))
    }
    

    And the running example: Playground

    评论
    解决 无用
    打赏 举报
  • dpyoh6553 2016-06-01 08:26

    Have a look at robpike's filter library. This would allow you to do:

    package main
    
    import (
        "fmt"
        "strings"
        "filter"
    )
    
    func isNoFoo7(a string) bool {
        return ! strings.HasPrefix(a, "foo_") && len(a) <= 7
    }
    
    func main() {
        a := []string{"test", "some_other_test", "foo_etc"}
        result := Choose(a, isNoFoo7)
        fmt.Println(result) // [test]
    }
    

    Interestingly enough the README.md by Rob:

    I wanted to see how hard it was to implement this sort of thing in Go, with as nice an API as I could manage. It wasn't hard. Having written it a couple of years ago, I haven't had occasion to use it once. Instead, I just use "for" loops. You shouldn't use it either.

    So the most idiomatic way according to Rob would be something like:

    func main() {
        a := []string{"test", "some_other_test", "foo_etc"}
        nofoos := []string{}
        for i := range a {
            if(!strings.HasPrefix(a[i], "foo_") && len(a[i]) <= 7) {
                nofoos = append(nofoos, a[i])
            }
        }
        fmt.Println(nofoos) // [test]
    }
    

    This style is very similar, if not identical, to the approach any C-family language takes.

    评论
    解决 无用
    打赏 举报
  • duanguochong0397 2018-03-19 19:52

    "Select Elements from Array" is also commonly called a filter function. There's no such thing in go. There are also no other "Collection Functions" such as map or reduce. For the most idiomatic way to get the desired result, I find https://gobyexample.com/collection-functions a good reference:

    [...] in Go it’s common to provide collection functions if and when they are specifically needed for your program and data types.

    They provide an implementation example of the filter function for strings:

    func Filter(vs []string, f func(string) bool) []string {
        vsf := make([]string, 0)
        for _, v := range vs {
            if f(v) {
                vsf = append(vsf, v)
            }
        }
        return vsf
    }
    

    However, they also say, that it's often ok to just inline the function:

    Note that in some cases it may be clearest to just inline the collection-manipulating code directly, instead of creating and calling a helper function.

    In general, golang tries to only introduce orthogonal concepts, meaning that when you can solve a problem one way, there shouldn't be too many more ways to solve it. This adds simplicity to the language by only having a few core concepts, such that not every developer uses a different subset of the language.

    评论
    解决 无用
    打赏 举报
  • dsx5201 2018-05-04 21:22

    Today, I stumbled on a pretty idiom that surprised me. If you want to filter a slice in place without allocating, use two slices with the same backing array:

    s := []T{
        // the input
    } 
    s2 := s
    s = s[:0]
    for _, v := range s2 {
        if shouldKeep(v) {
            s = append(s, v)
        }
    }
    

    Here's a specific example of removing duplicate strings:

    s := []string{"a", "a", "b", "c", "c"}
    s2 := s
    s = s[:0]
    var last string
    for _, v := range s2 {
        if len(s) == 0 || v != last {
            last = v
            s = append(s, v)
        }
    }
    

    If you need to keep both slices, simply replace s = s[:0] with s = nil or s = make([]T, 0, len(s)), depending on whether you want append() to allocate for you.

    评论
    解决 无用
    打赏 举报

相关推荐 更多相似问题