web-dev-qa-db-ja.com

Golangの配列から要素を選択する最も慣用的な方法は?

文字列の配列があり、foo_ ORが7文字より長い]で始まる値を除外したいと思います。

各要素をループして、ifステートメントを実行し、途中でスライスに追加できます。しかし、私はそれを達成するための慣用的またはよりゴランのような方法があるかどうか興味がありました。

たとえば、Ruby

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

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()関数を選択しました。だから私はあなたの「除外」条件を逆転させなければなりませんでした。明らかに、何を除外するか(何を含めるかではない)を伝えるテスター関数を期待するために、ヘルパー関数を書くことができます。

37
icza

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

興味深いことに、RobによるREADME.mdは次のとおりです。

私は、この種のことをGoで実装するのがどれだけ難しいか、管理できるAPIであるNiceを確認したかったのです。難しくありませんでした。数年前に書いたので、一度も使う機会はありませんでした。代わりに、「for」ループを使用します。どちらも使用しないでください。

したがって、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ファミリ言語がとるアプローチと同一ではないにしても、非常に似ています。

12
Elwin Arens

今日、私は驚いたかなりのイディオムにつまずいた。割り当てずにスライスを所定の場所にフィルター処理する場合は、同じバッキング配列を持つ2つのスライスを使用します。

_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 = make([]T, 0, len(s))を割り当てるかどうかに応じて、_s = s[:0]_を_s = nil_またはappend()に置き換えるだけです。

6
MicahStetson

GoでRubyのように1行で同じ結果を得ることができる慣用的な方法はありませんが、ヘルパー関数を使用すると、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

4
Simo Endre

「配列から要素を選択する」は、一般的にフィルター関数と呼ばれます。そのようなことはありません。 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
}

しかし、彼らはまた、関数をインライン化するだけでも大丈夫だと言っています:

場合によっては、ヘルパー関数を作成して呼び出す代わりに、コレクションを操作するコードを直接インライン化することが最も明確な場合があることに注意してください。

一般に、golangは直交概念のみを導入しようとします。つまり、ある方法で問題を解決できる場合、それ以上多くの方法で解決するべきではありません。すべての開発者が言語の異なるサブセットを使用するわけではないように、コアの概念をいくつか持つだけで、これにより言語がシンプルになります。

0
bersling