Joe Joe - 3 years ago 64
Swift Question

Pulling a random item from an array on a timer

I have an array of strings. I would like to display 3 unique items from this array randomly. Then every 5 seconds, one of the items gets replaced with another unique item (my idea here is adding an animation with a delay).

I can display the 3 strings, however sometimes they repeat, and the timer is not updating the factLabel label.

Here's my progress:

override func viewDidLoad() {
super.viewDidLoad()
updateUI()
}

func randomFact() -> String {
let arrayCount = model.cancunFacts.count
let randomIndex = Int(arc4random_uniform(UInt32(arrayCount)))
return model.cancunFacts[randomIndex]
}

// Display the facts
func updateUI() {
Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(randomFact), userInfo: nil, repeats: true)
factLabel.text = randomFact() + " " + randomFact() + " " + randomFact()
}


How do I get the text to always update randomly, without the 3 facts repeating?

Answer Source

Create an array of indexes. Remove a random index from the array, use it to index into your strings. When the array of indexes is empty, refill it.

Here is some sample code that will generate random, non-repeating strings:

var randomStrings = ["Traitor", "Lord Dampnut", "Cheeto-In-Chief", 
  "F***face Von Clownstick", "Short-Fingered Vulgarian", 
  "Drumpf", "Der Gropenf├╝hrer", "Pumpkin in a suit"]

var indexes =  [Int]()

func randomString() -> String {
    if indexes.isEmpty {
        indexes = Array(0...randomStrings.count-1)
    }
    let index = Int(arc4random_uniform(UInt32(indexes.count)))
    let randomIndex = indexes.remove(at: index)
    return randomStrings[randomIndex]
}

for i in 1...100 {
    print (randomString())
}

(Note that it may still generate repeating strings in the case when the array of indexes is empty and it needs to be refilled. You'd need to add extra logic to prevent that case.)

Edit:

Here is the same code, modified slightly to avoid repeats when the array of indexes is empty and needs to be refilled:

var randomStrings = ["tiny-fingered", "cheeto-faced", "ferret-wearing", "sh*tgibbon"]

var indexes =  [Int]()
var lastIndex: Int?

func randomString() -> String {
    if indexes.isEmpty {
        indexes = Array(0...randomStrings.count-1)
    }
    var randomIndex: Int
    repeat {
        let index = Int(arc4random_uniform(UInt32(indexes.count)))
        randomIndex = indexes.remove(at: index)
    } while randomIndex == lastIndex
    lastIndex = randomIndex
    return randomStrings[randomIndex]
}


for i in 1...10000 {
    print (randomString())
}

Even though it's using a repeat...while statement, the repeat condition will never fire twice in a row, because you'll never get a repeat except right after refilling the array of indexes.

With that code, if there is a repeat, the selected string will be skipped on that pass through the array. To avoid that, you'd need to adjust the code slightly to not remove a given index from the array until you verify that it is not a repeat.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download