web-dev-qa-db-fra.com

Le moyen le plus idiomatique de sélectionner des éléments d’un tableau dans Golang?

J'ai un tableau de chaînes et j'aimerais exclure les valeurs qui commencent par foo_ OR sont plus longues que 7 caractères.

Je peux parcourir chaque élément en boucle, exécuter l'instruction if et l'ajouter à une tranche en cours de route. Mais j'étais curieux de savoir s'il y avait une manière idiomatique ou plus semblable à un golang d'accomplir cela.

Par exemple, Ruby pourrait faire la même chose que

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

Il n'y a pas de ligne unique comme dans Ruby, mais avec une fonction d'assistance, vous pouvez le rendre presque aussi court.

Voici notre fonction d'assistance qui boucle sur une tranche, sélectionne et renvoie uniquement les éléments qui répondent à un critère capturé par une valeur de fonction:

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

En utilisant cette fonction d'assistance, votre tâche est la suivante:

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)

Sortie (essayez-le sur Go Playground ):

[asdf nfoo_1]

Remarque:

Si l'on s'attend à ce que de nombreux éléments soient sélectionnés, il peut être intéressant d'affecter une "grande" tranche ret au préalable et d'utiliser une affectation simple au lieu de la fonction append(). Et avant de revenir, découpez la ret pour qu’elle ait une longueur égale au nombre d’éléments sélectionnés.

Note 2:

Dans mon exemple, j'ai choisi une fonction test() qui indique si un élément doit être renvoyé. J'ai donc dû inverser votre condition "d'exclusion". Évidemment, vous pouvez écrire que la fonction d'assistance attend une fonction de testeur qui indique ce qu'il faut exclure (et non ce qu'il faut inclure).

26
icza

Jetez un coup d'œil à la bibliothèque de filtres de robpike . Cela vous permettrait de faire:

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

Fait intéressant, le fichier README.md de Rob:

Je voulais voir combien il était difficile d'implémenter ce genre de choses dans Go, avec une API aussi agréable que possible. Ce n'était pas difficile . Après l'avoir écrit il y a quelques années, je n'ai pas eu l'occasion de l'utiliser une seule fois. Au lieu de cela, je viens d'utiliser "pour" boucles . Vous ne devriez pas l'utiliser non plus.

Ainsi, selon Rob, la manière la plus idiomatique serait quelque chose comme:

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

Ce style est très similaire, voire identique, à l’approche adoptée par toutes les langues de la famille C.

11
Elwin Arens

Aujourd'hui, je suis tombé sur un joli idiome qui m'a surpris. Si vous souhaitez filtrer une tranche à la place sans allouer, utilisez deux tranches avec le même tableau de sauvegarde:

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

Voici un exemple spécifique de suppression des chaînes en double:

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

Si vous devez conserver les deux tranches, remplacez simplement s = s[:0] par s = nil ou s = make([]T, 0, len(s)), selon que vous souhaitez que append() vous alloue ou non.

3
MicahStetson

Il n’existe pas de manière idiomatique d’obtenir le même résultat attendu dans Go en une seule ligne que dans Ruby, mais avec une fonction d’aide, vous pouvez obtenir la même expressivité que dans Ruby. 

Vous pouvez appeler cette fonction d'assistance en tant que:

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

Voici le code complet:

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

Et l'exemple en cours: Playground

3
Simo Endre

"Sélectionner des éléments dans un tableau" est également communément appelée fonction filter. Il n'y a rien de tel en aller. Il n’existe pas non plus d’autres "fonctions de collecte" telles que mapper ou réduire. Pour trouver le moyen le plus idiomatique d’obtenir le résultat souhaité, je trouve https://gobyexample.com/collection-functions une bonne référence:

[...] dans Go, il est courant de fournir des fonctions de collecte si et quand elles sont spécifiquement nécessaires pour votre programme et vos types de données.

Ils fournissent un exemple d'implémentation de la fonction de filtrage pour les chaînes:

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
}

Cependant, ils disent aussi qu'il est souvent correct de simplement intégrer la fonction:

Notez que dans certains cas, il peut être plus simple de simplement insérer le code manipulant la collection directement, au lieu de créer et d’appeler une fonction d'assistance.

En général, golang essaie d'introduire uniquement des concepts orthogonaux, ce qui signifie que lorsque vous pouvez résoudre un problème d'une manière, il ne devrait pas y avoir trop de façons de le résoudre. Cela ajoute de la simplicité au langage en ne disposant que de quelques concepts de base, de sorte que tous les développeurs n'utilisent pas un sous-ensemble différent du langage.

0
bersling