С нарезкой и копированием
Встроенная функция append()
всегда добавляет элементы к фрагменту. Вы не можете использовать его (отдельно) для добавления элементов.
Сказав это, если у вас есть емкость среза, превышающая длину (имеет «свободное» пространство после его элементов), к которому вы хотите добавить элемент, вы можете повторно нарезать исходный срез, скопировать все элементы в индекс на один выше, чтобы освободить место для нового элемента, а затем присвойте элементу 0th индекс. Это не потребует нового распределения. Вот как это может выглядеть:
func prepend(dest []int, value int) []int {
if cap(dest) > len(dest) {
dest = dest[:len(dest)+1]
copy(dest[1:], dest)
dest[0] = value
return dest
}
// No room, new slice need to be allocated:
// Use some extra space for future:
res := make([]int, len(dest)+1, len(dest)+5)
res[0] = value
copy(res[1:], dest)
return res
}
Тестирование:
s := make([]int, 0, 5)
s = append(s, 1, 2, 3, 4)
fmt.Println(s)
s = prepend(s, 9)
fmt.Println(s)
s = prepend(s, 8)
fmt.Println(s)
Вывод (попробуйте на Go Playground):
[1 2 3 4]
[9 1 2 3 4]
[8 9 1 2 3 4]
Примечание: если нет места для нового элемента, поскольку сейчас важна производительность, мы не просто сделали:
res := append([]int{value}, dest...)
Потому что он делает больше выделений и копий, чем нужно: выделяет слайс для литерала ([]int{value}
), затем append()
выделяет новый при добавлении к нему dest
.
Вместо этого наше решение выделяет только один новый массив (по make()
, даже резервируя место для будущего роста), затем просто устанавливает value
в качестве первого элемента и копирует dest
(предыдущие элементы).
Со связанным списком
Если вам нужно много раз добавлять начало, обычный фрагмент может быть неправильным выбором. Более быстрой альтернативой было бы использование связанного списка, для которого добавление элемента в начало не требует выделения фрагментов/массивов и копирования, вы просто создаете новый элемент узла и назначаете его корнем, указывая на старый корень ( первый элемент).
Стандартная библиотека предоставляет общую реализацию в пакете container/list
.
С ручным управлением большим резервным массивом
Придерживаясь обычных срезов и массивов, есть другое решение.
Если вы хотите самостоятельно управлять большим резервным массивом (или слайсом), вы можете сделать это, оставив свободное место перед используемым слайсом. При добавлении вы можете создать новое значение среза из резервного большего массива или среза, который начинается с индекса, оставляющего место для добавления 1 элемента.
Без полноты, просто для демонстрации:
var backing = make([]int, 15) // 15 elements
var start int
func prepend(dest []int, value int) []int {
if start == 0 {
// No more room for new value, must allocate bigger backing array:
newbacking := make([]int, len(backing)+5)
start = 5
copy(newbacking[5:], backing)
backing = newbacking
}
start--
dest = backing[start : start+len(dest)+1]
dest[0] = value
return dest
}
Тестирование/использование:
start = 5
s := backing[start:start] // empty slice, starting at idx=5
s = append(s, 1, 2, 3, 4)
fmt.Println(s)
s = prepend(s, 9)
fmt.Println(s)
s = prepend(s, 8)
fmt.Println(s)
// Prepend more to test reallocation:
for i := 10; i < 15; i++ {
s = prepend(s, i)
}
fmt.Println(s)
Результат (попробуйте на Go Playground):
[1 2 3 4]
[9 1 2 3 4]
[8 9 1 2 3 4]
[14 13 12 11 10 8 9 1 2 3 4]
Анализ: это решение не производит выделения и копирования, когда в фрагменте backing
есть место для добавления значения в начале! Все, что происходит, — это создание нового фрагмента из фрагмента backing
, который покрывает место назначения +1. для значения, которое должно быть добавлено, устанавливает его и возвращает это значение среза. Вы не можете стать лучше, чем это.
Если места нет, то он выделяет больший фрагмент backing
, копирует содержимое старого, а затем выполняет «нормальное» добавление.
С хитрым использованием фрагмента
Идея: представьте, что вы всегда храните элементы в срезе в обратном порядке.
Хранение элементов в обратном порядке в срезе означает, что препан становится добавлением!
Таким образом, чтобы «подготовить» элемент, вы можете просто использовать append(s, value)
. И это все.
Да, у этого есть свои ограничения (например, добавление к срезу, хранящемуся в обратном порядке, имеет те же проблемы и сложность, что и «обычный» срез и операция prepan), и вы теряете многие удобства (возможность перечислять элементы, используя for range
, просто чтобы назвать один ), но с точки зрения производительности ничто не сравнится с предварительным добавлением значения, просто используя append()
.
Примечание: итерация по элементам, которые хранят элементы в обратном порядке, должна использовать нисходящий цикл, например:
for i := len(s) - 1; i >= 0; i-- {
// do something with s[i]
}
Заключительное замечание: все эти решения можно легко расширить, чтобы добавить срез, а не просто значение. Как правило, дополнительное пространство при перенарезке составляет не +1
, а +len(values)
, и не просто установка dst[0] = value
, а вызов copy(dst, values)
.
person
icza
schedule
29.01.2017