3

I tried to generate an array with strings in random order, but always got the error "Thread1:EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)" at the end of function randomPile. Below is my code:

import UIKit

class RandomView: UIViewController {

override func viewDidLoad() {
    super.viewDidLoad()

    var cardOrder = ["HeartSix","HeartNine", "ClubQueen", "SpadeKing" ]

    // cannot randomlize due to the lanuage drawbacks.
    cardOrder = randomPile(cardOrder)

}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
}

// random the order of the original card pile
func randomPile(arrayPile: String[]) -> String[] {
    var arry = arrayPile
    for( var i = arry.count-1; i > 0; --i){
        var r = Int(arc4random())%(i+1)
        var a = arry[r]
        arry[r] = arry[i]
        arry[i] = a

    }
    return arry
}    
}
Peterxwl
  • 1,023
  • 3
  • 16
  • 24
  • 1
    You should use `arc4random_uniform(i+1)` to avoid modulo bias. – pjs Jul 01 '14 at 02:11
  • Tried, but arc4random_uniform(i+1) also has an error: "Could not find an overload for '+' that accepts the supplied arguments". – Peterxwl Jul 01 '14 at 02:27
  • Sorry, forgot to mention that you need to cast the argument as `UInt32`. – pjs Jul 01 '14 at 02:32
  • 1
    Your problem is that arc4random() returns UInt32. On a *32-bit target* — say an iPhone 4S simulator — you'll be trying to stuff the result into a *signed* 32-bit integer, resulting in your crash, *sometimes*. It'll work in a playground (which will almost certainly be 64-bit) or on a 64-bit simulator or device. See http://stackoverflow.com/questions/24087518/crash-when-casting-the-result-of-arc4random-to-int – Matt Gibson Jul 01 '14 at 07:45
  • @MattGibson So using `arc4random_uniform()` with a 31-bit or less argument should safely cast to `Int`? Yet another reason to use it in addition to the modulo bias issue. – pjs Jul 01 '14 at 13:34

2 Answers2

3

Not an answer, presented here for code formation:

This works for me both in a playground and in an app:

var cardOrder: String[] = ["HeartSix","HeartNine", "ClubQueen", "SpadeKing" ]
println(cardOrder)

cardOrder = randomPile(cardOrder)
println(cardOrder)

Perhaps the error is elsewhere.

Note: var r = Int(arc4random_uniform(UInt32(i+1))) is both simpler and avoids bias.

zaph
  • 111,848
  • 21
  • 189
  • 228
  • The function also works for me in playground, and first time in this UIViewController. But when I re-run it again, the error appears and every time it appears in different cycle of the loop. And for experiment I only build this UIViewController, connected to a blank controller in storyboard. Plus, your note also generate an error for me, shows "Could not find an overload for '+' that accepts the supplied arguments", even I added space for it. – Peterxwl Jul 01 '14 at 02:24
  • Ya gotta love Swift for all the pain--OK I don't. (Make sure using there current arc4random_uniform example, I fixed the error, really bad error message, eh?) – zaph Jul 01 '14 at 02:33
1

Also not an answer, because I can also run in playground and so I don't know where your problem is coming in. However, there's no need to create a new reference to the array and return it. I also implemented a Fisher-Yates shuffle variant which is geared towards an integer PRNG that excludes its upper bound, as arc4random_uniform does:

func randomPile(myArray: String[]) -> Void {
    for i in 0..(myArray.count - 1) {
        let j = Int(arc4random_uniform(UInt32(myArray.count - i))) + i
        let tmp = myArray[i]
        myArray[i] = myArray[j]
        myArray[j] = tmp
    }
}

let cardOrder: String[] = ["HeartSix","HeartNine", "ClubQueen", "SpadeKing" ]
println(cardOrder)
randomPile(cardOrder)
println(cardOrder)

After invoking this on your array, it's shuffled, no need for reassignment to cardOrder.

Addendum - I just checked, and since cardOrder doesn't appear again on the left of an assignment it can be declared with let.

You can also make the shuffle capability generic, so why not?

func shuffle<T>(myArray: T[]) -> Void {
    for i in 0..(myArray.count - 1) {
        let j = Int(arc4random_uniform(UInt32(myArray.count - i))) + i
        let tmp:T = myArray[i]
        myArray[i] = myArray[j]
        myArray[j] = tmp
    }
}

let cardOrder: String[] = ["HeartSix","HeartNine", "ClubQueen", "SpadeKing"]
println(cardOrder)  // [HeartSix, HeartNine, ClubQueen, SpadeKing]
shuffle(cardOrder)
println(cardOrder)  // sample result: [SpadeKing, HeartNine, HeartSix, ClubQueen]
let intValues = [1,2,3,4,5,6,7,8,9,10]
println(intValues)  // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
shuffle(intValues)
println(intValues)  // sample result: [3, 10, 8, 4, 9, 7, 1, 2, 5, 6]
pjs
  • 18,696
  • 4
  • 27
  • 56
  • One more question on your answer: why there is no need to create new reference? How swift knows whether this function is used to set or get the input? In my original code, the reference carOrder is declared in func viewDidLoad(); it is local. – Peterxwl Jul 01 '14 at 02:51
  • You're passing an array reference as the argument, not a copy of the array. Operations based on indexing from that reference manipulate the contents of the original array, just as in C or Objective-C. – pjs Jul 01 '14 at 03:06
  • Wow, we are reverting back to return by reference. we have been struggling for years to rid code of this concept. – zaph Jul 01 '14 at 10:35
  • @Zaph Seems more like Java's object references than C/C++/Objective-C pointers. They can be lvalues, and you can do offset access with `[]` but it's bounds-checked. I haven't found a way to do other arithmetic with them. Fairly benign, and saves copying massive amounts of data. – pjs Jul 01 '14 at 13:25