Swift语言中的随机数生成问题 - EXC_BAD_INSTRUCTION

3

我尝试生成一个随机顺序的字符串数组,但是在 randomPile 函数结束时总是出现错误 "Thread1:EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)"。以下是我的代码:

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

1
你应该使用 arc4random_uniform(i+1) 来避免取模偏差。 - pjs
尝试了一下,但是arc4random_uniform(i+1)也出现了一个错误:"找不到接受提供的参数的'+'的重载"。 - Peterxwl
抱歉,我忘了提到你需要将参数转换为 UInt32 - pjs
1
你的问题在于arc4random()返回UInt32。在一个32位目标上,比如iPhone 4S模拟器,你会试图将结果塞入一个有符号的32位整数中,导致你的崩溃,有时候。它会在playground(几乎肯定是64位)或64位模拟器或设备上工作。请参见https://dev59.com/jmAf5IYBdhLWcg3w_Gyr。 - Matt Gibson
@MattGibson 所以使用参数为31位或更少的 arc4random_uniform() 可以安全地转换为 Int 吗?这是除了模数偏差问题之外使用它的另一个原因。 - pjs
2个回答

3

以下内容仅供参考,不是答案:

这对我来说在 playground 和 app 中都有效:

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

cardOrder = randomPile(cardOrder)
println(cardOrder)

也许错误出现在其他地方。 注意:var r = Int(arc4random_uniform(UInt32(i+1))) 更简单且避免偏差。

该函数在我的playground中也能正常工作,并且第一次在这个UIViewController中运行时也没有问题。但是当我再次运行它时,错误就会出现,并且每次都会在循环的不同周期中出现。为了进行实验,我只构建了这个UIViewController,并将其连接到storyboard中的一个空控制器。另外,您的注释对我也会生成一个错误,显示“找不到接受所提供参数的'+'的重载”,即使我为其添加了空格。 - Peterxwl
你一定会喜欢Swift,尽管它带来了很多痛苦——好吧,我不是很喜欢。 (确保使用当前的arc4random_uniform示例,我已经修复了错误,真的是一个非常糟糕的错误消息,是吧?) - zaph

1
也不是答案,因为我也可以在playground中运行,所以我不知道你的问题出在哪里。然而,没有必要创建一个新的对数组的引用并返回它。我还实现了一种Fisher-Yates洗牌变体,它针对排除其上限的整数PRNG,就像arc4random_uniform一样:
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)

在数组上调用此函数后,它将被打乱,无需重新分配给cardOrder
补充说明-我刚刚检查了一下,由于cardOrder不再出现在赋值的左侧,因此可以使用let声明。
您还可以使洗牌功能成为通用功能,为什么不呢?
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]

你回答中还有一个问题:为什么不需要创建新的引用?Swift如何知道这个函数是用于设置还是获取输入?在我的原始代码中,引用carOrder在func viewDidLoad()中声明,它是局部的。 - Peterxwl
你正在将一个数组引用作为参数传递,而不是数组的副本。基于从该引用索引的操作会操纵原始数组的内容,就像在C或Objective-C中一样。 - pjs
哇,我们又回到了按引用返回的方式。多年来,我们一直在努力消除这个概念。 - zaph
@Zaph 看起来更像是Java的对象引用,而不是C/C++/Objective-C指针。它们可以是lvalue,并且您可以使用[]进行偏移访问,但它受到边界检查。我还没有找到一种方法来对它们进行其他算术运算。相当温和,可以节省大量数据复制。 - pjs

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接