Reputation: 113
I have structure called person
struct person{
var name : String
var score: Int
}
Then I create an array called
let people : [person] = [person("a", 1), person("a", 3), person("b", 5)]
As you can see, there are two objects with the same name "a" here.
Now I would like to turn this one into a cumulative dictionary, that shows the total score of each person. In this case
dict = {"a": 4 (3+1), "b": 5}
I know I'm violating the OO design rules. Thanks
Upvotes: 0
Views: 63
Reputation: 42598
I'm assuming you are new to Swift. Let me walk you though a few iterations of this.
The most basic is the straight forward approach.
var dict = [String: Int]()
for person in people {
if let score = dict[person.name] {
// If the person already has a score in dict, then add this new score to it.
dict[person.name] = score + person.score
} else {
// If this is the first score for this person, then add the person to dict.
dict[person.name] = person.score
}
}
Next, we will used subscript(_:default:)
operator to combine the two parts of the if conditional.
var dict = [String: Int]()
for person in people {
// Add the new score to the person's existing score. If there is no existing
// score, then default to 0 and add the new score.
dict[person.name, default: 0] += person.score
}
Finally, use reduce(into:_:)
to get rid of the for loop (see no raw loops segment of C++ Seasoning)
// Reduce people into a dictionary of names and cumulative scores.
let dict = people.reduce(into: [String: Int]()) { result, person in
result[person.name, default: 0] += person.score
}
Upvotes: 0
Reputation: 63399
There's a fundamental modelling issue here. Your struct person
doesn't actually model a person. It models something like a RoundResult
.
I would refactor this by making a Player
that truly models a person, (with only fields like name: String
), and make a RoundResult
that contains a winner: Player
and a score: Score
.
struct Player: Hashable { // Perhaps should be a class, if names aren't unique.
let name: String
}
struct RoundResult {
let winner: Player
let score: Int
}
let playerA = Player(name: "a")
let playerB = Player(name: "b")
let roundResults = [
RoundResult(winner: playerA, score: 1),
RoundResult(winner: playerA, score: 3),
RoundResult(winner: playerB, score: 5),
]
let scoresByPlayer = Dictionary(grouping: roundResults, by: \.winner)
.mapValues { roundResults -> Int in
let scores = roundResults.lazy.map(\.score)
return scores.reduce(0, +)
}
print(scoresByPlayer)
From here, you can add a score
variable on player, which actually models the players score, not just a single sliver of it from a single round/game/match/whatever
Upvotes: 1
Reputation: 49590
You can achieve this in two steps: 1) group into a dictionary, and 2) sum scores of the grouping:
// grouping = { "a": [person("a", 1), person("a", 2)], "b": [person("b": 3)]
let grouping = Dictionary.init(grouping: people, by: { person in person.name })
let dict = grouping.mapValues { group in
group.reduce(0, { sum, person in sum + person.score })
}
Or in its shorter, but more cryptic form:
let d = Dictionary.init(grouping: people, by: { $0.name })
.mapValues{ $0.reduce(0, $0 + $1.score ) }
Upvotes: 0