Як видалити елемент із фрагмента в Golang


111
fmt.Println("Enter position to delete::")
fmt.Scanln(&pos)

new_arr := make([]int, (len(arr) - 1))
k := 0
for i := 0; i < (len(arr) - 1); {
    if i != pos {
        new_arr[i] = arr[k]
        k++
        i++
    } else {
        k++
    }
}

for i := 0; i < (len(arr) - 1); i++ {
    fmt.Println(new_arr[i])
}

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


2
Це гарне читання: blog.golang.org/go-slices-usage-and-internals
squiguy

Відповіді:


180

Порядок має значення

Якщо ви хочете зберегти свій масив упорядкованим, вам доведеться перенести всі елементи праворуч від індексу видалення на один вліво. Сподіваємось, це можна легко зробити в Golang:

func remove(slice []int, s int) []int {
    return append(slice[:s], slice[s+1:]...)
}

Однак це неефективно, оскільки ви можете в кінцевому підсумку перемістити всі елементи, що є дорогим.

Порядок не важливий

Якщо ви не дбаєте про замовлення, у вас є набагато швидша можливість поміняти елемент для видалення на той, що знаходиться в кінці фрагмента, а потім повернути перші елементи n-1:

func remove(s []int, i int) []int {
    s[len(s)-1], s[i] = s[i], s[len(s)-1]
    return s[:len(s)-1]
}

За допомогою методу розмноження спорожнення масиву з 1 000 000 елементів займає 224 с, а для цього - лише 0,06 нс. Я підозрюю, що внутрішньо go лише змінює довжину зрізу, не змінюючи його.

Редагувати 1

Короткі примітки на основі коментарів нижче (завдяки їм!).

Оскільки метою є видалення елемента, коли порядок не має значення, потрібен один обмін, другий буде витрачений даремно:

func remove(s []int, i int) []int {
    s[i] = s[len(s)-1]
    // We do not need to put s[i] at the end, as it will be discarded anyway
    return s[:len(s)-1]
}

Крім того, ця відповідь не виконує перевірку меж . В якості вхідних даних він очікує дійсний індекс. Це означає, що негативні значення або індекси, більші або рівні len (-ів), спричинять Go для паніки. Зрізи та масиви, які індексуються 0, видалення n-го елемента масиву означає надання вводу n-1 . Щоб видалити перший елемент, зателефонуйте видаленню (s, 0) , для видалення другого - виклику remove (s, 1) тощо, тощо.


20
Схоже, якщо ви не дбаєте про збереження вихідного фрагмента, вам навіть не знадобиться обмін, і цього s[i] = s[len(s)-1]; return s[:len(s)-1]було б достатньо.
Brad Peabody

@bgp Це просто вискакування (видалення останнього елемента) фрагмента, а не видалення того під індексом, який надано, як у вихідному питанні. Подібним чином можна змінити (видалити перший елемент), return s[1:]який також не відповідає на вихідне запитання.
shadyyx

2
Хм, не дуже. Це: s[i] = s[len(s)-1]безумовно копіює останній елемент у елемент із індексом i. Потім return s[:len(s)-1]повертає фрагмент без останнього елемента. Два твердження там.
Brad Peabody

1
Помилка з len (arr) == 2, а елемент, який потрібно видалити, є останнім: play.golang.org/p/WwD4PfUUjsM
zenocon

1
@zenocon У Golang масиви мають 0-індекс, що означає, що допустимі індекси для масиву довжиною 2 дорівнюють 0 і 1. Дійсно, ця функція не перевіряє межі масиву і очікує надання дійсного індексу. Коли len (arr) == 2 , допустимими аргументами є 0 або 1 . Що-небудь інше спричинить доступ за межі заборони, і Go запанікує.
T. Claverie

41

Видаліть один елемент із фрагмента (це називається повторним нарізанням):

package main

import (
    "fmt"
)

func RemoveIndex(s []int, index int) []int {
    return append(s[:index], s[index+1:]...)
}

func main() {
    all := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    fmt.Println(all) //[0 1 2 3 4 5 6 7 8 9]
    all = RemoveIndex(all, 5)
    fmt.Println(all) //[0 1 2 3 4 6 7 8 9]
}

8
Варто зазначити, що це називається "повторним нарізуванням" і є відносно дорогим, хоча це ідіоматичний спосіб зробити це в Go. Тільки не плутайте це з такою операцією, як видалення вузла зі зв’язаного списку, оскільки це не те, і якщо ви збираєтеся робити це багато, особливо з великими колекціями, вам слід розглянути альтернативні конструкції, які уникають цього.
evanmcdonnal

Так, це ідіоматично в Golang, і навіть у C / Assembly видалення одного випадкового елемента з відсортованого масиву є дорогим, вам потрібно змістити (скопіювати) всі праві елементи на одну позицію вліво. Так, у деяких випадках використання зв’язаний список є кращим рішенням для видалення випадкового елемента зі списку.

1
Зверніть увагу, що цей метод призводить до того, що всі модифікуються, і тепер n і всі вказують на частину того самого базового масиву. Це дуже ймовірно призведе до помилок у вашому коді.
mschuett

1
Помилка цієї помилки2019/09/28 19:46:25 http: panic serving 192.168.1.3:52817: runtime error: slice bounds out of range [7:5] goroutine 7 [running]:
OhhhThatVarun

11
це могло б спричинити, panicякщо індекс є останнім елементом у фрагменті
STEEL

28

Менша точка (код гольфу), але в тому випадку, коли порядок не має значення, вам не потрібно міняти місцями значення. Просто перезапишіть видалену позицію масиву дублікатом останньої позиції, а потім поверніть усічений масив.

func remove(s []int, i int) []int {
    s[i] = s[len(s)-1]
    return s[:len(s)-1]
}

Той самий результат.


13
Найчитабельнішою реалізацією було б скопіювати перший елемент до вказаного індексу s[i] = s[0], а потім повернути масив лише з останніми n-1 елементами. return s[1:]
Кент


2
@Kent Проблема з виконанням s[1:]проти, s[:len(s)-1]полягає в тому, що пізніший буде працювати набагато краще, якщо пізніше зріз буде appendвидалено або видалення змішані з appends. Пізніший зберігає ємність зрізу там, де - як і перший.
Dave C

1
Якщо ви видалите 0-й елемент за допомогою цієї функції, він інвертує результат.
ivarec

22

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

Ось код, що порівнює відповіді користувачів у цій темі та оригінальний пост. Ось ігровий майданчик, щоб поспішати з цим кодом.

Додайте видалення на основі

package main

import (
    "fmt"
)

func RemoveIndex(s []int, index int) []int {
    return append(s[:index], s[index+1:]...)
}

func main() {
    all := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    fmt.Println("all: ", all) //[0 1 2 3 4 5 6 7 8 9]
    removeIndex := RemoveIndex(all, 5)

    fmt.Println("all: ", all) //[0 1 2 3 4 6 7 8 9 9]
    fmt.Println("removeIndex: ", removeIndex) //[0 1 2 3 4 6 7 8 9]

    removeIndex[0] = 999
    fmt.Println("all: ", all) //[999 1 2 3 4 6 7 9 9]
    fmt.Println("removeIndex: ", removeIndex) //[999 1 2 3 4 6 7 8 9]
}

У наведеному вище прикладі ви бачите, як я створюю зріз і заповнюю його вручну цифрами від 0 до 9. Потім ми видаляємо індекс 5 з усіх і призначаємо його для видалення індексу. Однак, коли ми збираємося роздруковувати все зараз, ми бачимо, що воно також було змінено. Це тому, що зрізи є вказівниками на базовий масив. Списання його на removeIndexпричини, які також allповинні бути модифіковані з різницею, allдовше на один елемент, до якого більше неможливо досягти removeIndex. Далі ми змінюємо значення в, removeIndexі ми бачимо, що воно також allзмінюється. Ефективна детальна деталізація цього питання.

У наступному прикладі я не буду розглядати, але він робить те саме для наших цілей. І лише ілюструє, що використання копії нічим не відрізняється.

package main

import (
    "fmt"
)

func RemoveCopy(slice []int, i int) []int {
    copy(slice[i:], slice[i+1:])
    return slice[:len(slice)-1]
}

func main() {
    all := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    fmt.Println("all: ", all) //[0 1 2 3 4 5 6 7 8 9]
    removeCopy := RemoveCopy(all, 5)

    fmt.Println("all: ", all) //[0 1 2 3 4 6 7 8 9 9]
    fmt.Println("removeCopy: ", removeCopy) //[0 1 2 3 4 6 7 8 9]

    removeCopy[0] = 999
    fmt.Println("all: ", all) //[99 1 2 3 4 6 7 9 9]
    fmt.Println("removeCopy: ", removeCopy) //[999 1 2 3 4 6 7 8 9]
}

На запитання оригінальна відповідь

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

package main

import (
    "fmt"
)

func OriginalRemoveIndex(arr []int, pos int) []int {
    new_arr := make([]int, (len(arr) - 1))
    k := 0
    for i := 0; i < (len(arr) - 1); {
        if i != pos {
            new_arr[i] = arr[k]
            k++
        } else {
            k++
        }
        i++
    }

    return new_arr
}

func main() {
    all := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    fmt.Println("all: ", all) //[0 1 2 3 4 5 6 7 8 9]
    originalRemove := OriginalRemoveIndex(all, 5)

    fmt.Println("all: ", all) //[0 1 2 3 4 5 6 7 8 9]
    fmt.Println("originalRemove: ", originalRemove) //[0 1 2 3 4 6 7 8 9]

    originalRemove[0] = 999
    fmt.Println("all: ", all) //[0 1 2 3 4 5 6 7 8 9]
    fmt.Println("originalRemove: ", originalRemove) //[999 1 2 3 4 6 7 8 9]
}

Як бачите, цей результат діє так, як очікувало б більшість людей, і, ймовірно, те, чого хоче більшість людей. Модифікація originalRemoveне викликає змін, allа операція видалення індексу та його присвоєння також не спричиняє змін! Фантастично!

Цей код трохи довгий, хоча вищезазначене можна змінити на це.

Правильна відповідь

package main

import (
    "fmt"
)

func RemoveIndex(s []int, index int) []int {
    ret := make([]int, 0)
    ret = append(ret, s[:index]...)
    return append(ret, s[index+1:]...)
}

func main() {
    all := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    fmt.Println("all: ", all) //[0 1 2 3 4 5 6 7 8 9]
    removeIndex := RemoveIndex(all, 5)

    fmt.Println("all: ", all) //[0 1 2 3 4 5 6 7 8 9]
    fmt.Println("removeIndex: ", removeIndex) //[0 1 2 3 4 6 7 8 9]

    removeIndex[0] = 999
    fmt.Println("all: ", all) //[0 1 2 3 4 5 6 7 9 9]
    fmt.Println("removeIndex: ", removeIndex) //[999 1 2 3 4 6 7 8 9]
}

Майже ідентичний оригінальному рішенню індексу видалення, однак ми робимо новий фрагмент, який слід додати до повернення.


3
це має бути відповіддю на питання, оскільки єдине, що пояснює ризик модифікації
опорного

У видаленні на основі додавання перший зріз allтакож змінюється після, removeIndex := RemoveIndex(all, 5)оскільки додаток повторно використовує той самий базовий масив, якщо він має достатню ємність (видалення елемента, звичайно, не вимагає більшої ємності). І навпаки, якщо ми додамо елементи та призначимо для removeIndex, append виділить новий масив і allне зміниться.
blackgreen

12

Це, як ви Видалити З скибочки golang шлях . Вам не потрібно будувати функцію, яка вбудована в додаток. Спробуйте тут https://play.golang.org/p/QMXn9-6gU5P

z := []int{9, 8, 7, 6, 5, 3, 2, 1, 0}
fmt.Println(z)  //will print Answer [9 8 7 6 5 3 2 1 0]

z = append(z[:2], z[4:]...)
fmt.Println(z)   //will print Answer [9 8 5 3 2 1 0]

8

З книги Мова програмування Go

Щоб видалити елемент із середини зрізу, зберігаючи порядок решти елементів, використовуйте copy, щоб зсунути елементи з більшими номерами на один, щоб заповнити пробіл:

func remove(slice []int, i int) []int {
  copy(slice[i:], slice[i+1:])
  return slice[:len(slice)-1]
}

3
Зверніть увагу, що цей метод призводить до модифікації переданого оригінального фрагмента.
mschuett

7

Я використовую нижченаведений підхід, щоб видалити предмет у фрагменті. Це допомагає читати іншим. А також незмінний.

func remove(items []string, item string) []string {
    newitems := []string{}

    for _, i := range items {
        if i != item {
            newitems = append(newitems, i)
        }
    }

    return newitems
}

Мені більше подобається такий підхід: ви фактично видаляєте всі випадки цього елемента.
вийти

Це можна легко перетворити на фільтрування декількох предметів зі зрізу, приємно!
eldad Levy

2

найкращий спосіб це зробити - використовувати функцію додавання:

package main

import (
    "fmt"
)

func main() {
    x := []int{4, 5, 6, 7, 88}
    fmt.Println(x)
    x = append(x[:2], x[4:]...)//deletes 6 and 7
    fmt.Println(x)
}

https://play.golang.org/p/-EEFCsqse4u


1

Можливо, ви можете спробувати цей метод:

// DelEleInSlice delete an element from slice by index
//  - arr: the reference of slice
//  - index: the index of element will be deleted
func DelEleInSlice(arr interface{}, index int) {
    vField := reflect.ValueOf(arr)
    value := vField.Elem()
    if value.Kind() == reflect.Slice || value.Kind() == reflect.Array {
        result := reflect.AppendSlice(value.Slice(0, index), value.Slice(index+1, value.Len()))
        value.Set(result)
    }
}

Використання:

arrInt := []int{0, 1, 2, 3, 4, 5}
arrStr := []string{"0", "1", "2", "3", "4", "5"}
DelEleInSlice(&arrInt, 3)
DelEleInSlice(&arrStr, 4)
fmt.Println(arrInt)
fmt.Println(arrStr)

Результат:

0, 1, 2, 4, 5
"0", "1", "2", "3", "5"

1
Ймовірно, тому, що це не ідіоматично та надмірно розроблено для того, що задає питання. Це цікавий спосіб вирішити це, але ніхто не повинен цим користуватися.
mschuett

1
Дякую! Насправді у мене дуже добре працював інтерфейс, оскільки це, здається, є універсальним, можливо
DADi590

1

Можливо, цей код допоможе.

Він видаляє елемент із заданим індексом.

Бере масив та індекс для видалення та повертає новий масив, майже як функція додавання.

func deleteItem(arr []int, index int) []int{
  if index < 0 || index >= len(arr){
    return []int{-1}
  }

    for i := index; i < len(arr) -1; i++{
      arr[i] = arr[i + 1]

    }

    return arr[:len(arr)-1]
}

Тут ви можете пограти з кодом: https://play.golang.org/p/aX1Qj40uTVs


1

Знайдіть шлях сюди, не переїжджаючи.

  • змінює порядок
a := []string{"A", "B", "C", "D", "E"}
i := 2

// Remove the element at index i from a.
a[i] = a[len(a)-1] // Copy last element to index i.
a[len(a)-1] = ""   // Erase last element (write zero value).
a = a[:len(a)-1]   // Truncate slice.

fmt.Println(a) // [A B E D]
  • стежити за порядком
a := []string{"A", "B", "C", "D", "E"}
i := 2

// Remove the element at index i from a.
copy(a[i:], a[i+1:]) // Shift a[i+1:] left one index.
a[len(a)-1] = ""     // Erase last element (write zero value).
a = a[:len(a)-1]     // Truncate slice.

fmt.Println(a) // [A B D E]

0

Не потрібно перевіряти кожен окремий елемент, якщо ви не дбаєте про вміст і не можете використовувати додавання фрагмента. Спробуй

pos := 0
arr := []int{1, 2, 3, 4, 5, 6, 7, 9}
fmt.Println("input your position")
fmt.Scanln(&pos)
/* you need to check if negative input as well */
if (pos < len(arr)){
    arr = append(arr[:pos], arr[pos+1:]...)
} else {
    fmt.Println("position invalid")
}

-1

ось приклад дитячого майданчика з покажчиками в ньому. https://play.golang.org/p/uNpTKeCt0sH

package main

import (
    "fmt"
)

type t struct {
    a int
    b string
}

func (tt *t) String() string{
    return fmt.Sprintf("[%d %s]", tt.a, tt.b)
}

func remove(slice []*t, i int) []*t {
  copy(slice[i:], slice[i+1:])
  return slice[:len(slice)-1]
}

func main() {
    a := []*t{&t{1, "a"}, &t{2, "b"}, &t{3, "c"}, &t{4, "d"}, &t{5, "e"}, &t{6, "f"}}
    k := a[3]
    a = remove(a, 3)
    fmt.Printf("%v  ||  %v", a, k)
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.