Not correctly referencing random number in Swift object - ios

I have an array of objects where each object has an Exercise Name and a random number of reps.
I then have a function to generate a random workout (with between 3 - 6 exercises in it)
However, when I print it the reps are almost always 1, 2 or occasionally 14, despite loading it 30 times or so.
Am I doing something wrong here?
Here's my objects and struct :
struct exerciseInWorkout {
let name : String
let reps : Int
}
let exerciseBankArray = [
exerciseInWorkout(name: "Squat", reps: (Int(arc4random_uniform(10)))),
exerciseInWorkout(name: "Push Ups", reps: (Int(arc4random_uniform(5)))),
exerciseInWorkout(name: "Viking Press", reps: (Int(arc4random_uniform(20)))),
]
and here's my function :
func generateWorkout(){
let possibleExercises = exerciseBankArray
let numberOfExercisesKey = Int(arc4random_uniform(4) + 3)
let workoutSet : [exerciseInWorkout] = (1...numberOfExercisesKey).map { _ in
let randomKey = Int(arc4random_uniform(UInt32(possibleExercises.count)))
return exerciseInWorkout(name: exerciseBankArray[randomKey].name, reps: exerciseBankArray[randomKey].reps)}
print (workoutSet)
}
}
Also, is there a way to create a set of these to avoid the same exercise coming up twice? I tried using Set but didn't seem to work at all.
Lastly, when I print it, each object is prepended with "project.exerciseInWorkout... is there any way to just print/return the clean array i.e. [["name: "press ups", reps: 12], [name:xyz, reps: 30]]?
Reason being I want to pass this to a new VC to put in a table view next and presume I need a clean array to do that.

Short Answer
It looks like your array exerciseBankArray is stored globally, which means exerciseInWorkout components are initialized once for the whole app. Having said that, it is then normal that the number of reps is always the same, the arc4random_uniform is only executed on the first access.
Quick Fix
If you want to keep the same structure, I recommend this
remove the arc4random_uniform from exerciseBankArray and just write the maximum amount of reps you want
let exerciseBankArray = [
exerciseInWorkout(name: "Squat", maxReps: 10),
exerciseInWorkout(name: "Push Ups", maxReps: 5),
exerciseInWorkout(name: "Viking Press", maxReps: 20)
]
Call the random function inside generateWorkout() like this
func generateWorkout(){
let possibleExercises = exerciseBankArray
let numberOfExercisesKey = Int(arc4random_uniform(4) + 3)
let workoutSet : [exerciseInWorkout] = (1...numberOfExercisesKey).map { _ in
let randomKey = Int(arc4random_uniform(UInt32(possibleExercises.count)))
return exerciseInWorkout(
name: exerciseBankArray[randomKey].name,
reps: Int(arc4random_uniform(exerciseBankArray[randomKey].maxReps))
)
}
}
Longer Improvements
If you're open to making a better architecture for your code, here are some suggestions
Remove global variables
Split your model for exercise into two classes / structs:
One that represents an actual exercise
struct WorkoutExercise {
let name: String
let reps: Int
}
One that represents a generator of exercise
struct WorkoutExerciseGenerator {
let name: String
let maxReps: Int
// Add any other parameter you need to generate a good exercise
func generate() -> WorkoutExercise {
return WorkoutExercise(
name: name,
reps: Int(arc4random_uniform(maxReps))
)
}
}
More Improvements / Q&A
When you say remove global variables do you mean store the array of exercises in each VC that needs them? I just thought that would be “repeating myself” (from DRY principles etc?)
I totally agree with the DRY guidelines, but there are many ways to not repeat yourself. The issue with global variables (a variable that is not inside any class, just free-floating) are numerous:
It gets awkward when you want to include it in different targets
It's not part of any namespace, so it might overload another one from another library of file, it messes up the auto-completion, etc...
etc... you can find more documentation in this thread
Also, if I change to the 2nd example above, how would I then call the
right amount of those? Just replace “return exerciseInWorkout” with
the new function? Or would it be unchanged because the func is
contained in the struct?
So I understand correctly, what you actually want to create is a set of default generators for exercises that have a name and a max count of reps, and these should be available in the whole project (hence why you used global variables).
A good way to improve this code is by defining static generators, for instance you can update WorkoutExerciseGenerator to be
struct WorkoutExerciseGenerator {
let name: String
let maxReps: Int
// Add any other parameter you need to generate a good exercise
func generate() -> WorkoutExercise {
return WorkoutExercise(
name: name,
reps: Int(arc4random_uniform(maxReps))
)
}
// Generates a "Squat" workout
static var squat {
return WorkoutExerciseGenerator(name: "Squat", maxReps: 10)
}
// Generates a "Push Up" workout
static var pushUp {
return WorkoutExerciseGenerator(name: "Push Ups", maxReps: 5)
}
// Generates a "Viking Press" workout
static var vikingPress {
return WorkoutExerciseGenerator(name: "Viking Press", maxReps: 20)
}
}
Now that you have these specific generators, it looks like you also want to have a way to generate a whole workout. If that's the case, then you can simply create, in addition to what I wrote about, some objects to represent a workout and a workout generator.
/// This represents a whole workout that contains
/// multiple exercises
struct Workout {
let exercises: [WorkoutExercise]
}
/// This allows to dynamically creates a Workout
struct WorkoutGenerator {
// This is the "pool" of exercises from
// which it generates a workout (similar to your
// `exerciseBankArray`)
let exercisePool: [ExerciseGenerators]
// Min and max amount of workouts
let minCount: Int
let maxCount: Int
// Generates a workout from the generator
func generate() -> WorkoutGenerator {
let amount = Int(arc4random_uniform(maxCount - minCount)) + minCount
let exercises = (0..<amount).map { _ in
// Selects an exercise generator at random
let index = Int(arc4random_uniform(exercisePool.count))
// Generates a random workout exercise from this generator
return exercisePool[index].generate()
}
return Workout(exercises: exercises)
}
// Same thing here, you can use a static variable to create
// a "default" workout generator that contains the exercises
// you had inside your `exerciseBankArray`
static var default: WorkoutGenerator {
return WorkoutGenerator(
exercisePool: [.squat, .pushUp, .vikingPress],
minCount: 3,
maxCount: 6
)
}
}
Now that you have all of this, the only thing you need to do to create a totally random work-out according to your requirements is
let myWorkout = WorkoutGenerator.default.generate()
If you want to add more exercise types, just create more static ExerciseGenerator, and if you want to create different types of workouts (maybe with different exercise pools, some hard or some easy), just create additional static WorkoutGenerator. (Note that you don't need static, you can also just create the object directly in your VC).
Hope that helps!

Related

How can I group messages by date?

Let's say that I am fetching messages (objects) from a database. They do not arrive at the same time, a message always arrives alone (one fetch -> one message; a function gets called for every message) and at any time.
Every message contains a time stamp that represents the date when the message was created. So I can call message.date and I will get the date when the message was created.
The messages do not arrive in any order; it can be that the created last message is at the third/fourth/... position.
I'd like to group these messages by day in order to present them in a UITableView.
Each section represents a day. Eeach section header includes the day and every cell includes the time (kind of like in WhatsApp Messenger).
I know how to create custom header views, insert sections, rows etc.
The problem is that I don't know how or as what data type to sort the messages in order to create the TableView easily and resource-saving and efficient (in terms of storage requirement and clarity).
It would be easy if I had a two-dimensional array, but I am not clever enough to think up an efficient approach to sort (or rather group) the messages.
Thanks a lot for help!
The time stamp is a date, so sort by date (array) and group by day (dictionary).
Sorting an array that includes date information by date is a one-liner in Swift.
Grouping a sorted array that includes date information into a dictionary keyed by day is also a one-liner in Swift.
So that's two lines of code, and you didn't have to change anything.
However, a dictionary has no order, so I would then suggest taking a third step where you transform the dictionary into an array of some custom struct reflecting the section-row structure of your table. The correct data source for any sectioned table view is going to have a structure like this:
struct Row {
// row properties
}
struct Section {
var rowData : [Row]
// section properties
}
var model : [Section]!
So after you've made your dictionary as a way of grouping, you just map it onto an array of Section and maintain that going forward.
Of course if you have no data to start with and the data arrives one item at a time, then you can omit the two bulleted steps above. Just start with the structured model and keep slotting each item into the right spot as it arrives.
EDIT: You expressed interest (in a comment) on how to insert an element into the right place in an already sorted array, so here's an example (see https://stackoverflow.com/a/26679191/341994):
extension Array {
func insertionIndex(of elem: Element, by f: (Element, Element) -> Bool) -> Int {
var lo = 0
var hi = self.count - 1
while lo <= hi {
let mid = (lo + hi)/2
if f(self[mid], elem) {
lo = mid + 1
} else if f(elem, self[mid]) {
hi = mid - 1
} else {
return mid // found at position mid
}
}
return lo // not found, would be inserted at position lo
}
mutating func insertSorted(_ elem:Element, by f: (Element, Element) -> Bool) {
self.insert(elem, at:self.insertionIndex(of:elem, by:f))
}
}
Here's a test; of course your ordering function won't be as simple as < but that's really the only difference:
var arr = [Int]()
arr.insertSorted(1, by:<)
arr.insertSorted(10, by:<)
arr.insertSorted(9, by:<)
arr.insertSorted(3, by:<)
arr.insertSorted(5, by:<)
arr.insertSorted(7, by:<)
arr.insertSorted(6, by:<)
// [1, 3, 5, 6, 7, 9, 10]
It is very easy you can grouped it.
for example messages contain these following:
struct message {
let senderName:String
let mess:String
let reciever:String
let time:Date
}
and you have some messages:
var messages = [message]()
messages.append(message(senderName: "snow", mess: "Hello", reciever: "Dani", time: Date(timeIntervalSince1970: 1533078663)))
messages.append(message(senderName: "john", mess: "Hello", reciever: "Dani", time: Date(timeIntervalSince1970: 1533078606)))
messages.append(message(senderName: "alix", mess: "Hello", reciever: "Dani", time: Date(timeIntervalSince1970: 1533078633)))
you can grouped it easily by using this:
let groupedMessage = Dictionary(grouping: messages) { (mess) -> Date in
return mess.time
}

Compare different rows and make them one at tableview - Firebase/Swift 3

I am new in Swift 3 and Firebase programming, but I am making good progress. But sometimes things come harder and here I am again because I could not find some text as specifically as I need, neither updated for Swift 3. I have tried many many ways.
The issue is that I am building an app for some teachers to make a kind of examination with their students. Each Student has the same examination by two different teachers at different times and the results are shown at a table view controller like the following image:
As you can see, I have two rows for each student with two different scores (partial). What I wish is to filter this Firebase data, search for those two “repeated” students and make an average score from them and show the data at a table view controller (exam1 + exam2 / 2) for the final average score. This is my currently Firebase structure (I will post an alternative later at this question):
Now, parts of the code:
1. Struct for variables and snapshot
import Foundation
import Firebase
struct Examination {
var nome: String?
var preceptor: String?
var dataHoje: String?
var nota: String?
var key: String?
init(nome: String, preceptor: String, nota: String, dataHoje: String, key: String) {
self.nome = nome
self.preceptor = preceptor
self.nota = nota
self.dataHoje = dataHoje
self.key = key
}
init(snapshot: FIRDataSnapshot) {
key = snapshot.key
let snapshotValue = snapshot.value as? [String: AnyObject]
nome = snapshotValue?["nome"] as? String
preceptor = snapshotValue?["preceptor"] as? String
nota = snapshotValue?["nota"] as? String
dataHoje = snapshotValue?["dataHoje"] as? String
notaAtitude = snapshotValue?["notaAtitude"] as? String
}
func toAnyObject() -> Any {
return [
"nome": nome,
"preceptor": preceptor,
"nota": nota,
"dataHoje": dataHoje,
"userKey": key,
]
}
}
Load method for main table view controller
import Foundation
import Firebase
var refAlunos: [Examination] = []
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
Load_HM1()
}
func Load_HM1() {
let refW = FIRDatabase.database().reference().child("semestre_1_2017").child("avaliacaoHM12017").child("cheklists")
refW.queryOrdered(byChild: "nome").observe(.value, with: {(snapshot) in
var newTeste2: [Examination] = []
for resposta in snapshot.children {
let itemsAadicionar = Examination(snapshot: resposta as! FIRDataSnapshot)
newTeste2.append(itemsAadicionar)
}
self.refAlunos = newTeste2
})
}
At last, the Firebase structure I have also tried, but I always receive nil as result, table view empty:
The code for this alternative way (method load), I could not implement:
func Load_HM1() {
let refW = FIRDatabase.database().reference().child("semestre_1_2017").child("avaliacaoHM12017").child("cheklists") ... I CRASHED HERE..
// The rest of this method is the same shown before
So, resuming the issue, I need a way to compare rows with the same student name (always will be 2) and calculate the average of these two scores and publish a table view with this final score and name, almost as the first image, but only changing the score to the final average. I really don’t know if there is a way and I don’t know which way to go. I have searched a lot, but nothing found. I guess this second firebase structure could be more friendly, but my rows are empty. If need more information to understand this issue, ask me that I update the question. Thank you very much for any help.
I'm not aware of Firebase providing such functionalities out of the box, therefore I would recommend doing your calculations manually.
Before setting self.refAlunos at the end of the callback function you can extract the corresponding pairs of examinations, compute the average grade for each pair and create a new instance containing the computed average grade and the remaining attributes. Afterwards you can store an array of the generated examinations in self.refAlunos.
Additionally, you should take a look at Structure Your Database and Structuring your firebase data. Applying these guidelines will help to flatten your data and improve performance as your data grows.
Edit:
I'm currently not on my Mac, but I hope my suggestions can point you in the right direction, although they might not be 100% syntactically correct.
func Load_HM1() {
let refW = FIRDatabase.database().reference().child("semestre_1_2017").child("avaliacaoHM12017").child("cheklists")
refW.queryOrdered(byChild: "nome").observe(.value, with: {(snapshot) in
var newTeste2: [Examination] = []
var examinations: [Examination] = []
var position = -1
for resposta in snapshot.children {
let itemsAadicionar = Examination(snapshot: resposta as! FIRDataSnapshot)
newTeste2.append(itemsAadicionar)
for exam in 0..<examinations.count {
if (examinations[exam].nome == itemsAadicionar.nome) {
position = exam
break
}
}
if (position != -1) {
examinations[position].nota += itemsAadicionar.nota
examinations[position].nota /= 2
position = -1
}
}
self.refAlunos = examinations
})
}
I have edited your code and I hope you will get the gist. However, this approach is only suitable if you always have two exams per student. If you have more, you need to adapt it a little bit.

Remove duplicate structs in array based on struct property in Swift

I have made a simple struct and implemented the Equatable protocol :
extension MyModelStruct: Equatable {}
func ==(lhs: NModelMatch, rhs: NModelMatch) -> Bool {
let areEqual = lhs.id == rhs.id
return areEqual
}
public struct MyModelStruct {
var id : String?
var staticId : String?
init(fromDictionary dictionary: NSDictionary){
id = dictionary["id"] as? String
...
}
Then in my project i get an array of [MyModelStruct], what i what to do is to remove all the MyModelStruct that have the same id
let val1 = MyModelStruct(id:9, subId:1)
let val2 = MyModelStruct(id:10, subId:1)
let val3 = MyModelStruct(id:9, subId:10)
var arrayOfModel = [val1,val2,val3]; // or set but i do not know how to use a set
var arrayCleaned = cleanFunction[M2,M3]
How can i make the cleanFunction ?
Can someone help please.
Thanks for all.
Xcode : Version 7.3.1
I really don't want people to just take an answer because it's the only one, that's why I'm showing you how you can use the power of sets. Sets are used wherever it doesn't make sense to have more than one, either it's there or not. Sets provide fast methods for checking whether an element is in the set (contains), removing an element (remove), combining two sets (union) and many more. Often people just want an array because they're familiar with it, but often a set is really what they need. With that said, here is how you can use a set:
struct Model : Hashable {
var id : String?
var hashValue: Int {
return id?.hashValue ?? 0
}
}
func ==(l: Model, r: Model) -> Bool {
return l.id == r.id
}
let modelSet : Set = [
Model(id: "a"),
Model(id: "hello"),
Model(id: "a"),
Model(id: "test")
]
// modelSet contains only the three unique Models
The only requirement for a type in order to be in a set is the Hashable protocol (which extends Equatable). In your case, you can just return the underlying hashValue of the String. If your id is always a number (which it probably is), you should change the type of id to be an Int, because Strings are much less efficient than Ints and it doesn't make sense to use a String.
Also consider storing this property somewhere, so that every time you receive new models, you can just do
modelSet.unionInPlace(newModels)
Use a Set instead of an Array.
I agree you are better off using a Set. You should be able to initialize the Set using an Array, e.g., var arrayOfModel: Set = [val1, val2, val3]. But because you are using a custom type, you will need to make sure that MyModelStruct conforms to hashable. This link has a good explanation.
But if you want to use an array then you need to change
let areEqual = rhs.id == lhs.id
to
let areEqual = rhs.id == lhs.id && rhs.subId == lhs.subId)
You need to modify your struct to have a subId property (and make the variables Int instead of String.
In answer to your question, yes you do need to iterative over the array.
If you extend the Array type with this function :
extension Array
{
func uniqueValues<V:Equatable>( value:(Element)->V) -> [Element]
{
var result:[Element] = []
for element in self
{
if !result.contains({ value($0) == value(element) })
{ result.append(element) }
}
return result
}
}
You'll be able to clean up duplicates using :
var arrayCleaned = arrayOfModel.uniqueValues({$0.id!})
without needing to make the structure Equatable.
Please note that this is not going to be efficient if your array is very large and you might want to consider filtering insertions into your array at the source if possible.

How to pull data out of dictionaries

I'm trying to figure out how to basically create an if statement that is able to find out how many goals are scored in the first half of a soccer match that Barcelona played in. The stats are all there with the number beside then representing the minute the goal was scored in. So how would I write my if statement like that. I know how to do it with a simple variable with just a single value like either a string or a number but how would I find a value in a dictionary with a specific number value and the specific number value I want?
var barcelonavsRealMadrid1 = [[String : Int]]() // if you want this globally
var barcelonavsRealMadrid1goals : [String : Int] = ["barcelonaGoal":21,"RealMadridGoal":23]
var barcelonavsRealMadrid1penaltys : [String : Int] = ["RealMadridPenalty":21,"barcelonaPenalty":23]
if barcelonavsRealMadrid1goals == ["barcelonaGoal":21] {
print("Fond it")
}
You can retrieve data in dictionaries easily:
barcelonavsRealMadrid1goals["barcelonaGoal"]
But I have to tell you that I think your data structure will not work the way you want it to. For example, you can't put several goals for one team into the dictionary.
Build classes for your data and encapsulate properly. Don't throw "everything in something" and build on some magic element names. Understand the concepts you are using.
I think reading a good programming introduction might be called for, and then the Swift programming guide comes into mind.
Edit
Here's a very simplified example of what a game data structure could look like:
class Game {
var team1 : String
var team2 : String
var minutesOfGoalsTeam1: [Int]
var minutesOfGoalsTeam2: [Int]
init(team1 t1 : String, team2 t2: String) {
team1 = t1
team2 = t2
minutesOfGoalsTeam1 = []
minutesOfGoalsTeam2 = []
}
func goalsTeam1() -> Int {
return minutesOfGoalsTeam1.count
}
func goalsTeam2() -> Int {
return minutesOfGoalsTeam2.count
}
func addGoal(team : String, minute : Int) {
if team == team1 {
minutesOfGoalsTeam1.append(minute)
}
if team == team2 {
minutesOfGoalsTeam2.append(minute)
}
}
func winner() -> String {
if goalsTeam1() > goalsTeam2() {
return team1
}
if goalsTeam2() > goalsTeam1() {
return team2
}
return "DRAW"
}
}
let game = Game(team1: "Barcelona", team2: "Madrid")
game.addGoal("Barcelona", minute: 5)
game.winner()
Adding penalties is left as an exercise. This is really just the tip of the ice berg. There are hundreds of ways to do it, and I wouldn't consider this the best approach. For example, one could think about a dedicated team class. Or using some game event class with subclasses for goals and penalties, and adding those to an array and looping over it for the concrete data.
var goalCount=0
for (goal,numbers) in barcelonavsRealMadrid1{
for(var number in numbers){
if(number < 45)
goalCount++
}
}
i think it will work

How to use a Set type with custom objects as datasource

I would like to use my
var savings = Set<Saving>()
as Data source in my UITableView.
I can't use something like
cell.name = savings[indexPath.row].name
Because the subscript is only Saving. Is there a way to do this anyway?
I believe what you are looking for is something like:
var savings = Array<Saving>()
and then call it as
cell.name = savings[indexPath.row].name
Update
if you have savings as Set. you can get array out of it and use it like this:
let savingsArray = Array(savings)
and then call it as
cell.name = savingsArray[indexPath.row].name
Set's are inherently unordered, but UITableViewDataSource implementations are required to have a defined ordering. You'll have to convert the set to an Array (which has a defined ordering) and use that to implement the data source methods.
I should first mention that perhaps Set is not the best collection type for you in this case, as---as mentioned by Bill---Set is inherently unordered.
You can, however, access the name property of a Saving instance by matching the indexPath.row integer property to integer id property in your Saving class / structure, making use of Set method .filter(...).
Consider the following example. We first create a Saving structure (or class, as I gather you have) that conforms to Hashable and Equatable protocols, to be eligible as members of collection type Set:
/* Saving: conform to Hashable and Equatable for use as
collection type Set members */
struct Saving : Hashable {
var id: Int
var name: String
/* Make Saving conform to Hashable */
var hashValue: Int {
return self.id
}
init(id: Int, name: String) {
self.id = id
self.name = name
}
}
/* Make Saving conform to Equtable */
func ==(lhs: Saving, rhs: Saving) -> Bool {
return lhs.id == rhs.id
}
Now, consider the following example, of accessing specific members of you Set (of type Saving) given a integer id, e.g. such as corresponding row index (indexPath.row) in your table view:
/* Example */
var savings = Set<Saving>()
savings.insert(Saving(id: 1, name: "Savings A"))
savings.insert(Saving(id: 2, name: "Savings B"))
savings.insert(Saving(id: 3, name: "Savings C"))
let indexPathRow = 1 // for our example
var cellName = savings.filter({ $0.id == indexPathRow }).first?.name ?? "No such Savings id"
print(cellName) // Savings A
Alternatively, create the .name by .id extraction as a function.
func getSavingsName(mySavingsSet: Set<Saving>, myId: Int) -> String {
return mySavingsSet.filter({ $0.id == myId }).first?.name ?? "No such Savings id"
}
/* for your specific example */
cell.name = getSavingsName(savings, myId: indexPath.row)
Preferably handle the case "No such Saving id" using optional return instead, to avoid running into any undetermined behaviour when (trying to) find members of your Set corresponding to row id:s of your table view.

Resources