I needed to sum a number of elements in an integer array for an app that I am writing, but only for the first x numbers. As I couldn't find anything in the standard SwiftUI that fitted the bill (it doesn't mean that it doesn't exist, just that I did a quick search and couldn't find anything suitable), I wrote my own function.
This made me think, are there other related functions that could also be useful (in probably only a handful of cases), and could I make them generic, at least for numbers. I came up with four general scenarios.
- sum the first x numbers in the array.
- sum the last y numbers, starting after the first x number in the array.
- sum the numbers between position x and y, inclusive, in the array.
- sum the numbers in the array outside of the range of postion x to position (so excluding x and y values, and those inbetween).
Position selected that are outside the array need to be handled, as well as if in some of the function calls the Start
and End
position values are reversed.
I also made a simplistic view, to be able to see the result, and to set the start and end index, for the various summation functions.
Here is my code for you to use, if you need it.
I am sure that there improvements to be made.
func sumNumberListTo<T: Numeric>(_ upto: Int, numberList: [T]) -> T {
guard !numberList.isEmpty else { return 0 }
guard upto > 0 else { return 0 }
guard upto < numberList.count else { return numberList.reduce(0, +) }
return numberList
.dropLast(numberList.count - upto)
.reduce(0, +)
}
func sumNumberListFrom<T: Numeric>(_ from: Int, numberList: [T]) -> T {
guard !numberList.isEmpty else { return 0 }
guard from > 0 else { return numberList.reduce(0, +) }
guard from <= numberList.count else { return 0 }
return numberList
.dropFirst(from - 1)
.reduce(0, +)
}
func sumNumberListBetween<T: Numeric>(_ from: Int, _ to: Int, numberList: [T]) -> T {
guard !numberList.isEmpty else { return 0 }
guard from > 0 else { return sumNumberListTo(to, numberList: numberList) }
guard from <= numberList.count else { return sumNumberListFrom(to, numberList: numberList) }
// At this point 'from' is within the range
guard to > 0 else { return sumNumberListTo(from, numberList: numberList) }
guard to <= numberList.count else { return sumNumberListFrom(from, numberList: numberList) }
// At this point 'to' is also within the range
if from == to {
return numberList[from - 1]
} else if from > to { // handle swapped boundary positions
return numberList
.dropLast(numberList.count - from) // work on the back of the array first
.dropFirst(to - 1)
.reduce(0, +)
} else {
return numberList
.dropLast(numberList.count - to) // work on the back of the array first
.dropFirst(from - 1)
.reduce(0, +)
}
}
func sumNumberListExcept<T: Numeric>(_ from: Int, _ to: Int, numberList: [T]) -> T {
// It is debateable whether this `let` should be defined to improves clarity and make the code smaller,
// versus if the speed of operation is more important where you would write the code repeatedly, where needed,
// at the expense of code size.
let sumAllNumberList = numberList.reduce(0, +)
guard !numberList.isEmpty else { return 0 }
guard from > 0 else { return sumNumberListFrom(to + 1, numberList: numberList) }
guard from <= numberList.count else { return sumAllNumberList - sumNumberListFrom(to, numberList: numberList) }
// At this point 'from' is within the range
guard to > 0 else { return sumNumberListFrom(from + 1, numberList: numberList) }
guard to <= numberList.count else { return sumNumberListTo(from - 1, numberList: numberList) }
// At this point 'to' is also within the range
if from == to {
return sumAllNumberList - numberList[from - 1]
} else if from > to { // handle swapped boundary positions
return sumAllNumberList - (numberList
.dropLast(numberList.count - from) // work on the back of the array first
.dropFirst(to - 1)
.reduce(0, +))
} else {
return sumAllNumberList - (numberList
.dropLast(numberList.count - to) // work on the back of the array first
.dropFirst(from - 1)
.reduce(0, +))
}
}
struct ContentView: View {
let numberList = [7, 4, 38, 21, 16, 15, 12, -33, 31, 49]
let emptyList: [Int] = []
@State private var startFrom = 1
@State private var endHere = 1
var body : some View {
let totalSumNumbersTo = numberList.reduce(0, +)
let lowIndexSum = sumNumberListTo(endHere, numberList: numberList)
let highIndexSum = sumNumberListFrom(startFrom, numberList: numberList)
let emptySum = sumNumberListFrom(startFrom, numberList: emptyList)
let betweenIndexSum = sumNumberListBetween(startFrom, endHere, numberList: numberList)
let exceptIndexSum = sumNumberListExcept(startFrom, endHere, numberList: numberList)
VStack(alignment: .leading) {
HStack{
ForEach(numberList, id: \.self) { value in
Text("\(value) ")
}
}
.padding([.trailing, .leading, .bottom])
Stepper("Start from \(startFrom) (range -5 to 15)", value: $startFrom, in: -5...15)
.padding([.trailing, .leading, .bottom])
Stepper("End here \(endHere) (range -5 to 15)", value: $endHere, in: -5...15)
.padding([.trailing, .leading, .bottom])
Text("Total for the first \(endHere < 0 ? 0 : endHere) numbers is \(lowIndexSum)")
.padding([.trailing, .leading, .bottom])
Text("Total from position \(startFrom) for \((numberList.count - startFrom + 1) < 0 ? 0 : startFrom < 0 ? numberList.count : numberList.count - startFrom + 1) numbers is \(highIndexSum)")
.padding([.trailing, .leading, .bottom])
Text("Total between position \(startFrom) to position \(endHere) is \(betweenIndexSum)")
.padding([.trailing, .leading, .bottom])
Text("Except for the range between position \(startFrom) to position \(endHere), total is \(exceptIndexSum)")
.padding([.trailing, .leading, .bottom])
Text("The empty list sum is \(emptySum)")
.padding([.trailing, .leading, .bottom])
}
}
}