从数组中选择一个随机元素

Posted

技术标签:

【中文标题】从数组中选择一个随机元素【英文标题】:Pick a random element from an array 【发布时间】:2014-07-23 01:58:02 【问题描述】:

假设我有一个数组,我想随机选择一个元素。

最简单的方法是什么?

显而易见的方法是array[random index]。但也许有像 ruby​​ 的array.sample 之类的东西?或者如果不能使用扩展来创建这样的方法?

【问题讨论】:

您尝试过其他方法了吗? 我会尝试array[random number from 0 to length-1],但我找不到如何快速生成随机整数,如果我没有被阻止,我会在堆栈溢出时询问它:) 我不想要当可能有 ruby​​ 之类的 array.sample 时,用一半的解决方案污染问题 你使用 arc4random() 就像在 Obj-C 中一样 没有解释为什么您的问题没有收到与 JQuery 对应的相同反馈。但总的来说,您在发布问题时应该遵循这些准则。 How to ask a good question?。让您看起来像是在向其他人寻求帮助之前付出了一些努力来找出解决方案。当我用谷歌搜索“快速选择随机数”时,第一页充满了建议 arc4random_uniform 的答案。此外,RTFD ...“阅读 f'ing 文档”。令人惊讶的是,有多少问题可以通过这种方式回答。 感谢您的友好反馈。是的,我想我应该自己回答这个问题,但给别人几乎免费的声望点似乎很容易。我写它的时候甚至连官方的 Apple swift 文档都没有公开,当时肯定没有谷歌的结果。但是问题曾经是-12,所以我很有信心它最终会是积极的:) 【参考方案1】:

Swift 4.2 及以上版本

推荐的新方法是 Collection 协议的内置方法:randomElement()。它返回一个可选项以避免我之前假设的空情况。

let array = ["Frodo", "Sam", "Wise", "Gamgee"]
print(array.randomElement()!) // Using ! knowing I have array.count > 0

如果您不创建数组并且不能保证计数 > 0,您应该执行以下操作:

if let randomElement = array.randomElement()  
    print(randomElement)

Swift 4.1 及更低版本

只是为了回答你的问题,你可以这样做来实现随机数组选择:

let array = ["Frodo", "sam", "wise", "gamgee"]
let randomIndex = Int(arc4random_uniform(UInt32(array.count)))
print(array[randomIndex])

铸件很难看,但我相信除非其他人有其他方法,否则它们是必需的。

【讨论】:

为什么 Swift 不提供返回 Int 的随机数生成器?这第二行似乎非常冗长,只是为了返回随机选择的 Int。与 Int 相比,返回 UInt32 是否有一些计算/语法优势?另外,为什么 Swift 不提供 Int 替代该函数或允许用户指定他们希望返回的整数类型? 添加注释,这种随机数生成器方法可以防止“模偏差”。参考man arc4random和***.com/questions/10984974/… @AustinA,Swift 4.2 确实有一个本地随机数生成器函数,可以在您可能希望的所有标量数据类型上实现:Int、Double、Float、UInt32 等。它允许您提供目标值的范围。非常便利。您可以在 Swift 4.2 中使用 array[Int.random(0.. 我希望 Swift 4.2 除了 randomElement() 之外还实现了 removeRandomElement() 函数。它将以removeFirst() 为模型,但在随机索引处删除一个对象。 @DuncanC 您应该避免使用0..<array.count(原因有很多,主要原因是它不适用于切片,而且容易出错)。你可以做let randomIndex = array.indices.randomElement(),然后是let randomElement = array.remove(at: randomIndex)。你甚至可以将它内联到let randomElement = array.remove(at: array.indices.randomElement())【参考方案2】:

根据 Lucas 所说,您可以像这样创建 Array 类的扩展:

extension Array 
    func randomItem() -> Element? 
        if isEmpty  return nil 
        let index = Int(arc4random_uniform(UInt32(self.count)))
        return self[index]
    

例如:

let myArray = [1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16]
let myItem = myArray.randomItem() // Note: myItem is an Optional<Int>

【讨论】:

在 swift 2 中 T 已重命名为 Element 注意这里空数组会导致崩溃 @Berik 好吧,您可以返回一个可选元素,然后始终执行guard 检查数组是否为空,然后返回nil 同意。数组在越界时崩溃,以便它们可以发挥作用。调用arc4random 会使任何性能提升都变得微不足道。我已经更新了答案。【参考方案3】:

Swift 4 版本:

extension Collection where Index == Int 

    /**
     Picks a random element of the collection.

     - returns: A random element of the collection.
     */
    func randomElement() -> Iterator.Element? 
        return isEmpty ? nil : self[Int(arc4random_uniform(UInt32(endIndex)))]
    


【讨论】:

这可能会因startIndex != 0 的集合上的索引超出范围而崩溃【参考方案4】:

Swift 2.2 中,这可以概括为:

UInt.random
UInt8.random
UInt16.random
UInt32.random
UInt64.random
UIntMax.random

// closed intervals:

(-3...3).random
(Int.min...Int.max).random

// and collections, which return optionals since they can be empty:

(1..<4).sample
[1,2,3].sample
"abc".characters.sample
["a": 1, "b": 2, "c": 3].sample

首先,为UnsignedIntegerTypes 实现静态random 属性:

import Darwin

func sizeof <T> (_: () -> T) -> Int  // sizeof return type without calling
    return sizeof(T.self)


let ARC4Foot: Int = sizeof(arc4random)

extension UnsignedIntegerType 
    static var max: Self  // sadly `max` is not required by the protocol
        return ~0
    
    static var random: Self 
        let foot = sizeof(Self)
        guard foot > ARC4Foot else 
            return numericCast(arc4random() & numericCast(max))
        
        var r = UIntMax(arc4random())
        for i in 1..<(foot / ARC4Foot) 
            r |= UIntMax(arc4random()) << UIntMax(8 * ARC4Foot * i)
        
        return numericCast(r)
    

然后,对于 ClosedIntervals 和 UnsignedIntegerType 边界:

extension ClosedInterval where Bound : UnsignedIntegerType 
    var random: Bound 
        guard start > 0 || end < Bound.max else  return Bound.random 
        return start + (Bound.random % (end - start + 1))
    

然后(涉及更多一点),对于带有SignedIntegerType 边界的ClosedIntervals(使用下面进一步描述的辅助方法):

extension ClosedInterval where Bound : SignedIntegerType 
    var random: Bound 
        let foot = sizeof(Bound)
        let distance = start.unsignedDistanceTo(end)
        guard foot > 4 else  // optimisation: use UInt32.random if sufficient
            let off: UInt32
            if distance < numericCast(UInt32.max) 
                off = UInt32.random % numericCast(distance + 1)
             else 
                off = UInt32.random
            
            return numericCast(start.toIntMax() + numericCast(off))
        
        guard distance < UIntMax.max else 
            return numericCast(IntMax(bitPattern: UIntMax.random))
        
        let off = UIntMax.random % (distance + 1)
        let x = (off + start.unsignedDistanceFromMin).plusMinIntMax
        return numericCast(x)
    

...其中unsignedDistanceTounsignedDistanceFromMinplusMinIntMax辅助方法可以实现如下:

extension SignedIntegerType 
    func unsignedDistanceTo(other: Self) -> UIntMax 
        let _self = self.toIntMax()
        let other = other.toIntMax()
        let (start, end) = _self < other ? (_self, other) : (other, _self)
        if start == IntMax.min && end == IntMax.max 
            return UIntMax.max
        
        if start < 0 && end >= 0 
            let s = start == IntMax.min ? UIntMax(Int.max) + 1 : UIntMax(-start)
            return s + UIntMax(end)
        
        return UIntMax(end - start)
    
    var unsignedDistanceFromMin: UIntMax 
        return IntMax.min.unsignedDistanceTo(self.toIntMax())
    


extension UIntMax 
    var plusMinIntMax: IntMax 
        if self > UIntMax(IntMax.max)  return IntMax(self - UIntMax(IntMax.max) - 1) 
        else  return IntMax.min + IntMax(self) 
    

最后,对于Index.Distance == Int:

的所有集合
extension CollectionType where Index.Distance == Int 
    var sample: Generator.Element? 
        if isEmpty  return nil 
        let end = UInt(count) - 1
        let add = (0...end).random
        let idx = startIndex.advancedBy(Int(add))
        return self[idx]
    

...对于整数Ranges 可以稍微优化一下:

extension Range where Element : SignedIntegerType 
    var sample: Element? 
        guard startIndex < endIndex else  return nil 
        let i: ClosedInterval = startIndex...endIndex.predecessor()
        return i.random
    


extension Range where Element : UnsignedIntegerType 
    var sample: Element? 
        guard startIndex < endIndex else  return nil 
        let i: ClosedInterval = startIndex...endIndex.predecessor()
        return i.random
    

【讨论】:

【参考方案5】:

你也可以使用 Swift 内置的 random() 函数来进行扩展:

extension Array 
    func sample() -> Element 
        let randomIndex = Int(rand()) % count
        return self[randomIndex]
    


let array = [1, 2, 3, 4]

array.sample() // 2
array.sample() // 2
array.sample() // 3
array.sample() // 3

array.sample() // 1
array.sample() // 1
array.sample() // 3
array.sample() // 1

【讨论】:

其实random()是来自标准C库的桥接,你可以在终端看到它和朋友,“man random”。但很高兴您指出了可用性! 每次运行都会产生相同的随机序列 @iTSangar 你是对的! rand() 是正确的使用方法。更新我的答案。 这也容易受到模偏差的影响。 @mattt 写了一个nice article on generating random numbers。 TL;DR arc4random 系列中的任何一个都是更好的选择。【参考方案6】:

另一个 Swift 3 建议

private extension Array 
    var randomElement: Element 
        let index = Int(arc4random_uniform(UInt32(count)))
        return self[index]
    

【讨论】:

【参考方案7】:

跟随其他人的回答,但支持 Swift 2。

斯威夫特 1.x

extension Array 
    func sample() -> T 
        let index = Int(arc4random_uniform(UInt32(self.count)))
        return self[index]
    

斯威夫特 2.x

extension Array 
    func sample() -> Element 
        let index = Int(arc4random_uniform(UInt32(self.count)))
        return self[index]
    

例如:

let arr = [2, 3, 5, 7, 9, 11, 13, 17, 19, 23, 29, 31]
let randomSample = arr.sample()

【讨论】:

【参考方案8】:

检查空数组的替代功能实现。

func randomArrayItem<T>(array: [T]) -> T? 
  if array.isEmpty  return nil 
  let randomIndex = Int(arc4random_uniform(UInt32(array.count)))
  return array[randomIndex]


randomArrayItem([1,2,3])

【讨论】:

【参考方案9】:

这是一个数组扩展,带有空数组检查,以提高安全性:

extension Array 
    func sample() -> Element? 
        if self.isEmpty  return nil 
        let randomInt = Int(arc4random_uniform(UInt32(self.count)))
        return self[randomInt]
    

您可以就这么简单地使用它

let digits = Array(0...9)
digits.sample() // => 6

如果您更喜欢具有一些更方便功能的框架,请查看 HandySwift。您可以通过 Carthage 将它添加到您的项目中,然后像上面的示例一样使用它:

import HandySwift    

let digits = Array(0...9)
digits.sample() // => 8

此外,它还包括一个一次获取多个随机元素的选项:

digits.sample(size: 3) // => [8, 0, 7]

【讨论】:

【参考方案10】:

斯威夫特 3

导入 GameKit

func getRandomMessage() -> String 

    let messages = ["one", "two", "three"]

    let randomNumber = GKRandomSource.sharedRandom().nextInt(upperBound: messages.count)

    return messages[randomNumber].description


【讨论】:

【参考方案11】:

Swift 3 - 简单易用。

    创建数组

    var arrayOfColors = [UIColor.red, UIColor.yellow, UIColor.orange, UIColor.green]
    

    创建随机颜色

    let randomColor = arc4random() % UInt32(arrayOfColors.count)
    

    为您的对象设置该颜色

    your item = arrayOfColors[Int(randomColor)]
    

这是一个来自SpriteKit 项目的示例,用随机的String 更新SKLabelNode

    let array = ["one","two","three","four","five"]

    let randomNumber = arc4random() % UInt32(array.count)

    let labelNode = SKLabelNode(text: array[Int(randomNumber)])

【讨论】:

【参考方案12】:

如果您希望能够在无重复的情况下从数组中获取多个随机元素,GameplayKit 可以满足您的需求:

import GameplayKit
let array = ["one", "two", "three", "four"]

let shuffled = GKMersenneTwisterRandomSource.sharedRandom().arrayByShufflingObjects(in: array)

let firstRandom = shuffled[0]
let secondRandom = shuffled[1]

你有几个随机选择,见GKRandomSource:

GKARC4RandomSource 类使用的算法类似于 arc4random 的 C 函数系列。 (但是,此类的实例独立于对 arc4random 函数的调用。)

GKLinearCongruentialRandomSource 类使用比 GKARC4RandomSource 类更快但随机性更低的算法。 (具体来说,生成数字的低位比高位更频繁地重复。)当性能比稳健的不可预测性更重要时,请使用此来源。

GKMersenneTwisterRandomSource 类使用比 GKARC4RandomSource 类更慢但更随机的算法。当您对随机数的使用不显示重复模式很重要并且性能不太重要时,请使用此来源。

【讨论】:

【参考方案13】:

我发现使用 GameKit 的 GKRandomSource.sharedRandom() 最适合我。

import GameKit

let array = ["random1", "random2", "random3"]

func getRandomIndex() -> Int 
    let randomNumber = GKRandomSource.sharedRandom().nextIntWithUpperBound(array.count)
    return randomNumber

或者您可以在选择的随机索引处返回对象。确保函数首先返回一个字符串,然后返回数组的索引。

    return array[randomNumber]

简明扼要。

【讨论】:

【参考方案14】:

Collection 现在有一个内置方法:

let foods = ["?", "?", "?", "?"]
let myDinner = foods.randomElement()

如果您想从集合中提取最多 n 随机元素,您可以添加一个类似这样的扩展:

extension Collection 
    func randomElements(_ count: Int) -> [Element] 
        var shuffledIterator = shuffled().makeIterator()
        return (0..<count).compactMap  _ in shuffledIterator.next() 
    

如果您希望它们是唯一的,您可以使用Set,但集合的元素必须符合Hashable 协议:

extension Collection where Element: Hashable 
    func randomUniqueElements(_ count: Int) -> [Element] 
        var shuffledIterator = Set(shuffled()).makeIterator()
        return (0..<count).compactMap  _ in shuffledIterator.next() 
    

【讨论】:

【参考方案15】:

最新的 swift3 代码试一试,效果很好

 let imagesArray = ["image1.png","image2.png","image3.png","image4.png"]

        var randomNum: UInt32 = 0
        randomNum = arc4random_uniform(UInt32(imagesArray.count))
        wheelBackgroundImageView.image = UIImage(named: imagesArray[Int(randomNum)])

【讨论】:

【参考方案16】:

我想出了一个非常不同的方法来使用 Swift 4.2 中引入的新功能。

// ?? - 1 
public func shufflePrintArray(ArrayOfStrings: [String]) -> String 
// - 2 
       let strings = ArrayOfStrings
//- 3
       var stringans =  strings.shuffled()
// - 4
        var countS = Int.random(in: 0..<strings.count)
// - 5
        return stringans[countS] 



    我们声明了一个函数,其参数接受一个字符串数组并返回一个字符串。

    然后我们将 ArrayOfStrings 放入一个变量中。

    然后我们调用 shuffled 函数并将其存储在一个变量中。 (仅在 4.2 中支持) 然后我们声明一个变量,该变量保存字符串总数的混洗值。 最后,我们在 countS 的索引值处返回打乱后的字符串。

它基本上是对字符串数组进行混洗,然后随机选择计数总数,然后返回混洗数组的随机索引。

【讨论】:

以上是关于从数组中选择一个随机元素的主要内容,如果未能解决你的问题,请参考以下文章

如何从数组中删除随机元素。 Python

使用c ++在每个循环中从数组中随机选择n个元素

从数组中随机选择3个元素

如何从 Python 中的数组中选择随机元素? [复制]

有没有办法让两个元素从数组中选择随机项目但不会是同一个项目 - Javascript

如何从数组中随机选择四个元素而不用Java重复?