I made a Quiz Game in Swift 2 last year, now I need to use it again when I converted it to Swift 3 the answers randomize now... Here is a sample Question Structure...
Questions = [Question(Question: "What is the Biggest Hit of Bing Crosby?" , Answers: ["Swinging on a Star", "Now is the Hour", "White Christmas", "Beautiful Dreamer"], Answer: 2),]
This is where I randomize the questions and put them into the labels
func PickQuestions() {
counter += 1
score += 1
scoreLbl.text = "\(score)"
restartBtn.isEnabled = false
if Questions.count > 0 && counter <= 15 {
QNumber = Int(arc4random_uniform(UInt32(Questions.count)))
QLabel.text = Questions[QNumber].Question
AnswerNumber = Questions[QNumber].Answer
for i in 0..<Buttons.count{
Buttons[i].setTitle(Questions[QNumber].Answers[i], for: UIControlState())
}
Questions.remove(at: QNumber)
}
}
I had to change the following line manually which may have caused an issue from...
QNumber = Int(arc4random_uniform(UInt32(Questions.count)))
to
QNumber = random() % Questions.count
Thanks
Arrays are unordered collections, meaning that their order could potentially change without your knowing. Your best bet would be to create a single array containing a struct that holds both the question and answer, like so:
struct QuizItem {
var question: String!
var answer: String!
var answerOptions: [String]!
init(q: String, a: String, aOptions:[String]) {
self.question = q
self.answer = a
}
}
Change your declaration to look like the following:
let items = [QuizItem(...)]
Your code like this:
func PickQuestions() {
counter += 1
score += 1
scoreLbl.text = "\(score)"
restartBtn.isEnabled = false
if items.count > 0 && counter <= 15 {
QNumber = Int(arc4random_uniform(UInt32(Questions.count)))
let item = items[QNumber]
QLabel.text = item.question
for i in 0..<Buttons.count{
Buttons[i].setTitle(item.answerOptions[i], for: UIControlState())
}
items.remove(at: QNumber)
}
}
Also, kind of picky but worth highlighting still. Swift is a camel case language and although this rule isn't set in stone, you should try and stick to the widely recognised coding practices.
Related
I am trying to provide a summary of items within an ArrayList (where order matters). Basically, I am setting up an exercise plan with two different types of activities (Training and Assessment). I then will provide a summary of the plan after adding each training/assessment to it.
The structure I have is something along the lines of:
exercisePlan: [
{TRAINING OBJECT},
{TRAINING OBJECT},
{ASSESSMENT OBJECT},
{TRAINING OBJECT}
]
What I want to be able to do is summarise this in a format of:
2 x Training, 1 x Assessment, 1 x Training, which will be displayed in a TextView in a Fragment. So I will have an arbitrarily long string that details the structure and order of the exercise plan.
I have tried to investigate using a HashMap or a plain ArrayList, but it seems pretty messy so I'm looking for a much cleaner way (perhaps a MutableList). Thanks in advance!
ArrayList is just a specific type of MutableList. It's usually preferable to use a plain List, because mutability can make code a little more complex to work with and keep robust.
I'd create a list of some class that wraps an action and the number of consecutive times to do it.
enum class Activity {
Training, Assessment
}
data class SummaryPlanStep(val activity: Activity, val consecutiveTimes: Int) {
override fun toString() = "$consecutiveTimes x $activity"
}
If you want to start with your summary, you can create it and later convert it to a plain list of activities like this:
val summary: List<SummaryPlanStep> = listOf(
SummaryPlanStep(Activity.Training, 2),
SummaryPlanStep(Activity.Assessment, 1),
SummaryPlanStep(Activity.Training, 1),
)
val plan: List<Activity> = summary.flatMap { List(it.consecutiveTimes) { _ -> it.activity } }
If you want to do it the other way around, it's more involved because I don't think there's a built-in way to group consecutive duplicate elements. You could a write a function for that.
fun <T> List<T>.groupConsecutiveDuplicates(): List<Pair<T, Int>> {
if (isEmpty()) return emptyList()
val outList = mutableListOf<Pair<T, Int>>()
var current = first() to 1
for (i in 1 until size) {
val item = this[i]
current = if (item == current.first)
current.first to (current.second + 1)
else {
outList.add(current)
item to 1
}
}
outList.add(current)
return outList
}
val plan: List<Activity> = listOf(
Activity.Training,
Activity.Training,
Activity.Assessment,
Activity.Training
)
val summary: List<SummaryPlanStep> = plan.groupConsecutiveDuplicates().map { SummaryPlanStep(it.first, it.second) }
This is what I have set up to work for me at the moment:
if (exercisePlanSummary.isNotEmpty() && exercisePlanSummary[exercisePlanSummary.size - 1].containsKey(trainingAssessment)) {
exercisePlanSummary[exercisePlanSummary.size - 1][trainingAssessment] = exercisePlanSummary[exercisePlanSummary.size - 1][trainingAssessment]!! + 1
} else {
exercisePlanSummary.add(hashMapOf(trainingAssessment to 1))
}
var textToDisplay = ""
exercisePlanSummary.forEach {
textToDisplay = if (textToDisplay.isNotEmpty()) {
textToDisplay.plus(", ${it.values.toList()[0]} x ${it.keys.toList()[0].capitalize()}")
} else {
textToDisplay.plus("${it.values.toList()[0]} x ${it.keys.toList()[0].capitalize()}")
}
}
where trainingAssessment is a String of "training" or "assessment". exercisePlanSummary is a ArrayList<HashMap<String, Int>>.
What #Tenfour04 has written above is perhaps more appropriate, and a cleaner way of implementing this. But my method is quite simple.
I am trying to create a quiz look-a-like app, where the person who holds the phone ask the question, and the other people answer. So there will be two Strings. One with question, and one with the answer. I have created the questions something like this:
var questions = ["Question1", "Question2", "Question3", "Question4", "Question5"]
var answers = ["Answer1", "Answer2", "Answer3", "Answer4", "Answer5"]
When the tap a button, a new question with correct answer pops up. I know how I can display a random string from questions, but how do I connect it to also display the correct answer?
Another option is to use a Dictionary, with the Question as the Key and the Answer as the Value:
let questions: [String : String] = [
"Question1" : "Answer1",
"Question2" : "Answer2",
"Question3" : "Answer3",
"Question4" : "Answer4",
"Question5" : "Answer5"
]
You can then get a random Question & Answer like this:
let randomQuestion = questions.randomElement()
Then access the Question and Answer Text:
let questionText = randomQuestion?.key ?? ""
let answerText = randomQuestion?.value ?? ""
In relation to your next question:
How can I make sure the same question does not show multiple times, and when there are no more questions
You can construct an Array from the Dictionary Keys like this. The keys will be unordered anyway, but you should shuffle them if you want to repeat.
You can then iterate through each question in the randomised Array:
Set your properties in viewDidLoad, not when the button is tapped.
let randomQuestions = questions.keys.shuffled()
var currentQuestionIndex = 0
#IBAction func newQuestionButton(_ sender: Any) {
guard currentQuestionIndex != questions.count else {
return
// or reset your questionIndex and reshuffle.
}
// This will give you the Question (and Key)
let question = randomQuestions[currentQuestionIndex]
// Use the Key to extract out the answer (value) from the Dictionary
let answer = questions[question] ?? ""
// Update your labels
questionLabel.text = question
answerLabel.text = answer
// Increment your question index
currentQuestionIndex += 1
}
You can simply zip together questions and answers and then call randomElement on the result. This will give you a Tuple containing a random question and its respective answer - assuming the indices of questions and answers are in sync.
var questions = ["Question1", "Question2", "Question3", "Question4", "Question5"]
var answers = ["Answer1", "Answer2", "Answer3", "Answer4", "Answer5"]
let questionsAndAnswers = Array(zip(questions, answers))
let randomQA = questionsAndAnswers.randomElement()
You could create a QuizItem type like. Since you always need them together it is good practice to tie them together in one element instead of having two arrays.
struct QuizItem {
var question: String
var answer: String
}
Then you create and array (or list) [QuizItem] and add all items you want. At last you simply take random element of the array.
Here is some pseudo code:
var quizList = [QuizItem]()
quizList.append(...) // add questions & answers
let randomIndex = randomIndex between 0 and quizList.length-1
let item = quizList[randomIndex]
questionLable.text = item.question
answereLabel.text = item.answere
Re:
Finding a value in an array of arrays (similar to VLOOKUP function in Excel) in Swift
The above shows a method for determining the next lowest value in a 2D array given a search value. Reproduced here for convenience:
let testArray: [[Double]] = [
[0,0],
[1000,20.5],
[3000,21],
[3500,22.5],
[3300,21],
]
let income: Double = 3500
var closest = testArray[0][0]
var closestDif = closest - income
for innerArray in testArray {
let value = innerArray[0]
let thisDif = value - income
guard thisDif <= 0 else {
continue
}
if closestDif < thisDif {
closestDif = thisDif
closest = value
guard closestDif != 0 else {
break
}
}
}
print(closest)
The value returned for closest is 3500. Can someone please describe how we then retrieve the corresponding second number in the array [3500, 22.5] i.e. 22.5?
(edit)
Is enumerated(){....} a cleaner way to do this?
Thanks!
You can easily modify Martin R's answer from the linked Q&A to keep the whole inner array in compactMap and then find the maximum based on the first element of each inner array.
let result = testArray.compactMap { $0[0] <= income ? $0 : nil }.max(by: {$0.first! < $1.first!})! // [3500, 22.5]
#David Pasztor thanks your solution works nicely. I’m working swift 3 so I had to substitute “flatMap” for "compactMap" but otherwise it works great and just one line of code! I have used the same technique to also obtain the nearest higher values in the data to the search value (income) and then interpolate to get a value in the second column proportional to the search value income. The interpolation requires guarding against divide by zero when the search value income equals one of the values in the first column in which case the corresponding result0[0],[1] and result1[0],[1] are identical.
let testarray:[[Double]] = [
[0,0],
[1000,20.5],
[3000,21],
[3500,22.5],
[3300,21],
]
let income:Double = 3400
let result0 = testarray.flatMap { $0[0] <= income ? $0 : nil }.max(by: {$0.first! < $1.first!})!
let result1 = testarray.flatMap { $0[0] >= income ? $0 : nil }.min(by: {$0.first! < $1.first!})!
if income - result0[0] < 0.001 {
let interp = result0[1]
print(interp)
}
else {
let interp = result0[1] + (result1[1] - result0[1])*(income - result0[0])/(result1[0] - result0[0])
print(interp) // 21.75
}
sorry I had issues formulating my question.
to explain it better :
I have a = ["alice", "jean", "bob"]
Now i want to let the user choose who will start the game.
If its jean, the new array should be like this
a = ["jean", "bob", "alice"]
So far, this is working :
def sort_array_players(array_player, starter)
sort_array_player = []
array_player.map do |name|
if name == starter && name == array_player[0]
sort_array_player = [array_player[0], array_player[1], array_player[2]]
elsif name == starter && name == array_player[1]
sort_array_player = [array_player[1], array_player[2], array_player[0]]
elsif name == starter && name == array_player[2]
sort_array_player = [array_player[2], array_player[0], array_player[1]]
end
end
puts sort_array_player
end
I want to refractor this code but i'm a bit new to ruby, I've spend 2 hours trying to figure out this thing. My guess is that you need to use each.with_index and then create the new array starting by the first one and the following element would be the one with the index of the starter + 1..
Thanks for helping guys
As #rubish commented, you can use Array#rotate method.
You can use positive integer as count parameter to rotate items counter clockwise or negative integers to rotate them clockwise.
a = ["alice", "jean", "bob"]
starter = "jean"
count = a.index(starter) # => 1
a.rotate(count) # => ["jean", "bob", "alice"]
I would to know how to get key if I have the values. Which class get higher marks?
let higherMarks = [
"ClassA": [10,20,30,40,50,60],
"ClassB": [15,25,35,45,55,65],
"ClassC": [18,28,38,48,58,68],
]
var largest = 0
var className = ""
for (classTypes, marks) in higherMarks {
for mark in marks {
if mark > largest {
largest = mark
}
}
}
print(largest)
What I'm saying in my comment is that you need to get the classTypes when you get the mark. Because when you get the higher mark, you want to also get the corresponding key value.
Keeping your code's logic I would do something like this:
let higherMarks = [
"ClassA": [10,20,30,40,50,60],
"ClassB": [15,25,35,45,55,65],
"ClassC": [18,28,38,48,58,68],
]
func findBestClass(in results: [String: [Int]]) -> (name: String, score: Int) {
var largest = 0
var type = ""
for (classType, marks) in results {
if let max = marks.max(), max > largest {
largest = max
type = classType
}
}
return (type, largest)
}
let best = findBestClass(in: higherMarks)
print("The best class is \(best.name) with a score of \(best.score).")
I just replaced your inner loop with .max() and changed the name of the key variable because it should not be plural. My method also returns a tuple because I find it relevant in this situation. But I didn't change your logic, so you can see what I meant by "also get the classTypes".