Check another Dictionary if all values are false - swift

Im battling to figure out the best solution for this as I don't have a frame of reference and I'm still learning.
I have 3 Stages in my game that are stored in a Dictionary which look something like this:
var stages = ["Stage_01": ["Level_01": true, "Level_02": false],
"Stage_02": ["Level_03": true, "Level_04": false],
"Stage_03": ["Level_05": true, "Level_06": false]]
I created a function to loop through them to see if they are set to true or false. If they are true then they just return the level and thats that. If they are false it must move on to the next level.
If all the levels are false then I want it to load the next stage and this is where my knowledge is lacking, in layman terms I basically need it to just back to the top of the function in order to iterate through all the stages which clearly won't work with my current logic.
Any direction would be appreciated.
func loadNextLevel() -> String {
var falseCounter = 0
var stageNumber = 1
var loadLevel = ""
if var stage = stages["Stage_0\(stageNumber)"] as? Dictionary<String, Bool> {
for (level, value) in stage {
if value == true {
// Set it to false and update the value then return the level
stage[level] = false
stages.updateValue(stage, forKey: "Stage_0\(stageNumber)")
return level
} else {
falseCounter += 1
}
if falseCounter == 5 {
//All levels are done, load next stage
if self.stageNumber < 4 {
self.stageNumber += 1
loadNextLevel()
} else {
// reset all the levels back to true and start again
}
return loadLevel
}

Related

Using swift built in partition to manage elements in an array

iOS 14, Swift 5.x
I watched this excellent WWDC from 2018
https://developer.apple.com/videos/play/wwdc2018/223/
And I wrote a shapes editor... and have been trying to use partition as Dave in the video says you should. I got the first three to work, but the last one I had to use a loop- cannot for the life of me figure out how to get it to work with partition.
Can someone see how I might do this?
The first method moves the selected object to the end of the list, works perfectly.
func bringToFrontEA() {
let subset = objects.partition(by: { $0.selected })
let selected = objects[subset...]
let unselected = objects[..<subset]
let reordered = unselected + selected
objects = Array(reordered)
}
The second method moves the selected object to the front of the list. Works prefectly.
func sendToBackEA() {
let subset = objects.partition(by: { !$0.selected })
let selected = objects[subset...]
let unselected = objects[..<subset]
let reordered = unselected + selected
objects = Array(reordered)
}
The third method moves the element just one element back in the list. Works perfectly.
func sendBackEA() {
if let i = objects.firstIndex(where: { $0.selected }) {
if i == 0 { return }
let predecessor = i - 1
let shapes = objects[predecessor...].partition(by: { !$0.selected })
let slice = objects[predecessor...]
let row = objects[..<predecessor]
let selected = Array(slice[..<shapes])
let unselected = Array(slice[shapes...])
objects = row + selected + unselected
}
}
The last method moves the element forward in the list, works perfectly... but unlike the other methods it will not scale as described in the WWDC video.
func bringForwardEA() {
let indexes = objects.enumerated().filter { $0.element.selected == true }.map{$0.offset}
for i in indexes {
if objects[i+1].unused {
return
}
objects.swapAt(i+1, i)
}
}
Objects is an array of shapes with a property indicating if it is selected or not. I want to exchange the loop in the last method by using a partition as I did in the first three. It needs to work for one or more selected shapes.
Looking at the WWDC video, it appears that what you are calling sendBackEA is what WWDC calls bringForward, and what you are calling bringForwardEA is what WWDC calls sendBack.
Just like how you move the first selected element forward one index (index decreases) in sendBackEA, then move all the other selected elements to immediately after that first selected element. bringForwardEA should do the reverse: move the last selected element backward one index (index increases), then move all the other selected elements to immediately before the last selected element. (See circa 19:10 in the video)
You seem to have confused yourself by trying to increase the indices of all the selected index by 1. This obviously cannot be done with a partition in general.
Also note that partition(by:) already modifies the collection, you don't need to get each partition, then recombine.
Your 4 methods can be written like this:
func bringToFrontEA() {
objects.partition(by: { $0.selected })
}
func sendToBackEA() {
objects.partition(by: { !$0.selected })
}
func sendBackEA() {
if let i = objects.indices.first(where: { objects[$0].selected }) {
if i == 0 { return }
let predecessor = i - 1
objects[predecessor...].partition(by: { !$0.selected })
}
}
func bringForwardEA() {
if let i = objects.indices.last(where: { objects[$0].selected }) {
if i == objects.indices.last { return }
let successor = i + 1
objects[...successor].partition(by: { !$0.selected })
}
}
Notice the symmetry between sendBackEA and bringForwardEA.

first or filter function of swift array doesnt give the right reference object

I have defined a struct in my viewcontroller before my viewdidload
struct CustomFilterButton {
var Id : Int = 0;
var Name : String = "";
var selected : Bool = false;
}
then I create reference for it in global
var customButtons = [CustomFilterButton]();
then in my viewdidload I appended some customFilterButton objects in customButtons array
customButtons.append(CustomFilterButton.init(Id: 1, Name: "A", selected: false))
customButtons.append(CustomFilterButton.init(Id: 2, Name: "B", selected: false))
customButtons.append(CustomFilterButton.init(Id: 3, Name: "C", selected: true))
customButtons.append(CustomFilterButton.init(Id: 4, Name: "D", selected: false))
in viewdidload or in any other function when I try to get an object in array using first or filter and change it, but it doesnt work.
print(customButtons[0].selected);
print("--")
var bt = customButtons.first{
$0.Id == 1
}
bt?.selected = true;
print(bt?.selected);
print(customButtons[0].selected);
here is the result
false
--
Optional(true)
false
the same applies to filter also!
Am I missing something or am I doing something wrong?
Note: I need to get the object that first or filter found and change it, not the hard copy of it
When you deal with a Struct, you have to understand that it is a value type.
So, that means that everytime you pass a value around, it's a COPY of the value and not the reference
And when you do this:
var bt = customButtons.first{
$0.Id == 1
}
You're asking the compiler to retrieve a copy of the CustomFilterButton whose Id is 1 and assign the copy to your bt variable.
To tackle this, you can access the element by its index and directly modify its value without passing it around (assigning to a new variable)
// Get the index of the element you're trying to modify
if let indexYouWantToModify = customButtons.firstIndex(where: {$0.Id == 1}){
// Modify it directly through its index
customButtons[indexYouWantToModify].selected = true
}
Oh, and though changing your Struct to a Class works for you, I think it's unnecessary just for this little use case. Structs and Classes hold their own benefits and trade-offs. I'm not sure what you're planning to do over this CustomFilterButton over the long run so I suggest you to read this article and decide for yourselves!
var bt = customButtons.first{
$0.Id == 1
}
At this moment bt has no any relation to your customButtons[0], its value was copied.
Access to item by index
print(customButtons[0].selected);
print("--")
var offset = 0
var bt = customButtons.enumerated().first{
if $0.element.Id == 1 {
offset = $0.offset
return true
}
return false
}
customButtons[offset].selected = true;
print(customButtons[0].selected);
You should use class instead of struct.

Xcode 9.4.1 Swift 4 - Comparing a non-primitive Set to a primitive set containing Int

I am stumped on how to approach this scenario as I cannot figure out how to check if a single value within each list of the listGroup Exists before returning true and if one value is missing from one of the 4 lists the func must return false.
The list pasted through will have a data structure containing id, name, group
Example of the object passed through within objList:
Object - id: Int, name: String, group: String
init(){
//contains value which will match objList id's
let list1 : Set<Int> = [1,2]
let list2 : Set<Int> = [3,4]
let list3 : Set<Int> = [5,6,7]
let list4 : Set<Int> = [8,9]
//Set of Sets
listGroups = [list1,list2,list3,list4]
}
func checklist(_ objList: [Details]) -> Bool {
//I want to check that each sub set(list1-list4) elements exist
//E.G. if objList contains 2, 3, 7, 8, 15, 21 it will return true
//and if objList contains 1, 4, 7, return false as it doesn't contain a
//number from each set
//How I thought to approach but became stuck
for obj in objList {
for set in listGroups {
if set.contains(i.id){
//return true if objList contains numbers from list1, list2, list3 and list4
}
}
}
//I require an id from each list to be present in the objList
//but how would i know which set had which id and count how many group of 4
//there would be
}
The "Details" passed through contain details about them, however what I want to do is check whether the Int within the listGroups exist within the objList passed through. However the func can only return true if a value from each of the sub-sets of listGroups exists.
A single value from all 4 subsets must be present before I can return true and if a single one or more is missing func must return false
Create a Set from the id values of itemList and use intersection to check if one set contains at least one item of another set.
func checklist(_ objList: [Details]) -> Bool {
let idSet = Set(objList.map{$0.id})
for set in listGroups {
if set.intersection(idSet).isEmpty { return false }
}
return true
}
For a very long list it might be more efficient to subtract the currently checked values from idSet
func checklist(_ objList: [Details]) -> Bool {
var idSet = Set(objList.map{$0.id})
for set in listGroups {
let commonSet = set.intersection(idSet)
if commonSet.isEmpty { return false }
idSet.subtract(set)
}
return true
}

How to make changes to a variable inside an if statement

I'm trying to make changes to a var inside an if statement, for some reason I can not get it to change.
func checkIfFieldsEmpty()-> Bool{
var textfieldStatus:Bool!
var v = [
"name" : ""
]
for (key, value) in v {
if value == "" {
textfieldStatus = false
} else {
textfieldStatus = true
}
}
return textfieldStatus
}
That will always return false. If you want to check if the values are completely empty, then I think you want this.
for (key, value) in textfield {
if value == "" {
textfieldStatus = false
}else{
textfieldStatus = true
break
}
}
According to #fridgemagnet answer, the difference is you are calling a method which required a Dictionary parameter. But you are always setting a
var v = [ "name":""]
And you are checking the variable "v" value which should always return false and by default your "textfieldStatus" value is false. So there is no chance to change your "textfieldStatus" value. Please make sure what you exactly want.
You define v as empty, and then check if its empty. Try v = ["name": "peter"](but, it's not really what you want). When you declare var v = ..., you are clearing the previous values for v if it is defined earlier in the program.
This solution work well form me
func checkIfFieldsEmpty()-> Bool{
var v = [
"name":""
]
var textfieldStatus:Bool = true
var fielderrorCount:Int = 0
for (key, value) in v{
if value == ""{
fielderrorCount = 1
}
}
if fielderrorCount > 0{
textfieldStatus = false
}
return textfieldStatus
}

Best way to check for all values in a Int

I don't know if there is a better way of doing this, my solution works but just seems to repetitive.
I have a life indicator which is represented using an icon within SpriteBuilder, these are set to not visible, and then within source I have implemented the following code:
lives = 3 // this is reduced or increased within game
if (lives == 5) {
lifeIcon1.visible = true
lifeIcon2.visible = true
lifeIcon3.visible = true
lifeIcon4.visible = true
lifeIcon5.visible = true
} else if (lives == 4) {
lifeIcon1.visible = true
lifeIcon2.visible = true
lifeIcon3.visible = true
lifeIcon4.visible = true
} else if (lives == 3) {
lifeIcon1.visible = true
lifeIcon2.visible = true
lifeIcon3.visible = true
} else if (lives == 2) {
lifeIcon1.visible = true
lifeIcon2.visible = true
} else if (lives == 1) {
lifeIcon1.visible = true
}
This works fine but just seems repetitive, and is difficult to scale if I wanted to increase the lives to more than 5 at a later date.
Any suggestions?
Assuming you want to set your visible to false when you decrease your lives, you can use something like this:
let lifeIcons = [lifeIcon1, lifeIcon2, lifeIcon3, lifeIcon4, lifeIcon5]
...
for (index, lifeIcon) in lifeIcons.enumerate() {
lifeIcon.visible = index < lives
}
Or if you like using closures:
lifeIcons.enumerate().forEach{$1.visible = $0 < lives}
(Here $1 is the current life icon in the iteration, and $0 is the index of that icon)
The key point here is to use an array, instead of multiple variables to keep track of your life icons. You can then iterate through this array, using the indices of the elements in order to determine which icon they are. enumerate() is used in order to create a lazy sequence of pairs of elements with their indices.
In this case we can compare the indices of the elements with the current number of lives. Because indices are 0 based, we can check whether a given index is less than the current number of lives in order to determine whether it should be visible.
How about this:
let icons = [lifeIcon1, lifeIcon2, lifeIcon3, lifeIcon4, lifeIcon5]
icons[0..<lives].forEach { $0.visible = true }
It is similar to #Adam Heeg's answer, but the c-style loops are in the process of being deprecated. I would slice the array by subscript and use forEach instead.
It seems like you have some other code which you don't wnat to change. I would suggest the following.
Load all your lifeIcon's into an array then do the following. (adjust for if you are 0 based or not).
for (int i = 0; i < lives; i++)
{
lifeIconArray[i].visible = true;
}