App crashes within for loop in swift 3 - ios

Here I am just getting array count and using for loop but got crashed at let arr = arrayAdaptor[i] this line after completing my array count don't know why it's crashing can anyone help me how to resolve this
var arrayAdaptor = [Struct_Row_Rating]()
for i in 0...arrayAdaptor.count {
let arr = arrayAdaptor[i]
let number = arr.row
let row = number + 1
dict.updateValue("\(arr.rating)", forKey: "\(row)")
print(dict)
}
struct Struct_Row_Rating {
var row: Int
var rating: Double
init(row: Int , rating: Double) {
self.row = row
self.rating = rating
}}

The operator ... exceeds the range of the array. You have to write
for i in 0..<arrayAdaptor.count
or
for i in 0...arrayAdaptor.count - 1
Basically don't use these index based for loops in Swift at all.
Use Fast Enumeration:
for arr in arrayAdaptor {
and if you really need the index
for (index, arr) in arrayAdaptor.enumerated() {

Why you are using 0...arrayAdaptor.count range style avoid it and Simply Use enumerated() of an Array:
for (_,value) in arrayAdaptor.enumerated() {
let number = value.row
let row = number + 1
dict.updateValue("\(arr.rating)", forKey: "\(row)")
print(dict)
}
If you don't want any index to try with this:
for value in arrayAdaptor {
let number = value.row
let row = number + 1
dict.updateValue("\(arr.rating)", forKey: "\(row)")
print(dict)
}
See this

One of the options not mentioned in the answers is forEach approach.
arrayAdaptor.forEach { item in
print(item)
}
or
arrayAdaptor.forEach {
print($0) // without named parameter
}
// Your case:
arrayAdaptor.forEach { item in
let arr = item
let number = item.row
let row = number + 1
dict.updateValue("\(arr.rating)", forKey: "\(row)")
print(dict)
}
This is pretty much the same as the handy for..in mentioned in vadian's answer:
for arr in arrayAdaptor { ... }

From Swift 4 you can use One sided ranges.
i... is favored over i..< because the latter is ugly. We have to pick one, two would be redundant and likely to cause confusion over which is the "right" one. Either would be reasonable on pedantic correctness grounds – (i as Int)... includes Int.max consistent with ..., whereas a[i...] is interpreted as a[i..<a.endIndex] consistent with i..<.
Example:
var names = ["Jack", "New", "peter"]
let first = names[0...]
let second = names[..<names.count]
print(first)//prints ["Jack", "New", "peter"]
print(second)//prints ["Jack", "New", "peter"]

You can also iterate loop by 'map' function:
let _ = arrayAdaptor.map({ adaptor in
let number = adaptor.row
let row = number + 1
dict.updateValue("\(adaptor.rating)", forKey: "\(row)")
print(dict)
})

Related

Filter dictionary of array objects

I am trying to filter my dictionary according to user input in UISearchController. I have following model and array of objects.
struct People {
var name: String
var id: Int
}
let first = People(name: "Atalay", id: 1)
let second = People(name: "Ahmet", id: 2)
let third = People(name: "Mehmet", id: 3)
let fourth = People(name: "Yusuf", id: 4)
let peoples: [People] = [first, second, third, fourth, fifth]
I put them into a dictionary to create section indexed table view with following code.
var dict: [String: [People]] = Dictionary(grouping: peoples, by: { (people) -> String in
return String(people.name.prefix(1))
})
Above code gives me a dictionary with first letter of People names. Now, I would like to filter my array according to user input. However, I tried following code for filtering but it is not working as I expected.
let filteredDict = (dict.filter { $0.1.contains { $0.name.lowercased().contains("ata") } })
It returns all "A" letter section indexes like ["A": People(name: "Atalay", id: 1), People(name: "Ahmet", id: 2)]
How can I achieve filter also my array inside dictionary?
If I'm not mistaken, you want your final dictionary to have all the keys and only the filtered array of items as the values. If that is right, reduce is the tool for that:
let filtered = dict.reduce(into: [String: [People]]()) {
$0[$1.key] = $1.value.filter { $0.name.lowercased().contains("ata") }
}
I decided it was simplest to get this right by using an old fashioned for loop and filter each group separately
var filtered = [String: [People]]()
for (k, v) in dict {
let result = v.filter {$0.name.lowercased().contains("ata")}
if result.count > 0 {
filtered[k] = result
}
}
Note that if you want to keep all the groups in the result dictionary just skip the if result.count > 0 condition
How can I achieve filter also my array inside dictionary?
You should have an array first, you can use flatMap to group all the values in your filteredDict
let array = filteredDict.flatMap { $0.value }
Then you just filter the array as usually
let filteredArray = array.filter { $0.name.lowercased().contains("ata") }

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
}

Applying filter and calculating Average on NSDictionary with JSON origin

I read some posts here and google about it, but still couldn't understand how to do a simple filter using the swift filter feature. I am new to Swift and functional programing, so forgive me if that is too basic.
I have the following JSON:
{
"-KjirKH7Bo7c5vq7ZH9N" = {
rank = 2;
placa = "xxx-0003";
uid = yNpL0uzI5LRj6etFGVgoWYEK2E52;
};
"-Kjiyi_i7FLl6dks6xKL" = {
rank = 5;
placa = "xxx-0003";
uid = yNpL0uzI5LRj6etFGVgoWYEK2E52;
};
}
I was able to create an array of the values with:
if let dict = snapshot.value as? NSDictionary{
let myArray = dict.map{$0.value} //array of values
}
Which creates this:
[{
rank = 5;
placa = "xxx-0003";
uid = yNpL0uzI5LRj6etFGVgoWYEK2E52;
}, {
rank = 2;
placa = "xxx-0003";
uid = yNpL0uzI5LRj6etFGVgoWYEK2E52;
}]
My goal now is:
Apply a filter to retrieve only items that the property "rank" is greater than 0.
After that I want to calculate the items rank average (in this example 2+5/2 = 3.5).
I have tried this:
myArray.filter{$0.rank > 0 }
But it fails with "Value of type 'Any' has no member 'rank'"
Any idea how I can filter this array?
I have tried with NSPredicate, but I am wondering if there is some way to take advantage of the native filter.
Looks like you have two issues:
myArray's items are of type Any and in reality they are dictionaries so you have to help compiler to understand that. Here is how to do it:
let myArray: [[String: Any]] = dict.map{ $0.value }
Because myArray contains dictionaries, you have to modify accessing values in filter:
myArray.filter{ (($0["rank"] as? Int) ?? 0) > 0 }
Hope it helps!
myArray.filter{ ((($0 as! NSDictionary)["rank"] as? Int) ?? 0) > 0 }
appending the answer of K.K Cast $0 to NSDictionary. I hope that will work

Swift 3 For loop compare and change

Here is my code so far
var counter = 0
for i in 0...9 {
var val = NamePicker()
// array to find duplicates
var buttonValues = ["", "", "", "", "", "", "", "", "", ""] // array for button names
buttonValues.insert(val, at: counter)
print(buttonValues[counter])
counter += 1
}
This code is putting 10 string values into my array. What I would like to do is find a way to check each value in my array. for eample if my end result array is ["a","a","a","b","b","c","c","e","f","c"] I want to see if there is a triple of the same name(single and duplicates are fine). However if there is a triple I would like to change the 3rd value to another val from my NamePicker() function.
so with my array of
["a","a","a","b","b","c","c","e","f","c"]
there are 3 "a" and 3 "c", having two of the same is ok, i would like to change the 3rd to a new values and if the new value makes another triple it will change until there are no more triples.
so that array could possible have an end result of
["a","a","f","b","b","c","c","e","f","z"]
this is where the triples where changed.
Any help on how to do this efficiently?
Both options below asume that your NamePciker() function can generate at least 5 distinct values so there exists an array that satisfies your requirement.
Your requirement is better handled by not generating so many duplicates to begin with. If all you want is an array of names when each name cannot be repeated more than twice, try this:
var buttonValues = [String]()
var dict = [String: Int]()
while buttonValues.count < 10 {
let name = NamePicker()
let count = dict[name] ?? 0
guard count < 2 else { continue }
buttonValues.append(name)
dict[name] = count + 1
}
If you already have the array and want to correct it, do this:
var buttonValues = ["a","a","a","b","b","c","c","e","f","c"]
// Scan the array to tally how many times each name appears
var totalDict = [String: Int]()
buttonValues.forEach { totalDict[$0] = (totalDict[$0] ?? 0) + 1 }
// Now scan it again to update names that appear too many times
var runningDict = [String: Int]()
for (index, value) in buttonValues.enumerated() {
let count = runningDict[value] ?? 0
if count >= 2 {
while true {
let newValue = NamePicker()
let newTotal = (totalDict[newValue] ?? 0) + 1
if newTotal < 3 {
buttonValues[index] = newValue
totalDict[newValue] = newTotal
break
}
}
} else {
runningDict[value] = count + 1
}
}
Dictionary is the best way I think. Have the key be the character and the value be the count of that character. Your runtime will be O(n) since you only have to run through each input once. Here is an example:
let chars = ["a","a","a","b","b","c","c","e","f","c"]
var dict = [String: Int]()
for char in chars {
//If already in Dictionary, increase by one
if var count = dict[char] {
count += 1
dict[char] = count
} else {//else is not in the dictionary already, init with 1
dict[char] = 1
}
}
Output:
["b": 2, "e": 1, "a": 3, "f": 1, "c": 3]
Now I'm not sure how you want to replace the value that's the same character for a third time, but this is probably the best way to group the strings to determine which are over the limit.
Instead of inserting the wrong value and then checking if the values are correct, I would suggest to automatically create the correct array.
//array for button names
var buttonValues = Array<String>()
//tracks what value has been inserted how many times
var trackerDict = [String: Int]()
for i in 0...9 {
//we initialize a new variable that tells us if we found a valid value (if the value has not been inserted 2 times already)
var foundValidValue = false
while !foundValidValue{
var val = NamePicker()
//now we check if the value exists and if it is inserted less than 2 times
if let count = trackerDict[val] {
if count < 2 {
foundValidValue = true
}
}
//if we found the value, we can add it
if foundValidValue {
trackerDict[val] = (trackerDict[val] ?? 0) + 1
buttonValues.append(val)
}
//if we did not find it, we just run through the loop again
}
}
I added a dictionary because it is faster to keep track of the count in a dictionary than counting the number of occurrences in the array every time.

Convert String Array to JSON or 2D Array SWIFT

I currently have a NSMutableArray "localArray" and I am trying to create that into a JSON Array or a 2D Array. I get this data my creating a database and running a query using a for loop on the database.
{
Food,
Burger,
3.99,
1.25,
POP,
Crush,
1.99,
.89,
and more.
}
The reason why I am looking for a JSON or 2d Array is I want to hold the data in the localArray in such a way that I can identify by type and then do something like .valueForKey("Name") or .valurForKey("Price") and add that to my tableview's cell text label or labels.
{
{
Type Food,
Name Burger,
Price 3.99,
Cost 1.25,
},
{
Type POP,
Name Crush,
Price 1.99,
Cost .89,
},
and more
}
I have already tried JSONSerialization, but that failed and also tried 2d Array but no luck.
Any help will be highly appreciated.
This is how I Query and add the data to localArray
let queryType = data.select(ada, code, name, proof, size, case_size, price)
.filter(bevType == type)
let rows = Array(queryType)
for row in rows{
let name = row[self.name]
let type = row[self.type]
let cost = row[self.cost]
let price = row[self.price]
localArray.addObject(name)
localArray.addObject(type)
localArray.addObject(cost)
localArray.addObject(price)
}
I solved it myself by creating a dictionary.
for row in rows{
var rDict: Dictionary = [String: String]()
rDict["Name"] = row[self.name]
rDict["Type"] = row[self.type]
rDict["Cost"] = row[self.cost]
rDict["Price"] = row[self.price]
localArray.addObject(rDict)
}
If fields are always repeating in count of 4, you can try doing this:
var array = [[String: AnyObject]]()
for var i = 0 ; i < array.count ; i += 4 {
var k = 0
var dict = [String: AnyObject]
dict["Type"] = array[i + k++]
dict["Name"] = array[i + k++]
dict["Price"] = array[i + k++]
dict["Cost"] = array[i + k]
array.append(dict)
}
Then extract dictionary from this swift array and use same keys to extract data from dictionary to be used in your cell like
let dict = array[indexPath.row]
cell.title = dict["Name"]

Resources