Here's my solution to that problem:
struct Student {
var name: String
var surname: String
}
enum Letter : String, CaseIterable {
case A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, hash
}
let students = [
Student(name: "Arabella", surname: "Catharine"),
Student(name: "Antony", surname: "Caisy"),
Student(name: "Ćukasz", surname: "Patsy"),
Student(name: "hash", surname: "Foo"),
Student(name: "bob", surname: "gimley"),
Student(name: "", surname: ""),
]
func groupStudentsByFirstLetter(_ students: [Student]) -> [Letter? : [Student]] {
func letterForStudent(_ student: Student) -> Letter? {
Letter.allCases.first(where: {
student.name.starts(with: $0.rawValue)
})
}
return Dictionary(grouping: students, by: letterForStudent)
}
print(groupStudentsByFirstLetter(students))
/*
[
Optional(Letter.hash): [Student(name: "hash", surname: "Foo")],
Optional(Letter.A): [Student(name: "Arabella", surname: "Catharine"), Student(name: "Antony", surname: "Caisy")],
nil: [Student(name: "Ćukasz", surname: "Patsy"), Student(name: "bob", surname: "gimley"), Student(name: "", surname: "")]
]
*/
func groupStudentsByFirstCharacter(_ students: [Student]) -> [Character? : [Student]] {
return Dictionary(grouping: students, by: { $0.name.first })
}
print(groupStudentsByFirstCharacter(students))
/*
[
nil: [Student(name: "", surname: "")],
Optional("h"): [Student(name: "hash", surname: "Foo")],
Optional("A"): [Student(name: "Arabella", surname: "Catharine"), Student(name: "Antony", surname: "Caisy")],
Optional("Ć"): [Student(name: "Ćukasz", surname: "Patsy")],
Optional("b"): [Student(name: "bob", surname: "gimley")]
]
*/
I've made two versions of the method, one that groups by the enum you wrote (groupStudentsByFirstLetter
) and one that groups by the first Character
in the name (groupStudentsByFirstCharacter
)
Parts that you should pay attention to:
-
first(where: { ... })
will return the first element that satisfies some condition, in this case that the name of the student starts with that element
-
.rawValue
will give you the name of an enum case as a String
-
Character
in Swift is an "unicode extended grapheme cluster", which differs from most of programming languages
Notice how I made the key of the dictionary optional. It's because there are situations where a student doesn't fit into any letter. What should happen in that case?
Should "Bob" and "bob" be filed under the same key?
What to do with the letters you didn't think about, like "Ć"?
Is "hash" even a letter?
some stylistic choices I made, but are not important for the code to work
- changed
let
to var
in the struct definition. In structs it doesn't protect against anything if you doesn't restrict the init.
- I moved all the enum cases into a single line. That way is much easier to read if case names are super short like in your code
- I renamed
Alphabet
to Letter
. In Swift we name the type in a way that describes a single instance, not the whole type in aggregate.
-
studentsArray
-> students
because we usually don't care what type of collection a variable is. The only thing we need to know is that it's a collection of students - plural name is enough for that
- I used full functions instead of closures. I find it very helpful to be more verbose when you're struggling with finding the correct types
- trailing comma in an array literal is cool