5

i am figuring out a way to get the sum of values of classes which are contained in an array. My setup is as follows:

class CustomClass {
    var value: Int?
    init(value: Int) {
       self.value = value
    }
}

let object1 = CustomClass(value: 2)
let object2 = CustomClass(value: 4)
let object3 = CustomClass(value: 8)

let array: [CustomClass] = [object1, object2, object3]

My current solution is as follows:

var sumArray = [Int]()
for object in array {
    sumArray.append(object.value!)
}
let sum = sumArray.reduce(0, +)

The problem is that it gets very complex with classes with many other values, does anybody know a better solution?

3
  • When you say "very complex with classes with many other values," Does this mean you are also trying to include values of different types of classes into the sum operation? Commented Feb 15, 2019 at 17:18
  • Yes, indeed. In fact my class contains Int and Date type but the date type is not supposed to be "merged".. Commented Feb 15, 2019 at 17:24
  • @AndreasSchultz I see nothing complex so far. You have to somehow convert the objects to a number (which you are already doing, although .map would be a better solution) and then just sum the numbers. Commented Feb 15, 2019 at 17:28

4 Answers 4

13

You can compactMap your array of custom class into array of integer and then reduce that array to its sum. Like,

let sum = array.lazy.compactMap { $0.value }
            .reduce(0, +)
Sign up to request clarification or add additional context in comments.

4 Comments

use trailing closures, You can simplify to .reduce(0, +)
I would put a lazy in there, to skip the intermediate array allocation as a result of compactMap
@Alexander I wonder how to do that. Is it like lazy var sum = array.compactMap { $0.value }.reduce(0, +) ??
No no, the lazy attribute makes the expression computed upon first request. It still involves the intermediate array. Instead, you use the lazy computed property of the array, which returns a lazy view which keeps track of the operations you want to perform, without actually performing them until the result is needed. array.lazy.compactMap { $0.value }.reduce(0, +)
3

You can use a single reduce on array.

let sumOfValues = array.reduce({$0 += ($1.value ?? 0)})

4 Comments

There is no need to use reduce into for that. reduce(0) { $0 + ($1.value ?? 0) }
@LeoDabus you're right, there's no need to use reduce(into:,_:), but I also don't see any advantage of using reduce(_:,_:) over it
There is but I not the right one to explain it to you. Before the implementation of the reduce(into:) in Swift 4 people sometimes needed to declare the result as variable to be able to change it inside the closure. if you don't need to change it there just use plain reduce and return the sum result plus the element.
Resuming If you use the plain reduce method there is no variables just constants inside your closure
1

I would create a protocol for your class or structures that contains a value. And change its declaration to non optional.

protocol Valueable {
    var value: Int { get }
}

Then you will need to make your class conform to that protocol:

class CustomClass: Valueable {
    let value: Int
    init(value: Int) {
        self.value = value
    }
}

Now you can extend the collection protocol with a read only instance property to return the sum of all elements in your array.

extension Collection where Element: Valueable {
    var sum: Int {
        return reduce(0) { $0 + $1.value }
    }
} 

let object1 = CustomClass(value: 2)
let object2 = CustomClass(value: 4)
let object3 = CustomClass(value: 8)

let objects = [object1, object2, object3]

let sum = objects.sum   // 14

edit/update:

Another option is to extend sequence and add a generic sum method that accepts a key path that its property conforms to AdditiveArithmetic


or add an associated type to the protocol that conforms to AdditiveArithmetic:

protocol Valueable {
    associatedtype Value: AdditiveArithmetic
    var value: Value { get }
}

extension Collection where Element: Valueable {
    var sum: Element.Value { reduce(.zero) { $0 + $1.value } }
}

class CustomClass: Valueable {
    let value: Decimal
    init(value: Decimal) {
        self.value = value
    }
}

Usage:

let object1 = CustomClass(value: 123.4567)
let object2 = CustomClass(value: 12.34567)
let object3 = CustomClass(value: 1.234567)

let objects = [object1, object2, object3]

let sum = objects.sum   // 137.036937

4 Comments

This is horribly complex, sorry.
@user3069232 Well this depends on your programing level. This is very basic protocol usage which is the main reason of Swift success (protocol oriented language)
@user3069232 another option is to extend sequence and add a generic sum method that accepts a key path that its property conforms to AdditiveArithmetic
@user3069232 check my last edit. Thats when it start to get complex.
0

Short way:

let sum = customArray.reduce(0) { $0 + $1.intValue }

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.