Удаление повторяющихся элементов из массива в Swift

у меня есть массив, который выглядит следующим образом:

[1, 4, 2, 2, 6, 24, 15, 2, 60, 15, 6]

или, действительно, любая последовательность подобных типизированных частей данных. Что я хочу сделать, так это убедиться, что существует только один из каждого идентичного элемента. Например, вышеупомянутый массив станет:

[1, 4, 2, 6, 24, 15, 60]

обратите внимание, что дубликаты 2, 6 и 15 были удалены, чтобы убедиться, что существует только один из каждого идентичного элемента. Обеспечивает ли Swift способ сделать это легко, или мне придется сделать это самому?

30 ответов


Вы можете свернуть свой собственный, например как этот (обновлено для Swift 1.2 с Set):

func uniq<S : SequenceType, T : Hashable where S.Generator.Element == T>(source: S) -> [T] {
    var buffer = [T]()
    var added = Set<T>()
    for elem in source {
        if !added.contains(elem) {
            buffer.append(elem)
            added.insert(elem)
        }
    }
    return buffer
}

let vals = [1, 4, 2, 2, 6, 24, 15, 2, 60, 15, 6]
let uniqueVals = uniq(vals) // [1, 4, 2, 6, 24, 15, 60]

Swift 3 версия:

func uniq<S : Sequence, T : Hashable>(source: S) -> [T] where S.Iterator.Element == T {
    var buffer = [T]()
    var added = Set<T>()
    for elem in source {
        if !added.contains(elem) {
            buffer.append(elem)
            added.insert(elem)
        }
    }
    return buffer
}

вы можете легко преобразовать в набор и обратно в массив:

let unique = Array(Set(originals))

это не гарантирует сохранение исходного порядка массива.


многие ответы доступны здесь, но я пропустил это простое расширение, подходящее для Swift 2 и выше:

extension Array where Element:Equatable {
    func removeDuplicates() -> [Element] {
        var result = [Element]()

        for value in self {
            if result.contains(value) == false {
                result.append(value)
            }
        }

        return result
    }
}

делает его супер просто. Можно назвать так:

let arrayOfInts = [2, 2, 4, 4]
print(arrayOfInts.removeDuplicates()) // Prints: [2, 4]

фильтрация на основе свойств

для фильтрации массива на основе свойств можно использовать следующий метод:

extension Array {

    func filterDuplicates(@noescape includeElement: (lhs:Element, rhs:Element) -> Bool) -> [Element]{
        var results = [Element]()

        forEach { (element) in
            let existingElements = results.filter {
                return includeElement(lhs: element, rhs: )
            }
            if existingElements.count == 0 {
                results.append(element)
            }
        }

        return results
    }
}

который можно назвать так:

let filteredElements = myElements.filterDuplicates { .PropertyOne == .PropertyOne && .PropertyTwo == .PropertyTwo }

Это берет некоторую хорошую информацию, которая уже есть на этой странице, и применяет подход хэширования/набора, когда это возможно, и возвращается к эквивалентному коду в противном случае.

Swift 4 изменение для


Swift 3.0

let uniqueUnordered = Array(Set(array))
let uniqueOrdered = Array(NSOrderedSet(array: array))

Swift 4

гарантированно держать заказ.

extension Array where Element: Equatable {
    func removingDuplicates() -> Array {
        return reduce(into: []) { result, element in
            if !result.contains(element) {
                result.append(element)
            }
        }
    }
}

ограничение элементов коллекции до Equatable вы можете использовать содержит:

extension Collection where Element: Equatable {
    var orderedSet: [Element]  {
        var array: [Element] = []
        return compactMap {
            if array.contains() {
                return nil
            } else {
                array.append()
                return 
            }
        }
    }
}

другой вариант-ограничение элемент коллекции Hashable и использовать для управления тем, какие элементы у вас в карте в результате:

extension Collection where Element: Hashable {
    var orderedSet: [Element]  {
        var set = Set<Element>()
        return compactMap { set.insert().inserted ?  : nil }
    }
}

С помощью Filter:

extension Collection where Element: Hashable {
    var orderedSet: [Element]  {
        var set = Set<Element>()
        return filter { set.insert().inserted }
    }
}

или с помощью NSOrderedSet:

extension Array where Element: Hashable {
    var orderedSet: Array {
        return NSOrderedSet(array: self).array as? Array ?? []
    }
}

использование Swift 4 уменьшить(в:)

extension Collection where Element: Hashable {
    var orderedSet: [Element] {
        var set: Set<Element> = []
        return reduce(into: []) { set.insert().inserted ? .append() : () }
    }
}

let integers = [1, 4, 2, 2, 6, 24, 15, 2, 60, 15, 6]
let integersOrderedSet = integers.orderedSet // [1, 4, 2, 6, 24, 15, 60]

Swift 4

public extension Array where Element: Hashable {
    func uniqued() -> [Element] {
        var seen = Set<Element>()
        return filter{ seen.insert().inserted }
    }
}

любая попытка insert также вернет кортеж:(inserted: Bool, memberAfterInsert: Set.Element). См.документация.

использование возвращенного значения помогает нам избежать цикла или выполнения любой другой операции.


альтернативное (если не оптимальное) решение из здесь использование неизменяемых типов, а не переменных:

func deleteDuplicates<S: ExtensibleCollectionType where S.Generator.Element: Equatable>(seq:S)-> S {
    let s = reduce(seq, S()){
        ac, x in contains(ac,x) ? ac : ac + [x]
    }
    return s
}

включено, чтобы противопоставить императивный подход Жан-Пиллиппа с функциональным подходом.

в качестве бонуса эта функция работает со строками как массивами!


swift 2

С uniq функции ответа:

func uniq<S: SequenceType, E: Hashable where E==S.Generator.Element>(source: S) -> [E] {
    var seen: [E:Bool] = [:]
    return source.filter({ (v) -> Bool in
        return seen.updateValue(true, forKey: v) == nil
    })
}

использование:

var test = [1,2,3,4,5,6,7,8,9,9,9,9,9,9]
print(uniq(test)) //1,2,3,4,5,6,7,8,9

вот категория на SequenceType который сохраняет исходный порядок массива, но использует Set сделать contains поиск, чтобы избежать O(n) стоимость массива contains(_:) метод.

public extension Sequence where Iterator.Element: Hashable {

    public func unique() -> [Iterator.Element] {
        var buffer: [Iterator.Element] = []
        var lookup = Set<Iterator.Element>()

        for element in self {
            guard !lookup.contains(element) else { continue }

            buffer.append(element)
            lookup.insert(element)
        }

        return buffer
    }
}

или если у вас нет Hashable, вы можете сделать это:

public extension Sequence where Iterator.Element: Equatable {

    public func unique() -> [Iterator.Element] {
        var buffer: [Iterator.Element] = []

        for element in self {
            guard !buffer.contains(element) else { continue }

            buffer.append(element)
        }

        return buffer
    }
}

вы можете вставить оба из них в свое приложение, Swift выберет правильный в зависимости от вашей последовательности Iterator.Element тип.


вы можете напрямую использовать коллекцию set для удаления дубликатов, а затем вернуть ее в массив

var myArray = [1, 4, 2, 2, 6, 24, 15, 2, 60, 15, 6]
var mySet = Set<Int>(myArray)

myArray = Array(mySet) // [2, 4, 60, 6, 15, 24, 1]

тогда вы можете заказать свой массив, как вы хотите

myArray.sort{ < } // [1, 2, 4, 6, 15, 24, 60]

еще одно решение Swift 3.0 для удаления дубликатов из массива. Это решение улучшает многие другие решения, уже предложенные:

  • сохранение порядка элементов во входном массиве
  • Линейная сложность O( n): однопроходный фильтр O(n) + установка вставки O(1)

дан массив целых чисел:

let numberArray = [10, 1, 2, 3, 2, 1, 15, 4, 5, 6, 7, 3, 2, 12, 2, 5, 5, 6, 10, 7, 8, 3, 3, 45, 5, 15, 6, 7, 8, 7]

код:

func orderedSet<T: Hashable>(array: Array<T>) -> Array<T> {
    var unique = Set<T>()
    return array.filter { element in
        return unique.insert(element).inserted
    }
}

orderedSet(array: numberArray)  // [10, 1, 2, 3, 15, 4, 5, 6, 7, 12, 8, 45]

код расширения массива:

extension Array where Element:Hashable {
    var orderedSet: Array {
        var unique = Set<Element>()
        return filter { element in
            return unique.insert(element).inserted
        }
    }
}

numberArray.orderedSet // [10, 1, 2, 3, 15, 4, 5, 6, 7, 12, 8, 45]

этот код использует результат, возвращенный insert работы на Set, который выполняет на O(1), и возвращает кортеж, указывающий, был ли элемент вставлен или уже существовал в наборе.

, если элемент был в наборе, filter будет исключить из конечного результата.


чуть более лаконичная синтаксическая версия Даниэль кром Swift 2 Ответ, используя трейлинг-закрытие и сокращенное имя аргумента, которое, по-видимому, основано на скорость начальной скоростью ответ:

func uniq<S: SequenceType, E: Hashable where E == S.Generator.Element>(source: S) -> [E] {
  var seen = [E: Bool]()
  return source.filter { seen.updateValue(true, forKey: ) == nil }
}

пример реализации пользовательского типа, который может использоваться с uniq(_:) (который должен соответствовать Hashable и так Equatable, потому что Hashable выходит Equatable):

func ==(lhs: SomeCustomType, rhs: SomeCustomType) -> Bool {
  return lhs.id == rhs.id // && lhs.someOtherEquatableProperty == rhs.someOtherEquatableProperty
}

struct SomeCustomType {

  let id: Int

  // ...

}

extension SomeCustomType: Hashable {

  var hashValue: Int {
    return id
  }

}

в выше код...

id, как используется в перегрузки == не может Equatable type (или метод, который возвращает Equatable тип, например, someMethodThatReturnsAnEquatableType()). Закомментированный код демонстрирует расширение проверки на равенство, где someOtherEquatableProperty другое свойство Equatable type (но также может быть методом, который возвращает Equatable type).

id, как в hashValue вычисляемое свойство (требуется, чтобы соответствовать Hashable), может быть любой Hashable (и таким образом Equatable) свойство (или метод, который возвращает Hashable type).

пример использования uniq(_:):

var someCustomTypes = [SomeCustomType(id: 1), SomeCustomType(id: 2), SomeCustomType(id: 3), SomeCustomType(id: 1)]

print(someCustomTypes.count) // 4

someCustomTypes = uniq(someCustomTypes)

print(someCustomTypes.count) // 3

для массивов, где элементы не являются ни Хэшируемыми, ни сопоставимыми (например, сложные объекты, словари или структуры), это расширение предоставляет обобщенный способ удаления дубликатов:

extension Array
{
   func filterDuplicate<T>(_ keyValue:(Element)->T) -> [Element]
   {
      var uniqueKeys = Set<String>()
      return filter{uniqueKeys.insert("\(keyValue())").inserted}
   }
}

// example usage: (for a unique combination of attributes):

peopleArray = peopleArray.filterDuplicate{ (.name, .age, .sex) }

вам не придется беспокоиться о принятии ценностей Hashable и это позволяет использовать различные комбинации полей на уникальность.


вы всегда можете использовать словарь, потому что словарь может содержать только уникальные значения. Например:

var arrayOfDates: NSArray = ["15/04/01","15/04/01","15/04/02","15/04/02","15/04/03","15/04/03","15/04/03"]

var datesOnlyDict = NSMutableDictionary()
var x = Int()

for (x=0;x<(arrayOfDates.count);x++) {
    let date = arrayOfDates[x] as String
    datesOnlyDict.setValue("foo", forKey: date)
}

let uniqueDatesArray: NSArray = datesOnlyDict.allKeys // uniqueDatesArray = ["15/04/01", "15/04/03", "15/04/02"]

println(uniqueDatesArray.count)  // = 3

Как вы можете видеть, результирующий массив не всегда будет в "порядок". Если вы хотите отсортировать/упорядочить массив, добавьте следующее:

var sortedArray = sorted(datesOnlyArray) {
(obj1, obj2) in

    let p1 = obj1 as String
    let p2 = obj2 as String
    return p1 < p2
}

println(sortedArray) // = ["15/04/01", "15/04/02", "15/04/03"]

.


я использовал ответ @Jean-Philippe Pellet и сделал расширение массива, которое выполняет операции set-like над массивами, сохраняя при этом порядок элементов.

/// Extensions for performing set-like operations on lists, maintaining order
extension Array where Element: Hashable {
  func unique() -> [Element] {
    var seen: [Element:Bool] = [:]
    return self.filter({ seen.updateValue(true, forKey: ) == nil })
  }

  func subtract(takeAway: [Element]) -> [Element] {
    let set = Set(takeAway)
    return self.filter({ !set.contains() })
  }

  func intersect(with: [Element]) -> [Element] {
    let set = Set(with)
    return self.filter({ set.contains() })
  }
}

позвольте мне предложить ответ, подобный ответ Скотта Гарднера но с более лаконичным синтаксисом с использованием reduce. Это решение удаляет дубликаты из массива пользовательских объектов (сохраняя начальный порядок)

// Custom Struct. Can be also class. 
// Need to be `equitable` in order to use `contains` method below
struct CustomStruct : Equatable {
      let name: String
      let lastName : String
    }

// conform to Equatable protocol. feel free to change the logic of "equality"
func ==(lhs: CustomStruct, rhs: CustomStruct) -> Bool {
  return (lhs.name == rhs.name && lhs.lastName == rhs.lastName)
}

let categories = [CustomStruct(name: "name1", lastName: "lastName1"),
                  CustomStruct(name: "name2", lastName: "lastName1"),
                  CustomStruct(name: "name1", lastName: "lastName1")]
print(categories.count) // prints 3

// remove duplicates (and keep initial order of elements)
let uniq1 : [CustomStruct] = categories.reduce([]) { .contains() ?  :  + [] }
print(uniq1.count) // prints 2 - third element has removed

и просто, если вам интересно, как это уменьшить магию работает-вот точно так же, но с использованием более расширенного синтаксиса уменьшения

let uniq2 : [CustomStruct] = categories.reduce([]) { (result, category) in
  var newResult = result
  if (newResult.contains(category)) {}
  else {
    newResult.append(category)
  }
  return newResult
}
uniq2.count // prints 2 - third element has removed

Вы можете просто скопировать-вставить этот код в Swift Playground и поиграть.


Это просто очень простая и удобная реализация. Вычисляемого свойства в расширение массив элементов equatable.

extension Array where Element: Equatable {
    /// Array containing only _unique_ elements.
    var unique: [Element] {
        var result: [Element] = []
        for element in self {
            if !result.contains(element) {
                result.append(element)
            }
        }

        return result
    }
}

func removeDublicate (ab: [Int]) -> [Int] {
var answer1:[Int] = []
for i in ab {
    if !answer1.contains(i) {
        answer1.append(i)
    }}
return answer1
}

использование:

let f = removeDublicate(ab: [1,2,2])
print(f)

Swift 4.2 Тестирование

extension Sequence where Iterator.Element: Hashable {
    func unique() -> [Iterator.Element] {
        var seen: [Iterator.Element: Bool] = [:]
        return self.filter { seen.updateValue(true, forKey: ) == nil }
    }
}

вот ответ, который я придумал после поиска в интернете и не найдя то, что искал. Используя набор, Вы можете добавить все элементы уменьшить. Затем я беру результат и преобразую его в отсортированный массив.

let initialArray = [1, 4, 2, 2, 6, 24, 15, 2, 60, 15, 6]

let distinct2 = initialArray.reduce(Set<Int>(), combine: { (set, current) -> Set<Int> in
    var tmp = set
    tmp.insert(current)
    return tmp
})

// distinct2 is now a set containing {2, 4, 60, 6, 15, 24, 1}

// Make it into a sorted array
let sorted = Array(distinct2).sorted(<) // Returns [1, 2, 4, 6, 15, 24, 60]

здесь я сделал некоторое o (n) решение для объектов. Не несколько строк решения, но...

struct DistinctWrapper <T>: Hashable {
    var underlyingObject: T
    var distinctAttribute: String
    var hashValue: Int {
        return distinctAttribute.hashValue
    }
}
func distinct<S : SequenceType, T where S.Generator.Element == T>(source: S,
                                                                distinctAttribute: (T) -> String,
                                                                resolution: (T, T) -> T) -> [T] {
    let wrappers: [DistinctWrapper<T>] = source.map({
        return DistinctWrapper(underlyingObject: , distinctAttribute: distinctAttribute())
    })
    var added = Set<DistinctWrapper<T>>()
    for wrapper in wrappers {
        if let indexOfExisting = added.indexOf(wrapper) {
            let old = added[indexOfExisting]
            let winner = resolution(old.underlyingObject, wrapper.underlyingObject)
            added.insert(DistinctWrapper(underlyingObject: winner, distinctAttribute: distinctAttribute(winner)))
        } else {
            added.insert(wrapper)
        }
    }
    return Array(added).map( { return .underlyingObject } )
}
func == <T>(lhs: DistinctWrapper<T>, rhs: DistinctWrapper<T>) -> Bool {
    return lhs.hashValue == rhs.hashValue
}

// tests
// case : perhaps we want to get distinct addressbook list which may contain duplicated contacts like Irma and Irma Burgess with same phone numbers
// solution : definitely we want to exclude Irma and keep Irma Burgess
class Person {
    var name: String
    var phoneNumber: String
    init(_ name: String, _ phoneNumber: String) {
        self.name = name
        self.phoneNumber = phoneNumber
    }
}

let persons: [Person] = [Person("Irma Burgess", "11-22-33"), Person("Lester Davidson", "44-66-22"), Person("Irma", "11-22-33")]
let distinctPersons = distinct(persons,
    distinctAttribute: { (person: Person) -> String in
        return person.phoneNumber
    },
    resolution:
    { (p1, p2) -> Person in
        return p1.name.characters.count > p2.name.characters.count ? p1 : p2
    }
)
// distinctPersons contains ("Irma Burgess", "11-22-33") and ("Lester Davidson", "44-66-22")

Я считаю, что было бы хорошо предложить uniq() и мутировать массив, удалив его значения. Это работает так же, как sort() и sortInPlace() функция, предоставляемая Swift. Кроме того, поскольку это массив, он должен сохранять исходный порядок элементов.

extension Array where Element: Equatable {

    public func uniq() -> [Element] {
        var arrayCopy = self
        arrayCopy.uniqInPlace()
        return arrayCopy
    }

    mutating public func uniqInPlace() {
        var seen = [Element]()
        var index = 0
        for element in self {
            if seen.contains(element) {
                removeAtIndex(index)
            } else {
                seen.append(element)
                index++
            }
        }
    }
}

вы можете использовать только uniqInPlace() на массиве переменных (т. е. var) так как вы не можете мутировать постоянный массив (т. е. let).

некоторые примеры использования:

var numbers = [1, 6, 2, 2, 4, 1, 5]
numbers.uniqInPlace() // array is now [1, 6, 2, 4, 5]

let strings = ["Y", "Z", "A", "Y", "B", "Y", "Z"]
let uniqStrings = strings.uniq() // uniqStrings is now ["Y", "Z", "A", "B"]

Swift 3

на основе ответ Жан-Филиппа Пелле, я обновил его синтаксис для Swift 3.

func uniq<S : Sequence, T : Hashable where S.Iterator.Element == T>(source: S) -> [T] {
    var buffer = [T]()
    var added = Set<T>()
    for elem in source {
        if !added.contains(elem) {
            buffer.append(elem)
            added.insert(elem)
        }
    }
    return buffer
}

let vals = [1, 4, 2, 2, 6, 24, 15, 2, 60, 15, 6]
let uniqueVals = uniq(source: vals) // [1, 4, 2, 6, 24, 15, 60]

в Swift 3.0 самое простое и быстрое решение, которое я нашел, чтобы устранить дублированные элементы, сохраняя порядок:

extension Array where Element:Hashable {
    var unique: [Element] {
        var set = Set<Element>() //the unique list kept in a Set for fast retrieval
        var arrayOrdered = [Element]() //keeping the unique list of elements but ordered
        for value in self {
            if !set.contains(value) {
                set.insert(value)
                arrayOrdered.append(value)
            }
        }

        return arrayOrdered
    }
}

Я сделал просто-как-возможно расширение для этой цели.

extension Array where Element: Equatable {

    func containsHowMany(_ elem: Element) -> Int {
        return reduce(0) {  == elem ?  + 1 :  }
    }

    func duplicatesRemoved() -> Array {
        return self.filter { self.containsHowMany() == 1 }
    }

    mutating func removeDuplicates() {
        self = self.duplicatesRemoved(()
    }
}

можно использовать duplicatesRemoved() чтобы получить новый массив, повторяющиеся элементы которого удаляются, или removeDuplicates() мутировать себя. См.:

let arr = [1, 1, 1, 2, 2, 3, 4, 5, 6, 6, 6, 6, 6, 7, 8]

let noDuplicates = arr.duplicatesRemoved()
print(arr) // [1, 1, 1, 2, 2, 3, 4, 5, 6, 6, 6, 6, 6, 7, 8]
print(noDuplicates) // [1, 2, 3, 4, 5, 6, 7, 8]

arr.removeDuplicates()
print(arr) // [1, 2, 3, 4, 5, 6, 7, 8]

Это также работает (Swift 4)

let sortedValues = Array(Set(array)).sorted()


Swift 4.x:

extension Sequence where Iterator.Element: Hashable {
  func unique() -> [Iterator.Element] {
    return Array(Set<Iterator.Element>(self))
  }

  func uniqueOrdered() -> [Iterator.Element] {
    return reduce([Iterator.Element]()) { .contains() ?  :  + [] }
  }
}

использование:

["Ljubljana", "London", "Los Angeles", "Ljubljana"].unique()

или

["Ljubljana", "London", "Los Angeles", "Ljubljana"].uniqueOrdered()

сохранить уникальные значения и сохранить сортировка в массиве.

(используя Swift 3)

    var top3score: [Int] = []


    outerLoop: for i in 0..<top10score.count {
        dlog(message: String(top10score[i]))

        if top3score.count == 3 {
            break
        }

        for aTop3score in top3score {
            if aTop3score == top10score[i] {
                continue outerLoop
            }
        }

        top3score.append(top10score[i])

    }

    print("top10score is \(top10score)")  //[14, 5, 5, 5, 3, 3, 2, 2, 2, 2]
    print("top3score is \(top3score)")   //[14, 5, 3]