使用 Swift 查找数组中的重复元素

Find Duplicate Elements In Array Using Swift

如何查找数组中的重复元素?我有 phone 个数字数组,所以在 phone 个数字中我应该从右侧开始搜索到左侧并找到相似的 6 个整数。那我应该把它们打印出来。

要查找重复项,您可以通过 phone 编号构建交叉引用,然后将其过滤为仅重复项。例如,考虑:

let contacts = [
    Contact(name: "Rob",     phone: "555-1111"),
    Contact(name: "Richard", phone: "555-2222"),
    Contact(name: "Rachel",  phone: "555-1111"),
    Contact(name: "Loren",   phone: "555-2222"),
    Contact(name: "Mary",    phone: "555-3333"),
    Contact(name: "Susie",   phone: "555-2222")
]

在Swift 4中,您可以通过以下方式构建交叉引用词典:

let crossReference = Dictionary(grouping: contacts, by: { [=11=].phone })

或者,在 Swift 5.2 中(感谢 SE-0249),您可以:

let crossReference = Dictionary(grouping: contacts, by: \.phone)

let crossReference: [String: [Contact]] = contacts.reduce(into: [:]) {
    [=13=][.phone, default: []].append()
}

然后,查找重复项:

let duplicates = crossReference
    .filter { .count > 1 }                 // filter down to only those with multiple contacts
    .sorted { [=14=].1.count > .1.count }      // if you want, sort in descending order by number of duplicates

显然可以使用对您有意义的任何模型类型,但上面使用了以下 Contact 类型:

struct Contact {
    let name: String
    let phone: String
}

有很多很多方法可以实现这个,所以我不会关注上面的实现细节,而是关注这个概念:通过一些键构建交叉引用原始数组(例如phone number),然后将结果过滤到只有那些具有重复值的键。


听起来您想将反映重复项的结构扁平化为一个联系人数组(我不确定您为什么要这样做,因为您丢失了识别重复项的结构彼此),但如果你想这样做,你可以 flatMap it:

let flattenedDuplicates = crossReference
    .filter { .count > 1 }                 // filter down to only those with multiple contacts
    .flatMap { [=16=].1 }                        // flatten it down to just array of contacts that are duplicates of something else

对于 Swift 2 或 3 个演绎版,请参阅 previous renditions of this answer

您可以使用 "Merge sort" 实现它,但您需要进行一次修改,在合并步骤中您应该忽略重复项。

查找重复元素的最简单方法是,如果 phone 数字只是一个 6 位数字并且类型为 Int,您可以对 phone 数字的数组进行排序,然后将其过滤为查找重复项。

var phoneNumbers = [123456, 234567, 345678, 123456, 456789, 135790, 456789, 142638]

func findDuplicates(sortedArray array: [Int]) -> [Int]
{
    var duplicates: [Int] = []

    var prevItem: Int = 0
    var addedItem: Int = 0

    for item in array
    {
        if(prevItem == item && addedItem != item)
        {
            duplicates.append(item)
            addedItem = item
        }

        prevItem = item
    }

    return duplicates
}

func sortPhoneNumbers(phoneNumbers: [Int]) -> [Int]
{
    return phoneNumbers.sorted({ return [=10=]< })
}

sortPhoneNumbers(phoneNumbers)
findDuplicates(sortPhoneNumbers(phoneNumbers))

此外,您可以通过不同的方式实现 findDuplicates 方法:

使用设置 (Swift 1.2+):

func findDuplicates(array: [Int]) -> [Int]
{
    var duplicates = Set<Int>()
    var prevItem = 0       

    for item in array
    {
        if(prevItem == item)
        {
            duplicates.insert(item)
        }

        prevItem = item
    }

    return Array(duplicates)
}

以此类推

感觉~聪明~。给定一个 Ints

的数组
let x = [1, 1, 2, 3, 4, 5, 5]
let duplicates = Array(Set(x.filter({ (i: Int) in x.filter({ [=10=] == i }).count > 1})))
// [1, 5]

请注意,这对所有相关人员来说都是极其低效的,包括编译器和您。

我就是来炫耀的

编辑: 大声笑有人对此投了反对票,这让我重申,以防万一:请不要在生产或其他任何地方使用它。

要根据属性筛选数组,可以使用此方法:

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: [=10=])
            }
            if existingElements.count == 0 {
                results.append(element)
            }
        }

        return results
    }
}

根据罗布的联系人示例,您可以按如下方式调用:

let filteredContacts = myContacts.filterDuplicates { [=11=].name == .name && [=11=].phone == .phone }

@tikhop 的回答相同,但作为数组扩展 (Swift 3):

extension Array where Element: Comparable & Hashable {

   public var duplicates: [Element] {

      let sortedElements = sorted { [=10=] <  }
      var duplicatedElements = Set<Element>()

      var previousElement: Element?
      for element in sortedElements {
         if previousElement == element {
            duplicatedElements.insert(element)
         }
         previousElement = element
      }

      return Array(duplicatedElements)
   }

}

一个非常简单的答案,它保留了所有重复项

let originalNums = [5, 3, 2, 3 , 7 , 5,3]
var nums = Array(originalNums)

let numSet = Set(nums)

for num in numSet {
  if let index = nums.index(of: num) {
     nums.remove(at: index)
  }
}

输出

[3, 5, 3]

我也遇到了类似的问题,通过下面的方法解决了。 (Xcode 8.3.2)

let a = [123456, 234567, 345678, 123456, 456789, 135790, 456789, 142638]
var b = a // copy-on-write so that "a" won't be modified

while let c = b.popLast() {
  b.forEach() {
    if [=10=] == c {
      Swift.print("Duplication: \(c)")
    }
  }
}

//  Duplication: 456789
//  Duplication: 123456

重点是比较的次数。它会比其他的小。

假设数组中的元素个数为N,每循环一次,元素个数减一。 因此,总数将是 (N-1) + (N-2) + (N-3) + ... + 2 + 1 = N * (N-1) / 2 当 N = 10 时,将是 9 + 8 + ... = 45

相比之下,某些算法可能是 N * N。当 N = 10 时,它将是 100。

尽管如此,考虑到深拷贝或浅拷贝的成本,我同意 在某些情况下,@Patrick Perini 的绝妙方法会比这更好,即使它的数量是 N * N。

编辑:

IteratorProtocol 的替代方法

let a = [123456, 234567, 345678, 123456, 456789, 135790, 456789, 142638]
var i = a.makeIterator()

while let c = i.next() {
  var j = i
  while let d = j.next() {
    if c == d {
      Swift.print("Duplication: \(c)")
    }
  }
}

//  Duplication: 123456
//  Duplication: 456789

这看起来更复杂,但使用了与以前相同的想法。这没有不必要的内存分配或副本。

我关心的是效率,即更快的 UI 响应、更长的电池寿命、更小的内存占用等。避免不必要的内存分配 and/or 由 Swift 自动完成的内存副本如果我们要提供有竞争力的产品,幕后的支持将是至关重要的。 (-;

我找到了一个使用reduce的方法,这里是代码(Swift 4):

let testNumbers = [1,1,2,3,4,5,2]
let nondupicate = testNumbers.reduce(into: [Int]()) {
    if ![=10=].contains() {
        [=10=].append()
    } else {
        print("Found duplicate: \()")
    }
}

作为副作用,它 returns 一个没有重复元素的数组。

您可以轻松修改它以计算重复元素的数量、检查字符串数组等。

Swift 3+ 语法

extension Array {

    func filterDuplicates(includeElement: @escaping (_ lhs: Element, _ rhs: Element) -> Bool) -> [Element] {

        var results = [Element]()

        forEach { (element) in

            let existingElements = results.filter {
                return includeElement(element, [=10=])
            }

            if existingElements.count == 0 {
                results.append(element)
            }
        }
        return results
    }
}

完全源自。为了清楚起见,我已经将其添加到 Array 扩展中并为中间步骤命名:

extension Array where Element: Hashable {
    func duplicates() -> Array {
        let groups = Dictionary(grouping: self, by: {[=10=]})
        let duplicateGroups = groups.filter {.count > 1}
        let duplicates = Array(duplicateGroups.keys)
        return duplicates
    }
}

[1, 2, 2, 3, 1].duplicates() -> [1, 2]

Swift 4+

2行,快速解决:

var numbers = [1,2,3,4,5,6,6,6,7,8,8]
let dups = Dictionary(grouping: numbers, by: {[=10=]}).filter { .count > 1 }.keys

//Results: [6, 8]
extension Array where Element: Hashable {
     func similar() -> Self {
        var used = [Element: Bool]()

        return self.filter { used.updateValue(true, forKey: [=10=]) != nil }
    }
}
// find duplicate number in an array 
var arrNum = [1, 2, 3 , 3, 2, 5, 6, 2] 
let setOfNum = Set(Array(arrNum))
print(setOfNum)
Output: [6, 3, 5, 1, 2]
// find duplicate string in an array 
var arrStr = ["1", "2", "3" , "3", "2", "5", "6", "2"]  
let setOfStr = Set(Array(arrStr))
print(setOfNum)
Output: [6, 3, 5, 1, 2]
let inputArray = [9820213496, 9546533545, 9820213496, 995543567]
var outputArray = [Int]()
for element in inputArray{
    if outputArray.contains(element){
        print("\(element) is Duplicate")
    }else{
        outputArray.append(element)
    }
}
print(outputArray) // print Array without duplication

这是一种高效的 O(n) 方法。这里的其他一些答案在 duplicates 数组甚至 return 值数组上使用 .filter,这使得操作在 O(n^2) 中工作(使用 .contains是一样的)。使用 Set 来存储重复项,我们可以将其设为 O(n).

此处显示的另一种方法是使用字典首先存储数组元素。这个想法是字典不能有重复的元素。但是,这并不能保证保留数组的原始顺序,因此我们需要一种不同的方法。

这是一个数组扩展,它添加了一个 removeDuplicates 方法,该方法高效并保证与原始数组的顺序相同的结果顺序。

extension Array where Iterator.Element == Int {
    func removeDuplicates() -> [Int] {
        var retVal: [Int] = []
        var duplicates: Set<Int> = []
        
        for number in self {
            if !duplicates.contains(number) {
                duplicates.insert(number)
                retVal.append(number)
            }
        }
        
        return retVal
    }
}

如果你想 return 重复元素,只需反转 for 循环中的一些检查(仍然是 O(n))。

extension Array where Iterator.Element == Int {
    func findDuplicates() -> [Int] {
        var retVal: [Int] = []
        var duplicates: Set<Int> = []
        
        for number in self {
            if duplicates.contains(number) {
                retVal.append(number)
            } else {
                duplicates.insert(number)
            }
        }
        
        return retVal
    }
}

Swift 中仍然缺少一些有用的可重复使用的东西来简化这个过程,但是 OrderedCollections 还没有被其他答案使用,可以更容易地获得“按顺序”重复。

XCTAssertEqual(
  .init("❤️‍❤️‍❤️‍".duplicates),
  "❤️‍"
)
import OrderedCollections

public extension Sequence where Element: Hashable {
  /// The non-unique elements of this collection, in the order of their first occurrences.
  var duplicates: OrderedSet<Element> {
    OrderedDictionary(bucketing: self).filter {  > 1 }.keys
  }
}
import struct OrderedCollections.OrderedDictionary

public protocol DictionaryProtocol {
  associatedtype Key
  associatedtype Value

  init<KeysAndValues: Sequence>(
    _: KeysAndValues,
    uniquingKeysWith: (Value, Value) throws -> Value
  ) rethrows where KeysAndValues.Element == (Key, Value)
}

extension Dictionary: DictionaryProtocol { }
extension OrderedDictionary: DictionaryProtocol { }

public extension DictionaryProtocol where Value == Int {
  /// Create "buckets" from a sequence of keys,
  /// such as might be used for a histogram.
  init<Keys: Sequence>(bucketing unbucketedKeys: Keys)
  where Keys.Element == Key {
    self.init(zip(unbucketedKeys, 1), uniquingKeysWith: +)
  }
}
/// `zip` a sequence with a single value, instead of another sequence.
@inlinable public func zip<Sequence: Swift.Sequence, Constant>(
  _ sequence: Sequence, _ constant: Constant
) -> LazyMapSequence<
  LazySequence<Sequence>.Elements,
  (LazySequence<Sequence>.Element, Constant)
> {
 sequence.lazy.map { ([=13=], constant) }
}