How to temporarily remove swift array members? - ios

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 }

Related

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

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
}

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.

Iterate through an optional array in Swift?

I'm using the cocoapod SQLite for this project like so
import SQLite
var db = try! Connection()
var id: Expression<Int>!
var identifier: Expression<String>!
With it I am reading a list of moves from a SQLite database.
Every monster has a moves that they can learn. Some monsters can learn more moves than others.
var monster: Monster!
var monArray = [Monster]()
var dataSource: DataSource!
To get the monsters move ID I use this code. This allows me to grab the first move in the array. Changing the 0 would get me the second, third move ect.
monster.moves![0]["move_id"] as! Int
Now I'm using the SQLite database because I need to match monster ID values in my plist with the ones in the SQLite database. I use this code to do so
override func viewWillAppear(_ animated: Bool) {
let movesArray = Array(try! db.prepare(Table("moves").where(identifier == moves.name!.lowercased())))
for user in movesArray {
monArray = dataSource.mons.filter{ $0.moves![0]["move_id"] as! Int == user[id] }
}
}
Everything works fine until I try to increase the index range.
for user in movesArray {
for i in 0...6 {
monArray = dataSource.mons.filter{ $0.moves![i]["move_id"] as! Int == user[id] }
}
}
See where I replace the 0 with the range i? I do that because since monsters have more than one move, if I leave it at 0 my app will only display the monsters that learn that move as their first move. To better explain, my current code does not look through whether the monster knows the move, it only looks through whether the monsters knows the move as its first move.
In the above code I increase the range thinking it would solve my issue, but my app will crash because some monsters only have 1 move in their index, so anything above index 0 will crash with the error
fatal error: Index out of range
So to recap, I need to iterate through the entire array instead of just the first index, without it crashing. How can I achieve this?
Without all the story arround your just asking how to iterate over an array like here
for item in array as type {
...
}
Your question seems to be more of a logical question. If I'm understanding what you have said, then each monster will have a minimum of 1 move, but not guaranteed to have more. So you would need to account for this.
Having a fixed limit like you do will certainly cause problems if not all monsters have that many moves as the array will not always be that size.
I would do something like:
let monsterMoveCount = user.moves.count
for i in 0...monsterMoveCount
// Do whatever logic here
Hopefully this helps!

Swift - iterating through characters in string causes memory leak

this is my very first post! I'm relatively new to Swift, and don't have a computer science background, so I'm still prone to a lot of newbie problems - but I've been able to solve them all so far, partly from browsing the excellent answers on StackOverflow. However, I've come across something that's really stumped me, and I've spend a whole week trying to solve it with no luck.
What I'm trying to do is take text in Chinese from a UITextView and then convert this to an array of individual Chinese characters, which is then used for various processing and analysis. However, this causes a leak.
In this greatly simplified example, which reproduces the same leak, there is a TextView and a Button; when the user presses the button, the function makeArray is called, which converts the text to an array of characters (actually Strings of single characters, because I need it to be strings for some of the stuff I will do with it). The class TextProcessing that contains this function is used as a singleton (yeah, I know that apparently singletons are supposed to be bad, for reasons I don't fully understand, but for various reasons involving other parts of the code it works best when there is a single instance of this class), and the text from the UITextView is passed into it, where it's then converted to the array, as you can see below:
class ViewController: UIViewController {
#IBOutlet weak var textBox: UITextView!
#IBOutlet weak var doneButton: UIButton!
#IBAction func pressDoneButton(_ sender: Any) {
let textToAnalyze = textBox.text!
TextProcessing.textProcessing.makeArray(textToAnalyze)
}
}
class TextProcessing {
static let textProcessing = TextProcessing()
private let language = "Chinese"
private var sourceTextArray: [String]!
func makeArray (_ sourceText: String) {
if language == "Chinese" {
sourceTextArray = sourceText.characters.map { String($0) }
} else if language == "English" {
sourceTextArray = sourceText.components(separatedBy: " ")
}
// then do some stuff with this array
}
}
When I run this on the Leaks Instruments I get leaks of "Malloc 16 Bytes" and "CFString", with the number of instances of each being roughly the same as the number of array elements (thus the number of characters in the string). When I look at the Call Tree and drill down, the problem line is "sourceTextArray = sourceText.characters.map { String($0) }".
By the way, this happens with relatively long texts - with short ones, either there's no problem or Instruments doesn't detect it.
However, if I make an array by separating the string into words according to spaces, like I would want in a language like English, there's no leak - so if I change the language variable to "English" in the example code, it works fine (but of course doesn't give me the array that I want). I thought that maybe the problem was in the "map" method, since it uses a closure and it's easy to have leaks with closures, but when I try other ways of putting it into an array of characters, such as using a for loop and iterating over each character that way, it still has the same problem.
If, instead of getting the text from the UITextView, I do this instead:
class ViewController: UIViewController {
#IBOutlet weak var textBox: UITextView!
#IBOutlet weak var doneButton: UIButton!
#IBAction func pressDoneButton(_ sender: Any) {
let textToAnalyze = "blah blah put a long string of Chinese text here"
TextProcessing.textProcessing.makeArray(textToAnalyze)
}
}
there's no problem. Likewise, if in the makeArray function, if I ignore sourceText and instead do this:
func makeArray (_ sourceText: String) {
if language == "Chinese" {
let anotherText = "blah blah some text here"
sourceTextArray = anotherText.characters.map { String($0) }
}
// then do some stuff with this array
}
there's also no leak. So, something about the combination of getting the string from the text box, passing it into the function, and then putting it into an array of characters is causing the leak.
I've spent countless hours scouring the internet and reading everything about ARC in Swift, and I've tried all sorts of stuff with weak/unowned etc. and nothing seems to work. What's going on here and how could this be solved?
Edit:
So it appears that this might just be a problem with Simulator and/or Instruments. When I run it on the device, and just monitor memory usage in xcode debug, there's no increase even when doing it 100+ times, so I guess it's OK...it still seems weird that it would show a leak in Instruments though.
It's instruments bug(there is a lot of issues). Your code is OK.
I just filed a bug report (FB7684067).
The following simple macOS command line application will grow to over 1GB in only a few minutes:
import Foundation
let line = "124;5678;90123"
while true {
let fields = line.components(separatedBy: ";")
assert(fields[1] == "5678")
}

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