在Golang中从数组中选择元素的最惯用的方法是什么?

时间:2016-06-01 08:05:13

标签: arrays go slice

我有一个字符串数组,并且我想要排除以foo_开头或超过7个字符的值。

我可以遍历每个元素,运行if语句,并将其添加到路径中的切片中。但我很好奇是否有一种惯用的或更像golang的方式来完成它。

例如,在Ruby中可以做同样的事情

my_array.select! { |val| val !~ /^foo_/ && val.length <= 7 }

9 个答案:

答案 0 :(得分:31)

在Ruby中没有单行,但是使用辅助函数可以使它几乎一样短。

这是我们的辅助函数循环切片,并选择并仅返回符合函数值捕获的条件的元素:

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

使用此助手功能您的任务:

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)

输出(在Go Playground上尝试):

[asdf nfoo_1]

注意:

如果预计会选择许多元素,预先分配“大”ret切片并使用简单赋值而不是append()可能会有利可图。在返回之前,将ret切片以使其长度等于所选元素的数量。

注意#2:

在我的例子中,我选择了一个test()函数,它告诉我们是否要返回一个元素。所以我不得不颠倒你的“排除”条件。显然,你可以编写辅助函数来期望一个测试函数,它告诉你要排除什么(而不是要包括什么)。

答案 1 :(得分:12)

看看robpike's filter library。这将允许你这样做:

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]
}

有趣的是Rob的README.md:

  

我想看看在Go中实现这种东西是多么困难,因为我能管理的API很好。这并不难。   几年前写完之后,我还没有机会使用它一次。相反,我只是使用&#34;用于&#34;循环。   你也不应该使用它。

所以根据Rob的最惯用的方式是:

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]
}

这种风格与任何C系列语言采用的方法非常相似(如果不相同)。

答案 2 :(得分:3)

没有一种惯用的方法可以像在Ruby中一样在Go中获得与Go相同的预期结果,但是使用辅助函数可以获得与Ruby相同的表现力。

您可以将此辅助函数称为:

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

以下是整个代码:

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
    }))
}

正在运行的例子:Playground

答案 3 :(得分:3)

今天,我偶然发现了一个令我惊讶的漂亮成语。如果要在不分配的情况下过滤切片,请使用具有相同后备阵列的两个切片:

Enable source server support

以下是删除重复字符串的具体示例:

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

如果您需要保留两个切片,只需将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) } } 替换为s = s[:0]s = nil,具体取决于您是否希望s = make([]T, 0, len(s))为您分配。

答案 4 :(得分:1)

看看这个库:github.com/thoas/go-funk 它在Go中提供了许多救生习惯的实现(例如,包括过滤数组中的元素)。

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

答案 5 :(得分:1)

有两种不错的方法来过滤片,而无需分配或新的依赖项。在the Go wiki on Github中找到:

  

过滤器(就地)

n := 0

for _, x := range a {
  if keep(x) {
      a[n] = x
      n++
  }

}
a = a[:n]

另一种更易读的方式:

  

过滤而不分配

     

此技巧使用了一个事实,即切片共享相同的后备数组   和原始容量一样,因此存储可以重复使用   过滤后的切片。当然,原始内容也会被修改。

b := a[:0]

for _, x := range a {
  if f(x) {
      b = append(b, x)
  }
}
     

对于必须被垃圾收集的元素,以下代码可以   之后再加入:

for i := len(b); i < len(a); i++ {
  a[i] = nil // or the zero value of T
}

我不确定的一件事是,第一种方法是否需要清除索引nil之后的切片a中的项目(设置为n),就像第二种方法一样方法。

编辑:第二种方法基本上是his answer中MicahStetson所描述的。在我的代码中,我使用了类似于以下的函数,就性能和可读性而言,它可能和它一样好:

func filterSlice(slice []*T, keep func(*T) bool) []*T {
    newSlice := slice[:0]

    for _, item := range slice {
        if keep(item) {
            newSlice = append(newSlice, item)
        }
    }
    // make sure discarded items can be garbage collected
    for i := len(newSlice); i < len(slice); i++ {
        slice[i] = nil
    }
    return newSlice
}

请注意,如果切片中的项目不是指针并且不包含指针,则可以跳过第二个for循环。

答案 6 :(得分:1)

这是一个优雅的折叠和过滤示例,它使用递归来完成过滤。 FoldRight 通常也很有用。它不是堆栈安全的,但可以通过蹦床来实现。一旦 Golang 有了泛型,它就可以完全泛化为任何 2 种类型:

func FoldRightStrings(as, z []string, f func(string, []string) []string) []string {
    if len(as) > 1 {//Slice has a head and a tail.
        h, t := as[0], as[1:len(as)]
        return f(h, FoldRightStrings(t, z, f))
    } else if len(as) == 1 {//Slice has a head and an empty tail.
        h := as[0]
        return f(h, FoldRightStrings([]string{}, z, f))
    }
    return z
}

func FilterStrings(as []string, p func(string) bool) []string {
    var g = func(h string, accum []string) []string {
        if p(h) {
            return append(accum, h)
        } else {
            return accum
        }
    }
    return FoldRightStrings(as, []string{}, g)
}

这里是一个例子,它过滤掉所有长度<8的字符串

    var p = func(s string) bool {
                if len(s) < 8 {
                    return true
                } else {
                    return false
                }
            }

 FilterStrings([]string{"asd","asdfas","asdfasfsa","asdfasdfsadfsadfad"}, p)

答案 7 :(得分:0)

“从数组中选择元素”通常也称为过滤器功能。没有这样的事情。也没有其他“收集功能”,如map或reduce。对于获得所需结果的最常用方法,我发现https://gobyexample.com/collection-functions是一个很好的参考:

  

[...]通常,如果您的程序和数据类型特别需要它们,则提供集合函数。

它们提供了字符串过滤函数的实现示例:

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
}

然而,他们也说,通常可以内联函数:

  

请注意,在某些情况下,最内容可能是最清晰的   集合 - 直接操作代码,而不是创建和调用   辅助功能。

一般来说,golang尝试只引入正交概念,这意味着当你可以用一种方法解决问题时,不应该有太多的方法来解决它。这只是通过一些核心概念增加了语言的简单性,这样每个开发人员都不会使用不同的语言子集。

答案 8 :(得分:0)

我正在开发这个库:https://github.com/jose78/go-collection。请尝试以下示例来过滤元素:

package main
    
import (
    "fmt"

    col "github.com/jose78/go-collection/collections"
)

type user struct {
    name string
    age  int
    id   int
}

func main() {
    newMap := generateMapTest()
    if resultMap, err := newMap.FilterAll(filterEmptyName); err != nil {
        fmt.Printf("error")
    } else {
        fmt.Printf("Result: %v\n", resultMap)

        result := resultMap.ListValues()
        fmt.Printf("Result: %v\n", result)
        fmt.Printf("Result: %v\n", result.Reverse())
        fmt.Printf("Result: %v\n", result.JoinAsString(" <---> "))
        fmt.Printf("Result: %v\n", result.Reverse().JoinAsString(" <---> "))

        result.Foreach(simpleLoop)
        err := result.Foreach(simpleLoopWithError)
        if err != nil {
            fmt.Println(err)
        }
    }
}

func filterEmptyName(key interface{}, value interface{}) bool {
    user := value.(user)
    return user.name != "empty"
}

func generateMapTest() (container col.MapType) {
    container = col.MapType{}
    container[1] = user{"Alvaro", 6, 1}
    container[2] = user{"Sofia", 3, 2}
    container[3] = user{"empty", 0, -1}
    return container
}

var simpleLoop col.FnForeachList = func(mapper interface{}, index int) {
    fmt.Printf("%d.- item:%v\n", index, mapper)
}

var simpleLoopWithError col.FnForeachList = func(mapper interface{}, index int) {
    if index > 0 {
        panic(fmt.Sprintf("Error produced with index == %d\n", index))
    }
    fmt.Printf("%d.- item:%v\n", index, mapper)
}

执行结果:

Result: map[1:{Alvaro 6 1} 2:{Sofia 3 2}]
Result: [{Sofia 3 2} {Alvaro 6 1}]
Result: [{Alvaro 6 1} {Sofia 3 2}]
Result: {Sofia 3 2} <---> {Alvaro 6 1}
Result: {Alvaro 6 1} <---> {Sofia 3 2}
0.- item:{Sofia 3 2}
1.- item:{Alvaro 6 1}
0.- item:{Sofia 3 2}
Recovered in f Error produced with index == 1

ERROR: Error produced with index == 1
Error produced with index == 1

当前DOC位于wiki section of the project中。您可以在此link中尝试一下。希望你喜欢...

REGARDS ...