使用 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)
}
以此类推
感觉~聪明~。给定一个 Int
s
的数组
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) }
}
如何查找数组中的重复元素?我有 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)
}
以此类推
感觉~聪明~。给定一个 Int
s
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 一个没有重复元素的数组。
您可以轻松修改它以计算重复元素的数量、检查字符串数组等。
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) }
}