9

With the U.S.'s large $1.5 Billion lottery this week, I wrote a function in Ruby to make Powerball picks. In Powerball, you choose 5 numbers from the range 1..69 (with no duplicates) and 1 number from the range 1..26.

This is what I came up with:

def pball
    Array(1..69).shuffle[0..4].sort + [rand(1..26)]
end

It works by creating an array of integers from 1 to 69, shuffling that array, choosing the first 5 numbers, sorting those, and finally adding on a number from 1 to 26.

To do this in Swift takes a bit more work since Swift doesn't have the built-in shuffle method on Array.

This was my attempt:

func pball() -> [Int] {
    let arr = Array(1...69).map{($0, drand48())}.sort{$0.1 < $1.1}.map{$0.0}[0...4].sort()
    return arr + [Int(arc4random_uniform(26) + 1)]
}

Since there is no shuffle method, it works by creating an [Int] with values in the range 1...69. It then uses map to create [(Int, Double)], an array of tuple pairs that contain the numbers and a random Double in the range 0.0 ..< 1.0. It then sorts this array using the Double values and uses a second map to return to [Int] and then uses the slice [0...4] to extract the first 5 numbers and sort() to sort them.

In the second line, it appends a number in the range 1...26. I tried adding this to the first line, but Swift gave the error:

Expression was too complex to be solved in reasonable time; consider breaking up the expression into distinct sub-expressions.

Can anyone suggest how to turn this into a 1-line function? Perhaps there is a better way to choose the 5 numbers from 1...69.

vacawama
  • 150,663
  • 30
  • 266
  • 294
  • 1
    Is is random? I get most of the time (the first time I call it) [7, 12, 30, 55, 58, x] – Leo Dabus Jan 16 '16 at 01:52
  • Good question, @LeoDabus. I'd assumed that is just the Playground being funny. – vacawama Jan 16 '16 at 01:54
  • 1
    I've heard about drand48 seeding first. Can that be it? – Leo Dabus Jan 16 '16 at 01:54
  • 1
    Perhaps it would be better to just use `arc4random()` to avoid having to seed the random function. – vacawama Jan 16 '16 at 02:09
  • 5
    An easier way in Ruby is to use [Array#sample](http://ruby-doc.org/core-2.2.0/Array.html#method-i-sample): `[*1..69].sample(5) << (1+rand(26)) #=> [24, 3, 61, 67, 22, 10]`. – Cary Swoveland Jan 16 '16 at 03:25
  • 1
    Various array shuffling methods are given here: [How do I shuffle an array in Swift?](http://stackoverflow.com/questions/24026510/how-do-i-shuffle-an-array-in-swift). – Martin R Jan 16 '16 at 06:21
  • 1
    ... and using `shuffle()` from the accepted answer, `Array(1...69).shuffle()[0...4].sort() + [Int(arc4random_uniform(26) + 1)]` does compile. – Martin R Jan 16 '16 at 06:35
  • 1
    "Expression too complex" not very useful compiler feedback is filed as rdar://24217037 – bbum Jan 16 '16 at 17:25
  • @bbum, Swift's compiler errors tend to be extremely **un**helpful. I hope the LLVM team goes back and does a major push to improve them soon. – Duncan C Dec 24 '16 at 18:51

5 Answers5

6

Xcode 8.3 • Swift 3.1

import GameKit 

var powerballNumbers: [Int] {
    return (GKRandomSource.sharedRandom().arrayByShufflingObjects(in: Array(1...69)) as! [Int])[0..<5].sorted() + [Int(arc4random_uniform(26) + 1)]
}

powerballNumbers   // [5, 9, 62, 65, 69, 2]

Swift 2.x

import GameKit 

var powerballNumbers: [Int] {
    return (GKRandomSource.sharedRandom().arrayByShufflingObjectsInArray(Array(1...69)) as! [Int])[0...4].sort() + [Int(arc4random_uniform(26).successor())]
}

powerballNumbers   // [21, 37, 39, 42, 65, 23]
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
5

I don't find the "one-liner" concept very compelling. Some languages lend themselves to it; others don't. I would suggest giving Swift a shuffle method to start with:

extension Array {
    mutating func shuffle () {
        for var i = self.count - 1; i != 0; i-- {
            let ix1 = i
            let ix2 = Int(arc4random_uniform(UInt32(i+1)))
            (self[ix1], self[ix2]) = (self[ix2], self[ix1])
        }
    }
}

But since I made this mutating, we still need more than one line to express the entire operation because we have to have a var reference to our starting array:

var arr = Array(1...69)
(1...4).forEach {_ in arr.shuffle()}
let result = Array(arr[0..<5]) + [Int(arc4random_uniform(26)) + 1]

If you really insist on the one-liner, and you don't count the code needed to implement shuffle, then you can do it, though less efficiently, by defining shuffle more like this:

extension Array {
    func shuffle () -> [Element] {
        var arr = self
        for var i = arr.count - 1; i != 0; i-- {
            let ix1 = i
            let ix2 = Int(arc4random_uniform(UInt32(i+1)))
            (arr[ix1], arr[ix2]) = (arr[ix2], arr[ix1])
        }
        return arr
    }
}

And here's your one-liner:

let result = Array(1...69).shuffle().shuffle().shuffle().shuffle()[0..<5] + [Int(arc4random_uniform(26)) + 1]

But oops, I omitted your sort. I don't see how to do that without getting the "too complex" error; to work around that, I had to split it into two lines:

var result = Array(1...69).shuffle().shuffle().shuffle().shuffle()[0..<5].sort(<)
result.append(Int(arc4random_uniform(26)) + 1)
matt
  • 515,959
  • 87
  • 875
  • 1,141
  • 5
    I understand your feelings on the "one-liner" concept. In this case, it is more of an academic exercise to see what can be done vs. what should be done. – vacawama Jan 16 '16 at 02:55
  • 1
    @vacawama I modified my answer so that _after_ defining `shuffle` you _do_ get a one-liner. But I don't like it. :) – matt Jan 16 '16 at 02:59
  • That for var loop will be removed in Swift 3.0 https://github.com/apple/swift-evolution/blob/master/proposals/0007-remove-c-style-for-loops.md – Leo Dabus Jan 16 '16 at 03:01
  • 2
    @LeoDabus Yeah, I know, I'm not looking forward to that. Or the loss of C-type for loops. Or much of anything. :( – matt Jan 16 '16 at 03:03
  • Now, from the other side of the Swift 3 transition I don't miss the `for (;;)` all that much. `for (index, element) in array.enumerated()` gives the same ability to iterate through array entries with an index. Granted things like making a Fibonacci sequence generator with a C-style for loop, or counting backwards by 3s aren't as easy, but how often do you really need to do those things? (And if you write a Fibonacci generator as a C `for(;;)` loop outside of a test question you should be shot anyway...) – Duncan C Dec 24 '16 at 15:10
  • @DuncanC Actually those things are very easy indeed, and elegant too, and efficient, thanks to the new `sequence` global function. – matt Dec 24 '16 at 15:34
  • @DuncanC see e.g. [this Q&A](http://stackoverflow.com/questions/40203182/fibonacci-numbers-generator-in-swift-3) for examples of the use cases mentioned by matt (global `sequence` function). – dfrib Jan 01 '17 at 13:34
3

Xcode 10 • Swift 4.2

Swift now has added shuffled() to ClosedRange and random(in:) to Int which now makes this easily accomplished in one line:

func pball() -> [Int] {
    return (1...69).shuffled().prefix(5).sorted() + [Int.random(in: 1...26)]
}

Further trimmings:

Because of the return type of pball(), the Int can be inferred in the random method call. Also, .prefix(5) can be replaced with [...4]. Finally, return can be omitted from the one-line function:

func pball() -> [Int] {
    (1...69).shuffled()[...4].sorted() + [.random(in: 1...26)]
}
vacawama
  • 150,663
  • 30
  • 266
  • 294
  • 1
    The usefulness of the new random features are really shown in problems such as this one (comparing with my quite complex pre-Swift 4 answer), nice! – dfrib Oct 03 '18 at 08:53
2

How about this:

let winningDraw = (1...69).sort{ _ in arc4random_uniform(2) > 0}[0...4].sort() + [Int(arc4random_uniform(26)+1)]

[edit] above formula wasn't random. but this one will be

(1...69).map({Int(rand()%1000*70+$0)}).sort().map({$0%70})[0...4].sort() + [Int(rand()%26+1)]
Alain T.
  • 40,517
  • 4
  • 31
  • 51
  • 1
    @Alain T. this is not true random as it will tend to pick number 2 like 20% of the draws and it will never pick number 1 – Leo Dabus Jan 16 '16 at 04:21
  • 1
    You are right, the internal algorithm for the sort() function is probably a variant of Knut's quick sort so it will have a tendency to spread the distribution toward the edges (low and high values) of the number list. My bad. – Alain T. Jan 16 '16 at 04:58
1

For the fun of it, a non-GameplayKit (long) one-liner for Swift 3, using the global sequence(state:next:) function to generate random elements from the mutable state array rather than shuffling the array (although mutating the value array 5 times, so some extra copy operations here...)

let powerballNumbers = Array(sequence(state: Array(1...69), next: { 
    (s: inout [Int]) -> Int? in s.remove(at: Int(arc4random_uniform(UInt32(s.count))))})
    .prefix(5).sorted()) + [Int(arc4random_uniform(26) + 1)]

... broken down for readability.


(Possible in future Swift version)

If the type inference weren't broken inout closure parameters (as arguments to closures), we could reduce the above to:

let powerballNumbers = Array(sequence(state: Array(1...69), next: {
    $0.remove(at: Int(arc4random_uniform(UInt32($0.count)))) })
    .prefix(5).sorted()) + [Int(arc4random_uniform(26) + 1)]

If we'd also allow the following extension

extension Int {
    var rand: Int { return Int(arc4random_uniform(UInt32(exactly: self) ?? 0)) }
}

Then, we could go on to reduce the one-line to:

let powerballNumbers = Array(sequence(state: Array(1...69), next: { $0.remove(at: $0.count.rand) }).prefix(5).sorted()) + [26.rand + 1]
Community
  • 1
  • 1
dfrib
  • 70,367
  • 12
  • 127
  • 192
  • Just curious, @dfri. What drew your attention to this nearly year-old question? – vacawama Dec 31 '16 at 17:19
  • @vacawama I randomly visited your profile via your comment [to this question](http://stackoverflow.com/questions/41409543/) (which I commented to as well) and the top item of _"Newest questions"_ on your profile (namely; this one) caught my attention :) – dfrib Dec 31 '16 at 17:21
  • The funny thing is that I was revisiting this question myself at the very time you answered. I wondered if somehow I had triggered the question to appear in some active list on SO. – vacawama Dec 31 '16 at 17:23
  • @vacawama Not in this case, seemingly rather some random funny synchronicity! – dfrib Dec 31 '16 at 17:24
  • 1
    Yeah. Coincidence is sometimes very funny/creepy! – vacawama Dec 31 '16 at 17:27
  • I've accepted this *crazy-good* answer. Why is the closure returning `Int?` instead of `Int` – vacawama Dec 31 '16 at 17:48
  • 1
    @vacawama it's too bad `sequence(state:next:)` don't yet accept a closure as the `state` (`inout` closure) due to [a bug](https://bugs.swift.org/browse/SR-3450), otherwise we could go really crazy with this function :) As to the optional return: the closure `next` must be allowed to return `nil`, in which case the sequence is terminated (as specified in the function signature for `sequence(state:next)`; `next: @escaping (inout State) -> T?`). This fact is never used here, however, as we simply prefix an never-terminated (or, runtime-crashing at 70th element) sequence for its `5` first members. – dfrib Dec 31 '16 at 17:53
  • FWIW, I was able to remove the `?` and it still works. Is that just Swift not enforcing the requirement of `next` returning an optional? – vacawama Dec 31 '16 at 17:59
  • 1
    @vacawama thanks for the info, that's peculiar (and surprising), since it means we may never terminate the sequence "internally" (e.g. above, we would like to add a `guard !s.isEmpty else { return nil }` prior to the return for runtime safety; not an issue here as the `prefix` number is not larger than the size of the initial `state` array). – dfrib Dec 31 '16 at 18:02
  • @vacawama I didn't know what you pointed out was possible, but it seems as if a closure with an optional return type accepts the "subset" type of same arguments but non-optional return type, much like an optional argument to some method accepts non-optionals ([gist](https://gist.github.com/dfrib/0ba54dd987fd9dc01ece63926576d812)). Although I don't find this as intuitive as for the "regular" argument case (since it isn't the full closure that's optional, just its return type), I guess we can assume its intended behaviour? – dfrib Dec 31 '16 at 18:12