从Swift数组中随机选择一个项目而不重复

4

这段代码从一个预设颜色数组中随机选择一种颜色。如何使得不会重复选择同一种颜色?

var colorArray = [(UIColor.redColor(), "red"), (UIColor.greenColor(), "green"), (UIColor.blueColor(), "blue"), (UIColor.yellowColor(), "yellow"), (UIColor.orangeColor(), "orange"), (UIColor.lightGrayColor(), "grey")]

var random = { () -> Int in
    return Int(arc4random_uniform(UInt32(colorArray.count)))
} // makes random number, you can make it more reusable


var (sourceColor, sourceName) = (colorArray[random()])

当按钮被触摸时,您可以保存所选颜色或索引的数组,并检查颜色或索引是否包含在数组中。 - kye
有很多漂亮而清晰的解决方案。有时候最简单的方法是最好的。我推荐你使用Achi的解决方案。这个方法之所以有效,是因为arc4random_uniform不仅生成随机数,而且还生成均匀分布的数字流。 - user3441734
6个回答

7
创建一个索引数组。从数组中删除一个索引,然后使用该索引来获取颜色。
类似于这样:
var colorArray = [
  (UIColor.redColor(), "red"), 
  (UIColor.greenColor(), "green"), 
  (UIColor.blueColor(), "blue"), 
  (UIColor.yellowColor(), "yellow"), 
  (UIColor.orangeColor(), "orange"), 
  (UIColor.lightGrayColor(), "grey")]

var indexes = [Int]();

func randomItem() -> UIColor
{
  if indexes.count == 0
  {
    print("Filling indexes array")
    indexes = Array(0..< colorArray.count)
  }
  let randomIndex = Int(arc4random_uniform(UInt32(indexes.count)))
  let anIndex = indexes.removeAtIndex(randomIndex)
  return colorArray[anIndex].0;
}

上述代码创建了一个数组indexes。函数randomItem检查indexes是否为空。如果是,则使用从0到colorArray.count - 1的索引值填充它。
然后,在indexes数组中选择一个随机索引,删除该索引处的值,并使用它从colorArray中获取和返回对象。(它不会从colorArray中删除对象。它使用间接方式,从最初包含每个条目的索引值的indexesArray中删除对象。
上述方法的唯一缺陷是,在从indexArray中获取最后一个项目之后,您将其重新填充了一个完整的索引集,下一个颜色可能与您上次获取的颜色相同。
可以添加额外的逻辑来防止这种情况发生。

错误,需要更改为 return colorArray[anIndex].0 - dimpiax
我猜你是对的,这是一个元组数组,不是吗?我没有仔细查看OP的数据结构。我编写了我的代码来从SOMETHING的数组中获取一个随机的、不重复的元素。它可以使用泛型推广到任何数组。 - Duncan C
在这种情况下,该函数必须是抽象的。为了提高可读性,我将使用数组的stride填充而不是范围。 - dimpiax
@dimpiax为什么不发布您的答案版本并提出您建议的增强功能?我对泛型还不熟悉,也不确定您使用stride的意思。 - Duncan C
哈哈,这里的气氛紧张,是吗?请查看我下面的帖子,其中包含两种方法。 - dimpiax
我实际上更喜欢使用 indexes = Array(0 ..< colorArray.count) 而不是使用 stride。 - Duncan C

2
基于事实,arc4random_uniform 生成的不仅是随机数,还是均匀分布的数字。
import Foundation // arc4random_uniform

class Random {
    var r:UInt32
    let max: UInt32
    init(max: UInt32) {
        self.max = max
        r = arc4random_uniform(max)
    }
    var next: UInt32 {
        var ret: UInt32
        repeat {
            ret = arc4random_uniform(max)
        } while r == ret
        r = ret
        return r
    }
}
// usage example
let r = Random(max: 5)
for i in 0..<10 {
    print(r.r, r.next) // there will never be a pair of the same numbers in the
    // generated stream
}
/*
 2 4
 4 0
 0 3
 3 0
 0 3
 3 4
 4 1
 1 3
 3 4
 4 3
 */

测试不同的K值和一百万流长度

class Random {
    var r:UInt32
    let max: UInt32
    init(max: UInt32) {
        self.max = max
        r = arc4random_uniform(max)
    }
    var next: (UInt32, Int) {
        var i = 0
        var ret: UInt32
        repeat {
            ret = arc4random_uniform(max)
            i += 1
        } while r == ret
        r = ret
        return (r,i)
    }
}
for k in 3..<16 {
    let r = Random(max: UInt32(k))
    var repetition = 0
    var sum = 0
    for i in 0..<1000000 {
        let j = r.next
        repetition = max(repetition, j.1)
        sum += j.1
    }
    print("maximum of while repetition for k:", k, "is", repetition, "with average of", Double(sum) / Double(1000000) )
}

打印

maximum of while repetition for k: 3 is 15 with average of 1.499832
maximum of while repetition for k: 4 is 12 with average of 1.334008
maximum of while repetition for k: 5 is 9 with average of 1.250487
maximum of while repetition for k: 6 is 8 with average of 1.199631
maximum of while repetition for k: 7 is 8 with average of 1.167501
maximum of while repetition for k: 8 is 7 with average of 1.142799
maximum of while repetition for k: 9 is 8 with average of 1.124096
maximum of while repetition for k: 10 is 6 with average of 1.111178
maximum of while repetition for k: 11 is 7 with average of 1.099815
maximum of while repetition for k: 12 is 7 with average of 1.091041
maximum of while repetition for k: 13 is 6 with average of 1.083582
maximum of while repetition for k: 14 is 6 with average of 1.076595
maximum of while repetition for k: 15 is 6 with average of 1.071965

最后,这里有一个基于相同思路的更加Swifty和函数式的方法

import Foundation

func random(max: Int)->()->Int {
    let max = UInt32(max)
    var last = arc4random_uniform(max)
    return {
        var r = arc4random_uniform(max)
        while r == last {
            r = arc4random_uniform(max)
        }
        last = r
        return Int(last)
    }
}

let r0 = random(8)
let r1 = random(4)
for i in 0..<20 {
    print(r0(), terminator: " ")
}
print("")
for i in 0..<20 {
    print(r1(), terminator: " ")
}

/*
 4 5 4 3 4 0 5 6 7 3 6 7 5 4 7 4 7 2 1 6
 0 3 0 1 0 2 3 1 2 0 1 0 1 0 1 3 0 3 0 2
 */

1
使用Fisher-Yates shuffle算法将颜色填充到数组中并对其进行洗牌。然后使用末尾的元素,将其移除,并将其插入到距离末尾至少n个位置的随机位置。
例如,假设我的数组有10个元素。我将其洗牌并取出最后一个元素。在再次看到它之前,我希望至少选择2个值,因此我在范围0...8内生成一个随机位置,并将其插入到那里。
var colorArray = [
  (UIColor.redColor()      , "red"   ),
  (UIColor.greenColor()    , "green" ),
  (UIColor.blueColor()     , "blue"  ),
  (UIColor.yellowColor()   , "yellow"),
  (UIColor.orangeColor()   , "orange"),
  (UIColor.lightGrayColor(), "grey"  )].shuffle() // shuffle() is from my link above

let spacing = 2 // Pick at least 2 colors before we see it again
if let randomColor = colorArray.popLast() {
  colorArray.insert(randomColor,
                    atIndex: Int(arc4random_uniform(UInt32(colorArray.count - spacing))))
}

0
一个案例,在这里描述:https://github.com/dimpiax/GenericSequenceType 另一个是功能性的:
func getRandomItem<T>(arr: [T]) -> (unique: Bool) -> T {
    var indexes: [Int]!

    return { value in
        let uniqIndex: Int

        if value {
            if indexes?.isEmpty != false {
                indexes = [Int](0.stride(to: arr.count, by: 1))
            }

            uniqIndex = indexes.removeAtIndex(Int(arc4random_uniform(UInt32(indexes.count))))
        }
        else {
            uniqIndex = Int(arc4random_uniform(UInt32(arr.count)))
        }
        return arr[uniqIndex]
    }
}

let generate = getRandomItem(colorArray)
generate(unique: true).0 // greenColor
generate(unique: true).0 // redColor
generate(unique: true).0 // lightGrayColor

0

尝试一下,对我有效并且经过100%测试

let arrString = ["1","2","3","4","5","6"]
var selectedIndix = -1

@IBAction func btnClick(_ sender: Any) {

    let randomElementIndex = randomElementString()
}
func randomElementString() -> Int{

    let randomm = Int(arc4random_uniform(UInt32(arrString.count)))

    if selectedIndix == randomm{
        return randomElementString()
    }else{
        selectedIndix = randomm
        return randomm
    }
}

输出:

5121242316513126

-2

尝试使用以下条件运行 while 循环:

while(self.source.backgroundColor == sourceColor) {
  // get a new random sourceColor
}

这将一直循环,直到选择了一个新的随机颜色。

编辑

附加说明:重点是while循环。有办法防止无限循环,这取决于编程人员找到合适的解决方案。我认为SO不是一个地方来写别人的代码,而是提供建议..我的建议只是一个开始。

但由于我的答案得到了如此负面的评价,我将坚持而不是轻推朝着正确的方向前进。

其他答案过于臃肿。那又怎样?我上面提供的答案的时间复杂度不太理想。所以,这是我的新答案(伪代码):

// array of all background colors
var arrayOfColors = [..]

// get a random index
var randomIndex = arc4random(size of arrayOfColors)

// select new background color
var newBGColor = arrayOfColors[randomIndex]

// old background color
var oldBGColor = self.source.backgroundColor

// remove new color from array (so that it's excluded from choices next time)
arrayOfColors.removeAtIndex(randomIndex)

// set the new color to the background
self.source.backgroundColor = newBGColor

// add current color back into the pool of potential colors
arrayOfColors.addObject(oldBGColor)

虽然这个解决方案可以工作,但有可能会无限循环,尽管这种情况不太可能发生。如果你得到一串糟糕的随机数,它也可能潜在地变慢。 - user887210
1
@KennethBruno 使用 arc4random_uniform,如果颜色数量> 2,则永远循环的可能性为零。颜色越多,潜在的减速就越少... 实际上,这个解决方案非常高效,完全符合 OP 的需求。欢迎您进行测试 :-) - user3441734
出于兴趣,我尝试使用while循环,因为我最初认为这可能是实现的方法。这是我使用它的方式,但如果颜色想要出现两次,它会给出EXC_BAD_ACCESS错误。这是我尝试使用它的方式。 - user5740195
变量背景 = self.source.backgroundColor当(last == background) { hello() } - user5740195
重点是while循环。有方法可以防止无限循环,这取决于编码人员找到正确的解决方案。我认为SO不是写别人代码的地方,而是提供建议的地方..我的建议是一个开始。 - esreli
@PeterGaffney 看看我的修订答案。它最容易理解,同时提供了最佳的时间和空间复杂度。 - esreli

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