I have two arrays that I am going to include in an array with object instances. When I try to do this, I get an error that the Index is out of range.
My code is below. I can't figure out why I'm getting this error. Can someone please let me know what I'm doing wrong?
import UIKit
let mealName = ["Breakfast", "Lunch", "Dinner", "Breakfast", "Lunch", "Dinner", "Breakfast", "Lunch", "Dinner", "Snack", "Supper", "Snack", "Breakfast", "Snack", "Lunch", "Dinner", "Supper", "Snack", "Breakfast", "Snack", "Dinner", "Snack", "Snack", "Snack", "Breakfast", "Snack", "Lunch", "Snack", "Snack", "Snack", "Breakfast", "Snack", "Lunch", "Snack", "Snack", "Snack", "Snack"]
let mealCalories = [725.03, 539.44, 849.49, 956.53, 267.18, 567.17, 353.47, 387.12, 700.76, 62.87, 857.35, 176.56, 123.79, 16.74, 813.74, 858.23, 127.26, 448.19, 932.62, 60.22, 880.55, 58.85, 78.48, 21.5, 564.74, 190.75, 911.01, 67.47, 81.44, 25.35, 954.94, 45.75, 858.71, 61.7, 189.75, 72.54, 75.84]
struct Meal {
var mealName: String
var mealCalories: Double
}
var meals = [Meal]()
for i in 0..<37 {
meals[i].mealName = mealName[i]
meals[i].mealCalories = mealCalories[i]
}
CodePudding user response:
You have initialized the meals array but it is empty and doesn't have length. Therefore, when you refer to meals[i] it raises an error. You can do something like this:
var meals: [Meal] = [Meal](repeating: Meal(mealName: "", mealCalories: 0.0), count: 37)
Then you can refer to meals[i] (0<= i <37).
If you want to start with an empty array you can add elements to it:
var meals = [Meal]()
Then:
for i in 0..<37 {
let meal = Meal(mealName: mealName[i], mealCalories: mealCalories[i])
meals.append(meal)
}
In this case, the length of meals will grow as i grows.
CodePudding user response:
Just avoid the parallel arrays
You already have a Meal
struct, which begs the question: Why not define your meals directly?
struct Meal {
// Don't prefix the properties with "meal".
// It leads to pointless code like "someMeal.mealName".
// You already know it's a meal by the type.
let name: String
let calories: Double
}
let meals = [
Meal(name: "Breakfast", calories: 725.03),
Meal(name: "Lunch", calories: 539.44),
Meal(name: "Dinner", calories: 849.49),
Meal(name: "Breakfast", calories: 956.53),
Meal(name: "Lunch", calories: 267.18),
Meal(name: "Dinner", calories: 567.17),
Meal(name: "Breakfast", calories: 353.47),
Meal(name: "Lunch", calories: 387.12),
Meal(name: "Dinner", calories: 700.76),
Meal(name: "Snack", calories: 62.87),
Meal(name: "Supper", calories: 857.35),
Meal(name: "Snack", calories: 176.56),
Meal(name: "Breakfast", calories: 123.79),
Meal(name: "Snack", calories: 16.74),
Meal(name: "Lunch", calories: 813.74),
Meal(name: "Dinner", calories: 858.23),
Meal(name: "Supper", calories: 127.26),
Meal(name: "Snack", calories: 448.19),
Meal(name: "Breakfast", calories: 932.62),
Meal(name: "Snack", calories: 60.22),
Meal(name: "Dinner", calories: 880.55),
Meal(name: "Snack", calories: 58.85),
Meal(name: "Snack", calories: 78.48),
Meal(name: "Snack", calories: 21.50),
Meal(name: "Breakfast", calories: 564.74),
Meal(name: "Snack", calories: 190.75),
Meal(name: "Lunch", calories: 911.01),
Meal(name: "Snack", calories: 67.47),
Meal(name: "Snack", calories: 81.44),
Meal(name: "Snack", calories: 25.35),
Meal(name: "Breakfast", calories: 954.94),
Meal(name: "Snack", calories: 45.75),
Meal(name: "Lunch", calories: 858.71),
Meal(name: "Snack", calories: 61.70),
Meal(name: "Snack", calories: 189.75),
Meal(name: "Snack", calories: 72.54),
Meal(name: "Snack", calories: 75.84),
]
This way you don't have to fidget around to find out which string in one array "lines up" with the double in the other array.
If the parallel arrays must stay
(e.g. they come from an API you don't control)
There are several huge issues:
You're hard-coding the number of meals, when it's entirely possible that they might change in the future.
An array already stores its own count, and hard-coding that count elsewhere is introducing a split source of truth. The risk here is that they go out of sync:
- If the new meal names/calories are added but you forget to update the
37
, then you'll be missing those meals from your result. - If some meal names/calories are removed, then your
37
will be too large and cause an out of bounds error.
- If the new meal names/calories are added but you forget to update the
You're indexing into an empty array rather than appending
Luckily, these are easily fixable!
Here's a progression of improvements:
Fix the indexing past the end, and use
append
instead:var meals = [Meal]() for i in 0..<37 { meals.append(Meal(name: mealNames[i], calories: mealCalories[i])) }
Un-hardcode the last index:
assert(mealNames.count == mealCalories.count, "Config error! The names and calories should match up one for one!") var meals = [Meal]() for i in 0..<meanNames.count { meals.append(Meal(name: mealNames[i], calories: mealCalories[i])) }
Un-harcode the start index:
assert(mealNames.count == mealCalories.count, ...) var meals = [Meal]() for i in mealNames.indices { meals.append(Meal(name: mealNames[i], calories: mealCalories[i])) }
Forget the indices entirely! Just use
zip(_:_:)
:assert(mealNames.count == mealCalories.count, ...) var meals = [Meal]() for (name, calories) in zip(mealNames, mealCalories) { meals.append(Meal(name: name, calories: calories)) }
Iterating over an input, transforming it, and producing a new array is just a map operation.
assert(mealNames.count == mealCalories.count, ...) let meals = zip(mealNames, mealCalories).map { name, calories in Meal(name: name, calories: calories)) }
Bonus: now that
meals
isn't constructed via repeated mutation, we can now make it alet
constant!