Swift3 Random Extension Method

2019-02-20 18:27发布

I was using this extension method to generate a random number:

func Rand(_ range: Range<UInt32>) -> Int {
        return Int(range.lowerBound + arc4random_uniform(range.upperBound - range.lowerBound + 1))
    }

I liked it b/c it was no nonsense, you just called it like this:

 let test = Rand(1...5) //generates a random number between 1 and 5

I honestly don't know why things need to be so complicated in Swift but I digress..

So i'm receiving an error now in Swift3

No '...' candidates produce the expected contextual result type 'Range<UInt32>'

Would anyone know what this means or how I could get my awesome Rand function working again? I guess x...y no longer creates Ranges or x..y must be explicitly defined as UInt32? Any advice for me to make things a tad easier?

Thanks so much, appreciate your time!

标签: ios swift swift3
3条回答
Deceive 欺骗
2楼-- · 2019-02-20 19:05

A nice solution is presented in Generic Range Algorithms (based on How to be DRY on ranges and closed ranges? in the swift-users mailing list).

It uses the fact that both CountableRange and CountableClosedRange are collections, and in fact a RandomAccessCollection.

So you can define a single (generic) function which accepts both open and closed integer ranges:

func rand<C: RandomAccessCollection>(_ coll: C) -> C.Iterator.Element {
    precondition(coll.count > 0, "Cannot select random element from empty collection")
    let offset = arc4random_uniform(numericCast(coll.count))
    let idx = coll.index(coll.startIndex, offsetBy: numericCast(offset))
    return coll[idx]
}

rand(1...5) // random number between 1 and 5
rand(2..<10) // random number between 2 and 9

but also:

rand(["a", "b", "c", "d"]) // random element from the array

Alternatively as a protocol extension method:

extension RandomAccessCollection {

    func rand() -> Iterator.Element {
        precondition(count > 0, "Cannot select random element from empty collection")
        let offset = arc4random_uniform(numericCast(count))
        let idx = index(startIndex, offsetBy: numericCast(offset))
        return self[idx]
    }
}

(1...5).rand()
(2..<10).rand()
["a", "b", "c", "d"].rand()
查看更多
【Aperson】
3楼-- · 2019-02-20 19:12

In Swift 3 there are four Range structures:

  • "x" ..< "y"Range<T>
  • "x" ... "y"ClosedRange<T>
  • 1 ..< 5CountableRange<T>
  • 1 ... 5CountableClosedRange<T>

(The operators ..< and ... are overloaded so that if the elements are stridable (random-access iterators e.g. numbers and pointers), a Countable Range will be returned. But these operators can still return plain Ranges to satisfy the type checker.)

Since Range and ClosedRange are different structures, you cannot implicitly convert a them with each other, and thus the error.

If you want Rand to accept a ClosedRange as well as Range, you must overload it:

// accepts Rand(0 ..< 5)
func Rand(_ range: Range<UInt32>) -> Int {
    return Int(range.lowerBound + arc4random_uniform(range.upperBound - range.lowerBound))
}

// accepts Rand(1 ... 5)
func Rand(_ range: ClosedRange<UInt32>) -> Int {
    return Int(range.lowerBound + arc4random_uniform(range.upperBound + 1 - range.lowerBound))
}
查看更多
We Are One
4楼-- · 2019-02-20 19:17

You could rewrite Rand() to use Int if that is your primary use case:

func Rand(_ range: Range<Int>) -> Int {
    let distance = UInt32(range.upperBound - range.lowerBound)
    return range.lowerBound + Int(arc4random_uniform(distance + 1))
}

Or as kennytm points out, use Rand(1..<6)

查看更多
登录 后发表回答