Home > Mobile >  Swift 5: Why the Index out of range error appears?
Swift 5: Why the Index out of range error appears?

Time:07-28

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]
}

enter image description here

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:

  1. 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.
  2. You're indexing into an empty array rather than appending

Luckily, these are easily fixable!

Here's a progression of improvements:

  1. 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]))
    }
    
  2. 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]))
    }
    
  3. 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]))
    }
    
  4. 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))
    }
    
  5. 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 a let constant!

  • Related