在包含范围内生成唯一随机数的数组

时间:2015-09-25 01:56:36

标签: arrays swift random swift2

我正在尝试在Apple Swift(iOS)中编写一个函数,该函数将生成给定的包含范围内的任意给定数量的唯一随机数,例如介于0和10之间。因此,如果我说我想要5个唯一的随机数在0到10之间,它将返回一个包含[7,10,2,3,0]或[7,10,2,8,0]等的数组。

我有这个部分:

// Returns an array of unique numbers
func uniqueRandoms(numberOfRandoms: Int, minNum: Int, maxNum: UInt32) -> [Int] {

    var uniqueNumbers = [Int]()

    while uniqueNumbers.count < numberOfRandoms {

        let randomNumber = Int(arc4random_uniform(maxNum + 1)) + minNum
        var found = false

        for var index = 0; index < uniqueNumbers.count; ++index {
                if uniqueNumbers[index] == randomNumber {
                    found = true
                    break
                }
        }

        if found == false {
            uniqueNumbers.append(randomNumber)
        }

    }

    return uniqueNumbers
}

print(uniqueRandoms(5, minNum: 0, maxNum: 10))

现在我想添加将我不想要的范围内的单个数字列入黑名单的功能。假设我仍然想要0到10之间的5个唯一随机数,但我不希望它包含8个。

那部分导致无限循环(25%以上的时间或更多),我无法弄清楚为什么?这就是我所拥有的:

var blackListNum = 8

// Returns an array of unique numbers
func uniqueRandoms(numberOfRandoms: Int, minNum: Int, maxNum: UInt32, checkBlackList: Bool = false) -> [Int] {

    var uniqueNumbers = [Int]()

    while uniqueNumbers.count < numberOfRandoms {

        let randomNumber = Int(arc4random_uniform(maxNum + 1)) + minNum
        var found = false

        for var index = 0; index < uniqueNumbers.count; ++index {
            if checkBlackList == false {
                if uniqueNumbers[index] == randomNumber {
                    found = true
                    break
                }
            } else {
                if uniqueNumbers[index] == randomNumber || uniqueNumbers[index] == blackListNum  {
                    found = true
                    break
                }
            }
        }

        if found == false {
            uniqueNumbers.append(randomNumber)
        }

    }

    return uniqueNumbers
}

print(uniqueRandoms(5, minNum: 0, maxNum: 10, checkBlackList: true))

我明白我的功能远没有效率,因为我刚刚开始学习Swift,但我想保持它的相似性,因为我想了解它是如何工作的。我不想简单地复制和粘贴别人更有效的解决方案而不理解它。我刚刚学习了变量,常量,if,while,for等语句和其他基础知识,并希望将其保留下去。

6 个答案:

答案 0 :(得分:8)

使用Set来存储所有随机数,直到达到预期的random数量,你可以让你的生活更轻松:

func uniqueRandoms(numberOfRandoms: Int, minNum: Int, maxNum: UInt32) -> [Int] {
    var uniqueNumbers = Set<Int>()
    while uniqueNumbers.count < numberOfRandoms {
        uniqueNumbers.insert(Int(arc4random_uniform(maxNum + 1)) + minNum)
    }
    return Array(uniqueNumbers).shuffle
}

print(uniqueRandoms(5, minNum: 0, maxNum: 10))

func uniqueRandoms(numberOfRandoms: Int, minNum: Int, maxNum: UInt32, blackList: Int?) -> [Int] {
    var uniqueNumbers = Set<Int>()
    while uniqueNumbers.count < numberOfRandoms {
        uniqueNumbers.insert(Int(arc4random_uniform(maxNum + 1)) + minNum)
    }
    if let blackList = blackList {
        if uniqueNumbers.contains(blackList) {
            while uniqueNumbers.count < numberOfRandoms+1 {
                uniqueNumbers.insert(Int(arc4random_uniform(maxNum + 1)) + minNum)
            }
            uniqueNumbers.remove(blackList)
        }
    }
    return Array(uniqueNumbers).shuffle
}

uniqueRandoms(3, minNum: 0, maxNum: 10, blackList: 8)  // [0, 10, 7]

您需要此扩展程序来重新调整结果:

extension Array {
    var shuffle:[Element] {
        var elements = self
        for index in 0..<elements.count {
            let anotherIndex = Int(arc4random_uniform(UInt32(elements.count-index)))+index
            if anotherIndex != index {
                swap(&elements[index], &elements[anotherIndex])
            }
        }
        return elements
    }
}

答案 1 :(得分:7)

直接的方法是创建一个数字数组供您选择,然后在您选择时删除数字:

// create an array of 0 through 10
var nums = Array(0...10)

// remove the blacklist number
nums.removeAtIndex(nums.indexOf(8)!)

var randoms = [Int]()
for _ in 1...5 {
    let index = Int(arc4random_uniform(UInt32(nums.count)))
    randoms.append(nums[index])
    nums.removeAtIndex(index)
}

此方法的优点是您只需要在数组中生成任意数量的随机数。由于您从每次仍然可用的数字中进行选择,因此您无需检查是否已经有随机值。

答案 2 :(得分:3)

Swift 4.0中的解决方案和性能细分

我最近发现自己需要一个解决这个问题的方法,但是没有黑名单,我在这个页面上看到了答案,也在this question处看到了答案,但是当我将数字设置为选择范围非常大,而且在选择大量数字时(例如,您选择的总数超过50%的数字)。

所以我尝试了一些解决方案。

第一种是你随机选择一个数字,检查之前是否已经选择了数字,然后选择另一个数字或将其添加到数字列表中。

func randomNumber(between lower: Int, and upper: Int) -> Int {
    return Int(arc4random_uniform(UInt32(upper - lower))) + lower
}

func generateRandomUniqueNumbers1(forLowerBound lower: Int, andUpperBound upper:Int, andNumNumbers iterations: Int) -> [Int] {
    guard iterations <= (upper - lower) else { return [] }
    var numbers: [Int] = []
    (0..<iterations).forEach { _ in
        var nextNumber: Int
        repeat {
            nextNumber = randomNumber(between: lower, and: upper)
        } while numbers.contains(nextNumber)
        numbers.append(nextNumber)
    }
    return numbers
}

第二个解决方案与vacawama建议的解决方案几乎相同。首先加载一个包含所有可用数字的数组,随机选择一个,然后将其从可用数组中删除,然后将其添加到数字数组中。根据需要重复多个数字。

func generateRandomUniqueNumbers2(forLowerBound lower: Int, andUpperBound upper:Int, andNumNumbers iterations: Int) -> [Int] {
    guard iterations <= (upper - lower) else { return [] }
    var indices: [Int] = (lower..<upper).sorted()
    var numbers: [Int] = []
    (0..<iterations).forEach { _ in
        let nextNumberIndex = randomNumber(between: 0, and: indices.count)
        let nextNumber: Int = indices[nextNumberIndex]
        indices.remove(at: nextNumberIndex)
        numbers.append(nextNumber)
    }
    return numbers
}

第三种解决方案是对第一种解决方案的改编,以解决阵列在寻找其中的元素方面很慢的事实。通过将存储的数字数组更改为Set,该操作应该更快。

func generateRandomUniqueNumbers3(forLowerBound lower: Int, andUpperBound upper:Int, andNumNumbers iterations: Int) -> [Int] {
    guard iterations <= (upper - lower) else { return [] }
    var numbers: Set<Int> = Set<Int>()
    (0..<iterations).forEach { _ in
        let beforeCount = numbers.count
        repeat {
            numbers.insert(randomNumber(between: lower, and: upper))
        } while numbers.count == beforeCount
    }
    return numbers.map{ $0 }
}

我非常确定解决方案1会在您有100个数字可供选择的情况下陷入困境,而您想要90个独特的数字。当你选择第80个数字时,你只有20%的机会选择一个尚未被选中的数字。如果你有5000个数字并且你仍然想要它们的90%,那它就会非常糟糕。

我认为解决方案2可以更好地处理它,但我不确定什么样的性能会从大型数组中删除这么多的值。

我不知道该怎么做解决方案3.可能在中间的某个地方是我的猜测。

我设置XCTest来测量两种算法在不同负载条件下的某些性能。有2个参数:人口和密度。人口是可供选择的总人数,密度是我们想要提取的人口的百分比(即:80人口意味着80个数字可供选择,50%的密度意味着我们想要选择80个人群随机抽取40个唯一数字。

我对3种不同种群(5,250和12,500)和密度值(10%,50%和90%)的每种组合进行了9次测试。根据测试能够完成的速度或速度,我调整了我执行的测试迭代次数(从一次传递到多达2,500次传递)。

结果如下:

解决方案1 ​​

(Population: 5;      Density: 10%; Iterations: 2,500): 0.0056s
(Population: 250;    Density: 10%; Iterations: 50)   : 0.0046s
(Population: 12,500; Density: 10%; Iterations: 10)   : 1.33s
(Population: 5;      Density: 50%; Iterations: 2,500): 0.0131s
(Population: 250;    Density: 50%; Iterations: 50)   : 0.0912s
(Population: 12,500; Density: 50%; Iterations: 1)    : 4.09s
(Population: 5;      Density: 90%; Iterations: 2,500): 0.0309s
(Population: 250;    Density: 90%; Iterations: 10)   : 0.0993s
(Population: 12,500; Density: 90%; Iterations: 1)    : 23s

解决方案2

(Population: 5;      Density: 10%; Iterations: 2,500): 0.0184s
(Population: 250;    Density: 10%; Iterations: 50)   : 0.0086s
(Population: 12,500; Density: 10%; Iterations: 10)   : 0.103s
(Population: 5;      Density: 50%; Iterations: 2,500): 0.0233s
(Population: 250;    Density: 50%; Iterations: 50)   : 0.0125s
(Population: 12,500; Density: 50%; Iterations: 1)    : 0.0209s
(Population: 5;      Density: 90%; Iterations: 2,500): 0.0242s
(Population: 250;    Density: 90%; Iterations: 10)   : 0.0046s
(Population: 12,500; Density: 90%; Iterations: 1)    : 0.0278s

解决方案3

(Population: 5;      Density: 10%; Iterations: 2,500): 0.00672s
(Population: 250;    Density: 10%; Iterations: 50)   : 0.0024s
(Population: 12,500; Density: 10%; Iterations: 10)   : 0.0148s
(Population: 5;      Density: 50%; Iterations: 2,500): 0.0134s
(Population: 250;    Density: 50%; Iterations: 50)   : 0.00769s
(Population: 12,500; Density: 50%; Iterations: 1)    : 0.00789s
(Population: 5;      Density: 90%; Iterations: 2,500): 0.0209s
(Population: 250;    Density: 90%; Iterations: 10)   : 0.00397s
(Population: 12,500; Density: 90%; Iterations: 1)    : 0.0163s

比较

(Case 1): Solution 1 is fastest; then 3; then 2
(Case 2): Solution 3 is fastest; then 1; then 2
(Case 3): Solution 3 is fastest; then 2; then 3
(Case 4): Solution 1 is fastest; then 3; then 2
(Case 5): Solution 3 is fastest; then 2; then 1
(Case 6): Solution 3 is fastest; then 2; then 1
(Case 7): Solution 3 is fastest; then 2; then 1
(Case 8): Solution 3 is fastest; then 2; then 1
(Case 9): Solution 3 is fastest; then 2; then 1

结论

正如我在第一个解决方案中所怀疑的那样,它确实陷入了大量人口和高密度的困境。当你没有那么多人口,或者当你只选择2个数字时,它仍然很快,但在这些条件下所有解决方案都非常快。即使情况解决方案1可以从比解决方案2或3更快的250个群体中选择25个随机数,实时差异也非常小。

但是,指出如果你想从一个非常庞大的人口中获得很少的唯一数字(即:来自12,500个池中的2个唯一数字),有用的是,解决方案1是最快的,比约快77%解决方案3,两者都比解决方案2快几个数量级。对于我的具体情况,几乎所有时间都接近我将要做的事情,所以我可能会做一个特定的功能,特别是使用解决方案1从大量数据池中选择2个唯一数字。

总的来说,解决方案3非常接近全能最佳算法。但是对于大型数据集,请根据您计划使用它们的方法来考虑这些解决方案。

答案 3 :(得分:2)

这是我的解决方案。 SwiftStub

extension Array {
    func shuffle() -> Array<Element> {
        var newArray = self

        for i in 0..<newArray.count {
            let j = Int(arc4random_uniform(UInt32(newArray.count)))
            guard i != j else { continue }
            swap(&newArray[i], &newArray[j])
        }

        return newArray
    }
}

func uniqueRandoms(count: Int, inRange range: Range<Int>, blacklist: [Int] = []) -> [Int] {
    var r = [Int](range)
        .filter{ !blacklist.contains($0) }
        .shuffle()

    return Array(r[0..<count])
}

let x = uniqueRandoms(5, inRange: 1...10000)
let y = uniqueRandoms(5, inRange: 1...10, blacklist: [2,4,6,8,10])
print(x)
print(y)

filter过滤掉黑名单上的数字。

shuffle是添加到Array课程的扩展程序。如果需要,可以将其作为单独的函数实现。

return Array(r[0..<count])从现有数组的Slice创建一个新数组。

当列表小于要求的count时,这有一个潜在的索引超出绑定错误。例如,这些会崩溃:

let a = uniqueRandoms(10, inRange: 1...5)
let b = uniqueRandoms(3, inRange: 1...5, blacklist: [1,2,3,4])

将处理作为OP的练习。

答案 4 :(得分:0)

<div class="bg">
  <h1>text</h1>
</div>

我创建了2个空数组&#34; randomNumbers&#34;和&#34; blackList&#34;然后设置一个for循环语句。 在循环语句中,我初始化变量&#34; number。&#34;我指定&#34;数字&#34;对于一个随机数,然后我使用while语句和.contians来检查&#34; number&#34;已经在数组&#34; randomNumbers&#34;或者如果&#34;数字&#34;在数组&#34; blackList&#34;如果任一数组包含&#34;数字,&#34; &#34;数字被重新分配,直到两个数组都不包含&#34; number&#34;。然后我randomNumbers.append(number)追加&#34; number&#34;进入&#34; randomNumbers&#34;然后循环重新开始。

答案 5 :(得分:-1)

这么简单的方法是:

  let UniqueIdNumber = CFUUIDCreateString(nil, CFUUIDCreate(nil))