How to sum the same category total in SwiftUI?

Hi, guys I have a array like this:

[ [icon: "A", amount: 11], [icon: "B", amount: 10], [icon: "A", amount: 15], [icon: "B", amount: 13], [icon: "C", amount: 5] ]

Right now I only can show them in list one by one like:

icon: "A", amount: 11

icon: "B", amount: 10

icon: "A", amount: 15

icon: "B", amount: 13

icon: "C", amount: 5

How can i show them in a List like:

icon: "A", amount: 26

icon: "B", amount: 23

icon: "C", amount: 5

I appreciate it for any help and suggestions !

Answered by Claude31 in 705268022

How are icon and amount defined ?

I tested this:

typealias Element = [String: Any] 
// I guessed a definition of icon and amount
let icon = "icon"
let amount = "amount"

let myDict : [Element] = [ [icon: "A", amount: 11], [icon: "B", amount: 10], [icon: "A", amount: 15], [icon: "B", amount: 13], [icon: "C", amount: 5] ]    

To calculate the sum for "A", we first filter the dictionary for icon "A"

let myDictA = myDict.filter() { $0[icon] as! String == "A" }

Then we can compute the sum of amount with reduce (note this is not yet totally robust if item[amount] was nil…

let totalA = myDictA.reduce(0) { sum, item in sum + (item[amount] as! Int)}

So we can write it in a single line

let totalA = myDict.filter() { $0[icon] as! String == "A" }.reduce(0) { sum, item in sum + (item[amount] as! Int)}

To compute for all icons, we shall first extract the icons ("A", "B", …) in an array icons.

var icons : [String] = []
for elem in myDict {
    if elem[icon] != nil && !icons.contains(elem[icon] as! String) { icons.append(elem[icon] as! String) }
}

Note that we test for nil, hence avoiding a crash.

To get the sum for each icon, we shall make the same computation that we did as example for totalA, looping through the icons we have found. And we put the result in the correct key in a dictionary.

var sums : [String] = []
for ic in icons {
    sums[ic] = myDict.filter() { $0[icon] as! String == ic }.reduce(0) { sum, item in sum + (item[amount] as! Int)}
}

print(sums)

We get the following:

["C": 5, "A": 26, "B": 23]

If you want it ordered, you could write:

let sortedSums = sums.map() { ($0.key, $0.value)}.sorted() { $0.0 < $1.0 } 
print(sortedSums)

and get:

[("A", 26), ("B", 23), ("C", 5)]

Accepted Answer

How are icon and amount defined ?

I tested this:

typealias Element = [String: Any] 
// I guessed a definition of icon and amount
let icon = "icon"
let amount = "amount"

let myDict : [Element] = [ [icon: "A", amount: 11], [icon: "B", amount: 10], [icon: "A", amount: 15], [icon: "B", amount: 13], [icon: "C", amount: 5] ]    

To calculate the sum for "A", we first filter the dictionary for icon "A"

let myDictA = myDict.filter() { $0[icon] as! String == "A" }

Then we can compute the sum of amount with reduce (note this is not yet totally robust if item[amount] was nil…

let totalA = myDictA.reduce(0) { sum, item in sum + (item[amount] as! Int)}

So we can write it in a single line

let totalA = myDict.filter() { $0[icon] as! String == "A" }.reduce(0) { sum, item in sum + (item[amount] as! Int)}

To compute for all icons, we shall first extract the icons ("A", "B", …) in an array icons.

var icons : [String] = []
for elem in myDict {
    if elem[icon] != nil && !icons.contains(elem[icon] as! String) { icons.append(elem[icon] as! String) }
}

Note that we test for nil, hence avoiding a crash.

To get the sum for each icon, we shall make the same computation that we did as example for totalA, looping through the icons we have found. And we put the result in the correct key in a dictionary.

var sums : [String] = []
for ic in icons {
    sums[ic] = myDict.filter() { $0[icon] as! String == ic }.reduce(0) { sum, item in sum + (item[amount] as! Int)}
}

print(sums)

We get the following:

["C": 5, "A": 26, "B": 23]

If you want it ordered, you could write:

let sortedSums = sums.map() { ($0.key, $0.value)}.sorted() { $0.0 < $1.0 } 
print(sortedSums)

and get:

[("A", 26), ("B", 23), ("C", 5)]

In the view i want to get the only one category with total amount like: Hotel : $610/ Not like the pic above :

Thanks for the feedback.

The results is not what i want

What would you expect ?

To get exactly

  • icon: "A", amount: 26
  • icon: "B", amount: 23
  • icon: "C", amount: 5

just do:

for (ic, am) in sortedSums {
    print("icon: \"\(ic)\", amount: \(am)")
}

For struct to be Comparable, need to define comparison func. The same for Equatable. I still do not see what icon is expected to be in [icon: "A", amount: 11]

So it is not icon, it is category, which is effectively a String. Adapting code should be easy.

Is the dataSource for the table an array [ExpenseData] ?

If so:

let myDict : [Element]

becomes

myData : [ExpenseData]

and instead of using

$0[icon]

you use

$0.category

Thank you sir, i tried it again but i got this error like below:


let newArray = expensesData.filter {format(date: $0.date) == format(date: self.currentDate)}

        let expenseArray = newArray.filter {$0.type == "Expense"}

        

//        self.sortedByExpense = expenseArray.sorted(by: {$0.amount > $1.amount})

        

        var icons: [String] = []

        for i in expenseArray {

            icons.append(i.expenseIcon)

        }

        print(icons)

        var sums: [String] = []

        for i in icons {

            sums[i] = expenseArray.filter{$0.expenseIcon == i}.reduce(0){sum, item in sum + item.amount} 
//No 'reduce' candidates produce the expected contextual result type 'String' 
 //No exact matches in call to subscript 
        }

I miss too much info to test.

But this is surprising:

for i in expenseArray {
    icons.append(i.expenseIcon)
}

You add all of them, even multiple times.

You should write:

        var icons: [String] = []
        for expense in expenseArray {    // I personally prefer explicit names
            if !icons.contains(expense.expenseIcon) {     // Don't append duplicates
                icons.append(expense.expenseIcon)
            }
        }

You define sums as [String].

  • Doing so, you cannot add something to it.
  • in addition, you index with icon which is a String. Arrays do not accept Strings as indexes. To index with a String, you need to use dictionary. That's my mistake, I incorrectly copied my test code ; correct one was:
var sums = [String: Int] ()
for ic in icons {
    sums[ic] = myDict.filter() { $0[icon] as! String == ic }.reduce(0) { sum, item in sum + (item[amount] as! Int)}
}

So, you should write

        var sums: [String: Int] = [:]    // If they are Int. If Double or Decimal, change
        for i in icons {
            sums[i] = expenseArray.filter{$0.expenseIcon == i}.reduce(0) { sum, item in sum + item.amount} 
            // No 'reduce' candidates produce the expected contextual result type 'String' 
            // No exact matches in call to subscript 
        }

I could not text because I miss some parts of your code. But tell me if that works now.

I got exactly what I need like below:

How to sum the same category total in SwiftUI?
 
 
Q