【问题标题】: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,所以我很有信心它最终会是积极的:)

标签: arrays swift random


【解决方案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 arc4randomstackoverflow.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 - 简单易用。

              1. 创建数组

                var arrayOfColors = [UIColor.red, UIColor.yellow, UIColor.orange, UIColor.green]
                
              2. 创建随机颜色

                let randomColor = arc4random() % UInt32(arrayOfColors.count)
                
              3. 为您的对象设置该颜色

                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] 
                        }
                        
                        
                        
                        1. 我们声明了一个函数,其参数接受一个字符串数组并返回一个字符串。

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

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

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

                        【讨论】:

                          猜你喜欢
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          • 2017-05-01
                          • 1970-01-01
                          • 1970-01-01
                          • 2011-11-04
                          相关资源
                          最近更新 更多