34561


swiftでarrayから複数の要素を同時に削除する方法はありますか？

以下のように  
ひとつひとつ削除するとうまくいかないようなので、  
同時に削除してしまいたいのです。

[code]

    var items : [Item]!   
    var selectedItems : [Item]!
    
    
    //選択中のアイテムの削除
        @IBAction func deleteItem(_ sender: Any) {
            let num = selectedItems.count
            for i in 0..<num {
                let deleteItem = selectedItems[i]
                let index = items.index(of: deleteItem)
                items.remove(at: index!)
                selectedItems.remove(at: i)
                coreDataStack.context.delete(deleteItem)
            }
    
        }
    
[/code]

* * *
[code]

    let item_1 : Item!
        let item_2 : Item!
        let item_3 : Item!
        ...
    
        array1 = [item_1,item_2,item_3,item_4,item_5]
    
        array2 = [item_3,item_1,item_5]
    
        for i in 0..<array2.count {
    
        let deleteItem = array2[i]
        let index = array1.index(of: deleteItem)
        array1.remove(at: index)
    
        }
    
[/code]


CC BY-SA 3.0
2017-05-08T14:51:58.477
34558
2017-05-09T06:53:42.287
2017-05-09T06:53:42.287
76
12297
question
swift
swiftでarrayから複数の要素を同時に削除する方法はありますか？


まず、「実際のコード」(現在のご質問文の上側のコード)で「fatal error: Index out of
range」が出てしまう原因を確認しておきましょう。

[code]

    var items : [Item] = []   //<- (*1)
    var selectedItems : [Item] = [] //<- (*1)
    
    
    //選択中のアイテムの削除
        @IBAction func deleteItem(_ sender: Any) {
            let num = selectedItems.count
            for i in 0..<num {
                let deleteItem = selectedItems[i]
                let index = items.index(of: deleteItem)
                items.remove(at: index!)
                selectedItems.remove(at: i) //<- ###
                coreDataStack.context.delete(deleteItem)
            }
    
        }
    
[/code]

`//<-###`でマーキングされた行を考えてください。`i`の値は`selectedItems`の`最初の要素数-1`まで変化していくのに対して、`seletedItems`の要素数はだんだんと減っていきますから、そのうち`i`の値がその瞬間の`seletedItems`の要素数以上になってしまい、そこで「fatal
error: Index out of range」が発生することになります。

もう削除対象ではないことを少しでも早く`selectedItems`に反映したかったのでしょうが、`i`の値が逆戻りすることはないので、いちいちループの中で`selectedItems.remove(at:
i)`を実行する必要はないですね。例えばこうすればIndex out of rangeにはならないはずです。

[code]

    @IBAction func deleteItem(_ sender: Any) {
        let num = selectedItems.count
        for i in 0..<num {
            let deleteItem = selectedItems[i]
            if let index = items.index(of: deleteItem) {
                items.remove(at: index)
                coreDataStack.context.delete(deleteItem)
            }
        }
        selectedItems = []
    }
    
[/code]

修正点は2点、`selectedItems`の内容をクリアするのはループ終了後にまとめたのと、`items.index(of:
deleteItem)`のnilチェックを加えたところです。(`index(of:)`メソッドはデータの中身によってはnilを返すのですから、常にチェックする癖はつけた方がいいでしょう。)

ちなみにSwiftのfor-inの構文を使うと、同じ処理はこんな風にも書けます。

[code]

    @IBAction func deleteItem(_ sender: Any) {
        for deleteItem in selectedItems {
            if let index = items.index(of: deleteItem) {
                items.remove(at: index)
                coreDataStack.context.delete(deleteItem)
            }
        }
        selectedItems = []
    }
    
[/code]

* * *

で、大きな配列では`remove(at:)`や`index(of:)`は、かなり重い処理なので、`items`の要素数が非常に大きい場合にはよくこんなテクニックが使われます。

[code]

    @IBAction func deleteItem(_ sender: Any) {
        let selectedSet = Set(selectedItems)
        items = items.filter {!selectedSet.contains($0)}
        selectedSet.forEach {
            coreDataStack.context.delete($0)
        }
        selectedItems = []
    }
    
[/code]

`selectedItems`に含まれない要素のみを残す、と言う形で`filter`を使用しています。配列の要素数が非常に大きい場合(かつ各要素型の`Set`を簡単に作成可能な場合)に有効な方法です。  
(`selectedItems`に含まれる要素の中に`items`には存在しないものがある場合、前2つのコードと動作が異なってきます。)

Swiftの配列には、中身を1個ずつ取り出しながら使う(取り出された要素は配列から削除される)と言ったメソッドも用意されているため、そちらを利用する手もあるのですが、長くなりすぎるので今回は省略しておきます。

CoreDataがらみの部分があるので、あなたのコードに当てはめた時に若干の修正が必要になるかもしれません。お好きなコードからお試しの上、うまく動かない等があれば、コメント等でお知らせください。

* * *

(*1) 本題から離れるのですが、配列型を暗黙アンラップ型(Implicitly Unwrapped
Optional)にする意味は無いように思われます。非Optional型で宣言し、空配列で初期化すればいいのでは無いでしょうか。(空配列と`nil`を区別する必要性があるならまた話は別ですが。)


CC BY-SA 3.0
2017-05-08T16:04:08.290
34561
2017-05-08T16:04:08.290
13972
34558
answer