Generics for repeated task with associated data - Swift - ios

I've been trying to wrap my mind around a seemingly simple task, but keep getting nowhere.
My goal is to create a user-choice flow.
Let's say I have a list of food-related questions like :
What is your favourite breakfast? What do you have for dinner? How do you like
your meat cooked? Whats your favourite spaghetti sauce? e.t.c.
And a set of reply options for each question
Q1: <<Pancakes|Waffles>>, Q2: <<Steak|Spaghetti>>, Q3: <<Raw|Welldone>>, Q4: <<Bolognese|Simple cheese>>
How do i load a next question with a set of reply options depending on the users choice in the previous question? But the main trouble is how do i make it generic and data-driven - without the need for a bunch of conditionals.
I've been trying to work with Arrays, NSDictionaries, NSRegularExpressions but can't come up with a proper logical solution.
Any insights are very appreciated!
Thank you in advance.

An alternative to dictionaries would be a custom class. I think it improves readability but you may have your own opinion.
class Question {
var ask: String
var answers: [String]
var nextQuestions = [Question?]()
init(question: String, ans: [String]) {
self.ask = question
self.answers = ans
}
func nextQuestion(answer: String) -> Question? {
var result: Question? = nil
if let index = find(self.answers, answer) {
result = self.nextQuestions[index]
}
return result
}
}
// Set up your test data
let q1 = Question(question: "What is your favourite breakfast", ans: ["Pancakes", "Waffles"])
let q2 = Question(question: "What do you have for dinner", ans: ["Steak", "Spaghetti"])
let q3 = Question(question: "How do you like your meat cooked", ans: ["Raw", "Welldone"])
let q4 = Question(question: "What's your favourite spaghetti sauce", ans: ["Bolognese", "Simple cheese"])
// This is quick and dirty.
// It would be better to have a func to hide the implementation.
q1.nextQuestions.append(q2)
q1.nextQuestions.append(q2)
q2.nextQuestions.append(q3)
q2.nextQuestions.append(q4)
// Pretend "Spaghetti" was the answer for q2
var theQuestion = q2
let userAnswer = "Spaghetti"
if let another = theQuestion.nextQuestion(userAnswer) {
theQuestion = another
}

An obvious solution would be a dictionary of questions which holds another dictionary of related answers and their possible (follow up) questions. Something like:
[Question: [Answer:Question]]
The question in the second dictionary then refers (recursive) to a question in the first one.

Related

What is better in this case: extension or function? [closed]

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 9 months ago.
Improve this question
I have a ViewController where there's a logic for the "known" and "unknown" location.
At a high level it looks like this:
class MyViewController {
private var myVar: CLLocationCoordinate2D? // is updated somewhere
private var myFunc1() {
let someCond = myVar == nil // "isLocationUnknown" logic
}
private var myFunc2() {
guard let myVar == nil else { return } // "isLocationUnknown" logic
}
}
Now there's a requirement to handle the "invalid location" case.
Which means in addition to the nullability check, the CLLocationCoordinate2D check should be performed.
And I can't decide what is better ( as I don't know where to learn about Swift implementation details except of reading the sources :) ) :
Approach #1:
private func isLocationUnknown(_ location: CLLocationCoordinate2D?) -> Bool {
guard let location = location else {
return true
}
return !CLLocationCoordinate2DIsValid(location)
}
Approach #2:
private extension Optional where Wrapped == CLLocationCoordinate2D {
var isUnknown: Bool {
guard let self = self else {
return true
}
return !CLLocationCoordinate2DIsValid(self)
}
}
The criterias of comparison:
semantics: I guess #2 is more "swifty"/expressive etc
compilation time: can there be any difference (at scale)?
run-time performance: can there be any difference (at scale)?
I believe in this particular case all criterias are not important, but if this enum would be public, and called e.g. many times per second, or within multiple places in the codebase, then I'd like to be more confident when making the decision.
A class and func are reference types and stored on the heap and is therefore slower to access. Since the type Optional is an enum it is stored on the stack and will be quicker to access. But in this case it would probably not be a noticeable difference. Do which ever you feel is right.
If you want to read about memory management, here's a good article:
https://manasaprema04.medium.com/memory-management-in-swift-heap-stack-arc-6713ca8b70e1m
And here is a question about stack vs. heap memory:
Swift stack and heap understanding
EDIT:
Another thought is that your first approach takes CLLocationCoordinate2D as a parameter which creates a new copy of CLLocationCoordinate2D. So the space complexity is probably larger as well. But if that's the case, minuscule.

Assign value with optional question mark

I'm currently learning Swift from basics. Now I'm on optionals and I'm trying to understand this case:
var text: String? = nil
text? = "some text"
What happens exactly if we assign value with question mark? I don't understand why the value of text is nil. Can you explain me what is the difference between assigning text = "some text" and text? = "some text"?
You're right to be surprised. text? = means "If text is nil don't perform the assignment."
You've stumbled across a highly obscure language feature. See https://ericasadun.com/2017/01/25/pretty-much-every-way-to-assign-optionals/
(As she rightly says, you can count on the fingers of zero hands the number of times you'll ever actually talk like this, because who would ever want to assign a value only just in case the lvalue is already non-nil?)
NOTE I prefer to look at this as a zero-length variant of optional chaining. It is extremely useful to be able to say e.g.
self.navigationController?.hidesBarsOnTap = true
meaning, if self.navigationController is nil, fuhgeddaboudit. Well, your use case is sort of a variant of that, with nothing after the question mark. Most people are unaware that if the last object in the chain is an Optional, the chain can end in a question mark. The expressions in f are all legal:
class C {
struct T {
var text : String?
}
var t : T?
func f() {
self.t?.text = "howdy"
self.t? = T()
self.t?.text? = "howdy"
}
}
But only the first one, self.t?.text =, is common to say in real life.

Quiz: How to integrate an alternative/a similar answer as declared correct in a UITextField

Dear StackOverflowers,
I did a search but couldn't find anything in here or Google. As a total beginner I hope not to beeing punished with these minus ratings :)
Imagine a little quiz. At App Start there's a randomly generated String from a Dictionary telling the correct answer. Let's say it's this Dictionary:
let dictionary: Dictionary = [0: "One", 1: "Two", 2: "Three", 3: "Four", 4: "Five"]
And now I'm generating a random String from it:
var randomNumber: Int = 0
var randomString: String = ""
randomNumber = Int(arc4random_uniform(UInt32(dictionary.count)))
randomString = Array(dictionary.values)[randomNumber]
Now there's a Question and the code knows that the correct answer is randomString.
I have a TextField for the user to guess the answer. Type your answer, click on „guess“ and then there's a result displayed in a label, generated with...
if textField.text == randomString {
resultLabel.text = "Correct!"
} else {
resultLabel.text = "Wrong!" }
Question: Is it possible to make partially correct answers also being displayed as „Correct!“? Let's say the user types „4“ instead of „Four“. Or in another case the correct answer would be „Harry and the Turtle Tubes“ and the user just types „Harry and the Turtles“ (shorter) or „harry and the turtle tubes“ (all words written in lower case).
I'm thankful for all your hints and code snippets.
Have a great day
Patrick
What you want to search for is "fuzzy string matching". You can read about it on Wikipedia.
There are a number of Swift libraries that exist that implement this. You will need to find one that best suits your needs.
Good luck!

Passing a Swift variable from one IBAction to another [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 7 years ago.
Improve this question
In my application I have a "Play" button which creates two random numbers and creates a string for those numbers, which is then used to fill in the two UIImage holders for cards (cards 0-10 which are set in a Array). Then the two random numbers are added together.
There is an input field where the user can input their guess and then hit "Submit". If their answer is correct it will flash "Correct". If the answer is wrong, it will flash Try Again. My question is how can I get the total variable passed to the Submit so I can successfully create my if else statements?
So here is the end code to the Play button Action which works perfectly
let total = firstRandomNumber + secondRandomNumber
Here is the code for the Submit button which is giving me an error
#IBAction func submit(sender: UIButton) {
correct.hidden = (true)
tryagain.hidden = (true)
let guess = Double(text.text!)
if guess = total {
correct.hidden = (false)
}
The error I'm getting is "undefined variable total"
So the variables are not going to be PASSED to Submit() because that's the function the button calls. The button can careless about your two variables. Instead you'll want to have two Global variables (in this case the two random numbers) and request them in Submit. These are variables that was created outside any functions and are set when they need to be set. So lets see how you'd do that with your code:
// Two random numbers (global)
var a: Int?
var b: Int?
func generateRandomNumber() {
a = // assign a to the number generator value
b = // same as above
}
func submit() { // your button call
if let userGuess = text.text { // do if let instead of ! it's safer
let total = a + b
if userGuess == total { // our check remember == is different from =
correctView.hidden = false
} else { // if it's wrong show this view
tryAgainView.hidden = false
}
}
}
Obviously there's a bunch missing but you can get the main idea. Let me know if these anything I can answer about this method.

Displaying random questions by looping through a dictionary

I started to learn swift by trying to make a quiz game. But i'm stuck at trying to display random questions from an dictionary. The reason why i use a dictionary is because the quiz i'm working on is based on chemistry elements which has different symbols.
So Based on the selected difficulty i created four different dictionaries:
//Defining the dictionaries per difficulty
var easy: [String: String] = [
"H": "Waterstof",
"He": "Helium",
"Li": "Lithium",
"B": "Boor",
"C": "Koolstof",
"O": "Zuurstof",
"Ne": "Neon",
"Al": "Aliminium",
"Si": "Silicium",
"K": "Kalium",
"Fe": "Ijzer",
"Ni": "Nikkel",
"Zn": "Zink",
"Cd": "Cadmium",
"I": "Jood"
]
var normal: [String: String] = [
"N": "Stikstof",
"F": "Fluor",
"Na": "Natrium",
"Mg": "Magnesium",
"P": "Fosfor",
"CI": "Chloor",
"Ar": "Argon",
"S": "Zwavel",
"Ca": "Calcium",
"Cu": "Koper",
"Au": "Goud",
"Br": "Broom",
"Ag": "Zilver",
"Pt": "Platina",
"Ba": "Barium"
]
and so on.. (hard, extreme)
This is my viewDidLoad() method:
override func viewDidLoad() {
switch (self.selectedDifficultyByUser){
case "Makkelijk":// Means 'easy' in dutch
//Assigning the easy dictionary to a temporary dictionary
self.temp = self.easy
case "Normaal":
//Assigning the normal dictionary to a temporary dictionary
self.temp = self.normal
case "Moeilijk":
//Assigning the hard dictionary to a temporary dictionary
self.temp = self.moeilijk
case "Extreem":
//Assigning the extreme dictionary to a temporary dictionary
self.temp = self.extreem
default:
println("Something went wrong")
}
super.viewDidLoad()
self.answers = [self.btn1, self.btn2, self.btn3, self.btn4]
pickRandomElement()
randomizeAnswers()
let updateTime : Selector = "updateTime"
timer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: updateTime, userInfo: nil, repeats: true)
startTime = NSDate.timeIntervalSinceReferenceDate()
}
As you can see i'm assigning all my buttons to the array 'answers' which i will be looping through soon.
The function pickRandomElement will then be called, which looks like this:
func pickRandomElement() -> () {
//First here we make sure that the 'answerOptions' Array is cleared,
//Because it could cause that the correct answer won't be showed.
answerOptions = [String]()
if self.selectedDifficultyByUser == "Makkelijk" {
let index: Int = Int(arc4random_uniform(UInt32(easy.count)))
let symbol = Array(easy.keys)[index]
let element = Array(easy.values)[index]
self.currentQuestion = symbol
self.correctAnswer = element
//Assign the correctAnswer to the answerOptions array
self.answerOptions.append(element)
//Show the question to the user
self.questionLabel.text = self.currentQuestion
//remove the correctanswer from the dictionary, to
//make sure that the answers won't be duplicated
self.easy[symbol] = nil
self.easy[element] = nil
for (var i = 0; i < 3; i++) {
let randomIndex: Int = Int(arc4random_uniform(UInt32(easy.count)))
let optionSymbol = Array(easy.keys)[randomIndex]
let optionElement = Array(easy.values)[randomIndex]
self.answerOptions.append(optionElement)
//Removing 'optionSymbol' and 'optionElement' from the array
//to prevent duplicated answer
self.easy[optionElement] = nil
self.easy[optionSymbol] = nil
}
self.easy = self.temp //Filling the 'easy' array with the temporary array
}
}
The problem i'm having is in the for loop shown above. I'm looping three times to pick random elements and show them to the user. And during this process i'm deleting the randomly chosen elements from the (easy) array to prevent duplicate answer.
And because i'm removing those elements from the easy array i'm assigning the temporary array, which i created in the begin, back to the easy array.
If i don't do this, the whole easy array will be empty after three rounds or so.
If i do the opposite i will get an infinte loop.
Can someone please put me in the right direction if i'm doing this the wrong way or help me out of this problem?
Thanks in advance!
UPDATED
Firo's solution worked perfectly for me. But i'm know faced with another bug. Namely, the pickRandomElement function returns sometimes the asked question more than once. I tried to make another dictionary and put the asked questions in there and check to see if that question has already been asked. However, as the number of questions answered increases, this would require a linearly increasing amount of lookups for each question asked, until all lookups is reached. Does someone know how i can take care of this problem? –
So without all of your code I can only give you an educated guess as to a good way of handing your infinite loop situation with your current code.
An easy way to prevent an infinite loop here is to verify you still have remaining questions you want to ask. Keep an instance value to check how many questions you still want to ask.
var remainingSteps = 5
Then when the user answers the correct question, check it before presenting the next question
func userAnsweredQuestionCorrectly() {
if self.remainingSteps > 0 {
// Present next question
// Decrement remaining steps
self.remainingSteps--
else {
// Done asking questions, back to menu (or whatever is next)
}
}
Firo's solution is awesome. This one might help you with getting a unique question each time to avoid repetition. Let me know here or on codementor if you have any questions.

Resources