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

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

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

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

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

Ответ 1

В 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() которая сообщает, должен ли элемент быть возвращен. Так что мне пришлось поменять ваше "исключающее" состояние. Очевидно, что вы можете написать вспомогательную функцию, ожидающую функцию тестера, которая сообщает, что исключать (а не что включать).

Ответ 2

Взгляните на библиотеку фильтров 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 Роба:

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

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

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.

Ответ 3

Нет идиоматического способа добиться того же ожидаемого результата в 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
    }))
}

И пример выполнения: Игровая площадка

Ответ 4

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

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() для вас,

Ответ 5

"Выбор элементов из массива" также обычно называют функцией фильтра. Там нет такой вещи в ходу. Также нет других "функций коллекции", таких как 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
}

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

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

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