Chris
Chris

Reputation: 325

Swift Array Extension to replace value of index n by the sum of the n-previous values

I am trying to write an extension for Array Types that sums the n-previous indexes in the index n.

let myArray = [1, 2, 3, 4, 5]
let mySumArray = myArray.sumNIndex()
print(mySumArray)
// returns [1,3,6,10,15]

I have tried various approaches which all failed at some point. For instance, the example hereafter triggers a compile error "Cannot invoke 'reduce' with an argument list of type '(Int, _)'":

extension Array {
    mutating func indexSum() {
        var tempArray = [Any]()
        for index in 1...self.count - 1 {
        self[index] += self[.prefix(index + 2).reduce(0, +)]
        }
    }
}

This other attempt triggers another compile error: "Binary operator '+=' cannot be applied to two 'Element' operands"

extension Array {
    mutating func indexSum() {
        var tempArray = [Any]()
        for index in 1...self.count - 1 {
        self[index] += self[index - 1]
        }
    }
}

Any idea is welcome! Thank you very much for your help!

EDIT: Many thanks to @Martin and @Carpsen who figured it out in 2 different ways

@Martin using map method:

extension Array where Element: Numeric {
    func cumulativeSum() -> [Element] {
        var currentSum: Element = 0
        return map {
            currentSum += $0
            return currentSum
        }
    }
}

@Carpsen using reduce method:

extension Array where Element: Numeric {
    func indexSum() -> [Element] {
        return self.reduce(into: [Element]()) {(acc, element) in
            return acc + [(acc.last ?? 0) + element]
        })
    }
}

Upvotes: 1

Views: 1134

Answers (2)

ielyamani
ielyamani

Reputation: 18591

Try this:

let myArray = [1, 2, 3, 4, 5]

myArray.reduce([Int](), {accumulator, element in
    return accumulator + [(accumulator.last ?? 0) + element]
})
//[1, 3, 6, 10, 15]

What this reduce does is:

  • Start with an empty array
  • With each element from myArray it calculates its sum with the last element in the accumulator
  • Return the previous array plus the last sum

Here is a simpler, but longer version:

let myArray = [1, 2, 3, 4, 5]

let newArray = myArray.reduce([Int](), {accumulator, element in
    var tempo = accumulator
    let lastElementFromTheAccumulator = accumulator.last ?? 0
    let currentSum = element + lastElementFromTheAccumulator
    tempo.append(currentSum)
    return tempo
})

print(newArray)  //[1, 3, 6, 10, 15]

A more efficient solution, as suggested by Martin R in the comments, uses reduce(into:):

myArray.reduce(into: [Int]()) { (accumulator, element) in
    accumulator += [(accumulator.last ?? 0) + element]
}
//[1, 3, 6, 10, 15]

And you could have it as an extension:

extension Array where Element: Numeric {
    func indexSum() -> [Element] {
        return self.reduce([Element](), {acc, element in
            return acc + [(acc.last ?? 0) + element]
        })
    }
}

myArray.indexSum()  //[1, 3, 6, 10, 15]

Here a solution that will work with strings too:

extension Array where Element == String {
    func indexSum() -> [String] {
        return self.reduce(into: [String]()) {(accumulator, element) in
            accumulator += [(accumulator.last ?? "") + element]
        }
    }
}

["a", "b", "c", "d"].indexSum() //["a", "ab", "abc", "abcd"]

If you'd like to have a separator between the elements of the initial array elements, you could use this extension:

extension Array where Element == String {
    func indexSum(withSparator: String) -> [String] {
        return self.reduce(into: [String]()) {(accumulator, element) in
            var previousString = ""
            if let last = accumulator.last {
                previousString = last + " "
            }
            accumulator += [previousString +  element]
        }
    }
}

["a", "b", "c", "d"].indexSum(withSparator: " ") //["a", "a b", "a b c", "a b c d"]

Upvotes: 0

Martin R
Martin R

Reputation: 539965

The main problem is that the addition operator + is not defined for elements of arbitrary arrays. You need to restrict the extension method, e.g. to arrays of Numeric elements.

Also there is no need to use Any.

Here is a possible implementation as a non-mutating method:

extension Array where Element: Numeric {
    func cumulativeSum() -> [Element] {
        var currentSum: Element = 0
        return map {
            currentSum += $0
            return currentSum
        }
    }
}

Examples:

let intArray = [1, 2, 3, 4, 5]
print(intArray.cumulativeSum()) // [1, 3, 6, 10, 15]

let floatArray = [1.0, 2.5, 3.25]
print(floatArray.cumulativeSum()) [1.0, 3.5, 6.75]

In a similar fashion we can “cumulatively join” the elements of a string array. enumerated() is now used to provide the current element index together with the element, and that is used to decide whether to insert the separator or not:

extension Array where Element == String {
    func cumulativeJoin(separator: String) -> [Element] {
        var currentJoin = ""
        return enumerated().map { (offset, elem) in
            if offset > 0 { currentJoin.append(separator) }
            currentJoin.append(elem)
            return currentJoin
        }
    }
}

Examples:

let stringArray = ["a", "b", "c"]
print(stringArray.cumulativeJoin()) // ["a", "ab", "abc"]
print(stringArray.cumulativeJoin(separator: ":")) // ["a", "a:b", "a:b:c"]

Upvotes: 1

Related Questions