我可能有一个像下面这样的数组:
[1, 4, 2, 2, 6, 24, 15, 2, 60, 15, 6]
或者,实际上,任何类似类型的数据部分的序列。我要做的是确保每个相同的元素只有一个。例如,上面的数组将变成:
[1, 4, 2, 6, 24, 15, 60]
请注意,删除了2、6和15的重复项,以确保每个相同的元素中只有一个。Swift是否提供了一种容易做到这一点的方法,还是我必须自己做?
我可能有一个像下面这样的数组:
[1, 4, 2, 2, 6, 24, 15, 2, 60, 15, 6]
或者,实际上,任何类似类型的数据部分的序列。我要做的是确保每个相同的元素只有一个。例如,上面的数组将变成:
[1, 4, 2, 6, 24, 15, 60]
请注意,删除了2、6和15的重复项,以确保每个相同的元素中只有一个。Swift是否提供了一种容易做到这一点的方法,还是我必须自己做?
当前回答
像函数式程序员一样思考:)
要根据元素是否已经出现来筛选列表,需要索引。可以使用enumeration获取索引,并使用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))慢很多。正如评论中所指出的,它确实保持了顺序,并对非Hashable的元素起作用。
然而,@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.filter{uniqueKeys.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
其他回答
下面是SequenceType上的一个类别,它保留了数组的原始顺序,但使用Set来进行contains查找,以避免数组的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 stackoverflow.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 stackoverflow.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 stackoverflow.com/a/46354989/3141234
func uniqued() -> [Element] {
return self.uniqued(comparator: ==)
}
}
最后,你可以添加一个unique的关键路径版本,如下所示:
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 stackoverflow.com/a/46354989/3141234
///
func uniqued<T: Equatable>(_ keyPath: KeyPath<Element, T>) -> [Element] {
self.uniqued { $0[keyPath: keyPath] == $1[keyPath: keyPath] }
}
}
你可以把这两个都放在你的应用程序中,Swift会根据你的序列的迭代器选择正确的一个。元素类型。
对于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 stackoverflow.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]
}
}
但是(恕我直言)你最好把你自己的block传递给self.unique。
如果你想保持秩序,那么就用这个
let fruits = ["apple", "pear", "pear", "banana", "apple"]
let orderedNoDuplicates = Array(NSOrderedSet(array: fruits).map({ $0 as! String }))
斯威夫特5
extension Sequence where Element: Hashable {
func unique() -> [Element] {
NSOrderedSet(array: self as! [Any]).array as! [Element]
}
}
斯威夫特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
})
}
use:
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
我创建了一个时间复杂度为o(n)的高阶函数。另外,像map这样的功能可以返回您想要的任何类型。
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
}
}