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

Fel*_*len 180 arrays random swift

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

最简单的方法是什么?

显而易见的方式是array[random index].但也许有类似红宝石的东西array.sample?或者如果没有,可以使用扩展程序创建这样的方法?

Luc*_*ugh 305

Swift 4.2及以上版本

新推荐的方法是一种内置方法:randomElement().它返回一个可选项以避免我之前假设的空案例.

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

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

if let randomElement = array.randomElement() { 
    print(randomElement)
}
Run Code Online (Sandbox Code Playgroud)

Swift 4.1及以下版本

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

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

铸件是丑陋的,但我相信它们是必需的,除非别人有另一种方式.

  • 为什么Swift不提供返回Int的随机数生成器?第二行似乎非常冗长,只是为了返回随机选择的Int.返回UInt32而不是Int有一些计算/语法上的优势吗?另外,为什么Swift不提供此函数的Int替代或允许用户指定他们想要返回的整数类型? (4认同)
  • @AustinA,Swift 4.2 确实有一个原生随机数生成器函数,该函数在您可能想要的所有标量数据类型上实现:Int、Double、Float、UInt32 等。它允许您为值提供目标范围。非常便利。您可以在 Swift 4.2 中使用 array[Int.random(0..<array.count)]` (2认同)

Pha*_*sky 137

关于Lucas所说的,你可以像这样创建一个Array类的扩展:

extension Array {
    func randomItem() -> Element? {
        if isEmpty { return nil }
        let index = Int(arc4random_uniform(UInt32(self.count)))
        return self[index]
    }
}
Run Code Online (Sandbox Code Playgroud)

例如:

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

  • 请注意,空数组会导致崩溃 (25认同)
  • 在 swift 2 中,`T` 已重命名为 `Element`。 (2认同)

And*_*eev 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)))]
    }

}
Run Code Online (Sandbox Code Playgroud)

  • 哇,在12个答案中,这是唯一正确的答案. (5认同)

mil*_*los 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
Run Code Online (Sandbox Code Playgroud)

首先,randomUnsignedIntegerTypes 实现静态属性:

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)
    }
}
Run Code Online (Sandbox Code Playgroud)

那么,对于ClosedIntervalUnsignedIntegerType边界的s :

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))
    }
}
Run Code Online (Sandbox Code Playgroud)

然后(稍微复杂一点),对于ClosedInterval带有SignedIntegerType边界的s (使用下面进一步描述的辅助方法):

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)
    }
}
Run Code Online (Sandbox Code Playgroud)

...其中unsignedDistanceTo,unsignedDistanceFromMinplusMinIntMax辅助方法可以实现如下:

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) }
    }
}
Run Code Online (Sandbox Code Playgroud)

最后,对于所有集合,其中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]
    }
}
Run Code Online (Sandbox Code Playgroud)

...可以对整数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
    }
}
Run Code Online (Sandbox Code Playgroud)


Nat*_*bot 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
Run Code Online (Sandbox Code Playgroud)

  • 这也容易受到模偏差的影响. (6认同)

小智 9

另一个Swift 3的建议

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