613 votes

Concaténation de deux tranches en Go

J'essaie de combiner la tranche [1, 2] et la tranche [3, 4] . Comment puis-je faire cela en Go ?

J'ai essayé :

append([]int{1,2}, []int{3,4})

mais a obtenu :

cannot use []int literal (type []int) as type int in append

Cependant, la documentation semble indiquer que c'est possible, qu'est-ce que je manque ?

slice = append(slice, anotherSlice...)

1146voto

squint Points 28293

Ajoutez des points après la deuxième tranche :

//---------------------------vvv
append([]int{1,2}, []int{3,4}...)

C'est comme toute autre fonction variadique.

func foo(is ...int) {
    for i := 0; i < len(is); i++ {
        fmt.Println(is[i])
    }
}

func main() {
    foo([]int{9,8,7,6,5}...)
}

49 votes

append() une fonction variadique, et le ... vous permet de passer plusieurs arguments à une fonction variadique à partir d'une tranche.

16 votes

Est-ce que cela est performant lorsque les tranches sont assez grandes ? Ou bien le compilateur ne passe-t-il pas vraiment tous les éléments en paramètre ?

17 votes

@Toad : En fait, ça ne les étale pas. Dans le foo() exemple ci-dessus, le is contient une copie de la tranche originale, c'est-à-dire une copie de la référence légère au même tableau sous-jacent, len et cap. Si le paramètre foo modifie un membre, le changement sera visible sur l'original. Voici une démo . Ainsi, la seule surcharge réelle sera la création d'une nouvelle tranche si vous n'en aviez pas déjà une, par exemple : foo(1, 2, 3, 4, 5) ce qui créera une nouvelle tranche qui is tiendra.

91voto

peterSO Points 25725

Ajout et copie de tranches

La fonction variadique append ajoute zéro ou plusieurs valeurs x à s de type S qui doit être un type de tranche, et renvoie la tranche résultante résultante, également de type S . Les valeurs x sont passés à un paramètre de type ...TT est le type d'élément de S et les règles respectives de règles de passage des paramètres s'appliquent. Dans un cas particulier, append accepte également un premier argument assignable au type []byte avec un second argument de string suivi du type ... . Cette forme ajoute les octets de la chaîne.

append(s S, x ...T) S  // T is the element type of S

s0 := []int{0, 0}
s1 := append(s0, 2)        // append a single element     s1 == []int{0, 0, 2}
s2 := append(s1, 3, 5, 7)  // append multiple elements    s2 == []int{0, 0, 2, 3, 5, 7}
s3 := append(s2, s0...)    // append a slice              s3 == []int{0, 0, 2, 3, 5, 7, 0, 0}

Passage d'arguments aux paramètres ...

Si f est variadique avec le type de paramètre final ...T alors, dans la fonction l'argument est équivalent à un paramètre de type []T . Sur chaque appel de f l'argument passé au paramètre final est une nouvelle tranche de type tranche de type []T dont les éléments successifs sont les arguments réels, qui doivent tous être assignables au type T . La longueur de la tranche est donc le nombre d'arguments liés au paramètre final et peut différer pour chaque site d'appel.

La réponse à votre question est l'exemple s3 := append(s2, s0...) dans le Spécification du langage de programmation Go . Par exemple,

s := append([]int{1, 2}, []int{3, 4}...)

10 votes

Note : l'utilisation générale de append(slice1, slice2...) me semble assez dangereuse. Si slice1 est une tranche d'un tableau plus grand, les valeurs de ce tableau seront écrasées par slice2. (Cela me fait grincer des dents que cela ne semble pas être une préoccupation commune ?)

8 votes

@Hugo Si vous "remettez" une tranche de votre tableau, sachez que le "propriétaire" de la tranche pourra voir/écrire les parties du tableau qui sont au-delà de la longueur actuelle de la tranche. Si vous ne voulez pas cela, vous pouvez utiliser une balise expression de la tranche complète (sous la forme de a[low : high : max] ) qui spécifie également la valeur maximale capacité . Par exemple, la tranche a[0:2:4] aura une capacité de 4 et il ne peut pas être redécoupé pour inclure des éléments au-delà, même si le tableau de base contient un millier d'éléments après cela.

43voto

fiatjaf Points 954

Rien contre les autres réponses, mais j'ai trouvé la brève explication dans les docs plus facilement compréhensibles que les exemples qu'ils contiennent :

func append

func append(slice []Type, elems ...Type) []Type La fonction intégrée append ajoute des éléments à la fin d'une tranche. Si sa capacité est suffisante capacité, la destination est redécoupée pour accueillir les nouveaux éléments. Dans le cas contraire, un nouveau tableau sous-jacent sera alloué. Append renvoie la tranche mise à jour. Il est donc nécessaire de stocker le résultat de Append, souvent dans la variable contenant la tranche elle-même :

slice = append(slice, elem1, elem2)
slice = append(slice, anotherSlice...)

Dans un cas particulier, il est légal d'ajouter une chaîne de caractères à une tranche d'octets, comme ceci :

slice = append([]byte("hello "), "world"...)

2 votes

Merci de votre attention ! C'est précieux pour moi !

0 votes

J'aimerais que ce soit la première réponse !

33voto

icza Points 3857

Je pense qu'il est important de souligner et de savoir que si la tranche de destination (la tranche à laquelle vous ajoutez) a une capacité suffisante, l'ajout se fera "sur place", en redécoupant la destination (en la redécoupant en augmentation de sa longueur afin de pouvoir accueillir les éléments annexables).

Cela signifie que si la destination a été créée en découpant un tableau plus grand ou une tranche qui comporte des éléments supplémentaires au-delà de la longueur de la tranche résultante, ceux-ci peuvent être écrasés.

Pour le démontrer, voyez cet exemple :

a := [10]int{1, 2}
fmt.Printf("a: %v\n", a)

x, y := a[:2], []int{3, 4}
fmt.Printf("x: %v, y: %v\n", x, y)
fmt.Printf("cap(x): %v\n", cap(x))

x = append(x, y...)
fmt.Printf("x: %v\n", x)

fmt.Printf("a: %v\n", a)

Sortie (essayez-la sur le Go Playground ) :

a: [1 2 0 0 0 0 0 0 0 0]
x: [1 2], y: [3 4]
cap(x): 10
x: [1 2 3 4]
a: [1 2 3 4 0 0 0 0 0 0]

Nous avons créé un tableau de "sauvegarde". a avec la longueur 10 . Ensuite, nous créons le x de destination en découpant cette a le tableau, y est créée en utilisant le littéral composite []int{3, 4} . Maintenant, lorsque nous ajoutons y à x le résultat est celui attendu [1 2 3 4] mais ce qui peut être surprenant, c'est que le réseau de soutien a a également changé, car la capacité de x est 10 ce qui est suffisant pour ajouter y à elle, donc x est redécoupé, ce qui utilisera également la même a la matrice de sauvegarde, et append() copiera les éléments de y là-dedans.

Si vous souhaitez éviter cela, vous pouvez utiliser un fichier expression de la tranche complète qui a la forme

a[low : high : max]

qui construit une tranche et contrôle également la capacité de la tranche résultante en lui donnant la valeur suivante max - low .

Voir l'exemple modifié (la seule différence est que nous créons x comme ça : x = a[:2:2] :

a := [10]int{1, 2}
fmt.Printf("a: %v\n", a)

x, y := a[:2:2], []int{3, 4}
fmt.Printf("x: %v, y: %v\n", x, y)
fmt.Printf("cap(x): %v\n", cap(x))

x = append(x, y...)
fmt.Printf("x: %v\n", x)

fmt.Printf("a: %v\n", a)

Sortie (essayez-la sur le Go Playground )

a: [1 2 0 0 0 0 0 0 0 0]
x: [1 2], y: [3 4]
cap(x): 2
x: [1 2 3 4]
a: [1 2 0 0 0 0 0 0 0 0]

Comme vous pouvez le voir, nous obtenons le même x résultat mais le tableau de sauvegarde a n'a pas changé, car la capacité de x était "seulement" 2 (grâce à l'expression de la tranche complète a[:2:2] ). Ainsi, pour effectuer l'ajout, un nouveau tableau de sauvegarde est alloué et peut stocker les éléments des deux tableaux suivants x et y qui est distinct de a .

3 votes

C'est très utile pour le problème auquel je suis confronté. Je vous remercie.

0 votes

Merci, c'est très utile - cependant, est-ce que le comportement illustré seulement se passe-t-il si le tableau d'accompagnement est suffisamment court pour contenir les nouvelles valeurs ? Par exemple, si dans votre exemple y était de longueur 20, est-ce que le a restent inchangées ?

0 votes

@patrick Oui, s'il n'y a pas assez de place pour l'ajouter, append() alloue un nouveau tableau de support, copie l'ancien contenu, et effectue l'append sur le nouveau tableau de support en laissant l'ancien intact. Est-ce difficile d'essayer ? Go Playground

23voto

D.C. Joo Points 546

Je voudrais souligner la réponse d'@icza et la simplifier un peu car c'est un concept crucial. Je suppose que le lecteur est familier avec tranches .

c := append(a, b...)

C'est une réponse valable à la question. MAIS si vous avez besoin d'utiliser les tranches 'a' et 'c' plus tard dans le code dans un contexte différent, ce n'est pas la façon sûre de concaténer les tranches.

Pour expliquer, lisons l'expression non pas en termes de tranches, mais en termes de tableaux sous-jacents :

"Prenez le tableau (sous-jacent) 'a' et ajoutez-y les éléments du tableau 'b'". le tableau. Si le tableau 'a' a une capacité suffisante pour inclure tous les éléments du tableau 'b', le tableau sous-jacent 'c' ne sera pas un nouveau tableau. - le tableau sous-jacent de 'c' ne sera pas un nouveau tableau, il sera en fait le tableau 'a'. En fait, la tranche 'a' affichera les éléments len(a) du tableau sous-jacent 'a'. du tableau sous-jacent 'a', et la tranche 'c' montrera len(c) du tableau 'a'."

append() ne crée pas nécessairement un nouveau tableau ! Cela peut conduire à des résultats inattendus. Voir Exemple de terrain de jeu Go .

Utilisez toujours la fonction make() si vous voulez vous assurer qu'un nouveau tableau est alloué pour la tranche. Par exemple, voici quelques options laides mais assez efficaces pour cette tâche.

la := len(a)
c := make([]int, la, la + len(b))
_ = copy(c, a)
c = append(c, b...)

la := len(a)
c := make([]int, la + len(b))
_ = copy(c, a)
_ = copy(c[la:], b)

1 votes

Merci de signaler ces effets secondaires. Le contraste avec ce scénario modifié est étonnant. play.golang.org/p/9FKo5idLBj4 Toutefois, lorsqu'on fournit une capacité excédentaire, il convient de réfléchir attentivement à ces effets secondaires déroutants par rapport à une intuition plausible.

0 votes

Merci Joo, j'ai passé presque deux heures à chercher un problème dans le code qui était dû au fait que je n'ai pas suivi la ligne directrice que vous avez indiquée sur le fait qu'il n'est pas sûr de concaténer deux tranches que l'on utilisera plus tard (peut-être que l'on pourrait inclure cet avertissement dans ce document) : blog.golang.org/slices ). Et merci pour l'extrait de copie qui a l'air très savoureux !

0 votes

C'est la réponse qui devrait être acceptée. N'oubliez pas de toujours enregistrer la sortie d'append dans la même variable que le premier argument, comme suit : a := append(a, b...)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X