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

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 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
Отлично, ты должен любить Свифта (2). Мне очень нравится, как со временем становится возможным больше вещей и все становится
проще
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]

Изменить Чтобы преодолеть тихую неудачу, вы можете вернуть успех как bool:

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метод?
Snowman
Ваш метод подвержен сбоям во время выполнения. С моей функцией компилятор вообще предотвратит это.
drewag
1
@Isuru Этот метод работает с любым объектом, реализующим Equatableпротокол. UIView делает это, да, он будет работать с UIViews
drewag
4
Ух ты, написание цикла for для удаления элемента, вернемся к 90-м годам!
Зорайр
5
В последней версии swift. 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" )

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

Та же функциональность для классов,

Swift 2

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

Swift 3

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

Но если класс реализует Equatable, он становится неоднозначным, и компилятор выдает ошибку.

Шри Кришна Паритала
источник
1
i'm Binary operator '===' cannot be applied to two elements of type '_' and 'Element'
shoe
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})
    }

}
Valvoline
источник
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
    }
  }
}
Wcharysz
источник
-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