Naming
Swift naming conventions are listed in the API Design Guidelines. With respect to “Omit needless words” and “Strive for Fluent Usage” I would suggest to call the function
func randomPair(from range: ClosedRange<Int>) -> (Int, Int)?
Simplify the code
Your method is fine, but can be simplified a bit. First, the return values from randomElement()
can safely be forced-unwrapped because it is guaranteed at that point that the range is not empty. That makes the final optional binding obsolete:
func randomPair(from range: ClosedRange<Int>) -> (Int, Int)? {
guard range.count >= 2 else { return nil }
let firstElement = range.randomElement()!
var secondElement = range.randomElement()!
while firstElement == secondElement {
secondElement = range.randomElement()!
}
return (firstElement, secondElement)
}
Alternatively one can use a repeat-loop, so that the second element is assigned only at one place, that is a matter of personal preference:
func randomPair(from range: ClosedRange<Int>) -> (Int, Int)? {
guard range.count >= 2 else { return nil }
let firstElement = range.randomElement()!
var secondElement: Int
repeat {
secondElement = range.randomElement()!
} while firstElement == secondElement
return (firstElement, secondElement)
}
But actually the same can be achieved without a loop, if we pick the second element from a reduced range and adjust it if necessary:
func randomPair(from range: ClosedRange<Int>) -> (Int, Int)? {
guard range.count >= 2 else { return nil }
let firstElement = range.randomElement()!
var secondElement = range.dropLast().randomElement()!
if secondElement >= firstElement {
secondElement += 1
}
return (firstElement, secondElement)
}
Generalizations
Now we have a function to get a random pair from a closed range. But what about open ranges? What about ranges of other types? In fact it is not difficult to implement the same algorithm for arbitrary (random access) collections. The only difference is that we pick random indices first:
func randomPair<C>(from collection: C) -> (C.Element, C.Element)? where C: RandomAccessCollection {
guard collection.count >= 2 else { return nil }
let firstIndex = collection.indices.randomElement()!
var secondIndex = collection.indices.dropLast().randomElement()!
if secondIndex >= firstIndex {
collection.formIndex(after: &secondIndex)
}
return (collection[firstIndex], collection[secondIndex])
}
This covers closed ranges, open ranges, arrays etc:
randomPair(from: 0...10)
randomPair(from: 0..<10)
randomPair(from: ["a", "b", "c", "d"])
Alternatively one can implement this as an extension method for RandomAccessCollection
so that it closely resembles the existing randomElement()
method:
extension RandomAccessCollection {
func randomPair() -> (Element, Element)? {
guard count >= 2 else { return nil }
let firstIndex = indices.randomElement()!
var secondIndex = indices.dropLast().randomElement()!
if secondIndex >= firstIndex {
formIndex(after: &secondIndex)
}
return (self[firstIndex], self[secondIndex])
}
}
(0...10).randomPair()
(0..<10).randomPair()
["a", "b", "c", "d"].randomPair()