keeping two arrays in sync? - ios

I am currently trying to keep my two arrays in sync. One question label and a one text label..
For my question label, I used arc4Random to pick a random question from my array. However, in order to set the label just above it in the UI, I need it to match my other array.. (titleQuestions)
How do I pull out the titleQuestion by using the arc4random number I generate from the questions array..
Sorry in advance, I am relatively new to Swift and still learning.. first year..
//just vars to get data from my two models(i.e two arrays)
var questionArray = DataModel()
var tArray = TitleLabel()
// this here is questions for the lower UI label. random No generate here!
func questions() {
let questions = questionArray.questions
let randomQuestions = questions.index(questions.startIndex , offsetBy : questions.count.arc4random)
questionLabel.text = questions[randomQuestions]
}
// the questions marks is because I dont know what to index here because I want this number but cant seem to get it!
func titles() {
let titlesArray = tArray.titleQuestions
titleLabel.text = titlesArray[?????]
}
// When this button is pressed both UIlabel will be updated with a random question and the associated title
#IBAction func questionsButton(_ sender: UIButton) {
questions()
titles()
}
I understand this could might be a stupid question but I have wasted a better part of a week trying to work this out.. any help is encouraged! thought about using a closure to take the questions function but really dont know how to implement it..

Welcome to SO. this is a trivial problem, and one you should really work out on your own, but it sounds like you're stuck, so I'll take pity on you.
At the simplest, take the code that generates a random number out of the line that fetches an item, and instead assign the random number to a temp variable, and set both labels from a single function:
func questionsAndTitles() {
let randomInt = Int(arc4random_uniform(Uint32(questions.count)))
let questions = questionArray.questions
questionLabel.text = questions[randomInt]
let titlesArray = tArray.titleQuestions
titleLabel.text = titlesArray[randomInt]
}
(Assuming questions is of type Array, there's no reason to create an Index object in order to fetch an item from the array. Arrays allow integer subscripting.)
However, it might make more sense to create an array of structures that contain both a question and an answer. That way you would fetch an instance of a question-and-answer struct from your array and use it to populate both.
Edit:
If you wanted to create an array of structs it might look like this:
struct QuestionStruct {
let question: String
let possibleAnswers: [String]
let correctAnswerIndex: int
}
var questions: [QuestionStruct] =
[
QuestionStruct(
question: "What is the airspeed velocity of an unladen swallow?",
possibleAnswers: ["3 ft/sec", "2.2 ft/sec", "European or African"],
correctAnswerIndex: 2),
QuestionStruct(
question: "What is Your favorite color?",
possibleAnswers: ["Blue", "Green", "Blue. No, Green! Ieee!"],
correctAnswerIndex: 2)
]
//And then you might use code like this:
let randomIndex = Int(arc4random_uniform(UInt32(questions.count)))
let aQuestion = questions[randomIndex]
let correctAnswerIndex = aQuestion. correctAnswerIndex
questionLabel.text.text = aQuestion.question
correctAnswerLabel.text = aQuestion.possibleAnswers[correctAnswerIndex]

You can store your picked random number in a class variable so it is accessible both from your questions() and titles() methods.
Use a property declared at the class scope :
var lastRandomSeed: Int?
Reuse it in both of your methods :
if let lastRandomSeed = lastRandomSeed {
let randomQuestions = questions.index(questions.startIndex , offsetBy : lastRandomSeed)
lastRandomSeed = nil
} else {
lastRandomSeed = questions.count.arc4random
let randomQuestions = questions.index(questions.startIndex , offsetBy : lastRandomSeed)
}
and :
if let lastRandomSeed = lastRandomSeed {
titleLabel.text = titlesArray[lastRandomSeed]
lastRandomSeed = nil
} else {
lastRandomSeed = questions.count.arc4random
titleLabel.text = titlesArray[lastRandomSeed]
}
This also ensures you can call your methods in both order.
A better approach is to refactor your code, use computed property that will update the view :
var lastRandomSeed: Int = -1 {
didSet {
let questions = questionArray.questions
let randomQuestions = questions.index(questions.startIndex , offsetBy : lastRandomSeed)
let titlesArray = tArray.titleQuestions
questionLabel.text = questions[lastRandomSeed]
titleLabel.text = titlesArray[lastRandomSeed]
}
}
Just write it in your IBAction :
let questions = questionArray.questions
lastRandomSeed = questions.count.arc4random

You had to discipline yourself first. Try to name your variables, functions and even local variables by it's purpose. So at any point you can understand whats this variable/function/Type is used for.
So in you example I can assume that DataModel type is subclass of an array, because you assigning it's instance to variable questionArray. It is not very straightforward, but more or less ok (however better to name it DataArray). But then one who looking at you code can realise that your array not array. It is object that have array of questions in it. So you either had to name it let questionArrayHolder = DataModel() or fill it with questions: let questionArray = DataModel().questions.
But then you assign instance of TitleLabel to variable tArray. t can mean anything, and it definitely doesn't look like array. If you want to create array with single object in it, declare it titleArray = [TitleObjects]
Then you have function question()
It's name naturally suggests that it had to return some questions. But it returns nothing. If you want to generate questions name you function appropriate: func generateQuestions() . But your function do not generate questions, it picking up one question and assigning it to title. So you had name it like func pickupRandomQuestion().
You have lot of opportunities to share soothing between functions. You can declare member variable var selectedQuestion: Int? = 0 and assign your arc4rand number to it before retrieving you question.
You can find index of you question in array, assuming that all questions are unique, questions.index(of: questionLabel.text)
And you can return question number from you function:
func pickupRandomQuestion() -> Int {
let randNumber = questions.count.arc4random // I'm assuming that you have that function, and it works for you as you expect
let randomQuestion = questions.index(questions.startIndex , offsetBy : randNumber)
questionLabel.text = questions[randomQuestion]
return randNumber
}
And then supply it to other function
func pickupTitleForQuestion(at index: Int) {
titleLabel.text = titlesArray[index]
}

Related

Array of objects from multiple loops [duplicate]

This question already has answers here:
How can I run through three separate arrays in the same for loop?
(6 answers)
Closed 4 years ago.
I would like to end up with an array of custom objects. I'm having trouble figuring out a way to go about this when I have multiple arrays to iterate through in order to create the custom objects. I have something like this:
class CustomObject {
var name = ""
var place = ""
var price = ""
}
var nameArray = [“name1”,"name2","name3"]
var placeArray = [“place1”,"place2","place3"]
var priceArray [“price1”,"price2","price3"]
var customArray [CustomObject]()
createObject(name : nameArray, place : placeArray, price : priceArray)
func createObject(name : [String], place : [String], price : [String]) {
let instance = CustomObject()
for names in name {
instance.name = names
}
for places in place {
instance.place = places
}
for prices in price {
instance.price = prices
customArray.append(instance)
}
}
I would like to end up with an array of those custom "instances". Right now my code is giving each attribute the last index of each array.
You can try , also i suppose all the arrays have the same count
var customArray = [CustomObject]()
for i in 0..<nameArray.count {
let instance = CustomObject()
instance.name = nameArray[i]
instance.place = placeArray[i]
instance.price = priceArray[i]
customArray.append(instance)
}

Pulling a random item from an array on a timer

I have an array of strings. I would like to display 3 unique items from this array randomly. Then every 5 seconds, one of the items gets replaced with another unique item (my idea here is adding an animation with a delay).
I can display the 3 strings, however sometimes they repeat, and the timer is not updating the factLabel label.
Here's my progress:
override func viewDidLoad() {
super.viewDidLoad()
updateUI()
}
func randomFact() -> String {
let arrayCount = model.cancunFacts.count
let randomIndex = Int(arc4random_uniform(UInt32(arrayCount)))
return model.cancunFacts[randomIndex]
}
// Display the facts
func updateUI() {
Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(randomFact), userInfo: nil, repeats: true)
factLabel.text = randomFact() + " " + randomFact() + " " + randomFact()
}
How do I get the text to always update randomly, without the 3 facts repeating?
Create an array of indexes. Remove a random index from the array, use it to index into your strings. When the array of indexes is empty, refill it.
Here is some sample code that will generate random, non-repeating strings:
var randomStrings = ["Traitor", "Lord Dampnut", "Cheeto-In-Chief",
"F***face Von Clownstick", "Short-Fingered Vulgarian",
"Drumpf", "Der Gropenführer", "Pumpkin in a suit"]
var indexes = [Int]()
func randomString() -> String {
if indexes.isEmpty {
indexes = Array(0...randomStrings.count-1)
}
let index = Int(arc4random_uniform(UInt32(indexes.count)))
let randomIndex = indexes.remove(at: index)
return randomStrings[randomIndex]
}
for i in 1...100 {
print (randomString())
}
(Note that it may still generate repeating strings in the case when the array of indexes is empty and it needs to be refilled. You'd need to add extra logic to prevent that case.)
Version 2:
Here is the same code, modified slightly to avoid repeats when the array of indexes is empty and needs to be refilled:
var randomStrings = ["tiny-fingered", "cheeto-faced", "ferret-wearing", "sh*tgibbon"]
var indexes = [Int]()
var lastIndex: Int?
func randomString() -> String {
if indexes.isEmpty {
indexes = Array(0...randomStrings.count-1)
}
var randomIndex: Int
repeat {
let index = Int(arc4random_uniform(UInt32(indexes.count)))
randomIndex = indexes.remove(at: index)
} while randomIndex == lastIndex
lastIndex = randomIndex
return randomStrings[randomIndex]
}
for i in 1...10000 {
print (randomString())
}
Even though it's using a repeat...while statement, the repeat condition will never fire twice in a row, because you'll never get a repeat except right after refilling the array of indexes.
With that code, if there is a repeat, the selected string will be skipped on that pass through the array. To avoid that, you'd need to adjust the code slightly to not remove a given index from the array until you verify that it is not a repeat.
Version 3:
Version 2, above, will skip an entry if it picks a repeat when it refills the array. I wrote a 3rd version of the code that refills the array, removes the last item it returned so that it can't repeat, and then adds it back to the array once it's picked a random item. This third version will always return every item in the source array before refilling it and will also never repeat an item. Thus it's truly random with no bias:
import UIKit
var randomStrings = ["Traitor", "Lord Dampnut", "Cheeto-In-Chief",
"F***face Von Clownstick", "Short-Fingered Vulgarian",
"Drumpf", "Der Gropenführer", "Pumpkin in a suit"]
var indexes = [Int]()
var lastIndex: Int?
var indexToPutBack: Int?
func randomString() -> String {
//If our array of indexes is empty, fill it.
if indexes.isEmpty {
indexes = Array(0...randomStrings.count-1)
print("") //Print a blank line each time we refill the array so you can see
//If we have returned an item previously, find and remove that index
//From the refilled array
if let lastIndex = lastIndex,
let indexToRemove = indexes.index(of: lastIndex) {
indexes.remove(at: indexToRemove)
indexToPutBack = indexToRemove //Remember the index we removed so we can put it back.
}
}
var randomIndex: Int
let index = Int(arc4random_uniform(UInt32(indexes.count)))
randomIndex = indexes.remove(at: index)
//If we refilled the array and removed an index to avoid repeats, put the removed index back in the array
if indexToPutBack != nil{
indexes.append(indexToPutBack!)
indexToPutBack = nil
}
lastIndex = randomIndex
return randomStrings[randomIndex]
}
for i in 1...30 {
print (randomString())
}
Sample output:
Short-Fingered Vulgarian
F***face Von Clownstick
Pumpkin in a suit
Drumpf
Lord Dampnut
Traitor
Der Gropenführer
Cheeto-In-Chief
Der Gropenführer
Drumpf
Lord Dampnut
Short-Fingered Vulgarian
Cheeto-In-Chief
Pumpkin in a suit
Traitor
F***face Von Clownstick
Short-Fingered Vulgarian
F***face Von Clownstick
Drumpf
Traitor
Cheeto-In-Chief
Lord Dampnut
Pumpkin in a suit
Der Gropenführer
Lord Dampnut
Short-Fingered Vulgarian
Pumpkin in a suit
Cheeto-In-Chief
Der Gropenführer
F***face Von Clownstick
Your timer is calling random fact, which simply returns a fact and doesn't do anything. You should probably have some third method called initializeTimer that does the Timer.scheduledtimer, which you should take out of your updateUI method. That timer should call updateUI. This would fix your labels updating. Also you would call initializeTimer in your viewDidLoad instead of updateUI. As far as preventing the repetition of facts, Duncan C's idea of having a separate array that you remove items from as you set new random facts then fill back up when it's empty seems like a good idea.
It may be easiest to maintain two arrays, usedStrings and unusedStrings, of random strings, like this:
var unusedStrings: [String] = ["king", "philip", "calls", "out", "for", "good", "soup"]
var usedStrings: [String] = []
func randomString() -> String {
if unusedStrings.isEmpty() {
unusedStrings = usedStrings
usedStrings = []
}
let randomIndex = Int(arc4random_uniform(unusedStrings.count))
let randomString = unusedStrings[randomIndex]
usedStrings.append(randomString)
return randomString
}

How to sort NSmutableArray from one of it indexes (lat lng coordinates) in Swift

I'm new in swift and I'd know how to do that in php, but I'm lost with all those dictionaries and I have no idea how to do that in swift 2. I've been googling for a while and didn't found what I need.
I'm parsing a jSon and storing it's values in an NSMutableDictionary in a loop and at the end of the loop I store the NSMutableDictionary in an NSMutableArray, so at the end I have an NSMutableArray with 43 elements, and each element have about 10 keys with their values. I need to sort those 43 elements from their "distance" key and sort them descending. I don't know if that is posible with this current approach. The value of the key "distance" is an int number (meters). I don't know if to use an NSMutableDictionary inside an NSMutable Array is the correct approach to do this but I'm using it because it is possible to have string keys, and not numbers indexes, so for me it's easier to access the key "distance" than the index 8...
First I load the jSon content:
private func parseJson(json : NSMutableArray, tableView : UITableView){
var c : Int = 0
for j in json {
var jsonValues = NSMutableDictionary()
//Create main value
guard let value = j.valueForKey("value")?.valueForKey("value")! else{
continue
}
//Get name
guard let Name : String = (value.valueForKey("Name")?.valueForKey("en") as? String) else {
continue
}
jsonValues["name"] = Name
//more code like this....
TableData.append(Name)
nsDict.insertObject(jsonValues, atIndex: c)
c += 1
}
this is my NSMutableArray content after being loaded:
And this is the code I have this far. Im trying to load the sorted content in a new array, but in this new array some keys are missing.
//Reorder Array by shop distance from user...
var sortDescriptor:NSSortDescriptor = NSSortDescriptor(key: "distance", ascending: true)
var sortedArray : NSArray = nsDict.sortedArrayUsingDescriptors([sortDescriptor])//Crashes
print(sortedArray)
I've managed to sort the array with this technique:
created a new class with for my array
import Foundation
class JsonArrayValues {
init(){
}
var name = String()
var distance = Float()
var lat = Float()
var lng = Float()
var openingTime = String()
var country = String()
var code = String()
var address = String()
var categories = String()
var city = String()
var type = String()
var brands = String()
}
I instantiated one before the loop:
var jsonArrData : [JsonArrayValues] = []
And another one inside the loop, in which I've added the values:
var c : Int = 0
for j in json {
var jsonValues : JsonArrayValues = JsonArrayValues()
//Get name
guard let Name : String = (value.valueForKey("Name")?.valueForKey("en") as? String) else {
continue
}
jsonValues.name = Name
//more code...
jsonArrData.append(jsonValues)
c += 1
}
And finally I've been able to call the function to reorder the array:
//Reorder Array by shop distance from user...
jsonArrData.sortInPlace({$0.distance < $1.distance})
One of your first steps in any non-trivial project really should be to spend some time looking around on github for tools that simplify your problem. In this case, you'd find there are so very many tools to simplify working with JSON in Swift. I'd suggest you look at EVReflection and Gloss particularly, although there are also many people who use SwiftyJSON.
You don't mention how you're accessing the network; you could look at AFNetworking or Alamofire. The latter also has AlamofireJsonToObjects to help.
I also found JSONExport to be incredibly useful.
You'd be spending a lot less time futzing with details as in this unfortunate question and more getting on with your larger goal.

Swift, get variable name from a string

I have a question. I know that this can be done in Ruby, not sure about Swift, but never did anything similar.
What I'm working on is a program that works similar to this: if the user writes in a TextView "a + b", my code should elaborate the value of the sum between the variable "a" and "b".
What I mean is, how can I get variable names from a string? Is it possible at all?
If it's not, I'd use a switch (variable names are limited), but I'd need to "connect" a variable to the one written by the user. In the example, if the user writes "a + b", my sum function is something like "sum(par1,par2)", so, how can I make variable "par1" point to "a" and "par2" point to "b"? I can do that with pointers right? Is there any other way?
I'm not sure I've been clear, but it's quite hard to explain
Check this out. You can do with NSExpression and KVC.
class myclass:NSObject {
var a = 10;
var b = 20;
override init() {
super.init();
}
}
var obj = myclass();
// Expression from user
var expr:NSString = "a+b" ;
//Operators..
let opSet = NSMutableCharacterSet()
opSet.addCharactersInString("+-*/");
let components:NSArray = expr.componentsSeparatedByCharactersInSet(opSet)
let uniqueChars:NSSet = NSSet(array: components)
for variable in uniqueChars
{
expr = expr.stringByReplacingOccurrencesOfString ( variable as String,
withString : NSString(format: "%d", obj.valueForKey(variable as String) as Int),
options : NSStringCompareOptions.LiteralSearch,
range : NSMakeRange(0,expr.length) );
}
var result: AnyObject = NSExpression(format: expr).expressionValueWithObject(nil, context: nil)
print (result);

Find Object with Property in Array

is there a possibility to get an object from an array with an specific property? Or do i need to loop trough all objects in my array and check if an property is the specific i was looking for?
edit: Thanks for given me into the correct direction, but i have a problem to convert this.
// edit again: A ok, and if there is only one specific result? Is this also a possible method do to that?
let imageUUID = sender.imageUUID
let questionImageObjects = self.formImages[currentSelectedQuestion.qIndex] as [Images]!
// this is working
//var imageObject:Images!
/*
for (index, image) in enumerate(questionImageObjects) {
if(image.imageUUID == imageUUID) {
imageObject = image
}
}
*/
// this is not working - NSArray is not a subtype of Images- so what if there is only 1 possible result?
var imageObject = questionImageObjects.filter( { return $0.imageUUID == imageUUID } )
// this is not working - NSArray is not a subtype of Images- so what if there is only 1 possible result?
You have no way to prove at compile-time that there is only one possible result on an array. What you're actually asking for is the first matching result. The easiest (though not the fastest) is to just take the first element of the result of filter:
let imageObject = questionImageObjects.filter{ $0.imageUUID == imageUUID }.first
imageObject will now be an optional of course, since it's possible that nothing matches.
If searching the whole array is time consuming, of course you can easily create a firstMatching function that will return the (optional) first element matching the closure, but for short arrays this is fine and simple.
As charles notes, in Swift 3 this is built in:
questionImageObjects.first(where: { $0.imageUUID == imageUUID })
Edit 2016-05-05: Swift 3 will include first(where:).
In Swift 2, you can use indexOf to find the index of the first array element that matches a predicate.
let index = questionImageObjects.indexOf({$0.imageUUID == imageUUID})
This is bit faster compared to filter since it will stop after the first match. (Alternatively, you could use a lazy sequence.)
However, it's a bit annoying that you can only get the index and not the object itself. I use the following extension for convenience:
extension CollectionType {
func find(#noescape predicate: (Self.Generator.Element) throws -> Bool) rethrows -> Self.Generator.Element? {
return try indexOf(predicate).map({self[$0]})
}
}
Then the following works:
questionImageObjects.find({$0.imageUUID == imageUUID})
Yes, you can use the filter method which takes a closure where you can set your logical expression.
Example:
struct User {
var firstName: String?
var lastName: String?
}
let users = [User(firstName: "John", lastName: "Doe"), User(firstName: "Bill", lastName: "Clinton"), User(firstName: "John", lastName: "Travolta")];
let johns = users.filter( { return $0.firstName == "John" } )
Note that filter returns an array containing all items satisfying the logical expression.
More info in the Library Reference
Here is a working example in Swift 5
class Point{
var x:Int
var y:Int
init(x:Int, y:Int){
self.x = x
self.y = y
}
}
var p1 = Point(x:1, y:2)
var p2 = Point(x:2, y:3)
var p3 = Point(x:1, y:4)
var points = [p1, p2, p3]
// Find the first object with given property
// In this case, firstMatchingPoint becomes p1
let firstMatchingPoint = points.first{$0.x == 1}
// Find all objects with given property
// In this case, allMatchingPoints becomes [p1, p3]
let allMatchingPoints = points.filter{$0.x == 1}
Reference:
Trailing Closure
Here is other way to fetch particular object by using object property to search an object in array.
if arrayTicketsListing.contains({ $0.status_id == "2" }) {
let ticketStatusObj: TicketsStatusList = arrayTicketsListing[arrayTicketsListing.indexOf({ $0.status_id == "2" })!]
print(ticketStatusObj.status_name)
}
Whereas, my arrayTicketsListing is [TicketsStatusList] contains objects of TicketsStatusList class.
// TicketsStatusList class
class TicketsStatusList {
internal var status_id: String
internal var status_name: String
init(){
status_id = ""
status_name = ""
}
}

Resources