ส่วนขยาย Array เพื่อลบวัตถุตามค่า


140
extension Array {
    func removeObject<T where T : Equatable>(object: T) {
        var index = find(self, object)
        self.removeAtIndex(index)
    }
}

อย่างไรก็ตามฉันได้รับข้อผิดพลาด var index = find(self, object)

'T' ไม่สามารถแปลงเป็น 'T'

ฉันลองด้วยลายเซ็นวิธีนี้: func removeObject(object: AnyObject)แต่ฉันได้รับข้อผิดพลาดเดียวกัน:

'AnyObject' ไม่สามารถแปลงเป็น 'T'

วิธีที่เหมาะสมในการทำเช่นนี้คืออะไร?


ลองลบออกT whereจากการประกาศวิธีการของคุณ func removeObject<T: Equatable>ดังนั้นเพียงแค่ คำถามนี้เกี่ยวข้องกับ: stackoverflow.com/questions/24091046/…
ahruss

คำตอบ:


165

ในฐานะของสวิฟท์ 2นี้สามารถทำได้ด้วยวิธีขยายโปรโตคอล removeObject()ถูกกำหนดให้เป็นวิธีการในทุกประเภทที่สอดคล้องกับRangeReplaceableCollectionType(โดยเฉพาะอย่างยิ่งในArray) หากองค์ประกอบของคอลเลกชันคือEquatable:

extension RangeReplaceableCollectionType where Generator.Element : Equatable {

    // Remove first collection element that is equal to the given `object`:
    mutating func removeObject(object : Generator.Element) {
        if let index = self.indexOf(object) {
            self.removeAtIndex(index)
        }
    }
}

ตัวอย่าง:

var ar = [1, 2, 3, 2]
ar.removeObject(2)
print(ar) // [1, 3, 2]

อัปเดตสำหรับSwift 2 / Xcode 7 beta 2:ตามที่ Airspeed Velocity สังเกตเห็นในความคิดเห็นตอนนี้เป็นไปได้จริง ๆ แล้วในการเขียนวิธีการในประเภททั่วไปที่มีข้อ จำกัด มากกว่าในเทมเพลตดังนั้นวิธีนี้จึงสามารถกำหนดเป็นนามสกุล จากArray:

extension Array where Element : Equatable {

    // ... same method as above ...
}

ส่วนขยายของโพรโทคอลยังมีข้อได้เปรียบของการใช้งานกับชนิดของชุดที่มีขนาดใหญ่ขึ้น

อัปเดตสำหรับSwift 3:

extension Array where Element: Equatable {

    // Remove first collection element that is equal to the given `object`:
    mutating func remove(object: Element) {
        if let index = index(of: object) {
            remove(at: index)
        }
    }
}

1
สมบูรณ์แบบคุณต้องรัก Swift (2) ฉันชอบที่จะให้เวลาเป็นไปได้มากขึ้นและสิ่งต่าง ๆ ก็จะง่ายขึ้น
Kametrixom

1
จุดที่ดีในหลาย ๆ ด้านความจริงที่ว่าคำตอบนั้นยังคงถูกต้องทางเทคนิคเพียงแค่ไม่มีสำนวนอีกต่อไปยิ่งแย่ลง - ผู้คนจะมาอ่านคำตอบคิดว่าฟังก์ชั่นฟรีเป็นวิธีที่ถูกต้องในการแก้ปัญหา . สถานการณ์น่าเกลียดน่ารักทีเดียว จะโพสต์ไปยังเมตาดาต้า
ความเร็วของเครื่องบินความเร็ว

1
@AirspeedVelocity: ว้าวฉันพลาดไปแล้ว ครอบคลุมอยู่ในบันทึกประจำรุ่นหรือไม่
Martin R

1
หากคุณต้องการฟังก์ชั่นเดียวกับ ObjC (เช่นลบวัตถุที่ตรงกันทั้งหมดออกจากอันที่ 1) คุณสามารถเปลี่ยน "ถ้า" เป็น "ในขณะที่"
powertoold

2
รุ่น Swift 3 ยอดเยี่ยม แต่ฉันจะเปลี่ยนชื่อการประกาศเล็กน้อยremove(object: Element)เพื่อให้สอดคล้องกับแนวทางการออกแบบ Swift APIและหลีกเลี่ยงการใช้คำฟุ่มเฟื่อย ฉันได้ส่งการแก้ไขที่สะท้อนถึงสิ่งนี้
swiftcode

66

คุณไม่สามารถเขียนวิธีการในประเภททั่วไปที่มีข้อ จำกัด มากขึ้นในแม่แบบ

หมายเหตุ : ตั้งแต่ Swift 2.0 คุณสามารถเขียนวิธีการต่างๆได้ข้อ จำกัด เพิ่มเติมเกี่ยวกับแม่แบบ หากคุณได้อัปเกรดรหัสของคุณเป็น 2.0 ดูคำตอบอื่น ๆ เพิ่มเติมสำหรับตัวเลือกใหม่ในการใช้งานโดยใช้ส่วนขยาย

เหตุผลที่คุณได้รับข้อผิดพลาด'T' is not convertible to 'T'คือคุณกำลังกำหนดใหม่ Tในวิธีการที่ไม่เกี่ยวข้องกับ T ต้นฉบับทั้งหมดหากคุณต้องการใช้ T ในวิธีการของคุณคุณสามารถทำได้โดยไม่ต้องระบุวิธีการของคุณ

เหตุผลที่คุณได้รับข้อผิดพลาดที่สอง'AnyObject' is not convertible to 'T'คือค่าที่เป็นไปได้ทั้งหมดสำหรับ T ไม่ใช่คลาสทั้งหมด สำหรับอินสแตนซ์ที่จะถูกแปลงเป็น AnyObject ต้องเป็นคลาส (ไม่สามารถเป็น struct, enum, ฯลฯ )

ทางออกที่ดีที่สุดของคุณคือทำให้ฟังก์ชั่นที่ยอมรับอาร์เรย์เป็นอาร์กิวเมนต์:

func removeObject<T : Equatable>(object: T, inout fromArray array: [T]) {
}

หรือแทนการแก้ไขอาเรย์ดั้งเดิมคุณสามารถทำให้วิธีการของคุณปลอดภัยมากขึ้นและนำมาใช้ซ้ำได้โดยส่งคืนสำเนา:

func arrayRemovingObject<T : Equatable>(object: T, fromArray array: [T]) -> [T] {
}

เป็นทางเลือกที่ฉันไม่แนะนำคุณสามารถทำให้วิธีการของคุณล้มเหลวอย่างเงียบ ๆ หากประเภทที่เก็บไว้ในอาร์เรย์ไม่สามารถแปลงเป็นเทมเพลตวิธีการ (นั่นคือสมการ) (เพื่อความชัดเจนฉันใช้ U แทน T สำหรับเทมเพลตของวิธีการ):

extension Array {
    mutating func removeObject<U: Equatable>(object: U) {
        var index: Int?
        for (idx, objectToCompare) in enumerate(self) {
            if let to = objectToCompare as? U {
                if object == to {
                    index = idx
                }
            }
        }

        if(index != nil) {
            self.removeAtIndex(index!)
        }
    }
}

var list = [1,2,3]
list.removeObject(2) // Successfully removes 2 because types matched
list.removeObject("3") // fails silently to remove anything because the types don't match
list // [1, 3]

แก้ไขเพื่อเอาชนะความล้มเหลวเงียบคุณสามารถกลับสู่ความสำเร็จในฐานะบูล:

extension Array {
  mutating func removeObject<U: Equatable>(object: U) -> Bool {
    for (idx, objectToCompare) in self.enumerate() {  //in old swift use enumerate(self) 
      if let to = objectToCompare as? U {
        if object == to {
          self.removeAtIndex(idx)
          return true
        }
      }
    }
    return false
  }
}
var list = [1,2,3,2]
list.removeObject(2)
list
list.removeObject(2)
list

ลองดูคำตอบของฉันที่นี่: stackoverflow.com/a/24939242/458960ทำไมฉันถึงสามารถทำได้ด้วยวิธีนี้และไม่ได้ใช้findวิธีการ?
มนุษย์หิมะ

วิธีการของคุณไวต่อการล่มของรันไทม์ ด้วยฟังก์ชั่นของฉันคอมไพเลอร์จะป้องกันสิ่งนั้นไม่ให้เกิดขึ้นเลย
drewag

1
@Isuru วิธีนี้ใช้ได้กับวัตถุใด ๆ ที่ใช้Equatableโปรโตคอล UIView ทำเช่นนั้นใช่มันจะทำงานกับ UIViews
drewag

4
ว้าวเขียน a สำหรับลูปเพื่อลบองค์ประกอบกลับไปที่ 90s!
Zorayr

5
ในยุคล่าสุดอย่างรวดเร็ว enumerate(self)ต้องแก้ไขself.enumerate()
TomSawyer

29

สั้นและรัดกุม:

func removeObject<T : Equatable>(object: T, inout fromArray array: [T]) 
{
    var index = find(array, object)
    array.removeAtIndex(index!)
}

2
ที่นี่หนาว. แน่นอนมันสามารถทำได้โดยไม่ต้องinoutด้วยเช่นกัน แม้จะมีinoutสภาพสมบูรณ์ แต่ก็สามารถใช้งานarray = array.filter() { $0 != object }ได้ฉันคิดว่า
Dan Rosenstark

11
ระวังการใช้กำลังบังคับดัชนีที่ยังไม่ได้เจียระไนซึ่งอาจเป็นศูนย์ เปลี่ยนเป็น "if let ind = index {array.removeAtIndex (ind)}"
HotJard

17

หลังจากอ่านทั้งหมดข้างต้นในใจของฉันคำตอบที่ดีที่สุดคือ:

func arrayRemovingObject<U: Equatable>(object: U, # fromArray:[U]) -> [U] {
  return fromArray.filter { return $0 != object }
}

ตัวอย่าง:

var myArray = ["Dog", "Cat", "Ant", "Fish", "Cat"]
myArray = arrayRemovingObject("Cat", fromArray:myArray )

ส่วนขยายอาร์เรย์ Swift 2 (xcode 7b4):

extension Array where Element: Equatable {  
  func arrayRemovingObject(object: Element) -> [Element] {  
    return filter { $0 != object }  
  }  
}  

ตัวอย่าง:

var myArray = ["Dog", "Cat", "Ant", "Fish", "Cat"]
myArray = myArray.arrayRemovingObject("Cat" )

การอัพเดต Swift 3.1

กลับมาที่นี่ทันทีว่า Swift 3.1 หมดแล้ว ด้านล่างนี้เป็นส่วนขยายที่ให้การเปลี่ยนแปลงอย่างละเอียดรวดเร็วการกลายพันธุ์และการสร้าง

extension Array where Element:Equatable {
    public mutating func remove(_ item:Element ) {
        var index = 0
        while index < self.count {
            if self[index] == item {
                self.remove(at: index)
            } else {
                index += 1
            }
        }
    }

    public func array( removing item:Element ) -> [Element] {
        var result = self
        result.remove( item )
        return result
    }
}

ตัวอย่าง:

// Mutation...
      var array1 = ["Cat", "Dog", "Turtle", "Cat", "Fish", "Cat"]
      array1.remove("Cat")
      print(array1) //  ["Dog", "Turtle", "Socks"]

// Creation...
      let array2 = ["Cat", "Dog", "Turtle", "Cat", "Fish", "Cat"]
      let array3 = array2.array(removing:"Cat")
      print(array3) // ["Dog", "Turtle", "Fish"]

นี่จะไม่ส่งคืนอินสแตนซ์ใหม่ของอาร์เรย์อย่างสมบูรณ์หรือ
pxpgraphics

ใช่. มันเป็นสไตล์การทำงานมากขึ้น YMMV
ธันวาคม

ฉันมักจะเห็นด้วยกับสไตล์การใช้งานยกเว้นในกรณีนี้เมื่อfilterฟังก์ชั่นนี้จัดการฟังก์ชันการทำงานนั้นให้คุณแล้ว ดูเหมือนว่านี่จะเป็นการทำงานซ้ำซ้อน แต่คำตอบที่ดียังคง:]
pxpgraphics

13

ด้วยส่วนขยายโปรโตคอลคุณสามารถทำได้

extension Array where Element: Equatable {
    mutating func remove(object: Element) {
        if let index = indexOf({ $0 == object }) {
            removeAtIndex(index)
        }
    }
}

ฟังก์ชั่นเดียวกันสำหรับคลาส

สวิฟท์ 2

extension Array where Element: AnyObject {
    mutating func remove(object: Element) {
        if let index = indexOf({ $0 === object }) {
            removeAtIndex(index)
        }
    }
}

สวิฟท์ 3

extension Array where Element: AnyObject {
    mutating func remove(object: Element) {
        if let index = index(where: { $0 === object }) {
             remove(at: index)
        }
    }
}

แต่ถ้าคลาสดำเนินการ Equatable มันจะคลุมเครือและคอมไพเลอร์ทำให้เกิดข้อผิดพลาด


1
ฉันกำลังจะได้Binary operator '===' cannot be applied to two elements of type '_' and 'Element'
รองเท้า

6

ด้วยการใช้ส่วนขยายโปรโตคอลใน swift 2.0

extension _ArrayType where Generator.Element : Equatable{
    mutating func removeObject(object : Self.Generator.Element) {
        while let index = self.indexOf(object){
            self.removeAtIndex(index)
        }
    }
}

4

สิ่งที่เกี่ยวกับการใช้การกรอง? การทำงานต่อไปนี้ค่อนข้างดีแม้กับ [AnyObject]

import Foundation
extension Array {
    mutating func removeObject<T where T : Equatable>(obj: T) {
        self = self.filter({$0 as? T != obj})
    }

}

2

มีความเป็นไปได้อีกอย่างในการลบรายการออกจากอาร์เรย์โดยไม่ต้องมีการใช้งานที่ไม่ปลอดภัยเนื่องจากประเภททั่วไปของวัตถุที่จะลบไม่สามารถเหมือนกับชนิดของอาร์เรย์ได้ การใช้ตัวเลือกไม่ใช่วิธีที่สมบูรณ์แบบที่จะไปเพราะช้ามาก คุณสามารถใช้การปิดแบบที่มันถูกใช้ไปแล้วเมื่อเรียงลำดับอาร์เรย์ตัวอย่างเช่น

//removes the first item that is equal to the specified element
mutating func removeFirst(element: Element, equality: (Element, Element) -> Bool) -> Bool {
    for (index, item) in enumerate(self) {
        if equality(item, element) {
            self.removeAtIndex(index)
            return true
        }
    }
    return false
}

เมื่อคุณขยายArrayชั้นเรียนด้วยฟังก์ชั่นนี้คุณสามารถลบองค์ประกอบโดยทำสิ่งต่อไปนี้:

var array = ["Apple", "Banana", "Strawberry"]
array.removeFirst("Banana") { $0 == $1 } //Banana is now removed

อย่างไรก็ตามคุณสามารถลบองค์ประกอบได้หากมีที่อยู่หน่วยความจำเท่ากัน (สำหรับคลาสที่สอดคล้องกับAnyObjectโปรโตคอลเท่านั้น):

let date1 = NSDate()
let date2 = NSDate()
var array = [date1, date2]
array.removeFirst(NSDate()) { $0 === $1 } //won't do anything
array.removeFirst(date1) { $0 === $1 } //array now contains only 'date2'

สิ่งที่ดีคือคุณสามารถระบุพารามิเตอร์เพื่อเปรียบเทียบ ตัวอย่างเช่นเมื่อคุณมีอาร์เรย์ของอาร์เรย์คุณสามารถระบุการปิดความเท่าเทียมกัน{ $0.count == $1.count }และอาร์เรย์แรกที่มีขนาดเดียวกับที่จะลบจะถูกลบออกจากอาร์เรย์

คุณยังสามารถร่นสายงานโดยมีฟังก์ชั่นเป็นmutating func removeFirst(equality: (Element) -> Bool) -> Boolแล้วแทนที่การประเมินผลถ้าพร้อมequality(item)และเรียกฟังก์ชั่นโดยการarray.removeFirst({ $0 == "Banana" })ยกตัวอย่างเช่น


เนื่องจาก==เป็นฟังก์ชั่นคุณสามารถเรียกมันว่าแบบนี้สำหรับประเภทใดก็ได้ที่ใช้==(เช่น String, Int เป็นต้น):array.removeFirst("Banana", equality:==)
Aviel Gross

@AvielGross นี่เป็นของใหม่ใน Swift 2 ฉันคิดว่า - อย่าลังเลที่จะแก้ไขคำตอบหากคุณต้องการ
borchero

2

ไม่จำเป็นต้องขยาย:

var ra = [7, 2, 5, 5, 4, 5, 3, 4, 2]

print(ra)                           // [7, 2, 5, 5, 4, 5, 3, 4, 2]

ra.removeAll(where: { $0 == 5 })

print(ra)                           // [7, 2, 4, 3, 4, 2]

if let i = ra.firstIndex(of: 4) {
    ra.remove(at: i)
}

print(ra)                           // [7, 2, 3, 4, 2]

if let j = ra.lastIndex(of: 2) {
    ra.remove(at: j)
}

print(ra)                           // [7, 2, 3, 4]

1

ใช้indexOfแทนforหรือenumerate:

extension Array where Element: Equatable {

   mutating func removeElement(element: Element) -> Element? {
      if let index = indexOf(element) {
         return removeAtIndex(index)
      }
      return nil
   }

   mutating func removeAllOccurrencesOfElement(element: Element) -> Int {
       var occurrences = 0
       while true {
          if let index = indexOf(element) {
             removeAtIndex(index)
             occurrences++
          } else {
             return occurrences
          }
       }
   }   
}

1

บางทีฉันอาจไม่เข้าใจคำถาม

ทำไมถึงไม่ได้ผล

import Foundation
extension Array where Element: Equatable {
    mutating func removeObject(object: Element) {
        if let index = self.firstIndex(of: object) {
            self.remove(at: index)
        }
    }
}

var testArray = [1,2,3,4,5,6,7,8,9,0]
testArray.removeObject(object: 6)
let newArray = testArray

var testArray2 = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"]
testArray2.removeObject(object: "6")
let newArray2 = testArray2

0

ในที่สุดฉันก็ลงเอยด้วยรหัสต่อไปนี้

extension Array where Element: Equatable {

    mutating func remove<Element: Equatable>(item: Element) -> Array {
        self = self.filter { $0 as? Element != item }
        return self
    }

}

0

ฉันจัดการเพื่อลบ[String:AnyObject]จากอาร์เรย์[[String:AnyObject]]โดยการใช้นอกนับสำหรับห่วงจะเป็นตัวแทนของดัชนีตั้งแต่.findและไม่ได้เข้ากันได้กับ.filter[String:AnyObject]

let additionValue = productHarvestChoices[trueIndex]["name"] as! String
var count = 0
for productHarvestChoice in productHarvestChoices {
  if productHarvestChoice["name"] as! String == additionValue {
    productHarvestChoices.removeAtIndex(count)
  }
  count = count + 1
}

-1

การใช้งานใน Swift 2:

extension Array {
  mutating func removeObject<T: Equatable>(object: T) -> Bool {
    var index: Int?
    for (idx, objectToCompare) in self.enumerate() {
      if let toCompare = objectToCompare as? T {
        if toCompare == object {
          index = idx
          break
        }
      }
    }
    if(index != nil) {
      self.removeAtIndex(index!)
      return true
    } else {
      return false
    }
  }
}

-4

ฉันสามารถใช้งานได้กับ:

extension Array {
    mutating func removeObject<T: Equatable>(object: T) {
        var index: Int?
        for (idx, objectToCompare) in enumerate(self) {
            let to = objectToCompare as T
            if object == to {
                index = idx
            }
        }

        if(index) {
            self.removeAtIndex(index!)
        }
    }
}

การเปรียบเทียบif(index)ไม่ถูกต้อง
juanjo
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.