5

I'm trying to convert some Python code to Swift and wondering if there is an existing function to calculate the difference between successive elements in a Swift array. For example:

diff([1,3,5,6,10]) would return [2,2,1,4]
user3483203
  • 50,081
  • 9
  • 65
  • 94
Steve Conley
  • 147
  • 7

3 Answers3

8

No, but it could be very easily implemented:

let a = [1, 3, 5, 6, 10]
zip(a.dropFirst(), a).map(-) // => [2, 2, 1, 4]

It's simple enough that it's probably not worth wrapping into a function, but if you insist:

extension Collection where Element: Numeric {
    func diff() -> [Element] {
        return zip(self.dropFirst(), self).map(-)
    }
}

[1, 3, 5, 6, 10].diff() // => [2, 2, 1, 4]

If you need the result to be lazily evaluated, you can do this:

extension Collection where Element: Numeric {
    func diff() -> AnyCollection<Element> {
        return AnyCollection(zip(self.dropFirst(), self).lazy.map(-))
    }
}
Alexander
  • 59,041
  • 12
  • 98
  • 151
4

You can use reduce(into:) combined with dropfirst to achieve what you want:

extension Collection where Element: SignedNumeric {
    func diff() -> [Element] {
        guard var last = first else { return [] }
        return dropFirst().reduce(into: []) {
            $0.append($1 - last)
            last = $1
        }
    }
}

Another option is to use map and defer:

extension Collection where Element: SignedNumeric {
    func diff() -> [Element] {
        guard var last = first else { return [] }
        return dropFirst().map { element in
            defer { last = element }
            return element - last
        }
    }
}

let arr = [1,3,5,6,10]
print(arr.diff())  // "[2, 2, 1, 4]\n"
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
1

There's no built in function for this, but you can easily implement it recursively. Thanks for the HeadTail extension for @Alexander.

extension Array {
    func headTail<ReturnType>(_ closure: (Element?, [Element]) -> ReturnType) -> ReturnType {
        return closure(self.first, Array(self.dropFirst()))
    }
}

extension Array where Element == Int {
    func diff() -> [Int] {
        return self.headTail { head, tail in
            guard let head = head, let next = tail.first else { return [] } //base case, empty list
            return [next - head] + tail.diff()
        }
    }
}
Dávid Pásztor
  • 51,403
  • 9
  • 85
  • 116
  • 1
    I very strongly recommend against implementing this recursively. Not only is it way slower, the small stack size and lack of tail recursion is crippling. – Alexander Jun 04 '18 at 22:43
  • @Alexander are you sure that's the case? According to [Does Swift implement tail call optimization? and in mutual recursion case?](https://stackoverflow.com/questions/24023580/does-swift-implement-tail-call-optimization-and-in-mutual-recursion-case), the compiler does implement tail recursion. – Dávid Pásztor Jun 04 '18 at 22:47
  • Interesting. I stand corrected. In any case, I would avoid it for the complexity, when such a simpler solution exists – Alexander Jun 04 '18 at 22:50
  • @Alexander I do agree, your solution is way simpler and probably even more efficient. I'll just keep my answer here if OP/anyone else is interested in seeing different solutions, including a recursive one. – Dávid Pásztor Jun 04 '18 at 22:51
  • @Alexander thanks, that does improve readability, updated my answer. – Dávid Pásztor Jun 04 '18 at 23:08
  • There is no need to pass any arguments to your method `extension Array where Element == Int { func diff() -> [Int] { return headTail { head, tail in guard let head = head, let next = tail.first else { return [] } //base case, empty list return [next - head] + tail.diff() } } }` – Leo Dabus Jun 05 '18 at 13:11
  • `extension Array { func headTail(_ closure: (Element?, [Element]) -> T) -> T { return closure(first, Array(dropFirst())) } }` – Leo Dabus Jun 05 '18 at 13:11
  • @LeoDabus you're right, I forgot to update the method when I moved it to an extension. – Dávid Pásztor Jun 05 '18 at 14:11
  • @LeoDabus you're right again, now it should be fine. I'm completely aware of the naming convention, but I copied the `HeadTail` method from Alexander, so didn't want to modify it, but updated the method name. – Dávid Pásztor Jun 05 '18 at 14:43
  • @DávidPásztor btw no need to initialize a new Array for every tail. you can just change the return type to ArraySlice. `extension Array { func headTail(_ closure: (Element?, ArraySlice) -> T) -> T { return closure(first, dropFirst()) } }` – Leo Dabus Jun 05 '18 at 21:27
  • another option is to extend Collection instead of Array. `extension Collection { func headTail(_ closure: (Element?, SubSequence) -> T) -> T { return closure(first, dropFirst(1)) } } extension Collection where Element == Int { func diff() -> [Element] { return headTail { guard let head = $0, let next = $1.first else { return [] } return [next - head] + $1.diff() } } }` – Leo Dabus Jun 05 '18 at 21:32