Jak usunąć element z wycinka w Golang


125
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])
}

Używam tego polecenia, aby usunąć element z plasterka, ale nie działa, proszę zasugerować.



Odpowiedzi:


199

Porządek ma znaczenie

Jeśli chcesz zachować porządek w tablicy, musisz przesunąć wszystkie elementy po prawej stronie kasującego indeksu o jeden w lewo. Miejmy nadzieję, że da się to łatwo zrobić w Golang:

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

Jest to jednak nieefektywne, ponieważ może skończyć się przeniesieniem wszystkich elementów, co jest kosztowne.

Porządek nie jest ważny

Jeśli nie zależy Ci na kolejności, masz znacznie szybszą możliwość zamiany elementu do usunięcia z tym na końcu wycinka, a następnie zwrócenia n-1 pierwszych elementów:

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

W przypadku metody reslicingu opróżnienie tablicy 1 000 000 elementów zajmuje 224 s, przy tej zajmuje tylko 0,06 ns. Podejrzewam, że wewnętrznie go zmienia tylko długość plastra, bez modyfikowania go.

Edytuj 1

Szybkie notatki na podstawie poniższych komentarzy (dzięki!).

Ponieważ celem jest usunięcie elementu, gdy kolejność nie ma znaczenia, potrzebna jest pojedyncza zamiana, druga zostanie zmarnowana:

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]
}

Również ta odpowiedź nie sprawdza granic . Oczekuje prawidłowego indeksu jako danych wejściowych. Oznacza to, że ujemne wartości lub wskaźniki większe lub równe len (s) spowodują panikę. Wycinki i tablice są indeksowane 0, usunięcie n-tego elementu tablicy oznacza dostarczenie danych wejściowych n-1 . Aby usunąć pierwszy element, wywołaj remove (s, 0) , aby usunąć drugi, wywołanie remove (s, 1) itd.


22
Wygląda na to, że jeśli nie dbasz o zachowanie oryginalnego kawałka, nie potrzebujesz nawet zamiany i s[i] = s[len(s)-1]; return s[:len(s)-1]to wystarczy.
Brad Peabody

2
Hm, nie bardzo. This: s[i] = s[len(s)-1]zdecydowanie kopiuje ostatni element do elementu pod indeksem i. Następnie return s[:len(s)-1]zwraca wycinek bez ostatniego elementu. Tam są dwa stwierdzenia.
Brad Peabody,

1
Nie udaje się z len (arr) == 2, a element do usunięcia jest ostatni: play.golang.org/p/WwD4PfUUjsM
zenocon

1
@zenocon W języku Golang tablice mają indeks 0, co oznacza, że ​​prawidłowe indeksy dla tablicy o długości 2 to 0 i 1. Rzeczywiście, funkcja ta nie sprawdza granic tablicy i oczekuje podania prawidłowego indeksu. Gdy len (arr) == 2 , prawidłowe argumenty to 0 lub 1 . Cokolwiek innego spowodowałoby dostęp poza granice, a Go wpadnie w panikę.
T. Claverie,

1
Dodając to w celach informacyjnych, ponieważ kolejność nie ma znaczenia opcja, lepiej użyć s[len(s)-1], s[i] = 0, s[len(s)-1]. Zwłaszcza jeśli pracujesz z tablicami nieprymitywnymi. Jeśli masz do czegoś wskaźniki, lepiej jest utworzyć element, który chcesz usunąć nilprzed wycięciem, aby nie mieć wskaźników w podstawowej tablicy. Ta odpowiedź bardzo dobrze wyjaśnia, dlaczego . W skrócie: po przesunięciu ostatniego elementu w miejsce usuniętego elementu wyzeruj ostatni element przed cięciem.
Vlad V,

41

Usuń jeden element z Plasterka (nazywa się to „ponownym pokrojeniem”):

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
Warto zauważyć, że nazywa się to „ponownym cięciem” i jest stosunkowo drogie, chociaż jest to idiomatyczny sposób na zrobienie tego w Go. Po prostu nie myl tego z operacją, taką jak usunięcie węzła z połączonej listy, ponieważ tak nie jest i jeśli masz zamiar robić to dużo, szczególnie w przypadku dużych kolekcji, powinieneś rozważyć alternatywne projekty, które tego unikają.
evanmcdonnal

Tak, to jest idiomatyczny sposób w Golangu i nawet w C / Assembly usunięcie jednego przypadkowego elementu z posortowanej tablicy jest drogie, musisz przesunąć (skopiować) wszystkie prawe elementy o jedną pozycję w lewo. Tak, w niektórych przypadkach Lista połączona jest lepszym rozwiązaniem do usuwania losowego elementu z listy.

1
Zauważ, że ta metoda powoduje, że wszystkie są modyfikowane i teraz n i wszystkie wskazują na część tej samej podstawowej tablicy. Jest to bardzo prawdopodobne, że doprowadzi to do błędów w kodzie.
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]:
Otrzymuję

14
spowodowałoby to, panicgdyby indeks był ostatnim elementem w Wycinku
STAL

29

Drobny punkt (kod golfowy), ale w przypadku, gdy kolejność nie ma znaczenia, nie musisz zamieniać wartości. Po prostu nadpisz usuwaną pozycję tablicy duplikatem ostatniej pozycji, a następnie zwróć obciętą tablicę.

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

Ten sam wynik.


13
Najbardziej czytelną implementacją byłoby skopiowanie pierwszego elementu do określonego indeksu s[i] = s[0], a następnie zwrócenie tablicy zawierającej tylko ostatnie n-1 elementów. return s[1:]
Kent,

4
plac zabaw rozwiązania
@Kent

3
@Kent problem z robieniem w s[1:]porównaniu z s[:len(s)-1]tym, że późniejszy będzie działał znacznie lepiej, jeśli kawałek później zostanie edytowany lub usunięte appendzostaną wymieszane z appends. Ta ostatnia zachowuje pojemność plastra tam, gdzie ta pierwsza nie.
Dave C

1
Jeśli usuniesz zerowy element za pomocą tej funkcji, odwróci wynik.
ivarec

25

To trochę dziwne, ale większość odpowiedzi tutaj jest niebezpieczna i przesłania to, co faktycznie robią. Patrząc na pierwotne pytanie, które zostało zadane na temat usuwania elementu z plasterka, kopia plastra jest tworzona, a następnie jest wypełniana. Gwarantuje to, że gdy plasterki są przekazywane wokół programu, nie wprowadzasz subtelnych błędów.

Oto kod porównujący odpowiedzi użytkowników w tym wątku i oryginalny post. Oto plac zabaw, w którym można bawić się tym kodem.

Usuwanie na podstawie dołączania

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]
}

W powyższym przykładzie możesz zobaczyć, jak tworzę wycinek i ręcznie wypełniam go numerami od 0 do 9. Następnie usuwamy indeks 5 ze wszystkich i przypisujemy go do usunięcia indeksu. Jednak kiedy idziemy teraz wydrukować wszystko, widzimy, że zostało to również zmodyfikowane. Dzieje się tak, ponieważ plasterki są wskaźnikami do tablicy bazowej. Zapisanie go do removeIndexprzyczyn, które allmają być również zmodyfikowane, z tą różnicą, że alljest dłuższy o jeden element, z którego nie można już dotrzeć removeIndex. Następnie zmieniamy wartość w removeIndexi widzimy, że również allzostaje zmodyfikowana. Efektywne przejście do bardziej szczegółowych informacji na ten temat.

Poniższy przykład, którym nie będę się zajmował, ale robi to samo dla naszych celów. I tylko pokazuje, że używanie kopiowania nie jest inne.

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]
}

Na pytania oryginalna odpowiedź

Patrząc na oryginalne pytanie, nie modyfikuje plastra, z którego usuwa element. Jak dotąd oryginalna odpowiedź w tym wątku była jak dotąd najlepsza dla większości osób odwiedzających tę stronę.

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]
}

Jak widać, ten wynik działa zgodnie z oczekiwaniami większości ludzi i prawdopodobnie tym, czego większość ludzi chce. Modyfikacja originalRemovenie powoduje zmian, alla operacja usunięcia indeksu i przypisanie go również nie powoduje zmian! Fantastyczny!

Ten kod jest jednak trochę za długi, więc powyższy można zmienić na ten.

Prawidłowa odpowiedź

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]
}

Prawie identyczne z oryginalnym rozwiązaniem do usuwania indeksu, jednak przed powrotem tworzymy nowy wycinek do dołączenia.


4
to powinna być odpowiedź na to pytanie, ponieważ jest to jedyne, które wyjaśnia ryzyko modyfikacji tablicy kopii zapasowej tego wycinka
JessG

W przypadku usuwania opartego na dołączaniu pierwszy wycinek alljest również zmieniany później, removeIndex := RemoveIndex(all, 5)ponieważ funkcja append ponownie wykorzystuje tę samą podstawową tablicę, jeśli ma wystarczającą pojemność (usunięcie elementu oczywiście nie wymaga większej pojemności). I odwrotnie, jeśli dodamy elementy i przypiszemy do removeIndex, append przydzieli nową tablicę i allnie zmieni się.
czarnozielony

14

Z książki The Go Programming Language

Aby usunąć element ze środka plasterka, zachowując kolejność pozostałych elementów, użyj funkcji kopiowania, aby przesunąć elementy o wyższym numerze o jeden w dół, aby wypełnić lukę:

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

5
Zauważ, że ta metoda powoduje zmodyfikowanie przekazanego oryginalnego wycinka.
mschuett

12

W ten sposób możesz usunąć z kawałka w idiomatyczny sposób . Nie musisz budować funkcji, która jest wbudowana w append. Wypróbuj tutaj 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]

6

Podejmuję poniższe podejście, aby usunąć element w kawałku. To pomaga w czytelności dla innych. A także niezmienny.

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

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

    return newitems
}

Podoba mi się to podejście, ponieważ faktycznie usuwasz wszystkie wystąpienia elementu.
wyjdź

Można to łatwo przekształcić w filtrowanie kilku elementów z plasterka, nieźle!
opłata eldad

2

Znajdź drogę tutaj bez przeprowadzki.

  • zmienia kolejność
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]
  • pilnować porządku
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]


1

Może możesz wypróbować tę metodę:

// 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)
    }
}

Stosowanie:

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)

Wynik:

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

1
Prawdopodobnie dlatego, że nie jest to idiomatyczne i zbyt rozbudowane, aby odpowiedzieć na pytanie. To ciekawy sposób na rozwiązanie tego problemu, ale nikt nie powinien tego używać.
mschuett

1
Dzięki! Właściwie działało dobrze dla mnie z interfejsem, ponieważ wydaje się, że może być uniwersalny
DADi590,

1

Może ten kod pomoże.

Usuwa element o podanym indeksie.

Pobiera tablicę i indeks do usunięcia i zwraca nową tablicę, podobnie jak funkcja dołączania.

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]
}

Tutaj możesz pobawić się kodem: https://play.golang.org/p/aX1Qj40uTVs


0

Nie musisz sprawdzać każdego elementu, chyba że zależy Ci na zawartości i możesz użyć dołączania plasterków. Wypróbuj to

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")
}

0

Aby usunąć element ze środka plasterka, zachowując kolejność pozostałych elementów, użyj funkcji kopiowania, aby przesunąć elementy o wyższym numerze o jeden w dół, aby wypełnić lukę:

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

Jeśli nie jest konieczne zachowanie kolejności, możemy po prostu przenieść ostatni element do luki.

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

-3

oto przykład placu zabaw ze wskazówkami. 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)
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.