Видалити елемент з фрагмента


139
func main() {
    a := []string{"Hello1", "Hello2", "Hello3"}
    fmt.Println(a)
    // [Hello1 Hello2 Hello3]
    a = append(a[:0], a[1:]...)
    fmt.Println(a)
    // [Hello2 Hello3]
}

Як працює цей фокус видалення за допомогою функції додавання?

Здавалося б, це схоплює все перед першим елементом (порожній масив)

Потім додавання всього після першого елемента (нульове положення)

Що робить ... (dot dot dot)?



3
Чому б не подивитися на специфікацію мови? ...там детально пояснено?
Волкер

9
З одного боку, абсолютно вірно ( golang.org/ref/spec , всі); з іншого, про ці ідіоми для міграції піфоністів і т. д. достатньо незнайомих, що я не заперечую, щоб там з’ясували пояснення для інших.
twotwotwo

Відповіді:


276

Де aзнаходиться фрагмент та iіндекс елемента, який потрібно видалити:

a = append(a[:i], a[i+1:]...)

... є синтаксисом для різноманітних аргументів у Go.

В основному, визначаючи функцію, вона розміщує всі аргументи, які ви передаєте в один фрагмент цього типу. Роблячи це, ви можете передавати стільки аргументів, скільки хочете (наприклад, fmt.Printlnможете взяти стільки аргументів, скільки вам потрібно).

Тепер при виклику функції ...робиться навпаки: вона розпаковує фрагмент і передає їх як окремі аргументи варіативної функції.

Отже, що робить цей рядок:

a = append(a[:0], a[1:]...)

є по суті:

a = append(a[:0], a[1], a[2])

Тепер ви можете задатися питанням, чому б не просто так зробити

a = append(a[1:]...)

Що ж, визначення функції appendє

func append(slice []Type, elems ...Type) []Type

Отже, перший аргумент повинен бути фрагментом правильного типу, другий аргумент - варіативним, тому ми переходимо в порожній фрагмент, а потім розпаковуємо решту фрагмента для заповнення аргументів.


35
Чи не виходите ви винятком діапазону, якщо я останній елемент з фрагмента? a = append(a[:i], a[i+1:]...)
теміхай

5
@DaveC Я отримую цю помилку під час роботи зі своїми фрагментами в моєму проекті: /
Tyguy7

3
@ Tyguy7 з специфікації: "Для масивів або рядків індекси знаходяться в діапазоні, якщо 0 <= низький <= високий <= len (a), інакше вони знаходяться поза діапазоном." Можливо, у вашому випадку високий <низький; в такому випадку ви отримаєте помилку. ( golang.org/ref/spec#Slice_expressions )
мл

2
Як відбувається це? Я серйозно сподіваюся, що це не створить абсолютно новий фрагмент під кришкою ..
joonas.fi

7
@ Tyguy7 Я думаю, ви намагалися видалити елементи зрізу в циклі. Тож вам слід бути обережними з індексами.
Микола Бистрицький

42

Є два варіанти:

A: Ви дбаєте про збереження порядку масивів:

a = append(a[:i], a[i+1:]...)
// or
a = a[:i+copy(a[i:], a[i+1:])]

B: Вам не байдуже зберігати порядок (це, мабуть, швидше):

a[i] = a[len(a)-1] // Replace it with the last one. CAREFUL only works if you have enough elements.
a = a[:len(a)-1]   // Chop off the last one.

Перегляньте посилання, щоб побачити наслідки для витоку пам'яті, якщо ваш масив покажчиків.

https://github.com/golang/go/wiki/SliceTricks


Це цікаво, але насправді не відповідає на питання
Брайан

Це добре, але було б краще, якщо він може видалити єдиний елемент з масиву
Naguib Ihab

2
Просто голова вгору, спроба використання першого з b (замінити останнім елементом) очевидно не спрацьовує, якщо ви намагаєтесь видалити останній елемент з фрагмента lol
Sirens

13

Замість того, щоб думати про індекси в позначеннях [a:]-, [:b]- та - [a:b]як про елементи індексу, вважайте їх індексами прогалин навколо та між елементами, починаючи з проміжку, індексованого 0перед елементом, індексованим як 0.

введіть тут опис зображення

Дивлячись лише на блакитні цифри, набагато простіше побачити, що відбувається: [0:3]закриває все, [3:3]порожнє і [1:2]вийде {"B"}. Тоді [a:]є лише коротка версія [a:len(arrayOrSlice)], [:b]коротка версія [0:b]та [:]коротка версія [0:len(arrayOrSlice)]. Останній зазвичай використовується для перетворення масиву в фрагмент, коли це необхідно.


1
Це допомагає пояснити , чому відповідь «ні» , щоб themihai ігрова коментар на Дейва відповідь , посилаючись на [я + 1:] , навіть коли мова йде про го елемента: play.golang.org/p/E0lQ3jPcjX5
Нік P

5

... є синтаксисом для різноманітних аргументів.

Я думаю, що він реалізований complier за допомогою фрагмента ( []Type)як і додаток функції:

func append(slice []Type, elems ...Type) []Type

коли ви використовуєте "elems" у "append", насправді це фрагмент ([]). Отже " a = append(a[:0], a[1:]...)" означає " a = append(a[0:0], a[1:])"

a[0:0] - це шматочок, в якому немає нічого

a[1:] це "Hello2 Hello3"

Ось як це працює


2
a[0:0]- це не nilзріз довжиною 0. a[0:0]буде тільки бути , nilякщо aє nil.
icza

5

Я отримую індекс поза помилкою діапазону з прийнятим рішенням відповіді. Причина: Коли діапазон починається, це значення не повторюється по черзі, це ітерація за індексом. Якщо ви змінили фрагмент, поки він знаходиться в діапазоні, це спричинить певну проблему.

Стара відповідь:

chars := []string{"a", "a", "b"}

for i, v := range chars {
    fmt.Printf("%+v, %d, %s\n", chars, i, v)
    if v == "a" {
        chars = append(chars[:i], chars[i+1:]...)
    }
}
fmt.Printf("%+v", chars)

Очікується:

[a a b], 0, a
[a b], 0, a
[b], 0, b
Result: [b]

Фактична:

// Autual
[a a b], 0, a
[a b], 1, b
[a b], 2, b
Result: [a b]

Правильний шлях (рішення):

chars := []string{"a", "a", "b"}

for i := 0; i < len(chars); i++ {
    if chars[i] == "a" {
        chars = append(chars[:i], chars[i+1:]...)
        i-- // form the remove item index to start iterate next item
    }
}

fmt.Printf("%+v", chars)

Джерело: https://dinolai.com/notes/golang/golang-delete-slice-item-in-range-problem.html


3

У вікі-голангу він показує деякі хитрощі для фрагмента, включаючи видалення елемента з фрагмента.

Посилання: сюди введіть опис посилання

Наприклад, a - це фрагмент, з якого потрібно видалити елемент i i.

a = append(a[:i], a[i+1:]...)

АБО

a = a[:i+copy(a[i:], a[i+1:])]
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.