问题
I need to validate the length of a string. The allowed values for the character count are:
- 6 – 9 characters
- 12 characters
- 15 characters
All strings with a different character count are invalid. Thus, I would like to create a Swift function that accepts a number of ranges and evaluates the string:
extension String {
func evaluateLength(validCharacterCounts: Range<Int>...) -> Bool {
// Implementation
}
}
Now I can call the function for a single Int
range:
"Live long and prosper".evaluateLength(validCharacterCounts: 6..<10)
and multiple Int
ranges:
"Live long and prosper".evaluateLength(validCharacterCounts: 6..<10, 15..<20)
But I cannot call the function with single, isolated integer values:
"Live long and prosper".evaluateLength(validCharacterCounts: 6..<10, 12, 15)
because 12
and 15
are typed as Int
and not as Range<Int>
.
Swift compile error: Cannot convert value of type 'Int' to expected argument type 'Range'
Is there a way to treat a single Integer as a Range
in Swift, like casting it automatically to Range<Int>
?
(After all 5
is equivalent to 5..<6
, so mathematically speaking 5
is a range as well.)
回答1:
I came up with 3 solutions but I don't either of them are as pleasing as would be if the method accepts Int
or Range<Int>
but here we go?
1 - You have type safety but have to use some workarounds:
extension String {
func evaluateLength(validCharacterCounts: [Range<Int>]? = nil) -> Bool {
if let validCharCounts = validCharacterCounts {
for validCharCount in validCharCounts {
if validCharCount.contains(characters.count) {
return true
}
}
}
return false
}
}
extension Int {
var asRange: Range<Int> {
return self..<self+1 as Range<Int>
}
}
"Hello, playground".evaluateLength(validCharacterCounts: [17.asRange, 1, 25, 1..<45]) // true
2 - You don't have type safety:
extension String {
func evaluateLength(validCharacterCounts: [Any]? = nil) -> Bool {
if let validCharCounts = validCharacterCounts {
for validCharCount in validCharCounts {
if let range = validCharCount as? Range<Int> {
if range.contains(characters.count) {
return true
}
} else if let range = validCharCount as? CountableRange<Int> {
if range.contains(characters.count) {
return true
}
} else if let range = validCharCount as? CountableClosedRange<Int> {
if range.contains(characters.count) {
return true
}
} else if let int = validCharCount as? Int {
if int.asRange.contains(characters.count) {
return true
}
} else {
fatalError("Unexpected type: \(type(of: validCharCount))")
}
}
}
return false
}
}
extension Int {
var asRange: Range<Int> {
return self..<self+1 as Range<Int>
}
}
"Hello, playground".evaluateLength(validCharacterCounts: [12, 1, 4, 6, 14...18]) // true
3 - The best so far, but still not perfect solution:
extension String {
func evaluateLength(validCharacterCounts: [Int]? = nil) -> Bool {
guard let validCharCounts = validCharacterCounts else {
return false
}
return validCharCounts.contains(characters.count)
}
}
"Hello, playground".evaluateLength(validCharacterCounts: [12, 1, 4, 6] + Array(14...18)) // true
回答2:
This is how I would do it. Rather than dealing with range semantics, I'm just creating a Set extension with a relevant initializer.
extension Set where Element == Int {
init(with elements: [Int] = [], and ranges: Range<Int>...) {
var allElements = [elements]
ranges.forEach {
allElements.append(Array($0.lowerBound..<$0.upperBound))
}
self.init(allElements.joined())
}
}
let newSet = Set(with: [1, 3, 328], and: 6..<10, 15..<20)
newSet.contains(3) //true
newSet.contains(4) //false
newSet.contains(16) //true
This allows you to pass only single values, only ranges, or both. The only caveat is the named params separating them.
The Set
semantics also mean you can create static constants if desired and should be significantly faster as well.
来源:https://stackoverflow.com/questions/45620454/treat-a-single-integer-value-as-a-range-in-swift