Здесь, в посте, вы видите следующие подходы к объединению фрагментов в go.

  1. Обычно практикуется конкатенация двух слайсов
  2. Простое решение для объединения более двух фрагментов
  3. Эффективное решение для объединения более двух фрагментов

Обычно практикуется объединение двух фрагментов

package main

import "fmt"

func main() {
	slice1 := []string{"apple", "banana", "peach" }
	slice2 := []string{"avacado", "kiwi", "pineapple"}

	slice3 := append(slice1, slice2...)
	fmt.Println(slice3)
}

Конкатенацию срезов в Go можно выполнить с помощью встроенной функции append() из стандартной библиотеки.

Здесь, в приведенном выше примере, срез (slice1) является первым аргументом, а все элементы из второго среза (slice2) — вторым. используя функцию append(), когда мы передаем эти фрагменты в качестве аргументов, он возвращает обновленный фрагмент, содержащий все элементы.

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

Простое решение для объединения более двух фрагментов

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

package main

import "fmt"

func main() {

 slices := [][]string{{"apple", "banana", "peach"},
  {"orange", "grape", "mango"},
  {"strawberry", "blueberry", "raspberry"}}
 
 fmt.Println(concatAppend(slices))

}
func concatAppend(slices [][]string) []string {
 var tmp []string
 for _, s := range slices {
  tmp = append(tmp, s...)
 }
 return tmp
}

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

Эффективное решение (в 2 раза быстрее) для объединения более двух фрагментов

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

package main

import "fmt"

func main() {

 slices := [][]string{{"apple", "banana", "peach"},
  {"orange", "grape", "mango"},
  {"strawberry", "blueberry", "raspberry"}}
 
 fmt.Println(concatCopyPreAllocate(slices))

}

func concatCopyPreAllocate(slices [][]string) []string {
 var totalLen int
 for _, s := range slices {
  totalLen += len(s)
 }
 tmp := make([]string, totalLen)
 var i int
 for _, s := range slices {
  i += copy(tmp[i:], s)
 }
 return tmp
}

Вот ссылка на ответ Кэмерона Спарра на StackOverflow с примером бенчмаркинга.

Побочные эффекты объединения фрагментов с помощью append()

append() не всегда создает новый базовый массив для возвращаемого фрагмента. Если емкость фрагмента 1 достаточно велика для размещения элементов из фрагмента 2, результирующий фрагмент будет использовать тот же базовый массив, что и фрагмент 1. которые могут иметь непреднамеренные побочные эффекты.

Пример:

package main

import "fmt"

func main() {

 slice1 := make([]int, 3, 6)               // s1 has capacity of 5
 slice1[0], slice1[1], slice1[2] = 1, 2, 3 // s1 has length of 3
 slice2 := []int{4, 5, 6}

 slice3 := append(slice1, slice2...)

 fmt.Println("Before adding element to slice 3")
 fmt.Println("slice1:", slice1)
 fmt.Println("slice2:", slice2)
 fmt.Println("slice3:", slice3)
 //fmt.Println(slice1, slice3)

 slice3[0] = 7

 fmt.Println("After adding element to slice 3")
 fmt.Println("slice1:", slice1)
 fmt.Println("slice2:", slice2)
 fmt.Println("slice3:", slice3)
}

//output 
Before adding element to slice 3
slice1: [1 2 3]
slice2: [4 5 6]
slice3: [1 2 3 4 5 6]
After adding element to slice 3
slice1: [7 2 3]
slice2: [4 5 6]
slice3: [7 2 3 4 5 6]

Изменение значения элемента в фрагменте 3 также приводит к изменению фрагмента 1. Это произошло потому, что они оба используют один и тот же базовый массив, и это могло привести к ошибкам. Эту проблему можно предотвратить, убедившись, что срез, возвращаемый append(), поддерживается новым базовым массивом независимо от емкости слайса 1.

Вот как:

package main

import "fmt"

func main() {

 slice1 := make([]int, 3, 6)               // s1 has capacity of 5
 slice1[0], slice1[1], slice1[2] = 1, 2, 3 // s1 has length of 3
 slice2 := []int{4, 5, 6}

 slice3 := append(slice1[:len(slice1):len(slice1)], slice2...)

 fmt.Println("Before adding element to slice 3")
 fmt.Println("slice1:", slice1)
 fmt.Println("slice2:", slice2)
 fmt.Println("slice3:", slice3)
 //fmt.Println(slice1, slice3)

 slice3[0] = 7

 fmt.Println("After adding element to slice 3")
 fmt.Println("slice1:", slice1)
 fmt.Println("slice2:", slice2)
 fmt.Println("slice3:", slice3)
}

//output 
Before adding element to slice 3
slice1: [1 2 3]
slice2: [4 5 6]
slice3: [1 2 3 4 5 6]
After adding element to slice 3
slice1: [1 2 3]
slice2: [4 5 6]
slice3: [7 2 3 4 5 6]

Настройка функции добавления для

append(slice1[:len(slice1):len(slice1)], slice2...)

//output 
Before adding element to slice 3
slice1: [1 2 3]
slice2: [4 5 6]
slice3: [1 2 3 4 5 6]
After adding element to slice 3
slice1: [1 2 3]
slice2: [4 5 6]
slice3: [7 2 3 4 5 6]

Заключение

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

Удачного кодирования!

Повышение уровня кодирования

Спасибо, что являетесь частью нашего сообщества! Перед тем, как ты уйдешь:

  • 👏 Хлопайте за историю и подписывайтесь на автора 👉
  • 📰 Смотрите больше контента в публикации Level Up Coding
  • 🔔 Подписывайтесь на нас: Twitter | ЛинкедИн | "Новостная рассылка"

🚀👉 Присоединяйтесь к коллективу талантов Level Up и найдите прекрасную работу