Swift: how to always get different value from shuffled array [duplicate] - ios

This question already has answers here:
Get a random unique element from an Array until all elements have been picked in Swift
(5 answers)
Closed 3 years ago.
I created an array of words basically inside of a button, and each time I press my button I get a random item from the array. Now sometimes I get identical items. What if I don't want my items to repeat themselves and I always want to get new items? (Obviously I even want them to repeat their cycle after they all have ben showed once).
#IBOutlet weak var shoppingLabel : UILabel!
#IBAction func shoppingListButton(_ sender: Any) {
var shoppingList = ["Oranges","Apples","Broccoli"].shuffled()
print(shoppingList)
resultLabel.text = shoppingList.first ?? ""
}
this is not a duplicate as the similar question has an array outside of the button and is a var array, mine is a let. With my array I'm unable to remove items from it because it can't be changed, and no, I can't make it a var array...

To cycle through a random array:
Create the array
Shuffle it once
Pick values by cycling through the array from the beginning to the end
To achieve 1) and 2) simply define the array as a constant and shuffle it outside the method in which you want to use it.
To achieve 3) create an additional variable to keep track of which index of the array you are currently at, and increment it after picking a value.
To make sure you don't go beyond the bounds of the array and to achieve the "cycling" through of the array, reset the index to 0 when the index becomes greater than the last index of the array. A simple way to do this, is to use the remainder operator % in Swift.
E.g.
let shoppingList = ["Oranges", "Apples", "Broccoli"].shuffled()
var currentIndex = 0
#IBAction func shoppingListButton(_ sender: Any) {
// pick an item
let nextItem = shoppingList[currentIndex]
// update the label
resultLabel.text = nextItem
// increment the index to cycle through items
currentIndex = (currentIndex + 1) % shoppingList.count
}
To pick random non-repeating values from an array:
Create the array
Pick a random value from the array
If the picked value equals the last value, pick a new one
To achieve 2) use the randomElement() function to pick a random element. This is less computationally expensive than shuffling the entire array and picking the first element each time.
To achieve 3) use a while loop or similar to keep picking random elements until a new one is generated.
E.g.
let shoppingList = ["Oranges", "Apples", "Broccoli"]
#IBAction func shoppingListButton(_ sender: Any) {
// pick a random element that is not equal to the last one
var nextItem: String?
repeat {
nextItem = shoppingList.randomElement()
} while nextItem == resultLabel.text
// update the label
resultLabel.text = nextItem
}

Related

Swift array not maintaining items properly

Bear with me as I try to describe this. I'm creating a trivia game in Xcode which reads questions, answer choices, and the correct answer expected from a file. The file contains 20 questions worth of data separated by an asterisk (*) for each line. The first line is the question, the next four lines are the choices, and the last line is the correct answer (this format is repeated for each question).
In the code I create a string (questionFileContents) which contains all the text from the text file containing the questions.
In my createArrays() method I create a new array which contains each piece of the file as a separate string (determined by where the * is). I create a new array which contains 6 pieces of info (question, choices, and the correct answer) - this array gets loaded into the arrayOfArrays once it is full with the 6 pieces of info and then it moves on to adding a new array with another 6 pieces of info.
Hopefully, that makes sense.
The problem that I am getting is that when I use print(arrayOfArrays.count) it states that I only have 17 items within that array even though I should be getting 20 (for each of the 20 different questions). When I add a bunch of empty text to the text file (equivalent to the number of questions the arrayOfArrays was disregarding) it then disregards that and includes the questions which it had been disregarding before. So... what is causing the arrayOfArrays to not contain the 20 items it should be containing? Is this a compiler error, if not, where is my logic wrong?
I've included my code as well as the text file from which I am reading the question contents.
Thanks in advance!
import UIKit
class QuestionAnswerController: UIViewController {
#IBOutlet weak var textViewForQuestions: UITextView! // the textview which displays the current question text
#IBOutlet weak var questionNumberView: UITextView!
#IBOutlet weak var button1Text: UITextView! // these are the different textviews which correspond to the buttons and answers
#IBOutlet weak var button2Text: UITextView!
#IBOutlet weak var button3Text: UITextView!
#IBOutlet weak var button4Text: UITextView!
#IBOutlet weak var scoreView: UITextView! // textview which indicates the user's score
var questionFileContents : String = "" // blank string which will contain the different contents (questions, choices, answers)
var arrayOfArrays : Array = [[String]]() // array which contains the different arrays with their question components
var currentTrackerOfArrays : Int = 0 // keeps track of which item from the string is being added to the addingToArray array
var currentAnswer : String = "" // keeps track of what the correct answer is for this question
var userScore : Int = 0 // keeps track of the user's current score
var userSelectedAnswer : String = "" // keeps track of the user's current answer provided when they hit the answer button
var currentQuestion : Int = 1
#IBAction func answerButtonPressed(_ sender: UIButton) { // do something when the user presses a button
if sender.tag == 0 { // if the button pressed is (insert num here), do this
// sets the user's selected answer to whatever they chose
userSelectedAnswer = """
A
"""
checkForCorrectAnswer() // checks to see if the answer was correct
}
else if sender.tag == 1 {
userSelectedAnswer = """
B
"""
checkForCorrectAnswer()
}
else if sender.tag == 2 {
userSelectedAnswer = """
C
"""
checkForCorrectAnswer()
}
else if sender.tag == 3 {
userSelectedAnswer = """
D
"""
checkForCorrectAnswer()
}
newQuestionSet() // updates the list of choices as well as the question which is presented to the user
}
override func viewDidLoad() { // upon the view loading, do...
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
createArrays()
updateUI()
newQuestionSet()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func createQuestionsListString() { // takes all content from the questions file and makes a string with the content
if let filepath = Bundle.main.path(forResource: "TriviaQuestions-Formatted", ofType: "txt") { // the main filepath for the file
do {
let contents = try String(contentsOfFile: filepath) // attempts to make string with file
questionFileContents = contents // sets the questionFileContents variable to the contents of the file
} catch let error as NSError { // in the event of an error, do...
// contents could not be loaded
print("Contents could not be loaded.")
print(error.description)
}
}
}
func newQuestionSet() { // sets all the different elements of the user interface for the next question
if arrayOfArrays.count > 0 { // if there is still questions left in the array, do...
let randomQuestionInt = Int(arc4random_uniform(UInt32(arrayOfArrays.count) - 1)) // chooses a random question number
textViewForQuestions.text = arrayOfArrays[randomQuestionInt][0] // sets the text of the question
button1Text.text = arrayOfArrays[randomQuestionInt][1] // these set the different choices' text to be the choices from the question array
button2Text.text = arrayOfArrays[randomQuestionInt][2]
button3Text.text = arrayOfArrays[randomQuestionInt][3]
button4Text.text = arrayOfArrays[randomQuestionInt][4]
currentAnswer = arrayOfArrays[randomQuestionInt][5] // sets the current correct answer
arrayOfArrays.remove(at: randomQuestionInt) // prevents repeated questions
// print(arrayOfArrays.count)
}
else { // in the event that there are no more questions remaining, do...
textViewForQuestions.text = "Finished."
currentAnswer = ""
}
}
func updateUI() { // updates the user interface with the current score
scoreView.text = "Score: \(userScore)"
questionNumberView.text = "Question number: \(currentQuestion)"
}
func checkForCorrectAnswer() {
if userSelectedAnswer == String(currentAnswer) { // if the user selected answer is the same as the correct answer for the question, do...
userScore += 1 // update the score
currentQuestion += 1
updateUI() // update the UI with the new score
}
else {
currentQuestion += 1
updateUI()
}
}
func createArrays() { // creates the arrays for the questions and the array of arrays
createQuestionsListString() // calls the method to make the string from the questions file
let questionPiecesArray = questionFileContents.split(separator: "*") // breaks apart the string based on where an asterix is located (and puts the pieces in an array)
var addingToArray : Array = [String]() // the array which contains the different components of the question (question, choices, correct answer) which will be added to the array of arrays
for _ in questionPiecesArray { // for however many pieces are in the questionPiecesArray, do...
if addingToArray.count >= 6 { // if the array storing the current question gets filled with 6 or more objects, do...
arrayOfArrays.append(addingToArray) // adds the question array to the array containing all the question arrays
addingToArray.removeAll() // empties the question array to make room for new question components
}
else if addingToArray.count <= 6 { // if the array isn't full, do...
addingToArray.append(String(questionPiecesArray[currentTrackerOfArrays])) // addsar the current question component (from questionPiecesArray) to the question array
currentTrackerOfArrays += 1 // moves onto the next piece of information
}
}
print(arrayOfArrays.count)
print(questionPiecesArray.count)
print(arrayOfArrays)
// current problem, the array of arrays is maxing out at holding 17 arrays and won't hold anymore ...
// this problem makes no sense because the print(questionPiecesArray.count) method is showing ...
// that there are 120+ objects in the array so the for loop should add that many objects to the various arrays
// through testing it seems that the arrayOfArrays always has 4 less arrays than it should
// I'll just repeat the last 4 questions again so that they get included in the mix (?)...
// Perhaps it would be better to put in meaningless text for the last 4 blank question templates so that if a glitch occurs it will be more obvious
// Yeah, I'll do that
// Test: it seems to be working well with the empty text
}
}
This is what is contained in the txt file it is reading from:
How do crickets hear?*
Through their wings*
Through their belly*
Through their knees*
Through their tongue*
C*
Which American city invented plastic vomit?*
Chicago*
Detroit*
Columbus*
Baltimore*
A*
In ‘Ben Hur’, which modern thing can be seen during the chariot scene?*
A waitress*
A car*
A postbox*
A street lamp*
B*
What was Karl Marx’s favorite color?*
Brown*
Blue*
Red*
Purple*
C*
What’s the best way to stop crying while peeling onions?*
Lick almonds*
Suck lemons*
Eat cheese*
Chew gum*
D*
How old was the youngest Pope?*
11*
17*
22*
29*
A*
Which animal sleeps for only five minutes a day?*
A chameleon*
A koala*
A giraffe*
A beaver*
C*
How many words in the English language end in “dous"?*
Two*
Four*
Six*
Eight*
B*
One human hair can support how many kilograms?*
Three*
Five*
Seven*
Nine*
A*
The bikini was originally called the what?*
Poke*
Range*
Half*
Atom*
D*
Which European city is home to the Fairy Investigation Society?*
Poznan*
Dublin*
Bratislava*
Tallinn*
B*
What’s a frog’s favourite colour?*
Blue*
Orange*
Yellow*
Brown*
A*
Which one of these planets rotates clockwise?*
Uranus*
Mercury*
Pluto*
Venus*
D*
What perspires half a pint of fluid a day?*
Your scalp*
Your armpits*
Your feet*
Your buttocks*
C*
St Stephen is the patron saint of who?*
Plumbers*
Bricklayers*
Roofers*
Carpenters*
B*
Which country leads the world in cork production?*
Greece*
Australia*
Spain*
Mexico*
C*
On average, what do you do 15 times a day?*
Laugh*
Burp*
Break wind*
Lick your lips*
A*
What colour was Coca-Cola originally?*
Red*
Purple*
Beige*
Green*
D*
Bubble gum contains what?*
Plastic*
Calcium*
Rubber*
Pepper*
C*
The inventor of the paint roller was of which nationality?*
Hungarian*
Canadian*
Norwegian*
Argentinian*
B*
Please note: there were no problems with reading from the text file.
The problem is in your else if statement. Use this for loop to get the right result (I've tested it):
for item in questionPiecesArray {
// always add the current item to the array
addingToArray.append(String(item))
// if it was last for the current question, reset the addingToArray
if addingToArray.count >= 6 {
arrayOfArrays.append(addingToArray)
addingToArray.removeAll()
}
}
Also, using this you won't need currentTrackerOfArrays anymore.

(Swift) moving objects while iterating over array

I am trying to remove some objects from 1 array, and move them to another.
I am doing this by removing them from a reversed array, and adding them to another array, like so:
var array1 = [1,2,3,4,5,6]
var array2 = [1,2]
for (index, number) in array1.enumerated().reversed() {
if(number>2) {
array1.remove(at: index)
array2.append(number)
}
}
The problem is, the objects in array 2 are obviously reversed (1,2,6,5,4,3)
I can easily come up with complicated workarounds, but I was wondering if there are any straightforward ways of doing this.
Thanks in advance!
Rather than appending the numbers insert them
array2.insert(number, at: 2)
You can do the same thing without a loop
let droppedItems = array1.dropFirst(2)
array1.removeLast(array1.count - 2)
array2.append(contentsOf: droppedItems)
If I understand you correctly, you want to move numbers from array1 to array2 if they are higher than 2:
// get only numbers higher than 2 and append them to the second array
array2.append(contentsOf: array1.filter { $0 > 2 })
// filter the moved items from the first array
array1 = array1.filter { $0 <= 2 }
or
// split the array into two parts in place
let index = array1.partition { $0 > 2 }
// move the second part
array2 += array1[index...]
// remove the second part
array1.removeSubrange(index...)
Reverse it, grab subarray then append to array2. You don't need to mutate array1. Something like:
array2.append(contentsOf: array1.reversed()[0..<array1.count-1])

How to temporarily remove swift array members?

I'm a bit new to Swift (so sorry if my question is a bit nooby) but...
So far my program takes strings in an array and displays them on the screen (Xcode)
I would like to implement a mechanism which makes it so that the user cannot get the same string twice in a row (when he/she presses the button).
My idea was to see if the random generated string is equal to the string already displayed on the label and (if thats true), delete the generated string from the array, run the function to display a random string from the array and then add that same string back after displaying a random fact.
Array of facts (in FactModel.swift):
struct FactModel {
var facts = [
"Ants stretch when they wake up in the morning.",
"Ostriches can run faster than horses.",
"Olympic gold medals are actually made mostly of silver.",
"etc..."
]
Function that gets returns a fact and the array index of that fact (In FactModel.swift):
func getRandomFact() -> (String,Int) {
let randomNumber = GKRandomSource.sharedRandom().nextIntWithUpperBound(facts.count)
let checkFact = facts[randomNumber]
return (checkFact,randomNumber)
}
My ViewController code so far:
var mutableFactModel = FactModel()
#IBOutlet weak var FunFactButton: UIButton!
#IBOutlet weak var FunFact: UILabel!
#IBAction func ShowFunFact(sender: AnyObject) {
let localRandomFact = mutableFactModel.getRandomFact().0
if localRandomFact == FunFact.text {
mutableFactModel.facts.removeAtIndex(mutableFactModel.getRandomFact().1)
FunFact.text = mutableFactModel.getRandomFact().0
mutableFactModel.facts.append(mutableFactModel.getRandomFact().0)
} else {
FunFact.text = mutableFactModel.getRandomFact().0
}
}
It doesn't quite work the way I want it to, any ideas on making it work or a whole new way of going about it (not getting the same string twice in a row)?
Shuffle your facts array. Each time the user press the button, take the first element then remove it. Shuffling an array is akin to sort it in random order:
var facts = [
"Ants stretch when they wake up in the morning.",
"Ostriches can run faster than horses.",
"Olympic gold medals are actually made mostly of silver.",
"etc..."
].sort { f in arc4random_uniform(1000) % 2 == 0 }

Basic Xcode - reference in random number generator not working

I am working on a very basic (I think) program in Xcode. I am trying to write an app for "drawing straws" where the last person who is chosen is the loser. There are a few things I'd like to do. First, see the code below:
import UIKit
let players = 4
var playerNames: [String] = ["John", "Tyler", "Pete", "Dave"]
var draw = Int(arc4random_uniform(4))
playerNames.removeAtIndex(draw)
print(playerNames)
let round2 = playerNames.count
var draw2 = Int(arc4random_uniform(2))
playerNames.removeAtIndex(draw2)
print(playerNames)
let round3 = playerNames.count
var draw3 = Int(arc4random_uniform(1))
playerNames.removeAtIndex(draw3)
print(playerNames)
The first thing that's wrong is I'm currently hard-coding the random integer being drawn in var draw = Int(arc4random_uniform(4)). When I try to reference players instead of just typing in 4, I get an error. Can someone please help explain the problem there?
I'll stop there for now to see if I can fix that, and I'll wait until that is fixed before posting a new question. Thank you.
The function arc4random_uniform() takes a UInt32, so you need to declare the variable players as UInt32
let players: UInt32 = 4
var draw = (arc4random_uniform(players))
For handling the round variables, you would cast the count to UInt32.
let round2 = UInt32(playerNames.count)
You can also refactor your code
let players: UInt32 = 4
var playerNames: [String] = ["John", "Tyler", "Pete", "Dave"]
println(playerNames)
for loop in 1...players {
RemovePlayer(&playerNames)
println(playerNames)
}
And the code for the RemovePlayer function
func RemovePlayer(inout names: [String]) {
// get the number of names in the array
var maxNames = UInt32(names.count)
var draw = Int((arc4random_uniform(maxNames)))
names.removeAtIndex(draw)
}
As Blackfrog already stated your randomization function requires a UInt32
Since you stated that you just started programing I would like to give you some other advice. Your code can be written as:
var playerNames = ["John", "Tyler", "Pete", "Dave"]
for var index = playerNames.count ; index > 1 ; index -= 1{
var random:Int = Int(arc4random_uniform(UInt32(index)))
playerNames.removeAtIndex(random)
}
println(playerNames[0])
This has a few advantages. At the moment you are hardcoding the values for how many players join a game. You will need to re-write an unnecessary amount of code if you want to add a player.
Let’s see what the above code does:
We start by declaring the playerNames adding a type declaration of [String] isn’t necessary, swift already knows that it is this type by its initial value.
for var index = playerNames.count; index > 1 ; index -= 1{
We create a new variable called index this will be set to the amount of items in the playerNames array (4 in this case)
After this we will declare that we want the loop as long as our index is greater than 1 this will make it run 3 times and make sure we are left with 1 item in our array
The index -=1 will subtract 1 from the index after each iteration through the loop.
var random:Int = Int(arc4random_uniform(UInt32(index)))
Here we declare a new variable random this will be of type Integer. Let’s work from the inside to the outside. UIt32(index) will convert our index which is of type Int to a type of UInt32, this is needed cause our random function requires a UInt32
Next up we request a random value which will lay between the index and 0 (thus between the bounds of the array). We want to remove the player who belongs to this random value in our array. To do this we need to convert our random Uint32 back to an Int we can do this by using Int().
Next we remove the player at the index using
playerNames.removeAtIndex(random)
Lastly we print the first (and only) item left in the array using
println(playerNames[0])

How to implement functions count and dropLast in swift, IOS?

I am making calculator in Swift. Stuck in backspace button. If user press wrong digit then backspace button would help to delete digit off the display.
Though I wrote dropLast function and works. It return appropriate result. How to use count method, don't understand the return type of count method.
#IBOutlet weak var display: UILabel!
#IBAction func backspace() {
//how to use count method to check collection of elements
//dropLast drop the last digit and display result
let dropedDigit = dropLast(display.text!)
display.text = dropedDigit
}
How about something like this:
private func dropLast(text: String) -> String {
let endIndex = advance(text.endIndex, -1)
return text.substringToIndex(endIndex)
}
It calculates the index where you want to make the cut (endIndex of text - 1) and then returns the substring to this index. This function should drop the last character.
I am not using count method here, but for you reference Swift 1.2 introduces count(<#x: T#>) method that calculates length of sets including Strings.
I know this thread is outdated, but I just went through the process of making this work, myself, in Swift 2.2, and figured I could help answer it.
#IBAction func delButton(sender: AnyObject) {
if display.text != nil {
var tempString = Array(display.text!.characters)
tempString.removeLast(1)
display.text = ""
for num in 0..<tempString.count {
display.text = display.text! + String(tempString[num])
}
}
}
Basically, we're checking to see that the display label has stuff in it, so we don't throw an error, and if so, making a variable in the scope of the function to hold the label's characters individually in a string. After that, we remove the last character from the array, clear out the label to ensure we aren't adding what's already there to our new values, then iterating through the updated array of characters and adding it to the label.
It's important to note that we are casting the values contained in the array as String, because they've been put into the array as character values, which operate differently than the string value the label is expecting.
Like I said, I know the thread is a little out of date, but I've been going through courses in Swift, and have discovered that while there is a plethora of information out there for Objective-C, there is perilously little information out there for how to do a lot of those things in Swift. Since the language is being updated repeatedly, I've noticed a growing divide between the two languages.

Resources