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

このコマンドを使用してスライスから要素を削除していますが、機能していません。提案してください。


2
これは良い読み物です:blog.golang.org/go-slices-usage-and-internals
squiguy

回答:


177

注文事項

配列の順序を維持したい場合は、削除インデックスの右側にあるすべての要素を1つ左にシフトする必要があります。うまくいけば、これは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.06nsしかかかりません。内部的には、goはスライスの長さを変更せずに変更するだけだと思います。

編集1

以下のコメントに基づくクイックノート(彼らに感謝します!)。

目的は要素を削除することであるため、順序が重要ではない場合、単一のスワップが必要な場合、2番目は無駄になります:

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(s)以上の負の値またはインデックスにより、Goがパニックになることを意味します。スライスと配列のインデックスは0であり、配列のn番目の要素を削除すると、入力n-1が提供されます。最初の要素を削除するには、remove(s、0)を呼び出し、2番目の要素を削除するには、remove(s、1)を呼び出します。


20
元のスライスを保持する必要がない場合は、スワップすら必要ないように見えますs[i] = s[len(s)-1]; return s[:len(s)-1]。それで十分です。
ブラッドピーボディ2016

@bgpこれはスライスのポップ(最後の要素の削除)であり、元の質問のように提供されたインデックスの下の要素を削除するものではありません。同様に、元の質問に答えないシフト(最初の要素を削除)するreturn s[1:]こともできます。
shadyyx 2016

2
うーん、そうではない。これs[i] = s[len(s)-1]は、最後の要素をインデックスの要素に確実にコピーしますi。次に、return s[:len(s)-1]最後の要素のないスライスを返します。そこに2つのステートメント。
Brad Peabody 2016

1
len(arr)== 2で失敗し、削除する要素
zenocon

1
@zenocon Golangでは、配列は0インデックスです。つまり、長さ2の配列の有効なインデックスは0と1です。実際、この関数は配列の境界をチェックせず、有効なインデックスが提供されることを期待します。場合LEN(ARR)== 2、有効な引数は、このようにしている0または1。それ以外のものは範囲外のアクセスをトリガーし、Goはパニックになります。
T. Claverie 2018年

41

スライスから1つの要素を削除します(これは「再スライス」と呼ばれます)。

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 2016年

はい、これはGolangの慣用的な方法であり、C / Assemblyでソートされた配列からランダムな要素を1つ削除するのはコストがかかる場合でも、すべての右側の要素を1つ左にシフト(コピー)する必要があります。はい、一部のユースケースでは、リンクリストがリストからランダムな要素を削除するためのより良い解決策です。

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

10
これは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:]
ケント

4
@Kentのソリューションの遊び場
stevenspiel 2018年

2
@Kents[1:]対実行の問題s[:len(s)-1]は、スライスが後でappend編集されたり、削除がappendsと混ざり合ったりすると、後者のパフォーマンスが大幅に向上することです。後者はスライス容量を維持しますが、前者はそうではありません。
デイブ

1
この関数で0番目の要素を削除すると、結果が反転します。
ivarec

21

これを見るのは少し奇妙ですが、ここでのほとんどの答えは危険であり、実際に何をしているのかを理解しています。スライスからアイテムを削除することについて尋ねられた元の質問を見ると、スライスのコピーが作成されてから、それが埋められています。これにより、スライスがプログラムに渡されるときに、微妙なバグが発生することがなくなります。

これは、このスレッドと元の投稿のユーザーの回答を比較するコードです。これは、でこのコードをいじくり回すための遊び場です。

追加ベースの削除

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
スライスのバッキングアレイを変更するリスクを説明するのはこれだけなので、これが質問の答えになるはずです
JessG

アペンドベースの削除では、十分な容量がある場合、アペンドは同じ基になる配列を再利用するため、最初のスライスallも変更されremoveIndex := RemoveIndex(all, 5)ます(もちろん、要素を削除するためにそれ以上の容量は必要ありません)。逆に、要素を追加してに割り当てるとremoveIndex、appendは新しい配列を割り当て、all変更されません。
ブラックグリーン

11

これはあなたがスライスから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

本から TheGoプログラミング言語

スライスの中央から要素を削除し、残りの要素の順序を維持するには、コピーを使用して、番号の大きい要素を1つ下にスライドさせ、ギャップを埋めます。

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
}

私はこのアプローチが好きで、実際にアイテムのすべての出現を削除しています。
eexit

これは、スライスからいくつかのアイテムをフィルタリングするように簡単に変換できます。
エルダッド徴収

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
ありがとう!これは普遍的であるように思われるので、実際にはインターフェイスを使用して私にとっては問題なく機能しました
DADi5 9019

1

たぶん、このコードが役立つでしょう。

指定されたインデックスを持つアイテムを削除します。

配列とインデックスを取得して削除し、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]
}

ここでは、コードで遊ぶことができます:https//play.golang.org/p/aX1Qj40uTVs


1

それを行う最良の方法は、追加機能を使用することです。

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

移動せずにここで方法を見つけてください

  • 順序を変更します
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)
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.