如何从 Swift 中的集合中获取随机元素?
How to get random element from a set in Swift?
从 Swift 1.2 开始,Apple 引入了 Set
集合类型。
说,我有一组像:
var set = Set<Int>(arrayLiteral: 1, 2, 3, 4, 5)
现在我想从中取出一个随机元素。问题是如何? Set
不像 Array
那样提供 subscript(Int)
。相反,它有 subscript(SetIndex<T>)
。但首先,SetIndex<T>
没有可访问的初始化器(因此,我不能只用我需要的偏移量创建索引),其次,即使我可以获得集合中第一个元素的索引(var startIndex = set.startIndex
) 那么我可以到达第 N 个索引的唯一方法是通过连续调用 successor()
.
因此,我目前只能看到 2 个选项,既丑又贵:
- 将集合转换为数组(
var array = [Int](set)
)并使用其下标(完全接受Int
);或者
- 获取集合中第一个元素的索引,遍历
successor()
方法链得到第N个索引,然后通过集合的下标读取对应的元素。
我是否错过了其他方式?
如果您想要 Set
中的 'random' 元素,那么您可以使用:
/// A member of the set, or `nil` if the set is empty.
var first: T? { get }
获取第 0 个索引或第 1,000,000 个索引没有区别 - 它们都是任意对象。
但是,如果您想要重复调用 每次 return 一个可能不同的元素,那么 first
可能不符合要求。
可能最好的方法是 advance
,它会为您走 successor
:
func randomElementIndex<T>(s: Set<T>) -> T {
let n = Int(arc4random_uniform(UInt32(s.count)))
let i = advance(s.startIndex, n)
return s[i]
}
(编辑:嘿;注意到你实际上更新了问题以包含这个答案,然后我将它添加到我的答案中......好吧,这仍然是一个好主意,我也学到了一些东西。:D)
你也可以遍历集合而不是索引(这是我的第一个想法,但后来我想起来了advance
)。
func randomElement<T>(s: Set<T>) -> T {
let n = Int(arc4random_uniform(UInt32(s.count)))
for (i, e) in enumerate(s) {
if i == n { return e }
}
fatalError("The above loop must succeed")
}
extension Set {
func randomElement() -> Element? {
return count == 0 ? nil : self[advance(self.startIndex, Int(arc4random()) % count)]
}
}
根据上面的评论重新 Swift 更新,对 Set 的扩展使用了一个小改动:
func randomElement() -> Element?
{
let randomInt = Int(arc4random_uniform(UInt32(self.count)))
let index = startIndex.advancedBy(randomInt)
return count == 0 ? nil: self[index]
}
在swift3
extension Set {
public func randomObject() -> Element? {
let n = Int(arc4random_uniform(UInt32(self.count)))
let index = self.index(self.startIndex, offsetBy: n)
return self.count > 0 ? self[index] : nil
}
}
从Swift 4.2开始,可以使用randomElement
:
let random = set.randomElement()
从 Swift 1.2 开始,Apple 引入了 Set
集合类型。
说,我有一组像:
var set = Set<Int>(arrayLiteral: 1, 2, 3, 4, 5)
现在我想从中取出一个随机元素。问题是如何? Set
不像 Array
那样提供 subscript(Int)
。相反,它有 subscript(SetIndex<T>)
。但首先,SetIndex<T>
没有可访问的初始化器(因此,我不能只用我需要的偏移量创建索引),其次,即使我可以获得集合中第一个元素的索引(var startIndex = set.startIndex
) 那么我可以到达第 N 个索引的唯一方法是通过连续调用 successor()
.
因此,我目前只能看到 2 个选项,既丑又贵:
- 将集合转换为数组(
var array = [Int](set)
)并使用其下标(完全接受Int
);或者 - 获取集合中第一个元素的索引,遍历
successor()
方法链得到第N个索引,然后通过集合的下标读取对应的元素。
我是否错过了其他方式?
如果您想要 Set
中的 'random' 元素,那么您可以使用:
/// A member of the set, or `nil` if the set is empty.
var first: T? { get }
获取第 0 个索引或第 1,000,000 个索引没有区别 - 它们都是任意对象。
但是,如果您想要重复调用 每次 return 一个可能不同的元素,那么 first
可能不符合要求。
可能最好的方法是 advance
,它会为您走 successor
:
func randomElementIndex<T>(s: Set<T>) -> T {
let n = Int(arc4random_uniform(UInt32(s.count)))
let i = advance(s.startIndex, n)
return s[i]
}
(编辑:嘿;注意到你实际上更新了问题以包含这个答案,然后我将它添加到我的答案中......好吧,这仍然是一个好主意,我也学到了一些东西。:D)
你也可以遍历集合而不是索引(这是我的第一个想法,但后来我想起来了advance
)。
func randomElement<T>(s: Set<T>) -> T {
let n = Int(arc4random_uniform(UInt32(s.count)))
for (i, e) in enumerate(s) {
if i == n { return e }
}
fatalError("The above loop must succeed")
}
extension Set {
func randomElement() -> Element? {
return count == 0 ? nil : self[advance(self.startIndex, Int(arc4random()) % count)]
}
}
根据上面的评论重新 Swift 更新,对 Set 的扩展使用了一个小改动:
func randomElement() -> Element?
{
let randomInt = Int(arc4random_uniform(UInt32(self.count)))
let index = startIndex.advancedBy(randomInt)
return count == 0 ? nil: self[index]
}
在swift3
extension Set {
public func randomObject() -> Element? {
let n = Int(arc4random_uniform(UInt32(self.count)))
let index = self.index(self.startIndex, offsetBy: n)
return self.count > 0 ? self[index] : nil
}
}
从Swift 4.2开始,可以使用randomElement
:
let random = set.randomElement()