Programming in Swift · Challenge: Methods | Ray Wenderlich


This is a companion discussion topic for the original entry at https://www.raywenderlich.com/5994-programming-in-swift/lessons/45

May I use “for” loop to find maximum Grade?
Just iterate all Students

I also read that “map” is applying same actions for all elements in Array
for example:
let numbers = [“Tony”,“Bobby”,“Andy”]
let mapUs = numbers.map{ $0.lowercased()}

But in challenge we don’t apply anything for each element. How does it work?

Hi! Yes, you could use a for loop. map is just a specialized, condensed for loop, which, as you said, can apply the same action for each element in an array.

Technically, you don’t have to do anything to the elements for map to work. There wouldn’t be much point in it, but you could just return each element and get an exact copy of the array. You also technically don’t have to use the element at all. You could do something like return the number 1 for each element and get a new array like this: [1, 1, 1]. But, again, that wouldn’t be a very good use of map!

It may be more helpful to think of map like a loop that’s meant to run code that can use each element in some way.

In first use of map in the challenge, we used each student in the students array to grab their grade, and thus created an array of those grades.

The second map used each student to create a new, mutated student with a curved grade. That created a nearly identical students array, except for each student’s grade.

Hopefully that helps! If you’re still unsure, try writing a for loop for each use of map that you see.

Hi! I modified the challenge somewhat to return the student names with the best grades. It does work but my code appears somewhat verbose to me and I probably totally abused the map function.
Some specifics:

  • I would be interested to know if you could somehow incorporate the determination of the best grade into another method.
  • I tried to retrieve the indices of the best grade values and completely failed. I considered using the firstIndex(of: ) method but that doesn’t do the job. A indices(of: ) method would have been the thing for me, but that one doesn’t exist. I ended up somewhat awkwardly enumerating the array and mapping the offsets back to an array of indices.

I would be grateful for some hints as to how to improve the code if possible.

Here’s the code:

struct Student {
    var firstName: String
    var lastName: String
    var grade: Int
}

struct Classroom {
    var className : String
    var students : [Student]
    
    func getHighestGrades() -> [String] {

        // Create an array of grades with same indices as the students array
        let grades = self.students.map { student -> Int in
            return student.grade
            }
        
        // Determine the best grade (in my country 1 is the best and 6 the worst)
        var max = 6
        for grade in grades {
            if grade < max {
                max = grade
            } else {
                continue
            }
        }
        
        // Retrieve the indices of those grades that match the best grade
        let bestStudentsIndices = grades.enumerated().map { (index: Int, grade: Int) -> Int? in
            if grade == max {
                return index
            } else {
                return nil
            }
            }
        
        // Map the index paths back to the array of students and return their names as strings
        let bestStudents = bestStudentsIndices.map { (index) -> String? in
            if let index = index {
                return self.students[index].firstName + self.students[index].lastName
            } else {
                return nil
            }
        }.compactMap { $0 }
        
        return bestStudents
    }
}

let student1 = Student(firstName: "John", lastName: "Granger", grade: 2)
let student2 = Student(firstName: "Theon", lastName: "Lestrange", grade: 1)
let student3 = Student(firstName: "Gregor", lastName: "Weasley", grade: 1)
let student4 = Student(firstName: "Sansa", lastName: "Greyback", grade: 4)
let arrayOfStudents = [student1, student2, student3, student4]

let myClass = Classroom(className: "5b", students: arrayOfStudents)
myClass.getHighestGrades()

Edit: And I don’t quite understand yet why a for loop won’t compile in this scenario. Is it because the local ‘student’ variable created for the loop is considered part of the struct by the compiler and the for loop itself can’t be declared as mutating?

@jessycatterwaul @catie Can you please help with this when you get a chance? Thank you - much appreciated! :]

Hi Daniel!

Sure. I might write it like this:

extension Classroom {
  var bestGrade: Int {
    return students.map { $0.grade } .min()!
  }

  var studentsWithBestGrade: [String] {
    return
      students
      .filter { $0.grade == bestGrade }
      .map { "\($0.firstName)  \($0.lastName)" }
  }
}

Like for studentsWithBestGrade, going with a combination of filter and map is one option:

students.enumerated()
.filter { $1.grade == bestGrade }
.map { $0.offset }

When iterating over an array of structures (value types), the structures are copied. You’re not permitted to mutate the copies directly. And even if you could, they wouldn’t be in the place of the original structures. Reassigning to the entire students array, or altering at an index using subscripting, are the ways to perform mutation.

Nice work on your code!