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 建议的解决方案几乎相同。您首先加载一个包含所有可用数字的数组,随机选择一个,然后将其从可用数组中删除,然后将其添加到您的 numbers 数组中。重复任意数量的数字。
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
}
第三种解决方案是对第一种解决方案的改编,以解决数组在其中查找元素速度慢的问题。通过将存储的 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 来测量两种算法在不同负载条件下的一些性能。有两个参数:人口和密度。人口是可供选择的数字的总数,密度是我们想要抽出的人口的百分比(即:人口为 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 最快,比解决方案 3 快约 77%,并且两者都比解决方案 2 快几个数量级。对于我的具体情况,这更接近我几乎一直要做的事情,所以我可能会制作一个特定的函数,使用解决方案 1 从一个大池中专门选择 2 个唯一数字数据的。
总体而言,解决方案 3 非常接近一个全方位的最佳算法。但是对于大型数据集,请根据您计划使用它们的目的考虑这些解决方案中的每一个。