2

I'm facing a scenario where I have to generate three different random number. I know how to do that and already implement it. But I want to know is there any way to optimize my approach because I feel that my approach is too much costly.

var randomIndex = [Int]()
var generatedValue = [Int]()
var c = 0
randomIndex.removeAll()
while true {
    let randomValue = Int.random(in: 1 ... 10)
    generatedValue.append(randomValue)
    print(randomValue)
    for i in 0 ... generatedValue.count - 1{
        if randomValue != generatedValue[i]{
            randomIndex.append(randomValue)
            c += 1
        }
    }
    if c >= 3 {
        break
    }
}

Here I declare two different arrays, use an infinity loop and a variable to track the value. Is there any function to generate three different random number in a ranged loop from 0 to 2? or is there any optimized approach to do that? Please Help.

6 Answers 6

2

A Set is simpler, it guarantees unique values

var randomIndex = Set<Int>()

while randomIndex.count < 3 {
    randomIndex.insert(Int.random(in: 1 ... 10))
}

Another approach is to create an array of the values, pick a random element and remove this element from the array

var randomValues = Set<Int>()
var valueArray = Array(1...10)
for _ in 0..<3 {
    guard let randomElement = valueArray.randomElement() else { break }
    randomValues.insert(randomElement)
    valueArray.remove(at: valueArray.firstIndex(of: randomElement)!)
}

Regarding is there any way to optimize my approach the for loop in your code is extremely expensive.

First of all the recommended syntax for an index based loop is

for i in 0 ..< generatedValue.count {

But actually you don't need the index so Fast Enumeration is better

for value in generatedValue {
    if randomValue != value {

The most expensive part of the loop is that the entire array is always iterated even if the value was already found. At least add a break statement to exit the loop

for value in generatedValue {
    if randomValue != value {
        randomIndex.append(randomValue)
        c += 1
        break
    }
}

But there is still a more efficient syntax

if !generatedValue.contains(randomValue) {
    randomIndex.append(randomValue)
    c += 1  
}
Sign up to request clarification or add additional context in comments.

2 Comments

This approach becomes expensive the higher the values become and the more equal they are. For instance generating 1000 values from 1 - 1000 would tend towards n^2 or higher. (I know that you should prob be looking for a different approach at that point but still).
@Fogmeister I added another approach
2

You can use a set

var randomNumbers = Set<Int>()
while (randomNumbers.count < 3) {
    randomNumbers.update(with: Int.random(in: 1...10))
}

let randomIndex = Array(randomNumbers)

Comments

2

You can shuffle the array and get the first n elements

let randomIndex = Array(1...5)
let generatedValue = Array(randomIndex.shuffled().prefix(3))

5 Comments

What is the default implementation of the shuffle algorithm? Does it allow for numbers to remain in their start location? i.e. will 1 ever end up at index 0 after shuffling? If not then it might be best to run shuffle twice. (I wonder if it uses the Fisher-Yates shuffle, I will investigate).
OK... testing this code... print(Array(1...3).shuffled()) on this site... online.swiftplayground.run it looks like it does allow for values to end up where they started.
@Fogmeister values to end up where they started is also included in randomizing logic
@Fogmeister The possibility is higher when the array size is small. Try Array(1...50)
Yes, that was my point. I was just checking that with the implementation of the shuffle algorithm that that was still possible. If the Swift implementation used the Fisher-Yates shuffle (for instance) then this does not allow for values to remain where they are (which is bad). But the Swift shuffle seems to work to account for this.
1

You can use this extension:

extension Array where Element == Int {
    init(randomsCount: Int, minValue: Int, maxValue: Int) {
        self = []        
        var possibleNumbers = Array(minValue ... maxValue)        
        for _ in (0 ..< randomsCount) {
            self.append(possibleNumbers.remove(at: Int.random(in: 0..<possibleNumbers.count)))
        }
    }
}

and then just call i.e. [Int](randomsCount: 3, minValue: 0, maxValue: 10) to generate an array with 3 random Ints between 0 and 10.

Note that with this approach you calculate the random number exactly once per random number you want.

3 Comments

This does not guarantee 3 unique integer values will be generated.
This might generate duplicate values?
Updated with a way that only calculates the random number once (not a try and error approach). And makes the random values unique.
0

This is a helper function to generate a random number of x length.

func randomString(length: Int) -> Int {
    let letters = "0123456789"
    var randomString = String((0..<length).map{ _ in letters.randomElement()!})
    return Int(randomString)

}

2 Comments

you cannot return something in a function with a return type of nil
Seems like retuning something without defining the return type!!
0

The following code generates two random values within the same range, ensuring the second value lies outside the tolerance range of the first (if you are simply trying to avoid duplicate numbers, set threshold to 0):

import SwiftUI
import Foundation

func scale(oldMin: Double, oldMax: Double, value: Double, newMin: Double, newMax: Double) -> Double {
    return ((value - oldMin) / (oldMax - oldMin)) * (newMax - newMin) + newMin
}

struct ContentView: View {
    @State private var firstAngles: [Double] = []
    @State private var secondAngles: [Double] = []
    @State private var minusThresholdAngles: [Double] = []
    @State private var plusThresholdAngles: [Double] = []
    @State private var differences: [Double] = []
    let threshold: Double = 45.0

    var body: some View {
        VStack {
            Button(action: generateAngles) {
                Text("Generate Angles")
            }
            List(0 ..< firstAngles.count, id: \.self) { index in
                VStack(alignment: .leading) {
                    Text("\(Int(minusThresholdAngles[index]))° < \(Int(firstAngles[index]))° < \(Int(plusThresholdAngles[index]))° = \(Int(differences[index]))°")
                    Text("\(scale(oldMin: 0.0, oldMax: 360.0, value: firstAngles[index], newMin: 0.0, newMax: 1.0))")
                    Text("\(scale(oldMin: 0.0, oldMax: 360.0, value: differences[index], newMin: 0.0, newMax: 1.0))")
                }
            }
        }
        .padding()
    }

    private func generateAngles() {
        firstAngles = (0 ..< 10).map { _ in generateRandomAngle(max: 360.0) }
        secondAngles = (0 ..< 10).map { i in generateRandomAngle(max: firstAngles[i]) }
        minusThresholdAngles = firstAngles.map { wrapAngle($0 - threshold) }
        plusThresholdAngles = firstAngles.map { wrapAngle($0 + threshold) }
        differences = zip(secondAngles, zip(minusThresholdAngles, plusThresholdAngles)).map { calculateShortestDifference($0, $1.0, $1.1) }
        differences = (0 ..< 10).map { i in wrapAngle(differences[i] + firstAngles[i]) }
    }

    private func generateRandomAngle(max: Double) -> Double {
        return Double.random(in: 0 ... max)
    }

    private func calculateShortestDifference(_ angle2: Double, _ minusThreshold: Double, _ plusThreshold: Double) -> Double {
        let diffMinusThreshold = calculateShortestDistance(angle2, minusThreshold)
        let diffPlusThreshold = calculateShortestDistance(angle2, plusThreshold)

        return max(diffMinusThreshold, diffPlusThreshold)
    }

    private func calculateShortestDistance(_ angle1: Double, _ angle2: Double) -> Double {
        let diff = abs(angle1 - angle2)
        return min(diff, 360 - diff)
    }

    private func wrapAngle(_ angle: Double) -> Double {
        let wrapped = angle.truncatingRemainder(dividingBy: 360)
        return wrapped >= 0 ? wrapped : wrapped + 360
    }
}

Use the scale function to fit your results into a given range (ex.: 0 to 1):

scale(oldMin: 0.0, oldMax: 360.0, value: result, newMin: 0.0, newMax: 1.0)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.