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


189

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

最简单的方法是什么?

明显的方法是array[random index]。但是也许有像红宝石的东西array.sample?或者如果不能通过扩展创建这种方法?


1
您是否尝试过任何其他方法?
福特县长2014年

我会尝试的array[random number from 0 to length-1],但是我找不到如何快速生成随机int的方法,如果我没有被阻塞,我会在堆栈溢出时问它:)我不想在可能的情况下用一半的解决方案来污染问题像红宝石一样array.sample
Fela Winkelmolen

1
您可以像在Obj-C中一样使用arc4random()
Arbitur,2014年

没有解释为什么您的问题没有得到与JQuery对等方相同的反馈。但通常,发布问题时应遵循以下准则。如何问一个好问题?。让它看起来像您在寻求其他人帮助之前付出了一些努力来找出解决方案。当我用Google搜索“选择随机数迅速”时,第一页充满了建议arc4random_uniform的答案。另外,RTFD ...“阅读文档”。令人惊讶的是,用这种方式可以回答多少个问题。
奥斯汀

多谢您的宝贵意见。是的,我想我本应该回答这个问题,但是似乎很容易为别人提供几乎免费的声誉点。当我什至没有正式的Apple Swift文档公开发布时,我都写了这篇文章,当时Google绝对没有结果。但问题是曾经在-12点出现,因此我非常有信心最终将是积极的:)
Fela Winkelmolen 2014年

Answers:


321

Swift 4.2及更高版本

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

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

如果不创建数组并且不能保证count> 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])

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


4
Swift为什么不提供返回Int的随机数生成器?仅为了返回随机选择的Int,第二行看起来很冗长。返回UInt32而不是Int有一些计算/语法优势吗?另外,为什么Swift不提供此函数的Int替代方案,或者不允许用户指定他们希望返回的整数类型?
奥斯丁

补充一点,这种随机数生成器方法可以防止“模偏差”。请参阅man arc4randomstackoverflow.com/questions/10984974/...
肯特凫

1
@ AustinA,Swift 4.2 DOES具有本机随机数生成器功能,该功能已在您可能希望使用的所有标量数据类型上实现:Int,Double,Float,UInt32等。它使您可以提供值的目标范围。非常便利。您可以在Swift 4.2
Duncan C

我希望Swift 4.2 removeRandomElement()除了之外还实现其他功能randomElement()。它将基于建模removeFirst(),但会以随机索引删除对象。
邓肯C

@DuncanC您应该避免0..<array.count(由于多种原因,主要原因是它不适用于切片,并且容易出错)。您可以做let randomIndex = array.indices.randomElement(),然后是let randomElement = array.remove(at: randomIndex)。您甚至可以将其内联到let randomElement = array.remove(at: array.indices.randomElement())
亚历山大-恢复莫妮卡

137

根据卢卡斯的说法,您可以像这样创建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>

2
在swift中2 T已重命名为Element
GDanger 2015年

25
请注意,一个空数组将在此处导致崩溃
Berik 2015年

1
@Berik好吧,您可以返回一个可选的Element,然后始终进行guard检查以查看该数组是否为空,然后返回nil
Harish

1
同意 阵列超出边界时崩溃,因此它们可以发挥作用。调用arc4random会使任何性能提升都变得微不足道。我已经更新了答案。
Berik

45

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

21

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

首先,为实现静态random属性UnsignedIntegerType

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))
    }
}

然后(稍微复杂一些),对于ClosedIntervals有SignedIntegerType界限(使用下面进一步描述的辅助方法):

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
    }
}

18

您也可以使用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”中看到它和它的朋友。但是很高兴您指出了可用性!
David H

1
每次运行都会产生相同的随机序列
iTSangar 2015年

1
@iTSangar你是对的!rand()是正确使用的一种。更新我的答案。
NatashaTheRobot

6
这也容易受到模偏置的影响。
艾丹·戈麦斯

@mattt写了一篇很棒的文章,关于生成随机数。TL; DR任何arc4random系列产品都是更好的选择。
埃利塔隆

9

另一个Swift 3建议

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

4

以下其他答案,但有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()

2

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

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])

2

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

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

如果您更喜欢具有更多便捷功能的Framework,请签出HandySwift。您可以通过迦太基将其添加到项目中然后像上面的示例一样使用它:

import HandySwift    

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

此外,它还包含一个选项,可一次获取多个随机元素

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

2

迅捷3

导入GameKit

func getRandomMessage() -> String {

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

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

    return messages[randomNumber].description

}

2

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项目中SKLabelNode随机更新a 的示例String

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

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

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

2

如果您希望能够从数组中获得多个随机元素且没有重复元素,那么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类。当重要的是您对随机数的使用不显示重复的模式并且对性能的关注较少时,请使用此来源。


1

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

import GameKit

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

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

或者您可以按所选的随机索引返回对象。确保该函数先返回一个String,然后返回该数组的索引。

    return array[randomNumber]

简明扼要。


1

现在有一个内置方法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() }
    }
}

0

最新的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)])

-2

我想出了一种非常不同的方式来使用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. 然后,我们调用改组函数并将其存储在变量中。(仅在4.2中受支持)
  4. 然后,我们声明一个变量,该变量保存String的总计数的改组值。
  5. 最后,我们以countS的索引值返回经过改组的字符串。

它基本上是对字符串数组进行混洗,然后从总数的总数中随机选择一个数字,然后返回经过改组的数组的随机索引。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.