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
Related
I was wondering how I can enforce the following restrictions on this struct:
struct QandA {
let questions: [String] // Question to be displayed per page
let mcqs: [[String]] // Prompts to be displayed per page
let answers: [Int] // Answer will be the correct index
}
questions.count == mcqs.count == answers.count
forEach mcq in mcqs, mcq.count == 4 (must only be 4 options per mcq)
forEach answer in answers, answer < 4 (correct index must be 0...3)
The reason for this is that I think it would be very easy to perhaps, add an extra question, or even say the correct answer is 4 when I meant to put 3 (off by 1 error), and also add an extra prompt when there are only meant to be 4 multiple choices per question.
To protect myself from these errors here is what I think I can do:
Option 1
init?(questions: [String], mcqs: [[String]], answers: [Int]) {
guard
questions.count == mcqs.count && mcqs.count == answers.count,
mcqs.allSatisfy({ $0.count == 4 }),
answers.allSatisfy({ $0 < 4 })
else {
return nil
}
self.questions = questions
self.mcqs = mcqs
self.answers = answers
}
Option 2
init(questions: [String], mcqs: [[String]], answers: [Int]) {
assert(questions.count == mcqs.count && mcqs.count == answers.count)
assert(mcqs.allSatisfy({ $0.count == 4 }))
assert(answers.allSatisfy({ $0 < 4 }))
self.questions = questions
self.mcqs = mcqs
self.answers = answers
}
Option 3
Have my Views deal with these issues and have no management code for initialisation; perhaps use try-catch blocks somewhere?
Option 4
Just make sure I don't mess up when creating QandA's
My question is whether I should not allow a QandA instance to be initialised unless these restrictions are met (option 1); or should I add preconditions / asserts to not allow my code to continue if I mess up somewhere (option 2); or should my app just allow these things and me as a coder should take on the full responsibility of making sure the data is consistent, possibly making use of error handling (option 3 & 4). Perhaps using a #propertyWrapper may be the best option? What even are the pro's and con's of these different approaches?
I'm also wondering what is considered "best practice" for situations like this.
Thanks in advance.
If you really want to exclude invalid states, then you could consider modelling your quiz like this:
struct QandA {
let question: String
struct Right {
let value: String
}
struct Wrong {
let value: String
}
enum Options {
case A(Right, Wrong, Wrong, Wrong)
case B(Wrong, Right, Wrong, Wrong)
case C(Wrong, Wrong, Right, Wrong)
case D(Wrong, Wrong, Wrong, Right)
}
let options: Options
}
struct Quiz {
let questions: [QandA]
}
Here you can't have more than 4 (3 wrong + 1 right), and you must have exactly one right answer in the options.
If you find that you have two collections, e.g. [A] and [B] and you need them to be the same size always, you probably mean to say you have an array of pairs of A's and B's, like [(A, B)], and often where you have tuples, like pairs, then there's a named Struct that's waiting to be created.
You almost always want to make your data structure smart so your algorithms don't have to be. Use knowledge of "type algebra" to refactor invalid states out of existence - a classic example you can find online if you search, is the refactoring of the types in the completion handler of URLSession dataTask(with:completionHandler:) - some of which are not valid - into a type that make those invalid states inexpressible. The key idea is that sum types (enum) and product types (structs) can be thought of just like algebra A+B and A*B, and can be literally refactored just the same.
Swift: Option 1
var dictionaryWithoutDuplicates = [Int: Int]()
for item in arrayWithDuplicates {
if dictionaryWithoutDuplicates[item] == nil {
dictionaryWithoutDuplicates[item] = 1
}
}
print(dictionaryWithoutDuplicates.keys)
// [1,2,3,4]
Option 2
let arrayWithDuplicates = [1,2,3,3,2,4,1]
let arrayWithoutDuplicates = Array(Set(arrayWithDuplicates))
print(arrayWithoutDuplicates)
// [1,2,3,4]
For the first option there might be a more elegant way to do it but that's not my point, I just wanted to show an example that has a complexity of n.
Both options return an array without duplicates. Since the first option has a complexity of O(n), I was wondering if the second option even has a complexity and if so what is it?
What you did is pretty much exactly what Set does. A Set<T> is pretty much just a [T: Void] (a.k.a. Dictionary<T, Void>).
Both examples have O(arrayWithDuplicates.count) time and space complexity.
Is there a tidier or better way to write the below nested for...in loops in Swift please? Or is using for...in even the correct way to populate my cards?
for cardNumber in 1...3 {
for cardSymbolIdentifier in 1...3 {
for cardColorIdentifier in 1...3 {
for cardShadingIdentifier in 1...3 {
let card = Card(cardNumber: cardNumber, cardSymbolIdentifier: cardSymbolIdentifier, cardColorIdentifier: cardColorIdentifier, cardShadingIdentifier: cardShadingIdentifier)
deckOfCards.append(card)
}
}
}
}
It definitely does the job, but I can't find anything in the documentation about writing multiple nested loops.
Many thanks in advance,
Andy
There is absolutely nothing wrong with your for loops. They are excellent, well-written Swift. The only problem with your code is that it forces deckOfCards to be mutable (var), which may be undesirable. If it is, you could use a map, but I don't consider this particularly better Swift, just slightly different.
let d = (1...3).flatMap { number in
(1...3).flatMap { symbol in
(1...3).flatMap { color in
(1...3).map { shading in
Card.init(cardNumber: number,
cardSymbolIdentifier: symbol,
cardColorIdentifier: color,
cardShadingIdentifier: shading
)}}}}
I would probably write it this second way, but only for stylistic reasons. Your for loops are absolutely fine.
Note #user28434's comment below. My original version of this had a major bug (it returned the wrong type). I've been writing Swift since the day it was released. I teach Swift. I teach functional programming in Swift. And I screwed it up when writing it on the fly. I would never have made that mistake with a simple for loop. There's a major lesson in there.
If you Do that in a single loop, then it become arithmetically complex
for i in 0..<81 {
deckOfCards.append(
Card(cardNumber: i / 27, cardSymbolIdentifier: i/9 % 3,
cardColorIdentifier: i/3 % 3, cardShadingIdentifier: i % 3)
)
}
or
let deckOfCards = (0..<81).map {
Card(cardNumber: $0 / 27, cardSymbolIdentifier: $0/9 % 3,
cardColorIdentifier: $0/3 % 3, cardShadingIdentifier: $0 % 3)
}
In both example - indexing start with 0, so your class init function should shift indexing little
or
add +1 in every parameter before/after passing
Your code has no optimisation issues at all according to the need but you can make it a little more elegant or swifty (:p)
let values = [1,2,3]
values.forEach { (cardNumber) in
values.forEach { (cardSymbolIdentifier) in
values.forEach { (cardColorIdentifier) in
values.forEach { (cardShadingIdentifier) in
let card = Card(cardNumber: cardNumber, cardSymbolIdentifier: cardSymbolIdentifier, cardColorIdentifier: cardColorIdentifier, cardShadingIdentifier: cardShadingIdentifier)
deckOfCards.append(card)
}
}
}
}
Swift 4.2 introduced a new removeAll {where:} function. From what I have read, it is supposed to be more efficient than using filter {where:}. I have several scenarios in my code like this:
private func getListOfNullDates(list: [MyObject]) -> [MyObject] {
return list.filter{ $0.date == nil }
.sorted { $0.account?.name < $1.account?.name }
}
However, I cannot use removeAll{where:} with a param because it is a constant. So I would need to redefine it like this:
private func getListOfNullDates(list: [MyObject]) -> [MyObject] {
var list = list
list.removeAll { $0.date == nil }
return list.sorted { $0.account?.name < $1.account?.name }
}
Is using the removeAll function still more efficient in this scenario? Or is it better to stick with using the filter function?
Thank you for this question 🙏🏻
I've benchmarked both functions using this code on TIO:
let array = Array(0..<10_000_000)
do {
let start = Date()
let filtering = array.filter { $0 % 2 == 0 }
let end = Date()
print(filtering.count, filtering.last!, end.timeIntervalSince(start))
}
do {
let start = Date()
var removing = array
removing.removeAll { $0 % 2 == 0 }
let end = Date()
print(removing.count, removing.last!, end.timeIntervalSince(start))
}
(To have the removing and filtering identical, the closure passed to removeAll should have been { $0 % 2 != 0 }, but I didn't want to give an advantage to either snippet by using a faster or slower comparison operator.)
And indeed, removeAll(where:) is faster when the probability of removing elements (let's call it Pr)is 50%! Here are the benchmark results :
filter : 94ms
removeAll : 74ms
This is the same case when Pr is less than 50%.
Otherwise, filtering is faster for a higher Pr.
One thing to bear in mind is that in your code list is mutable, and that opens the possibility for accidental modifications.
Personally, I would choose performance over old habits, and in a sense, this use case is more readable since the intention is clearer.
Bonus : Removing in-place
What's meant by removing in-place is the swap the elements in the array in such a way that the elements to be removed are placed after a certain pivot index. The elements to keep are the ones before the pivot element :
var inPlace = array
let p = inPlace.partition { $0 % 2 == 0 }
Bear in mind that partition(by:) doesn't keep the original order.
This approach clocks better than removeAll(where:)
Beware of premature optimization. The efficiency of a method often depends on your specific data and configuration, and unless you're working with a large data set or performing many operations at once, it's not likely to have a significant impact either way. Unless it does, you should favor the more readable and maintainable solution.
As a general rule, just use removeAll when you want to mutate the original array and filter when you don't. If you've identified it as a potential bottleneck in your program, then test it to see if there's a performance difference.
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?