在 Swift 中从数组中删除重复元素
Posted
技术标签:
【中文标题】在 Swift 中从数组中删除重复元素【英文标题】:Removing duplicate elements from an array in Swift 【发布时间】:2014-11-02 12:46:15 【问题描述】:我可能有一个如下所示的数组:
[1, 4, <b>2</b>, <b>2</b>, <b>6</b>, 24, <b>15</b>, 2, 60, <b>15</b>, <b>6</b>]
或者,实际上,任何类似类型的数据部分序列。我想要做的是确保每个相同元素中只有一个。例如,上面的数组会变成:
[1, 4, <b>2</b>, <b>6</b>, 24, <b>15</b>, 60]
请注意,2、6 和 15 的重复项已被删除,以确保每个相同元素中只有一个。 Swift 是否提供了一种轻松完成此操作的方法,还是我必须自己做?
【问题讨论】:
最简单的方法是将数组转换成NSSet
,NSSet是一个无序的对象集合,如果需要NSOrderedSet保持有序。
您可以像在此类中找到的用于数组的函数一样使用交集函数:github.com/pNre/ExSwift/blob/master/ExSwift/Array.swift
不是 Swift 的一部分,但我使用 Dollar。 $.uniq(array)
github.com/ankurp/Dollar#uniq---uniq
下面的mxcl's answer 可能提供了最优雅、最聪明和最快的答案。这也有助于维持秩序
你为什么不直接使用 Swift 的 Set
呢?您将能够提供无序和独特元素的列表。
【参考方案1】:
从数组中删除重复项的简单方法
extension Array where Element: Equatable
mutating func removeDuplicates()
var result = [Element]()
for value in self
if !result.contains(value)
result.append(value)
self = result
【讨论】:
【参考方案2】:现在不需要写扩展了。
Apple 终于在其 Algorithms 包中引入了uniqued()
方法,可用于任何符合Sequence
协议的类型。
import Algorithms
let numbers = [1, 2, 3, 3, 2, 3, 3, 2, 2, 2, 1]
print(numbers.uniqued()) // prints [1, 2, 3]
更多信息https://github.com/apple/swift-algorithms/blob/main/Guides/Unique.md
【讨论】:
【参考方案3】:在插入检查哈希时包含相等检查,最安全的检查方式如下:
extension Array where Element: Hashable
/// Big O(N) version. Updated since @Adrian's comment.
var uniques: Array
// Go front to back, add element to buffer if it isn't a repeat.
var buffer: [Element] = []
var dictionary: [Element: Int] = [:]
for element in self where dictionary[element] == nil
buffer.append(element)
dictionary[element] = 1
return buffer
【讨论】:
这可能会在较小的数组上完成这项工作,但我在大型数据集上尝试过它并且速度非常慢。 感谢您的意见!啊,是的,包含的方法使它成为一个 O(N^2) 操作......很好。 如果hashValue
中存在冲突,这将不起作用。应该通过检查是否相等来处理碰撞。这就是Hashable
协议继承自Equatable
的原因。
刚刚更新到另一个尝试
@Adrian,你能检查一下这个吗?【参考方案4】:
这是SequenceType
上的一个类别,它保留了数组的原始顺序,但使用Set
进行contains
查找以避免Array 的contains(_:)
方法的O(n)
成本。
public extension Sequence where Element: Hashable
/// Return the sequence with all duplicates removed.
///
/// i.e. `[ 1, 2, 3, 1, 2 ].uniqued() == [ 1, 2, 3 ]`
///
/// - note: Taken from ***.com/a/46354989/3141234, as
/// per @Alexander's comment.
func uniqued() -> [Element]
var seen = Set<Element>()
return self.filter seen.insert($0).inserted
如果你不是 Hashable 或 Equatable,你可以传入一个谓词来做相等性检查:
extension Sequence
/// Return the sequence with all duplicates removed.
///
/// Duplicate, in this case, is defined as returning `true` from `comparator`.
///
/// - note: Taken from ***.com/a/46354989/3141234
func uniqued(comparator: @escaping (Element, Element) throws -> Bool) rethrows -> [Element]
var buffer: [Element] = []
for element in self
// If element is already in buffer, skip to the next element
if try buffer.contains(where: try comparator(element, $0) )
continue
buffer.append(element)
return buffer
现在,如果你没有Hashable,但是 Equatable,你可以使用这个方法:
extension Sequence where Element: Equatable
/// Return the sequence with all duplicates removed.
///
/// i.e. `[ 1, 2, 3, 1, 2 ].uniqued() == [ 1, 2, 3 ]`
///
/// - note: Taken from ***.com/a/46354989/3141234
func uniqued() -> [Element]
return self.uniqued(comparator: ==)
最后,您可以像这样添加唯一的密钥路径版本:
extension Sequence
/// Returns the sequence with duplicate elements removed, performing the comparison using the property at
/// the supplied keypath.
///
/// i.e.
///
/// ```
/// [
/// MyStruct(value: "Hello"),
/// MyStruct(value: "Hello"),
/// MyStruct(value: "World")
/// ].uniqued(\.value)
/// ```
/// would result in
///
/// ```
/// [
/// MyStruct(value: "Hello"),
/// MyStruct(value: "World")
/// ]
/// ```
///
/// - note: Taken from ***.com/a/46354989/3141234
///
func uniqued<T: Equatable>(_ keyPath: KeyPath<Element, T>) -> [Element]
self.uniqued $0[keyPath: keyPath] == $1[keyPath: keyPath]
您可以将这两个都粘贴到您的应用中,Swift 会根据您序列的 Iterator.Element
类型选择正确的。
对于 El Capitan,您可以扩展此方法以包含多个键路径,如下所示:
/// Returns the sequence with duplicate elements removed, performing the comparison using the property at
/// the supplied keypaths.
///
/// i.e.
///
/// ```
/// [
/// MyStruct(value1: "Hello", value2: "Paula"),
/// MyStruct(value1: "Hello", value2: "Paula"),
/// MyStruct(value1: "Hello", value2: "Bean"),
/// MyStruct(value1: "World", value2: "Sigh")
/// ].uniqued(\.value1, \.value2)
/// ```
/// would result in
///
/// ```
/// [
/// MyStruct(value1: "Hello", value2: "Paula"),
/// MyStruct(value1: "Hello", value2: "Bean"),
/// MyStruct(value1: "World", value2: "Sigh")
/// ]
/// ```
///
/// - note: Taken from ***.com/a/46354989/3141234
///
func uniqued<T: Equatable, U: Equatable>(_ keyPath1: KeyPath<Element, T>, _ keyPath2: KeyPath<Element, U>) -> [Element]
self.uniqued
$0[keyPath: keyPath1] == $1[keyPath: keyPath1] && $0[keyPath: keyPath2] == $1[keyPath: keyPath2]
但是(恕我直言)您最好将自己的块传递给self.uniqued
。
【讨论】:
嘿,终于有人提出了O(n)
解决方案。顺便说一句,您可以将“检查”和“插入”集合操作合二为一。见***.com/a/46354989/3141234
哦,这很聪明:)
@deanWombourne 如何通过多个键路径区分元素?
@EICaptainv2.0 您可以扩展 uniqued 方法以获取两个通用参数并检查它们是否相等 - 查看我刚刚进行的编辑。仅当 两个 键路径指定的值相同时,这些项目才会重复。
酷。谢谢@deanWombourne【参考方案5】:
正如 WWDC 2021 所述,Swift 拥有社区开发的算法、集合和数值包。 Algorithms 包具有uniqued()
算法。
这些还不是 Swift 标准库的一部分。您目前可以从 Apple 的 Github 页面下载它们和/或通过 Swift Package Manager 安装它们。
WWDC 视频:
https://developer.apple.com/videos/play/wwdc2021/10256/
Github 页面:
https://github.com/apple/swift-algorithms
uniqued()
和 uniqued(on:)
文档:
https://github.com/apple/swift-algorithms/blob/main/Guides/Unique.md
【讨论】:
这应该是最佳答案【参考方案6】:斯威夫特 4
public extension Array where Element: Hashable
func uniqued() -> [Element]
var seen = Set<Element>()
return filter seen.insert($0).inserted
对insert
的每次尝试也会返回一个元组:(inserted: Bool, memberAfterInsert: Set.Element)
。见documentation。
使用返回值意味着我们可以避免多次循环,所以这是 O(n)。
【讨论】:
经过简单的profiling,这个方法确实很快。它比使用 reduce(_: _:) 快数百倍,甚至是 reduce(into: _:) @Kelvin 因为所有其他算法都是O(n^2)
,没有人注意到。
@Kelvin 这个答案与Eneko Alonso answer + 我的评论相同(2017 年 6 月 16 日)。【参考方案7】:
您可以自己滚动,例如像这样:
func unique<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 vals = [1, 4, 2, 2, 6, 24, 15, 2, 60, 15, 6]
let uniqueVals = uniq(vals) // [1, 4, 2, 6, 24, 15, 60]
作为Array
的扩展:
extension Array where Element: Hashable
func uniqued() -> Array
var buffer = Array()
var added = Set<Element>()
for elem in self
if !added.contains(elem)
buffer.append(elem)
added.insert(elem)
return buffer
或者更优雅(Swift 4/5):
extension Sequence where Element: Hashable
func uniqued() -> [Element]
var set = Set<Element>()
return filter set.insert($0).inserted
将使用哪个:
[1,2,4,2,1].uniqued() // => [1,2,4]
【讨论】:
你也可以将该函数的主体实现为var addedDict = [T:Bool](); return filter(source) addedDict(true, forKey: $0) == nil
@AirspeedVelocity:你的意思是updateValue(true, forKey: $0)...
而不是addedDict(true, forKey: $0)...
哎呀是的对不起我的方法不小心!如你所说,应该是return filter(source) addedDict.updateValue(true, forKey: $0) == nil
。
请注意:避免讨论此类简单函数的性能,直到您可以证明依赖于它们的性能,此时您唯一应该做的就是基准测试。由于做出假设,我经常看到无法维护的代码甚至性能更低的代码。 :) 另外,这可能更容易掌握:let uniques = Array(Set(vals))
@Blixt 同意。再一次,这里的优势在于尊重原始数组的元素顺序。【参考方案8】:
var numbers = [1,2,3,4,5,10,10, 12, 12, 6,6,6,7,8,8, 8, 8, 8 , 7 , 1 , 1, 2 , 9]
var newArr : [Int] = []
for n in numbers
if !newArr.contains(n)
newArr.append(n)
输出 - [1, 2, 3, 4, 5, 10, 12, 6, 7, 8, 9]
上述解决方案保持秩序,但非常慢,因为 .contains 一次又一次地迭代。 因此使用有序集。
这将打印有序数组。
Array(NSOrderedSet.init(array: numbers))
输出 - [1, 2, 3, 4, 5, 10, 12, 6, 7, 8, 9]
这将打印一个无序数组。
let uniqueUnordered = Array(Set(numbers))
输出 - [4, 2, 1, 9, 10, 3, 5, 6, 8, 12, 7]
【讨论】:
【参考方案9】:如果你也想保持订单,那么使用这个
let fruits = ["apple", "pear", "pear", "banana", "apple"]
let orderedNoDuplicates = Array(NSOrderedSet(array: fruits).map( $0 as! String ))
【讨论】:
【参考方案10】:您可以很容易地转换为Set
并再次转换回Array
:
let unique = Array(Set(originals))
这不保证保持数组的原始顺序。
【讨论】:
有没有办法在保留数组原始顺序的同时使用集合? @Crashalot 看看我的回答。 如果您需要通过特定属性保持对象的唯一性,而不是在该类上实现 Hashable 和 Equatable 协议,而不是仅使用 Array->Set->Array 转换 如果originals
中的元素不是Hashable
则失败; Set 中只能添加 Hashable
数据类型,而数组中可以添加任何数据类型。
我不明白为什么这个答案有这么多赞成票。似乎维护数组的顺序几乎可以肯定是一个要求。否则,您不妨一开始就使用 Set 而不是 Array。【参考方案11】:
如果您将这两个扩展都放在代码中,将尽可能使用更快的Hashable
版本,而Equatable
版本将用作备用。
public extension Sequence where Element: Hashable
/// The elements of the sequence, with duplicates removed.
/// - Note: Has equivalent elements to `Set(self)`.
@available(
swift, deprecated: 5.4,
message: "Doesn't compile without the constant in Swift 5.3."
)
var firstUniqueElements: [Element]
let getSelf: (Element) -> Element = \.self
return firstUniqueElements(getSelf)
public extension Sequence where Element: Equatable
/// The elements of the sequence, with duplicates removed.
/// - Note: Has equivalent elements to `Set(self)`.
@available(
swift, deprecated: 5.4,
message: "Doesn't compile without the constant in Swift 5.3."
)
var firstUniqueElements: [Element]
let getSelf: (Element) -> Element = \.self
return firstUniqueElements(getSelf)
public extension Sequence
/// The elements of the sequences, with "duplicates" removed
/// based on a closure.
func firstUniqueElements<Hashable: Swift.Hashable>(
_ getHashable: (Element) -> Hashable
) -> [Element]
var set: Set<Hashable> = []
return filter set.insert(getHashable($0)).inserted
/// The elements of the sequence, with "duplicates" removed,
/// based on a closure.
func firstUniqueElements<Equatable: Swift.Equatable>(
_ getEquatable: (Element) -> Equatable
) -> [Element]
reduce(into: []) uniqueElements, element in
if zip(
uniqueElements.lazy.map(getEquatable),
AnyIterator [equatable = getEquatable(element)] in equatable
).allSatisfy(!=)
uniqueElements.append(element)
如果顺序不重要,那么您可以随时使用this Set initializer。
【讨论】:
@DavidSeek 像这样,uniqueArray = nonUniqueArray.uniqueElements 是的,别担心。之后就让它工作了。已经快 2 年了:P 这将有O(n²)
的时间性能,这对于大型数组来说真的很糟糕。
hahsable 版本会有更好的性能,但不会保留原始数组中元素的顺序。 Leo 的回答将同时提供 O(n)
性能并保留对象排序。
@Jessy 已经有多个O(1)
答案,但与大多数天真的O(n^2)
解决方案相比,它们的投票数要少得多。这个特别简单:***.com/a/46354989/3141234【参考方案12】:
使用Set
或NSOrderedSet
删除重复项,然后转换回Array
:
let uniqueUnordered = Array(Set(array))
let uniqueOrdered = Array(NSOrderedSet(array: array))
【讨论】:
让 uniqueOrderedNames = Array(NSOrderedSet(array: userNames)) 为! [String] 如果你有 String 数组,而不是 Any 如果array
中的元素不是Hashable
则失败; Set 中只能添加 Hashable
数据类型,而数组中可以添加任何数据类型。
在 Swift 5.1b5 中测试,考虑到元素是 Hashable 并且希望保留排序,NSOrderedSet(array: array).array 比使用集合的纯 swift func uniqued() 略快带过滤器。我测试了 5100 个字符串,产生了 13 个唯一值。
Array(NSOrderedSet(array: array))
在 Swift 5 中不起作用。请改用 NSOrderedSet(array: array).array as! [String]
。
第二个只适用于“原始”类型【参考方案13】:
我认为这是了解逻辑本身的更好方法
var arrayOfInts = [2, 2, 4, 4]
var mainArray = [Int]()
for value in arrayOfInts
if mainArray.contains(value) != true
mainArray.append(value)
print("mainArray:\(mainArray)")
【讨论】:
这是二次行为。循环调用的每次迭代都包含,它本身对所有元素使用循环。真的很慢。 mainArray.contains(value) == false 可以简化为 mainArray.contains(value) != true【参考方案14】:这是一个解决方案
不使用旧的NS
类型
使用O(n)
相当快
简洁
保留元素顺序
extension Array where Element: Hashable
var uniqueValues: [Element]
var allowed = Set(self)
return compactMap allowed.remove($0)
【讨论】:
这很好,但只适用于 Hashable 元素【参考方案15】:斯威夫特 3/斯威夫特 4/斯威夫特 5
只需一行代码即可省略重复数组而不影响顺序:
let filteredArr = Array(NSOrderedSet(array: yourArray))
【讨论】:
这里我们将一个数组类型转换为 Orderedset。 “集合”的定义 - 集合只允许不同的值(它不允许重复)。因此将省略重复项,因为我们使用 NSOrderedSet 进行类型转换,因此不会干扰数组顺序。【参考方案16】:像函数式程序员一样思考:)
要根据元素是否已经出现过滤列表,您需要索引。您可以使用enumerated
获取索引,使用map
返回值列表。
let unique = myArray
.enumerated()
.filter myArray.firstIndex(of: $0.1) == $0.0
.map $0.1
这保证了顺序。如果您不介意顺序,那么Array(Set(myArray))
的现有答案更简单,可能更有效。
更新:关于效率和正确性的一些说明
一些人评论了效率。我肯定会先编写正确和简单的代码,然后再找出瓶颈,尽管我很欣赏这是否比 Array(Set(array))
更清晰是值得商榷的。
这个方法比Array(Set(array))
慢很多。正如 cmets 中所指出的,它确实保留了顺序并适用于不可哈希的元素。
不过,@Alain T 的方法也可以保持顺序,而且速度也快很多。因此,除非您的元素类型不可散列,或者您只需要一个快速的衬垫,否则我建议您使用他们的解决方案。
以下是 MacBook Pro (2014) 在 Xcode 11.3.1 (Swift 5.1) 上的发布模式下的一些测试。
profiler函数和两种方法比较:
func printTimeElapsed(title:String, operation:()->())
var totalTime = 0.0
for _ in (0..<1000)
let startTime = CFAbsoluteTimeGetCurrent()
operation()
let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime
totalTime += timeElapsed
let meanTime = totalTime / 1000
print("Mean time for \(title): \(meanTime) s")
func method1<T: Hashable>(_ array: Array<T>) -> Array<T>
return Array(Set(array))
func method2<T: Equatable>(_ array: Array<T>) -> Array<T>
return array
.enumerated()
.filter array.firstIndex(of: $0.1) == $0.0
.map $0.1
// Alain T.'s answer (adapted)
func method3<T: Hashable>(_ array: Array<T>) -> Array<T>
var uniqueKeys = Set<T>()
return array.filteruniqueKeys.insert($0).inserted
还有少量的测试输入:
func randomString(_ length: Int) -> String
let letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
return String((0..<length).map _ in letters.randomElement()! )
let shortIntList = (0..<100).map_ in Int.random(in: 0..<100)
let longIntList = (0..<10000).map_ in Int.random(in: 0..<10000)
let longIntListManyRepetitions = (0..<10000).map_ in Int.random(in: 0..<100)
let longStringList = (0..<10000).map_ in randomString(1000)
let longMegaStringList = (0..<10000).map_ in randomString(10000)
作为输出:
Mean time for method1 on shortIntList: 2.7358531951904296e-06 s
Mean time for method2 on shortIntList: 4.910230636596679e-06 s
Mean time for method3 on shortIntList: 6.417632102966309e-06 s
Mean time for method1 on longIntList: 0.0002518167495727539 s
Mean time for method2 on longIntList: 0.021718120217323302 s
Mean time for method3 on longIntList: 0.0005312927961349487 s
Mean time for method1 on longIntListManyRepetitions: 0.00014377200603485108 s
Mean time for method2 on longIntListManyRepetitions: 0.0007293639183044434 s
Mean time for method3 on longIntListManyRepetitions: 0.0001843773126602173 s
Mean time for method1 on longStringList: 0.007168249964714051 s
Mean time for method2 on longStringList: 0.9114790915250778 s
Mean time for method3 on longStringList: 0.015888616919517515 s
Mean time for method1 on longMegaStringList: 0.0525397013425827 s
Mean time for method2 on longMegaStringList: 1.111266262292862 s
Mean time for method3 on longMegaStringList: 0.11214958941936493 s
【讨论】:
不像Array(Set(myArray))
,这适用于不是Hashable
的东西
... 与 Array(Set(myArray))
不同的是,您的数组顺序保持不变。
这对我来说似乎是最好的答案,至少目前 Swift 5 已经是当前版本。
@TimMB 哦,我看错了你的帖子。我看到有人改编使用了lastIndex(of:)
。在这种情况下,我完全不同意清晰度与优化点。我认为这个实现不是特别清楚,尤其是与简单的基于集合的解决方案相比。在任何情况下,都应该将此类代码提取到扩展函数中。即使输入量很小,例如几千到几万,该算法也基本上无法使用。找到这样的数据集并不难,人们可以拥有成千上万的歌曲、文件、联系人等。
查看我的基准测试结果:drive.google.com/a/ryerson.ca/file/d/… 完整代码:gist.github.com/amomchilov/299d012dccba375bf15880355684ebed【参考方案17】:
斯威夫特 5
extension Sequence where Element: Hashable
func unique() -> [Element]
NSOrderedSet(array: self as! [Any]).array as! [Element]
【讨论】:
我做了一些变化,所以我可以选择一个键来比较。extension Sequence // Returns distinct elements based on a key value. func distinct<key: Hashable>(by: ((_ el: Iterator.Element) -> key)) -> [Iterator.Element] var existing = Set<key>() return self.filter existing.insert(by($0)).inserted
当您使用的唯一值是true
时,无需使用Bool
。您正在寻找一种“单位类型”(一种只有一个可能值的类型)。 Swift 的单元类型是Void
,其唯一值是()
(也就是空元组)。所以你可以使用[T: Void]
。虽然你不应该那样做,因为你基本上只是发明了Set
。请改用Set
。见***.com/a/55684308/3141234请删除这个答案。
如果你的元素是Hasable,可以直接使用Array(Set(yourElements)
这会改变数组的顺序。【参考方案18】:
受https://www.swiftbysundell.com/posts/the-power-of-key-paths-in-swift 的启发,我们可以声明一个更强大的工具,它能够过滤任何keyPath 上的唯一性。感谢 Alexander cmets 关于复杂性的各种答案,以下解决方案应该接近最优。
非变异解决方案
我们扩展了一个能够过滤任何 keyPath 上的唯一性的函数:
extension RangeReplaceableCollection
/// Returns a collection containing, in order, the first instances of
/// elements of the sequence that compare equally for the keyPath.
func unique<T: Hashable>(for keyPath: KeyPath<Element, T>) -> Self
var unique = Set<T>()
return filter unique.insert($0[keyPath: keyPath]).inserted
注意:如果你的对象不符合 RangeReplaceableCollection,但符合 Sequence,你可以有这个额外的扩展,但返回类型总是一个数组:
extension Sequence
/// Returns an array containing, in order, the first instances of
/// elements of the sequence that compare equally for the keyPath.
func unique<T: Hashable>(for keyPath: KeyPath<Element, T>) -> [Element]
var unique = Set<T>()
return filter unique.insert($0[keyPath: keyPath]).inserted
用法
如果我们想要元素本身的唯一性,就像问题一样,我们使用 keyPath \.self
:
let a = [1, 4, 2, 2, 6, 24, 15, 2, 60, 15, 6]
let b = a.unique(for: \.self)
/* b is [1, 4, 2, 6, 24, 15, 60] */
如果我们想要其他东西的唯一性(例如对象集合的id
),那么我们使用我们选择的 keyPath:
let a = [CGPoint(x: 1, y: 1), CGPoint(x: 2, y: 1), CGPoint(x: 1, y: 2)]
let b = a.unique(for: \.y)
/* b is [x 1 y 1, x 1 y 2] */
变异解决方案
我们扩展了一个变异函数,该函数能够过滤任何 keyPath 上的唯一性:
extension RangeReplaceableCollection
/// Keeps only, in order, the first instances of
/// elements of the collection that compare equally for the keyPath.
mutating func uniqueInPlace<T: Hashable>(for keyPath: KeyPath<Element, T>)
var unique = Set<T>()
removeAll !unique.insert($0[keyPath: keyPath]).inserted
用法
如果我们想要元素本身的唯一性,就像问题一样,我们使用 keyPath \.self
:
var a = [1, 4, 2, 2, 6, 24, 15, 2, 60, 15, 6]
a.uniqueInPlace(for: \.self)
/* a is [1, 4, 2, 6, 24, 15, 60] */
如果我们想要其他东西的唯一性(比如对象集合的id
),那么我们使用我们选择的 keyPath:
var a = [CGPoint(x: 1, y: 1), CGPoint(x: 2, y: 1), CGPoint(x: 1, y: 2)]
a.uniqueInPlace(for: \.y)
/* a is [x 1 y 1, x 1 y 2] */
【讨论】:
现在这是一个很好的实现!我只是将关键路径转换为闭包,以便您可以使用闭包 arg 来支持任意代码(在闭包中)和单纯的属性查找(通过关键路径)。我要做的唯一更改是将keyPath
默认为\.self
,因为这可能是大多数用例。
@Alexander 我尝试默认为 Self,但我需要将 Element
始终设为 Hashable
。默认值的替代方法是添加一个不带参数的简单重载:extension Sequence where Element: Hashable func unique() ...
啊,是的,有道理!
很棒...简单,最重要的是“灵活”。谢谢。
@Alexander-ReinstateMonica:这看起来与您 2018 年 3 月的解决方案非常相似:gist.github.com/amomchilov/fbba1e58c91fbd4b5b767bcf8586112b???【参考方案19】:
在 Swift 5 中
var array: [String] = ["Aman", "Sumit", "Aman", "Sumit", "Mohan", "Mohan", "Amit"]
let uniq = Array(Set(array))
print(uniq)
输出将是
["Sumit", "Mohan", "Amit", "Aman"]
【讨论】:
这是对已经在这里的许多答案的重复,它不保留顺序。【参考方案20】:-
首先将数组的所有元素添加到 NSOrderedSet。
这将删除数组中的所有重复项。
再次将此有序集转换为数组。
完成....
例子
let array = [1,1,1,1,2,2,2,2,4,6,8]
let orderedSet : NSOrderedSet = NSOrderedSet(array: array)
let arrayWithoutDuplicates : NSArray = orderedSet.array as NSArray
arrayWithoutDuplicates 的输出 - [1,2,4,6,8]
【讨论】:
【参考方案21】:我创建了一个时间复杂度为 o(n) 的高阶函数。此外,地图之类的功能可以返回您想要的任何类型。
extension Sequence
func distinct<T,U>(_ provider: (Element) -> (U, T)) -> [T] where U: Hashable
var uniqueKeys = Set<U>()
var distintValues = [T]()
for object in self
let transformed = provider(object)
if !uniqueKeys.contains(transformed.0)
distintValues.append(transformed.1)
uniqueKeys.insert(transformed.0)
return distintValues
【讨论】:
这对每个元素执行两次散列操作,这是不必要的。insert
返回一个元组,告诉您该元素是否已经存在,或者是第一次添加。 ***.com/a/55684308/3141234请删除此答案。【参考方案22】:
我的解决方案,它似乎可以在 O(n) 时间内,因为哈希映射访问是 O(1),过滤器是 O(n)。它还使用闭包来选择属性,通过该属性来区分元素的顺序。
extension Sequence
func distinct<T: Hashable>(by: (Element) -> T) -> [Element]
var seen: [T: Bool] = [:]
return self.filter seen.updateValue(true, forKey: by($0)) == nil
【讨论】:
当您使用的唯一值是true
时,无需使用Bool
。您正在寻找一种“单位类型”(一种只有一个可能值的类型)。 Swift 的单元类型是Void
,其唯一值是()
(也就是空元组)。所以你可以使用[T: Void]
。虽然你不应该那样做,因为你基本上只是发明了Set
。请改用Set
。见***.com/a/55684308/3141234请删除这个答案。【参考方案23】:
编辑/更新 Swift 4 或更高版本
我们还可以扩展RangeReplaceableCollection
协议,使其也可以与StringProtocol
类型一起使用:
extension RangeReplaceableCollection where Element: Hashable
var orderedSet: Self
var set = Set<Element>()
return filter set.insert($0).inserted
mutating func removeDuplicates()
var set = Set<Element>()
removeAll !set.insert($0).inserted
let integers = [1, 4, 2, 2, 6, 24, 15, 2, 60, 15, 6]
let integersOrderedSet = integers.orderedSet // [1, 4, 2, 6, 24, 15, 60]
"abcdefabcghi".orderedSet // "abcdefghi"
"abcdefabcghi".dropFirst(3).orderedSet // "defabcghi"
变异方法:
var string = "abcdefabcghi"
string.removeDuplicates()
string // "abcdefghi"
var substring = "abcdefabcdefghi".dropFirst(3) // "defabcdefghi"
substring.removeDuplicates()
substring // "defabcghi"
对于 Swift 3,请点击 here
【讨论】:
我喜欢这个,它也适用于字典数组! O(N^2) 不好:( @Alexander Leo Dabus 已经替换了reduce
实现,所以现在复杂性不同了。
结果很有趣。对于 100 万个独特的项目和 800 万个,过滤器版本更快。然而,基于过滤器的版本需要 8.38 倍的时间来处理 800 万个唯一项(比O(n)
时间还要长),而基于平面图的版本需要 7.47 倍的时间来处理 800 万个唯一项,而不是 100 万个,这表明基于平面图的版本版本扩展性更好。不知何故,基于平面图的版本比O(n)
time 稍微好一点!
事实上,当我在数组中使用 64 倍以上的项目运行测试时,基于平面图的版本更快。【参考方案24】:
对于元素既不是 Hashable 也不是 Comparable 的数组(例如复杂对象、字典或结构),此扩展提供了一种通用的删除重复项的方法:
extension Array
func filterDuplicate<T:Hashable>(_ keyValue:(Element)->T) -> [Element]
var uniqueKeys = Set<T>()
return filteruniqueKeys.insert(keyValue($0)).inserted
func filterDuplicate<T>(_ keyValue:(Element)->T) -> [Element]
return filterDuplicate"\(keyValue($0))"
// example usage: (for a unique combination of attributes):
peopleArray = peopleArray.filterDuplicate ($0.name, $0.age, $0.sex)
or...
peopleArray = peopleArray.filterDuplicate "\(($0.name, $0.age, $0.sex))"
您不必费心将值设为 Hashable,它允许您使用不同的字段组合来实现唯一性。
注意:如需更稳健的方法,请参阅 Coeur 在下面的 cmets 中提出的解决方案。
***.com/a/55684308/1033581
[编辑] Swift 4 替代
在 Swift 4.2 中,您可以使用 Hasher 类更轻松地构建哈希。可以更改上述扩展以利用这一点:
extension Array
func filterDuplicate(_ keyValue:((AnyHashable...)->AnyHashable,Element)->AnyHashable) -> [Element]
func makeHash(_ params:AnyHashable ...) -> AnyHashable
var hash = Hasher()
params.forEach hash.combine($0)
return hash.finalize()
var uniqueKeys = Set<AnyHashable>()
return filteruniqueKeys.insert(keyValue(makeHash,$0)).inserted
调用语法有点不同,因为闭包接收一个额外的参数,其中包含一个函数来散列可变数量的值(必须单独散列)
peopleArray = peopleArray.filterDuplicate $0($1.name, $1.age, $1.sex)
它也适用于单个唯一性值(使用 $1 并忽略 $0)。
peopleArray = peopleArray.filterDuplicate $1.name
【讨论】:
这可能会根据"\()"
的行为给出随机结果,因为它可能不会为您提供符合Hashable
应该的唯一值。例如,如果您的元素都通过返回相同的description
符合Printable
,那么您的过滤将失败。
同意。选择将产生所需唯一性模式的字段(或公式)必须考虑到这一点。对于许多用例,这提供了一个简单的临时解决方案,不需要更改元素的类或结构。
@AlainT。不要这样做,真的。 String 的目的不是成为某种贫民窟的临时密钥生成机制。只需将 T
限制为 Hashable
。
@Alexander 我在一个新的答案中应用了这个想法:***.com/a/55684308/1033581
我想要的完美答案。非常感谢。【参考方案25】:
Xcode 10.1 - Swift 4.2 简单而强大的解决方案
func removeDuplicates(_ nums: inout [Int]) -> Int
nums = Set(nums).sorted()
return nums.count
例子
var arr = [1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9]
removeDuplicates(&arr)
print(arr) // [1,2,3,4,5,6,7,8,9]
【讨论】:
这不会保留原始订单:它应用一个新订单,可能相同也可能不同。即使是相同的订单,它的性能也不如只保留现有订单而不添加额外sorted
的解决方案。
是的,sorted()
电话是完全错误的责任。来电者要求进行重复数据删除。如果他们也想要排序,他们已经可以自己完成了。我建议删除这个答案。【参考方案26】:
here 使用不可变类型而不是变量的替代(如果不是最佳)解决方案:
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
将 Jean-Pillippe 的命令式方法与函数式方法进行对比。
作为奖励,此函数适用于字符串和数组!
编辑:此答案是在 2014 年为 Swift 1.0 编写的(在 Set
可用于 Swift 之前)。它不需要 Hashable 一致性并以二次时间运行。
【讨论】:
当心,不是一种,而是两种在二次时间中运行的方式——contains
和数组追加都在 O(n) 中运行。尽管它确实具有只需要可等式而不是可散列的好处。
这是一个非常复杂的写filter
的方式。它是 O(n^2)(如果您不想要求 Hashable
一致性,则需要),但您至少应该明确指出这一点【参考方案27】:
如果您需要对值进行排序,这可行 (Swift 4)
let sortedValues = Array(Set(array)).sorted()
【讨论】:
在这种情况下你失去了元素顺序。 一点也不,这就是最后的.sorted()
的用途。问候。
@MauricioChirino 如果你的原始数组是[2, 1, 1]
?它会出现[1, 2]
,这不是订购的:p
@MauricioChirino 不,我不是。如果目标是从序列中删除重复值,同时保留元素唯一出现的顺序,这不会这样做。 非常明显的反例是 @ 987654325@。独特元素的首次出现,依次为[2, 1]
。这才是正确答案。但是使用您的(不正确的)算法,您会得到[1, 2]
,它已排序,但不是正确的原始顺序。
如果array
中的元素不是Hashable
则失败; Set 中只能添加 Hashable
数据类型,而数组中可以添加任何数据类型。【参考方案28】:
这是 swift 4.2 及以下代码中最简单的方法
let keyarray:NSMutableArray = NSMutableArray()
for object in dataArr
if !keysArray.contains(object)
keysArray.add(object)
print(keysArray)
【讨论】:
哎呀。不要这样做。这是一个O(n^2)
算法(因为contains
是O(n)
,它本身运行n
次)。并且不要在 Swift 中使用 NSMutableArray【参考方案29】:
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]()) $0.contains($1) ? $0 : $0 + [$1]
用法:
["Ljubljana", "London", "Los Angeles", "Ljubljana"].unique()
或
["Ljubljana", "London", "Los Angeles", "Ljubljana"].uniqueOrdered()
【讨论】:
这是O(n^2)
。不要这样做。【参考方案30】:
这适用于 Swift 4,如果您不想/不需要将结果转换为数组,但可以使用 Set。结果默认不排序,但您可以使用 sorted() 来做到这一点,它返回一个数组,如 print 语句所示。
let array = [1, 4, 2, 2, 6, 24, 15, 2, 60, 15, 6]
var result = Set<Int>()
_ = array.map result.insert($0)
print(result.sorted()) // [1, 2, 4, 6, 15, 24, 60]
【讨论】:
这将不可逆转地失去排序。仅当您的原始排序是排序顺序时,排序才有意义,而您的示例并非如此。另外,不要滥用map
,forEach
会更有意义。即使这样,也可能只是let result = Set(array)
以上是关于在 Swift 中从数组中删除重复元素的主要内容,如果未能解决你的问题,请参考以下文章