Объединить два ломтика в Go


478

Я пытаюсь совместить ломтик [1, 2]и ломтик [3, 4]. Как я могу сделать это в Go?

Я старался:

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

но получил:

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

Тем не менее, документация указывает на то, что это возможно, что мне не хватает?

slice = append(slice, anotherSlice...)

Ответы:


879

Добавьте точки после второго среза:

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

Это так же, как и любая другая переменная функция.

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

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

37
append()функция с переменным числом, и ...позволяет передавать несколько аргументов функции с переменным срезом.

11
Это вообще эффективно, когда кусочки довольно большие? Или компилятор не передает все элементы как параметры?
жаба

15
@Toad: Это на самом деле не распространяет их. В foo()приведенном выше примере isпараметр содержит копию исходного среза, то есть копию легковесной ссылки на тот же базовый массив, len и cap. Если fooфункция изменяет член, изменение будет видно на оригинале. Вот демо . Таким образом, единственными реальными издержками будет то, что он создает новый фрагмент, если у вас его еще нет, например: foo(1, 2, 3, 4, 5)который создаст новый фрагмент, который isбудет содержать.

2
Ах. Если я правильно понимаю, функция variadic фактически реализована как массив параметров (вместо каждого параметра в стеке)? И так как вы передаете срез, он на самом деле отображает один на один?
жаба

@Toad: Да, когда вы используете ...существующий срез, он просто пропускает этот срез. Когда вы передаете отдельные аргументы, он собирает их в новый фрагмент и передает его. Я не из первых рук знания точной механики, но я предполагаю , что это: foo(1, 2, 3, 4, 5)и это: func foo(is ...int) {только де-сахар к этому: foo([]int{1, 2, 3, 4, 5})и это: func foo(is []int) {.

77

Добавление и копирование фрагментов

Функция variadic appendдобавляет ноль или более значений xк s типу S, который должен быть типом среза, и возвращает результирующий срез, также типа S. Значения xпередаются параметру типа, ...Tгде Tуказан тип элемента, Sи применяются соответствующие правила передачи параметров. В особом случае append также принимает первый аргумент, назначаемый типу, []byteсо вторым аргументом stringтипа, за которым следует .... Эта форма добавляет байты строки.

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}

Передача аргументов в ... параметры

Если fпеременная с конечным типом параметра ...T, то внутри функции аргумент эквивалентен параметру типа []T. При каждом вызове fаргумент, передаваемый последнему параметру, представляет собой новый фрагмент типа, []Tчьи последовательные элементы являются фактическими аргументами, которые все должны быть назначены типу T. Следовательно, длина фрагмента - это число аргументов, привязанных к конечному параметру, и может отличаться для каждого сайта вызова.

Пример ответа на ваш вопрос приведен s3 := append(s2, s0...)в спецификации языка программирования Go . Например,

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

6
Примечание: общее использование append (slice1, slice2 ...) кажется мне довольно опасным. Если slice1 - это фрагмент большего массива, значения этого массива будут перезаписаны slice2. (Это заставляет меня съеживаться, что это, кажется, не является общей проблемой?)
Хьюго

7
@Hugo Если вы «передаете» часть вашего массива, знайте, что «владелец» будет иметь возможность видеть / перезаписывать части массива, которые находятся за пределами текущей длины фрагмента. Если вы не хотите этого, вы можете использовать полное выражение среза (в форме a[low : high : max]), которое также определяет максимальную емкость . Например, срез a[0:2:4]будет иметь емкость, 4и он не может быть повторно включен для включения элементов, выходящих за рамки этого, даже если после этого массив резервных копий имеет тысячу элементов.
icza

30

Ничего против других ответов, но я нашел краткое объяснение в документах более понятным, чем примеры в них:

func append

func append(slice []Type, elems ...Type) []TypeВстроенная функция append добавляет элементы в конец фрагмента. Если он обладает достаточной пропускной способностью, место назначения изменяется для размещения новых элементов. Если этого не произойдет, будет выделен новый базовый массив. Append возвращает обновленный фрагмент. Поэтому необходимо сохранить результат добавления, часто в переменной, содержащей сам фрагмент:

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

В особом случае допустимо добавлять строку к фрагменту байта, например так:

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

1
Спасибо! Ценный для меня!
Корявин Иван

23

Я думаю, что важно указать и знать, что, если срез назначения (срез, к которому вы добавляете) имеет достаточную емкость, добавление произойдет «на месте», путем изменения назначения (с целью увеличения его длины, чтобы быть в состоянии разместить дополняемые элементы).

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

Чтобы продемонстрировать, посмотрите этот пример:

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)

Вывод (попробуйте на 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]

Мы создали «резервный» массив aс длиной 10. Затем мы создаем xсрез назначения, разрезая этот aмассив, yсрез создается с использованием составного литерала []int{3, 4}. Теперь , когда мы добавляем yк x, результат является ожидаемым [1 2 3 4], но то , что может быть удивительного в том , что массив поддержка aтакже изменился, потому что способность xIS , 10которая достаточна для добавления yк нему, так xкак resliced , который также будет использовать тот же aмассив подкладочный и append()скопирую элементы yтуда.

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

a[low : high : max]

который создает срез, а также управляет емкостью результирующего среза, установив его в max - low.

Смотрите модифицированный пример (единственное отличие в том, что мы создаем xтак 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)

Выход (попробуйте на 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]

Как вы можете видеть, мы получаем тот же xрезультат, но резервный массив aне изменился, потому что емкость xбыла «единственной» 2(благодаря полному выражению слайса a[:2:2]). Таким образом, чтобы выполнить добавление, выделяется новый резервный массив, который может хранить элементы обоих xи yотличных от a.


2
Это очень помогает решению проблемы, с которой я сталкиваюсь. Спасибо.
Эди

9

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

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

Это правильный ответ на вопрос. НО, если вам нужно использовать фрагменты 'a' и 'c' позже в коде в другом контексте, это не безопасный способ объединения фрагментов.

Чтобы объяснить, давайте прочитаем выражение не с точки зрения срезов, а с точки зрения базовых массивов:

«Возьмите (лежащий в основе) массив« a »и добавьте к нему элементы из массива« b ». Если массив« a »имеет достаточную емкость, чтобы включить все элементы из« b »- базовый массив« c »не будет новым массивом , на самом деле это будет массив 'a'. По сути, срез 'a' покажет len (a) элементов базового массива 'a', а срез 'c' покажет len (c) массива 'a'. "

append () не обязательно создает новый массив! Это может привести к неожиданным результатам. Смотрите пример Go Playground .

Всегда используйте функцию make (), если хотите убедиться, что для массива выделен новый массив. Например, вот несколько уродливых, но достаточно эффективных вариантов для этой задачи.

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)

Спасибо за указание на эти побочные эффекты. Удивительно контрастирует с этим измененным сценарием. play.golang.org/p/9FKo5idLBj4 Хотя, предоставляя избыточную емкость, следует тщательно подумать об этих удивительных побочных эффектах от вероятной интуиции.
olippuner

5

функция append () и оператор распространения

Два среза могут быть объединены с использованием appendметода в стандартной библиотеке Голанга. Что похоже на работу variadicфункции. Так что нам нужно использовать...

package main

import (
    "fmt"
)

func main() {
    x := []int{1, 2, 3}
    y := []int{4, 5, 6}
    z := append([]int{}, append(x, y...)...)
    fmt.Println(z)
}

вывод вышеуказанного кода: [1 2 3 4 5 6]


2

append([]int{1,2}, []int{3,4}...)будет работать. Передача аргументов... параметры.

Если fпеременная с конечным параметром pтипа ...T, то внутри fтипа pэквивалентна типу []T.

Если fвызывается без фактических аргументов p, передается значение pis nil.

В противном случае переданное значение представляет собой новый фрагмент типа []Tс новым базовым массивом, чьи последовательные элементы являются фактическими аргументами, которым все должны быть назначены T. Следовательно, длина и емкость фрагмента - это количество связанных аргументов, которые pмогут отличаться для каждого сайта вызовов.

Учитывая функцию и вызовы

func Greeting(prefix string, who ...string)
Greeting("nobody")
Greeting("hello:", "Joe", "Anna", "Eileen")
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.