Самый идиоматический способ выбора элементов из массива в Golang?

у меня есть массив строк, и я хотел бы исключить значения, которые начинаются с foo_ или длиннее 7 символов.

Я могу перебирать каждый элемент, запускать if оператор и добавьте его в срез по пути. Но мне было интересно, если там был идиоматическое или более golang-как способ достижения этого.

например, то же самое можно сделать в Ruby как

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

5 ответов


нет однострочного лайнера, как у вас в Ruby, но с вспомогательной функцией вы можете сделать его почти таким же коротким.

вот наша вспомогательная функция, которая петляет над срезом и выбирает и возвращает только элементы, которые соответствуют критериям, захваченным значением функции:

func choose(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 := choose(ss, mytest)

fmt.Println(s2)

вывод (попробуйте на Go Playground):

[asdf nfoo_1]

Примечание:

если ожидается, что будет выбрано много элементов, может быть выгодно выделить "большой" ret нарезать заранее и использовать простое назначение вместо append(). И прежде чем вернуться, нарежьте ret иметь длину, равную количеству выбранных элементов.

примечание #2:

в моем примере я выбрал test() функция, которая сообщает, должен ли быть возвращен элемент. Поэтому мне пришлось перевернуть ваше условие "исключения". Очевидно, вы можете написать вспомогательная функция ожидать функцию тестера, которая говорит, что исключить (а не что включить).


посмотреть библиотека фильтров robpike. Это позволит вам сделать:

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

интересно достаточно README.md by Rob:

Я хотел посмотреть, как трудно было реализовать такого рода вещи в Go, с таким хорошим API, как я мог управлять. Это было нетрудно. Написав его пару лет назад, я ни разу не воспользовался им. Вместо этого я просто использую "для" циклов. Тебе тоже не стоит им пользоваться.

Так наиболее идиоматичным способом, по мнению Роба, было бы что-то вроде:

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-family.


нет идиоматического способа достичь того же ожидаемого результата в Go в одной строке, что и в Ruby, но с помощью вспомогательной функции вы можете получить ту же выразительность, что и в 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
    }))
}

и запущенный пример:площадка


сегодня я наткнулся на довольно идиому, которая удивила меня. Если вы хотите отфильтровать срез на месте без выделения, используйте два среза с одним и тем же массивом поддержки:

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)) в зависимости от того, хотите ли вы append() выделить для вас.


"выбрать элементы из массива" также обычно называют фильтр. В го такого нет. Также нет других "функций сбора", таких как map или reduce. Для наиболее идиоматического способа получить желаемый результат я нахожу https://gobyexample.com/collection-functions хорошая ссылка:

[...] в Go обычно предоставляются функции сбора, если и когда они специально необходимы для вашей программы и данных типы.

они предоставляют пример реализации функции фильтра для строк:

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
}

однако они также говорят, что часто нормально просто встроить функцию:

обратите внимание, что в некоторых случаях это может быть ясным только текст collection-прямое управление кодом вместо создания и вызова вспомогательная функция.

В общем, голанг пытается ввести только ортогональные понятия, что означает, что когда можно решить проблему одним способом, не должно быть слишком много способов ее решения. Это добавляет простоту языку, имея только несколько основных концепций, так что не каждый разработчик использует другое подмножество языка.