Is it sensible to rely on hashValue for enum cases? - swift

While reviewing some code, I found a Rank enum implemented as:
enum Rank: String {
case first
case second
case third
}
However, the surprising part for me was that I saw a code similar to this:
let gold = [300, 200, 100]
let firstPrize = gold[Rank.first.hashValue] // 300
means that Rank.first.hashValue has been used as an index! For the first look, it seems to be not a good idea to use a hash value as an index for an array:
Hash values are not guaranteed to be equal across different executions
of your program. Do not save hash values to use during a future
execution.
hashValue
Nevertheless it never causes an issue (at least that's what they said).
I tried to trace the issue, by implementing:
print(Rank.first.hashValue) // 0
print(Rank.second.hashValue) // 1
print(Rank.third.hashValue) // 2
and I saw is the output is always the same.
Although we could declare a property in the enum to do such a functionality, as:
var index: Int {
switch self {
case .first:
return 0
case .second:
return 1
case .third:
return 2
}
}
hence:
let firstPrize = gold[Rank.first.index] // 300
I would prefer to know why using the hashValue seems to be ok in this case? Could it be related to: I misunderstand what exactly is hashValue?

Related

For Loops and Switches in Swift

I'm (relatively) new to programming. I was working through Apple's "Introduction to App Development" and since there isn't an answer booklet of some sort I've had to work things out on my own. However there are some things I'm a bit confused about:
There is a problem I'm working on:
This playground has a Chicken type built in to it. A Chicken has a breed and temper property, and both properties are enums.
Here is an array of chickens:
chickens = [{silkie, grumpy}...]
The task is to calculate how many chickens of the breed "leghorn" and temper "hilarious" there are in the array. I've come up with the following code:
var chickenOfInterestCount = 0
for chicken in chickens {
switch chicken.temper {
case .hilarious:
switch chicken.breed {
case .leghorn:
chickenOfInterestCount += 1
default:
chickenOfInterestCount += 0
}
default:
chickenOfInterestCount += 0
}
}
chickenOfInterestCount
It works, but I wonder if there is a more efficient way to do this? Can I make the switch check each chicken for {leghorn, hilarious} and count the number of required chickens directly, rather than using a nested switch? (All of my attempts to try this myself were squashed by scary looking error messages, so I'm guessing no) Following from that, how do for loops work?
When I originally learned for loops, I was under the impression that integers were used to loop through, e.g. the first loop was i = 0, and the code inside performed, then the second loop was i = 1, etc, where "i" could really be anything, and the idea would be the same. Here, "chicken" is used. Why is it that I can set the condition to chicken.temper? What does chicken mean here? Does a for loop respect the type of the array it is looping through? So is what is actually happening here something like "take the 0th Chicken in the array of chickens, call that a 'chicken', then check its temper"?
Well, the first thing I'll mention is that your approach works. As you suspect, it's not the most elegant approach - but you're already instinctively aware of this.
The filter functionality in Swift is powerful and efficient, and Dilan's example will set you in the right direction to begin exploring this. But you'll want to develop a good understanding of control flow on its own - aside from functional programming features like map, reduce, and filter.
For this situation if you're only looking for one combination of breed and temperament, then probably a basic if statement is cleaner than using switch.
var chickenOfInterestCount = 0
for chicken in chickens {
if chicken.breed == .leghorn && chicken.temper == .hilarious {
chickenOfInterestCount += 1
}
}
chickenOfInterestCount
Switch is more useful when you have to do something for each case - it ends up being much cleaner than multiple if / else if statements.
var bantamCount = 0
var leghornCount = 0
var unknownBreedCount = 0
var chillCount = 0
var hilariousCount = 0
var unknownTemperCount = 0
for chicken in chickens {
switch chicken.breed {
case .bantam:
bantamCount += 1
case .leghorn:
leghornCount += 1
default:
unknownBreedCount += 1
}
switch chicken.temper {
case .chill:
chillCount += 1
case .hilarious:
hilariousCount += 1
default:
unknownTemperCount += 1
}
}
Also on your question:
So is what is actually happening here something like "take the 0th Chicken in the array of chickens, call that a 'chicken', then check its temper"?
As Ben pointed out, you're reasoning about this correctly. Within the scope of the loop, there's a var called chicken that is an instance of Chicken (presumably - not sure what you named this Class or Struct) and is a member of the chickens array.
The Swift-y thing to do in these situations is to break out map(), reduce(), compactMap(), and filter()--in your case, filter():
let chickens: [Chicken] = [ silkie, grumpy, dopey, foghorn ]
let hilariousLeghorns = chickens.filter {
$0.breed == .leghorn
&& $0.temper == .hilarious
}
let chickenOfInterestCount = hilariousLeghorns.count
(Thank you for making your example funny!)
In your pseudo-code, your for-loop is a little nest-y, and that can be hard to read. (Mine is probably not much easier--I'd love a code review!) You are in fact declaring a chicken which is going to be assigned the value of each element of chickens in turn. You can do this with any Sequence, like an array, a map, or a set.
To get a old-school for loop, you can loop through a range:
for i in 1...5 {
print(i)
}
This does the same thing: i is assigned the values 1, 2, 3, 4, and 5, in turn, looping 5 times.
Welcome to Programming.This is the model for your scenario .
struct Chicken{
let id:Int
let breed:String
let temper:String
}
enum Breed{
static let leghorn = "leghorn"
static let other = "other"
}
enum Temper{
static let hilarious = "hilarious"
static let other = "other"
}
var chickens:[Chicken] = []
chickens.append(Chicken(id: 1, breed:.leghorn, temper:.hilarious))
chickens.append(Chicken(id: 2, breed:.other, temper:.hilarious))
chickens.append(Chicken(id: 3, breed:.leghorn, temper:.other))
You can get your result from one line with Swift.Use Filter function.This is like for loop and $0 mean current element.It is loop through your array and find elements that satisfy your condition.
print(chickens.filter({$0.breed == .leghorn && $0.temper == .hilarious}).count)
Apple Documentation
Extra info

How to Define Generic “Invalid“ Values for Different Types

In my app, I am using Integers, Doubles, Floats, and CGFloats to represent a number of different values. According to my app’s semantic, these values may become “invalid“, a state which I represent using a reserved value, i. e. -1. The simplest approach to make this usable in code would be this:
anIntVariable = -1
aFloatVariable = -1
aDoubleVariable = -1.0
...
To get away from this convention driven approach and increase readability and adaptability I defined a number of extensions:
extension Int {
static var invalid = -1
}
extension Float {
static var invalid = -1.0
}
extension Double {
static var invalid = -1.0
}
...
So the above code would now read:
anIntVariable = .invalid
aFloatVariable = .invalid
aDoubleVariable = .invalid
...
It does work. However, I’m not really happy with this approach. Does anyone of you have an idea for a better way of expressing this?
To add some complexity, in addition to simple types like Int, Float, or Double, I also use Measurement based types like this:
let length = Measurement(value: .invalid, unit: UnitLength.baseUnit())
Extra bonus point if you find a way to include “invalid“ measurements in your solution as well...
Thanks for helping!
Some Additional Thoughts
I know I could use optionals with nil meaning “invalid”. In this case, however, you’d have additional overhead with conditional unwrapping... Also, using nil as “invalid” is yet another convention.
It isn’t better or worse, just different. Apple uses “invalid” values in its own APIs, i. e. the NSTableViewmethod row(for:) will return -1 if the view is not in the table view. I agree, however, that this very method perfectly illustrates that returning an optional would make a lot of sense...
I'd use optionals for that.
If you want lack of value and invalid value to be different states in your app, i'd suggest creating a wrapper for your values:
enum Validatable<T> {
case valid(T)
case invalid
}
And use it like that:
let validValue : Validatable<Int> = .valid(5)
let invalidValue : Validatable<Int> = .invalid
var validOptionalDouble : Validatable<Double?> = .valid(nil)
validOptionalDouble = .valid(5.0)
let measurement : Validatable<Measurement> = .invalid
etc.
You can then check for value by switch on that enum to access the associated value like this:
switch validatableValue {
case .valid(let value):
//do something with value
case .invalid:
//handle invalid state
}
or
if case .valid(let value) = validatableValue {
//handle valid state
}
etc

Switch to match multiple cases from OptionSetType

I have a struct Person extends OptionSetType. Later in code, How can I use a switch statement to check if an instance of Person is more than one value?
Thank you
struct Person: OptionSetType {
let rawValue: Int
init(rawValue: Int){
self.rawValue = rawValue
}
static let boy = Person(rawValue: 1 << 0)
static let young = Person(rawValue: 1 << 1)
static let smart = Person(rawValue: 1 << 2)
static let strong = Person(rawValue: 1 << 3)
}
//later declared
var him: Person!
//later initialised
him = [.boy, .young]
//now test using a switch block
Switch (him) {
case .boy & .young // <----- How do you get this to work?
}
How would test for him == young and strong?
How to test him contains young and boy?
OptionSetType is a subtype of SetAlgebraType, so you can use set algebra methods to test one combination of options against another. Depending on exactly what you want to ask (and what the sets in question are), there may be multiple ways to do it.
First, I'll put the attributes I'm querying for in a local constant:
let youngBoy: Person = [.Young, .Boy]
Now, to use that for one kind of test that works well:
if him.isSupersetOf(youngBoy) {
// go to Toshi station, pick up power converters
}
Of course, what this is specifically asking is whether him contains all the options listed in youngBoy. That might be all you care about, so that's just fine. And it's also safe if you later extend Person to have other options, too.
But what if Person had other possible options, and you wanted to assert that him contains exactly the options listed in youngBoy, and no others? SetAlgebraType extends Equatable, so you can just test with ==:
if him == youngBoy {
// he'd better have those units on the south ridge repaired by midday
}
By the way, you don't want to use a switch statement for this. Switch is for selecting one out of several possible cases (is it A or B?), so using it to test combinatorics (is it A, B, A and B, or neither?) makes your code unwieldy.
Just simple case:
switch him {
case [.boy, .young]:
// code
default:
break
}

Implementing an enum ForwardIndexType

I have been struggling to properly implement the ForwardIndexType protocol for an enum, in particular the handling of the end case (i.e for the last item without a successor). This protocol is not really covered in the Swift Language book.
Here is a simple example
enum ThreeWords : Int, ForwardIndexType {
case one=1, two, three
func successor() ->ThreeWords {
return ThreeWords(rawValue:self.rawValue + 1)!
}
}
The successor() function will return the next enumerator value, except for the last element, where it will fail with an exception, because there is no value after .three
The ForwardTypeProtocol does not allow successor() to return a conditional value, so there seems to be no way of signalling that there is no successor.
Now using this in a for loop to iterate over the closed range of all the possible values of an enum, one runs into a problem for the end case:
for word in ThreeWords.one...ThreeWords.three {
print(" \(word.rawValue)")
}
println()
//Crashes with the error:
fatal error: unexpectedly found nil while unwrapping an Optional value
Swift inexplicably calls the successor() function of the end value of the range, before executing the statements in the for loop. If the range is left half open ThreeWords.one..<ThreeWords.three then the code executes correctly, printing 1 2
If I modify the successor function so that it does not try to create a value larger than .three like this
func successor() ->ThreeWords {
if self == .three {
return .three
} else {
return ThreeWords(rawValue:self.rawValue + 1)!
}
}
Then the for loop does not crash, but it also misses the last iteration, printing the same as if the range was half open 1 2
My conclusion is that there is a bug in swift's for loop iteration; it should not call successor() on the end value of a closed range. Secondly, the ForwardIndexType ought to be able to return an optional, to be able to signal that there is no successor for a certain value.
Has anyone had more success with this protocol ?
Indeed, it seems that successor will be called on the last value.
You may wish to file a bug, but to work around this you could simply add a sentinel value to act as a successor.
It seems, ... operator
func ...<Pos : ForwardIndexType>(minimum: Pos, maximum: Pos) -> Range<Pos>
calls maximum.successor(). It constructs Range<T> like
Range(start: minimum, end: maximum.successor())
So, If you want to use enum as Range.Index, you have to define the next of the last value.
enum ThreeWords : Int, ForwardIndexType {
case one=1, two, three
case EXHAUST
func successor() ->ThreeWords {
return ThreeWords(rawValue:self.rawValue + 1) ?? ThreeWords.EXHAUST
}
}
This is an old Question but I would like to sum up some things and post another possible solution.
As #jtbandes and #rintaro already stated a closed range created with the start...end operator is internally created with start..<end.successor()
Afaik this is an intentional behavior in Swift.
In many cases you can also use an Interval where you thought about using a Range or where Swift declared a Range by default. The point here is that intervals aren't collections.
So this is not possible with Intervals
for word in ThreeWords.one...ThreeWords.three {...}
================
For the following I assume the snippet above was just a debug case to cross-check the values.
To declare an Interval you need to explicitly specify the type. Either a HalfOpenInterval (..<) or a ClosedInterval (...)
var interval:ClosedInterval = ThreeWords.one...ThreeWords.four
This requires you to make your enumeration Comparable. Although Int is Comparable already, you still need to add it to the inheritance list
enum ThreeWords : Int, ForwardIndexType, Comparable {
case one=1, two, three, four
func successor() ->ThreeWords {
return ThreeWords(rawValue:self.rawValue + 1)!
}
}
And finally the enumeration need to conform to Comparable. This is a generic approach since your enumeration also conforms to the protocol RawRepresentable
func <<T: RawRepresentable where T.RawValue: Comparable>(lhs: T, rhs: T) -> Bool {
return lhs.rawValue < rhs.rawValue
}
Like I wrote you can't iterate over it in a loop anymore, but you can have a quick cross-check using a switch:
var interval:ClosedInterval = ThreeWords.one...ThreeWords.four
switch(ThreeWords.four) {
case ThreeWords.one...ThreeWords.two:
print("contains one or two")
case let word where interval ~= word:
print("contains: \(word) with raw value: \(word.rawValue)")
default:
print("no case")
}
prints "contains: four with raw value: 4"

How do I best handle returning a specific object depending on certain conditions when there's no real "else" case to cover everything?

I want to create and return an object in a specific method. I get passed an integer in the method, and I know it will be 0, 1 or 2.
So fundamentally I would structure it like:
if num == 0 {
return 12
} else if num == 1 {
return 24
} else if num == 2 {
return 36
}
Which I know would cover every circumstance.
But the compiler would complain that this doesn't cover every specific circumstance and I'd get an error. I need an else statement of some sort.
But what do I put as the else statement? I can't think of anything valid that would make sense. I could just change the last condition to an else, but it's not as clear later that it actually refers to a 2. It doesn't seem maintainable.
What should I do?
Simply remove the if from the last statement and leave the else only:
if num == 0 {
return 12
} else if num == 1 {
return 24
} else {
return 36
}
But I'd rather use a switch statement:
switch(num) {
case 0: return 12
case 1: return 24
case 2: fallthrough
default: return 36
}
Here the 2 case is handled by the default case, using the fallthrough keyword
An alternative way to achieving the same, taking into account that the possible values are consecutive and starting from 0, is using an array:
let values = [12, 24, 36]
return values[num]
If the function is a class/struct method, the values array can be defined as a static (class) property outside the function, just to avoid instantiating it for each function call.
Note that this solution generates a runtime exception if the num value is not in the [0, 2] range
You can't eat your cake and have it to. If it obvious then an else is fine. If it is not obvious, then you could use an enumeration so that the three options are unambiguously constrained.
For the former, you could use a trailing comment and assertion - that is probably the simplest:
assert(num >= 0 && num <= 2)
if num == 0 {
return 12
} else if num == 1 {
return 24
} else { // case that num == 2
return 36
}
or use an enum type in a switch statement, and the compiler will know that there are only three cases and that you've covered them all. You can use the toRaw() method as needed and base your enum type on an Int to facilitate that.
I imagine your actual case is a little more complicated than the one you presented, but the approaches remain unchanged.
i would do case with exception
case num of
0 -> ...
1 -> ...
2 -> ...
otherwise -> throw exception('unsupported value')
end
compiler will be happy and your code will be clean. in some languages you can go even further and change num to Enumerator. then you will have same structure of case but withoutl the otherwise part. and compiler will warn you every time you decide to add new value for your enumerator but forget to add the new case in your function