How to group the same element in array and sum it in Swift?

Viewed 1518

How to filter duplicate category element into different array and count their amount?

This is the format, record is from the core data.

var record = [Record]()

[<Record:...; data: {accountbook = "MyBook";
                     amount = "10.50";
                     category = "A";
                     id = 1; 
},<Record:...; data: {accountbook = "MyBook";
                     amount = "5.50";
                     category = "B";
                     id = 2;
},<Record:...; data: {accountbook = "MyBook";
                     amount = "4.50";
                     category = "B";
                     id = 3;
}]

What I want

var category = ["A", "B"] //success
var total = [10.50, 10.00]

This is what I do for finding the category, and it works but how to group the same category and sum the total?

var category =[String]()
for categoryObject in record{
    if let categoryItem = categoryObject.category{
        category.append(categoryItem)
    }
}

//I tried this code to group the same category but fail. 

let result = Set(record).map{ category in return record.filter{$0 == category} }

Another way is this. but how if I have A-Z category? it will have very long code..Is there any way can detect the same value and split it to different array so that I can sum it by category.

categoryFilter = record.filter { $0.category!.contains("A") }
4 Answers

First group your record object by category like this

extension Sequence {
    func group<GroupingType: Hashable>(by key: (Iterator.Element) -> GroupingType) -> [[Iterator.Element]] {
        var groups: [GroupingType: [Iterator.Element]] = [:]
        var groupsOrder: [GroupingType] = []
        forEach { element in
            let key = key(element)
            if case nil = groups[key]?.append(element) {
                groups[key] = [element]
                groupsOrder.append(key)
            }
        }
        return groupsOrder.map { groups[$0]! }
    }
}

Then you will get your distinct arrays based on category like this

var records : [Record] = []// your objects
let distinctRecords = records.group(by: {$0. category})

Now you can use reduce to calculate sum of values of that category

for items in distinctRecords{
    let sum = items.reduce(0.0){$0.0 + $1. amount ?? 0.0}// assuming you have float values in your amount
    print(items)// do whatever you want to do with your distinct array
    print(" \(sum)")
}

@Wan Jern I have written a piece of code, you can try this one. Hopefully, it will work.

var category = [String]()
var totalArr = [CGFloat]()
for categoryObject in record{
    if let categoryItem = categoryObject.category{
        if !category.contains(categoryItem) {
            category.append(categoryItem)
            totalArr.append(categoryObject.amount)
        } else {
            let index = category.index(of: categoryItem)
            let itemAtIndex = category[index]
            let itemAtIndex = itemAtIndex + categoryObject.amount
            totalArr.insert(itemAtIndex, at: index)
        }
    }
}

Do you have your record struct in a class model?

like my data model selected from sqlite:

//Data model
import Foundation
import UIKit
class scoreModel: NSObject {
    var lessonName:String = String()
    var lessonCode:String = String()
    var creditPoint:Double = Double()
    var totalStudentNumber:Int = Int()
    var teacherName:String = String()
    var semesterName:String = String()
    var scoreValue:String = String()
    var studentCount:Int = Int()
}

If the answer is yes, we can use pointer in array and repeat while loop to do this manually.

Like my code:

let mysql = ""
let dataArray = SQLiteManager.shareInstance.queryDB(sql:mysql)
var i = 0

while i<dataArray.count-1
{
    var scoreArray = [Dictionary<String, Int>]()
    var range = 0
    var test = 0
    test = i
    //print("pointer i is'\(test)'")

    while ((dataArray[test]as! scoreModel).lessonCode == (dataArray[test+range]as! scoreModel).lessonCode && (test+range)<dataArray.count-1)
    {
        let key = (dataArray[test+range]as! scoreModel).scoreValue
        let value = (dataArray[test+range]as! scoreModel).studentCount
        var dict: [String: Int] = [String: Int]()
        dict[key] = value
        scoreArray.append(dict)
        //print("working pointer is'\(test+range)'")
        range = range+1
    }

    //transfer array
    let model:resultModel = resultModel()
    model.lessonName = (dataArray[test]as! scoreModel).lessonName
    model.lessonCode = (dataArray[test]as! scoreModel).lessonCode
    model.creditPoint = (dataArray[test]as! scoreModel).creditPoint
    model.semesterName = (dataArray[test]as! scoreModel).semesterName
    model.teacherName = (dataArray[test]as! scoreModel).teacherName
    model.totalStudentNumber = (dataArray[test]as! scoreModel).totalStudentNumber
    model.scoreArray = scoreArray
    resultArray.add(model)
    i = i+range
    //print("range is'\(range)'")
}

Make the Records as Hashable with "category" as the unique in stubs function

struct Record: Hashable {

    var accountbook = ""
    var category = ""
    var amount = 0.0
    // added from stubs of Hashable
    var hashValue: Int { return category.hashValue }

    static func ==(lhs: Record, rhs: Record) -> Bool {
        return lhs.category == rhs.category
    }
}

Then filter the unique categories

let categories = Set(record).map { $0.category }
print(categories) // ["B", "A"]

And make a sum of each category

let totals = categories.map { c in
    record.filter { $0.category == c }.map{ $0.amount }.reduce(0, +)
}
print(totals) // get the sums as [10.0, 10.5]
Related