Cách xóa một phần tử khỏi Slice trong Golang


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

Tôi đang sử dụng lệnh này để xóa một phần tử khỏi Slice nhưng nó không hoạt động, vui lòng đề xuất.



Câu trả lời:


177

Vấn đề đặt hàng

Nếu bạn muốn giữ cho mảng của mình có thứ tự, bạn phải dịch chuyển tất cả các phần tử ở bên phải của chỉ mục đang xóa sang bên trái. Hy vọng rằng, điều này có thể được thực hiện dễ dàng trong Golang:

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

Tuy nhiên, điều này không hiệu quả vì bạn có thể phải di chuyển tất cả các yếu tố, điều này rất tốn kém.

Thứ tự không quan trọng

Nếu bạn không quan tâm đến việc sắp xếp thứ tự, bạn có khả năng nhanh hơn nhiều để hoán đổi phần tử cần xóa với phần tử ở cuối lát và sau đó trả về n-1 phần tử đầu tiên:

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

Với phương thức reslicing, việc làm trống mảng 1 000 000 phần tử mất 224 giây, với phương thức này chỉ mất 0,06ns. Tôi nghi ngờ rằng nội bộ, go chỉ thay đổi độ dài của lát cắt mà không sửa đổi nó.

Chỉnh sửa 1

Ghi chú nhanh dựa trên các bình luận bên dưới (cảm ơn chúng!).

Vì mục đích là xóa một phần tử, khi thứ tự không quan trọng, cần một lần hoán đổi duy nhất, phần tử thứ hai sẽ bị lãng phí:

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

Ngoài ra, câu trả lời này không thực hiện kiểm tra giới hạn . Nó mong đợi một chỉ mục hợp lệ làm đầu vào. Điều này có nghĩa là các giá trị hoặc chỉ số âm lớn hơn hoặc bằng (các) len sẽ khiến Go hoảng sợ. Các lát và mảng được lập chỉ mục 0, việc xóa phần tử thứ n của mảng ngụ ý cung cấp đầu vào n-1 . Để loại bỏ phần tử đầu tiên, hãy gọi remove (s, 0) , để xóa phần tử thứ hai, hãy gọi remove (s, 1) , v.v.


20
Có vẻ như nếu bạn không quan tâm đến việc bảo quản lát cắt ban đầu, bạn thậm chí sẽ không cần hoán đổi và như s[i] = s[len(s)-1]; return s[:len(s)-1]vậy là đủ.
Brad Peabody

@bgp Đây chỉ là popping (loại bỏ phần tử cuối cùng) của lát, không xóa phần dưới chỉ mục được cung cấp như trong câu hỏi ban đầu. Tương tự, bạn có thể thay đổi (loại bỏ phần tử đầu tiên) với phần tử return s[1:]này cũng không trả lời câu hỏi ban đầu.
shadyyx

2
Hm, không hẳn vậy. Điều này: s[i] = s[len(s)-1]chắc chắn sao chép phần tử cuối cùng vào phần tử tại chỉ mục i. Sau đó, return s[:len(s)-1]trả về lát cắt không có phần tử cuối cùng. Hai tuyên bố ở đó.
Brad Peabody

1
Không thành công với len (arr) == 2 và elem cần xóa là lần cuối cùng: play.golang.org/p/WwD4PfUUjsM
zenocon 21/1018

1
@zenocon Trong Golang, mảng là 0-index, nghĩa là các chỉ số hợp lệ cho một mảng có độ dài 2 là 0 và 1. Thật vậy, hàm này không kiểm tra giới hạn của mảng và mong đợi một chỉ mục hợp lệ được cung cấp. Khi len (arr) == 2 , các đối số hợp lệ do đó là 0 hoặc 1 . Bất cứ điều gì khác sẽ kích hoạt quyền truy cập ngoài giới hạn và Go sẽ hoảng sợ.
T. Claverie

41

Xóa một phần tử khỏi Slice (điều này được gọi là 'cắt lại'):

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
Đáng chỉ ra rằng điều này được gọi là 'cắt lại' và tương đối đắt mặc dù đây là cách thành ngữ để làm điều này trong cờ vây. Chỉ cần đừng nhầm lẫn điều này với một thao tác như xóa một nút khỏi danh sách được liên kết bởi vì nó không phải vậy và nếu bạn định làm điều đó nhiều, đặc biệt là với các bộ sưu tập lớn, bạn nên xem xét các thiết kế thay thế để tránh nó.
evanmcdonnal

Vâng, đây là cách nói thành ngữ trong Golang, và ngay cả trong C / Assembly, việc loại bỏ một phần tử ngẫu nhiên khỏi mảng đã sắp xếp là rất tốn kém, bạn cần chuyển (sao chép) tất cả các phần tử bên phải sang vị trí bên trái. Có trong một số trường hợp sử dụng Danh sách được liên kết là giải pháp tốt hơn để xóa phần tử ngẫu nhiên khỏi Danh sách.

1
Lưu ý rằng phương thức này khiến tất cả được sửa đổi và bây giờ n và tất cả đều trỏ đến một phần của cùng một mảng cơ bản. Điều này rất có thể dẫn đến lỗi trong mã của bạn.
mschuett

1
Bắt lỗi này2019/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

10
điều này sẽ gây ra panicnếu chỉ mục là phần tử cuối cùng trong Slice
STEEL

28

Điểm nhỏ (mã gôn), nhưng trong trường hợp thứ tự không quan trọng, bạn không cần hoán đổi các giá trị. Chỉ cần ghi đè vị trí mảng đang bị xóa bằng một bản sao của vị trí cuối cùng và sau đó trả về một mảng bị cắt ngắn.

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

Cùng một kết quả.


13
Cách triển khai dễ đọc nhất sẽ là sao chép phần tử đầu tiên vào chỉ mục được chỉ định s[i] = s[0], sau đó trả về một mảng chỉ có n-1 phần tử cuối cùng. return s[1:]
Kent

4
sân chơi của @Kent 's giải pháp
stevenspiel

2
@ Đã đề cập đến vấn đề với việc thực hiện s[1:]so với s[:len(s)-1]phần sau đó sẽ hoạt động tốt hơn nhiều nếu phần sau đó bị chỉnh sửa appendhoặc xóa xen kẽ với appends. Cái sau giữ dung lượng lát ở đâu - như cái trước thì không.
Dave C

1
Nếu bạn loại bỏ phần tử thứ 0 bằng hàm này, nó sẽ đảo ngược kết quả.
ivarec

21

Điều này hơi lạ khi thấy nhưng hầu hết các câu trả lời ở đây đều nguy hiểm và bóng bẩy hơn những gì chúng thực sự đang làm. Nhìn vào câu hỏi ban đầu được hỏi về việc xóa một mục khỏi lát, một bản sao của lát đang được tạo và sau đó nó được lấp đầy. Điều này đảm bảo rằng khi các phần được chuyển qua chương trình của bạn, bạn không tạo ra các lỗi nhỏ.

Đây là một số mã so sánh câu trả lời của người dùng trong chủ đề này và bài đăng gốc. Đây là một sân chơi để lộn xộn với mã này trong.

Nối xóa dựa trên

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

Trong ví dụ trên, bạn có thể thấy tôi tạo một lát cắt và điền nó theo cách thủ công với các số từ 0 đến 9. Sau đó, chúng tôi xóa chỉ mục 5 khỏi tất cả và gán nó để xóa chỉ mục. Tuy nhiên, khi chúng tôi đi in tất cả bây giờ, chúng tôi thấy rằng nó cũng đã được sửa đổi. Điều này là do các lát cắt là con trỏ đến một mảng bên dưới. Viết ra removeIndexnguyên nhân allđể sửa đổi cũng như sự khác biệt alllà do một yếu tố không còn có thể truy cập được nữa removeIndex. Tiếp theo, chúng tôi thay đổi một giá trị removeIndexvà chúng tôi có thể thấy cũng allđược sửa đổi. Hiệu quả đi vào một số chi tiết hơn về điều này.

Ví dụ sau đây tôi sẽ không đi sâu vào nhưng nó làm điều tương tự cho các mục đích của chúng tôi. Và chỉ minh họa rằng việc sử dụng bản sao không có gì khác biệt.

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

Câu trả lời ban đầu

Nhìn vào câu hỏi ban đầu, nó không sửa đổi phần mà nó đang xóa một mục. Làm cho câu trả lời ban đầu trong chủ đề này là tốt nhất cho đến nay đối với hầu hết mọi người đến trang này.

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

Như bạn có thể thấy đầu ra này hoạt động như hầu hết mọi người mong đợi và có thể là những gì hầu hết mọi người muốn. Việc sửa đổi originalRemovekhông gây ra thay đổi trong allvà hoạt động xóa chỉ mục và gán nó cũng không gây ra thay đổi! Tuyệt diệu!

Tuy nhiên, mã này hơi dài dòng vì vậy phần trên có thể được thay đổi thành phần này.

Một câu trả lời đúng

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

Gần giống với giải pháp xóa chỉ mục ban đầu, tuy nhiên, chúng tôi tạo một lát mới để thêm vào trước khi quay lại.


3
đây sẽ là câu trả lời cho câu hỏi vì nó là câu duy nhất giải thích rủi ro sửa đổi mảng hỗ trợ của lát cắt
JessG

Trong thao tác xóa dựa trên phần nối thêm, lát đầu tiên allcũng được thay đổi sau removeIndex := RemoveIndex(all, 5)vì phần nối tiếp sử dụng lại cùng một mảng bên dưới nếu nó có đủ dung lượng (tất nhiên việc xóa một phần tử không yêu cầu thêm dung lượng). Ngược lại, nếu chúng ta thêm các phần tử và gán vào removeIndex, append sẽ cấp phát một mảng mới và allkhông thay đổi.
blackgreen

11

Đây là cách bạn Xóa Từ một lát các cách golang . Bạn không cần phải xây dựng một chức năng mà nó được tích hợp vào phần phụ. Hãy thử tại đây 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

Từ cuốn sách Ngôn ngữ lập trình Go

Để xóa một phần tử khỏi phần giữa của lát cắt, giữ nguyên thứ tự của các phần tử còn lại, hãy sử dụng bản sao để trượt các phần tử được đánh số cao hơn xuống từng phần để lấp đầy khoảng trống:

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

3
Lưu ý rằng phương pháp này làm cho lát cắt ban đầu được chuyển vào bị sửa đổi.
mschuett

7

Tôi thực hiện cách tiếp cận dưới đây để loại bỏ mục trong lát. Điều này giúp người khác dễ đọc. Và cũng bất di bất dịch.

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

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

    return newitems
}

Tôi thích cách tiếp cận này hơn là bạn đang thực sự loại bỏ tất cả các lần xuất hiện của mục.
eexit

Điều này có thể dễ dàng chuyển đổi để lọc một số mục từ lát, tốt đẹp!
fieldsad levy

1

Có thể bạn có thể thử phương pháp này:

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

Sử dụng:

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)

Kết quả:

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

1
Có thể vì nó không phải là thành ngữ và được thiết kế quá mức cho những gì câu hỏi đang yêu cầu. Đó là một cách thú vị để giải quyết nó nhưng không ai nên sử dụng cách này.
mschuett

1
Cảm ơn! Trên thực tế chỉ làm việc tốt cho tôi với một giao diện, vì đây có vẻ là phổ quát, có lẽ
DADi590

1

Có thể mã này sẽ hữu ích.

Nó xóa mục với một chỉ số nhất định.

Đưa mảng và chỉ mục để xóa và trả về một mảng mới giống như hàm append.

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

Tại đây bạn có thể chơi với mã: https://play.golang.org/p/aX1Qj40uTVs


1

cách tốt nhất để làm điều đó là bạn sử dụng hàm append:

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

Tìm đường đến đây mà không cần di dời.

  • thay đổi thứ tự
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]
  • giữ trật tự
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

Không cần phải kiểm tra từng phần tử trừ khi bạn quan tâm đến nội dung và bạn có thể sử dụng phần nối thêm của lát cắt. thử nó ra

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

đây là ví dụ về sân chơi với các con trỏ trong đó. 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)
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.