Розширення масиву для видалення об'єкта за значенням


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

Станом на Swift 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, тепер фактично можливо написати метод на загальний тип, який є більш обмежувальним для шаблону, тому метод тепер фактично може бути визначений як розширення з 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
Ідеально, ти повинен кохати Свіфта (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, він повинен бути класом (це не може бути структура, перерахунок тощо).

Найкраще зробити це функцією, яка приймає масив як аргумент:

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
Нічого собі, написавши цикл для видалення елемента, ще до 90-х років це!
Зорайр

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 }, я думаю.
Дан Розенстарк

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

Швидке оновлення 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

З використанням розширень протоколу у швидкому 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, а потім замінити if-оцінка на 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.