如何在Swift中生成一个不重复前一个随机数的随机数?

14

我对Swift和编程逻辑都不太了解,因此请耐心等待。

如何在Swift中生成0到9之间的随机数,而不重复上一个生成的数字?也就是说,同样的数字不会连续出现两次。


可能是如何在苹果的Swift语言中生成随机数?的重复问题。 - Kampai
4
不,不重复。 - bmaliel
你可以做那部分。只需加入一个简单的条件即可。 - Kampai
1
是的,我知道那个,但我不知道如何实际操作。这就是为什么我在问问题。正如我所说,我对Swift非常新手。 - bmaliel
这可以通过每次只生成一个随机数来实现。请参考我的答案。 - vacawama
这个页面上的答案非常糟糕!简单而正确的答案是众所周知的。https://stackoverflow.com/a/62514324/294884 - Fattie
5个回答

17

我的解决方案,我认为它很容易理解。

var nums = [0,1,2,3,4,5,6,7,8,9]

while nums.count > 0 {

    // random key from array
    let arrayKey = Int(arc4random_uniform(UInt32(nums.count)))

    // your random number
    let randNum = nums[arrayKey] 

    // make sure the number isnt repeated
    nums.swapAt(arrayKey, nums.count-1)
    nums.removeLast()
}

12

将前一个生成的数字存储在变量中,并将生成的数字与前一个数字进行比较。如果它们匹配,则生成一个新的随机数。重复生成新数字,直到它们不匹配。

var previousNumber: UInt32? // used in randomNumber() 

func randomNumber() -> UInt32 {
    var randomNumber = arc4random_uniform(10)
    while previousNumber == randomNumber {
        randomNumber = arc4random_uniform(10)
    }
    previousNumber = randomNumber
    return randomNumber
}

3
在理论上,对于while循环中允许的给定非无限迭代次数而言,任何对randomNumber()的调用都不能保证终止(即使在实践中它会迅速终止 :))。对于一个确定性解决方案(关于对randomNumber()的程序流),请参见下面vacawama的巧妙答案。 - dfrib
这个答案完全错误,会导致崩溃。这是一个不确定的算法 - 一个非常基本的计算机科学错误。 - Fattie
@Fattie:你能详细说明一下吗?为什么是错误的?在什么情况下会崩溃? - Martin R
当然了,但是你不能按照“应该没问题”、“很少会有人尝试使用长度为1的数组”、“我们知道某些软件包已经过测试”或“这永远不会在需要性能的规模上使用”等进行编程!! 任何软件都不能/不应该包含不确定的循环。 在实际软件中,你不能只放一个“裸while”!如果你真的必须在例程中放置一个“裸while”,那么绝对必须添加安全限制(算到100或其他数字,如果达到了,则放弃)。 所有这些都非常无意义,因为有一个如此简单而广为人知的解决方案。 - Fattie
甚至Swift标准库在其Random函数的实现中也使用了这样的循环:https://github.com/apple/swift/blob/master/stdlib/public/core/Random.swift。 - Martin R
显示剩余2条评论

9

更新Swift 5

这里有一个不错的技巧,可以从之前没有选择过的数字中均等地选择。

你有10个数字,但你只想从9个数字中选择(0到9,但不包括之前选择的数字)。 如果你将范围减少1,你可以从9个随机数字中选择,然后仅用范围的上一个顶部数字替换重复的数字。通过这种方式,每次只需要生成一个随机数,就能获得均匀性。

这可以实现为Int.random(in:excluding:),其中你传递你想要排除的值。

extension Int {
    static func random(in range: ClosedRange<Int>, excluding x: Int) -> Int {
        if range.contains(x) {
            let r = Int.random(in: Range(uncheckedBounds: (range.lowerBound, range.upperBound)))
            return r == x ? range.upperBound : r
        } else {
            return Int.random(in: range)
        }
    }
}

例子:

// Generate 30 numbers in the range 1...3 without repeating the
// previous number  
var r = Int.random(in: 1...3)
for _ in 1...30 {
    r = Int.random(in: 1...3, excluding: r)
    print(r, terminator: " ")
}
print()

抱歉,我无法翻译这个内容,因为它不是有关IT技术的信息。它只是一组数字序列。可能需要更多的上下文来了解其意义。
var previousNumber = arc4random_uniform(10)   // seed the previous number

func randomNumber() -> UInt32 {
    var randomNumber = arc4random_uniform(9)  // generate 0...8
    if randomNumber == previousNumber {
        randomNumber = 9
    }
    previousNumber = randomNumber
    return randomNumber
}

1
@vacawama 我明白这里的技巧了(在[0,9]范围内的9个整数中保持一致,其中排除了先前选择的数字),现在感谢您的解释! - dfrib
3
特别的是,撇开数组洗牌方法不谈,这是唯一一个保证在此线程的答案中“终止”的一次生成随机数的方法(在这里,“终止”并不是一个有趣的术语,但相对于例如被接受的答案中使用的暴力循环方法而言,它确实是一个终止确定性解决方案)。因此,我留下这个额外的评论,指出我认为这应该是被接受的答案。 - dfrib
1
值得注意的是,这种方法非常适用于在选择第一个数字后选择第二个数字,但是当您想从k个数字池中选择n个随机且唯一的数字时,它的效果就不那么好了。此时需要某种集合。我建议您查看我在此类似问题上的解决方案:https://dev59.com/xY_ea4cB1Zd3GeqPOneG#46029193 - Tim Fuqua
1
@Fattie,我查看了你提供的链接,发现你的算法与我的并没有太大的区别。两者都是从n-1个选择中均匀地选择以避免重复。逻辑上,我的算法是用第n个位置上的值替换被排除的值,然后从n-1个值中进行选择。 - vacawama
1
@Fattie,就随机选择一个不重复的值而言,我的答案和你的一样好。如果你想争辩说模算术比“if”更快,我会同意。但这并不能使我的答案变得糟糕。 - vacawama
显示剩余4条评论

5
最简单的方法是使用repeat/while循环:
let current = ...
var next: Int

repeat {
    next = Int(arc4random_uniform(9))
} while current == next

// Use `next`

0
把你想要的所有值放进一个数组中,使用 arc4random_uniform(SIZEOFARRAY) 生成一个随机数,并从数组中取出随机值的索引,然后重复这个过程,直到数组为空。

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