Как сгруппировать массив объектов по имени и сумме

#swift

#swift

Вопрос:

Например:

         [Test(count: 5, name: "Ivan", innerNumber: "123123"),
         Test(count: 5, name: "Ivan", innerNumber: "123123"),
         Test(count: 10, name: "Miko", innerNumber: "4312432"),
         Test(count: 10, name: "Miko", innerNumber: "4312432")]
 

Я хочу сгруппировать массив по name полю и суммировать по count полю

В результате я хочу получить массив в виде:

         [Test(count: 10, name: "Ivan", innerNumber: "123123"),
         Test(count: 20, name: "Miko", innerNumber: "4312432")]
 

Комментарии:

1. Используйте словарь (группировка:по:), где вы группируете по имени, затем вычисляете общее количество для каждой записи в словаре и создаете новый тест с общим и уникальным именем. Затем вам нужно решить, что делать, если внутренний номер не является уникальным для группы

2. Вы можете написать несколько примеров кода, как это выглядит. Поле внутреннего номера можно игнорировать.

3. Я дал вам идею, как это сделать, почему бы не попробовать самому?

Ответ №1:

Просто убедитесь, что ваша коллекция отсортирована по имени, и используйте reduce для накопления значения count:

 let grouped: [Test] = tests.sorted(by: {$0.name < $1.name}).reduce(into: []){
    if let last = $0.last, last.name == $1.name,
        last.innerNumber == $1.innerNumber {
        $0[$0.indices.last!] = .init(count: last.count   $1.count, name: last.name, innerNumber: last.innerNumber)
    } else {
        $0.append($1)
    }
}
 

Комментарии:

1. Спасибо! выглядит лаконично!

Ответ №2:

Dictionary(grouping:by:) отлично подходит для этого:

 struct Test: Hashable {
    let count: Int
    let name: String
    let innerNumber: String // Well that's not really a number, is it 🤨
}

let input = [
    Test(count: 5, name: "Ivan", innerNumber: "123123"),
    Test(count: 5, name: "Ivan", innerNumber: "123123"),
    Test(count: 10, name: "Miko", innerNumber: "4312432"),
    Test(count: 10, name: "Miko", innerNumber: "4312432"),
]

let output = Dictionary(grouping: input, by: .name)
    .map { name, tests -> Test in
        let sum = tests.lazy.map(.count).reduce(0,  )
        
        // Assumes that all tests with the same name also have the same innerNumber.
        // You might want to verify that with an assertion.
        let innerNumber = tests.first!.innerNumber
        
        return Test(count: sum, name: name, innerNumber: innerNumber)
    }

for test in output {
    print(test)
}