我可能有一个像下面这样的数组:
[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是否提供了一种容易做到这一点的方法,还是我必须自己做?
当前回答
Daniel Krom的Swift 2答案的更简洁的语法版本,使用了一个尾随闭包和简写参数名,这似乎是基于Airspeed Velocity的原始答案:
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: $0) == 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类型(或返回Equatable类型的方法,例如someMethodThatReturnsAnEquatableType())。注释掉的代码演示了扩展相等性检查,其中someOtherEquatableProperty是Equatable类型的另一个属性(但也可以是返回Equatable类型的方法)。
在hashValue计算属性中使用的id(必须符合Hashable)可以是任何Hashable(因此是Equatable)属性(或返回Hashable类型的方法)。
使用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
其他回答
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]
斯威夫特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
受https://www.swiftbysundell.com/posts/the-power-of-key-paths-in-swift的启发,我们可以声明一个更强大的工具,它能够过滤任何keyPath上的唯一性。感谢Alexander关于复杂性的各种答案的评论,下面的解决方案应该是最优的。
Non-mutating解决方案
我们扩展了一个函数,它可以过滤任意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}] */
Swift 3/ Swift 4/ Swift 5
只需要一行代码就可以省略数组副本而不影响顺序:
let filteredArr = Array(NSOrderedSet(array: yourArray))
我的解决方案,它似乎可以在O(n)时间,因为哈希映射访问是O(1),过滤器是O(n)。它还使用by闭包来选择按顺序区分元素的属性。
extension Sequence {
func distinct<T: Hashable>(by: (Element) -> T) -> [Element] {
var seen: [T: Bool] = [:]
return self.filter { seen.updateValue(true, forKey: by($0)) == nil }
}
}