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]
2
  • No, there is not. Commented Jun 4, 2018 at 22:19
  • You can pretty easily write one though. Commented Jun 4, 2018 at 22:19

3 Answers 3

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(-))
    }
}
Sign up to request clarification or add additional context in comments.

6 Comments

Note that this uses twice memory as needed and it also iterates through it twice
@LeoDabus Not anymore :)
@LeoDabus can you explain more , are Zip for very large array of Int will be work badly
@LeoDabus Actually, zip is lazy, it merely wraps two sequences and defines an iterator over them that calls their iterators. It's just a single loop through the elements, done by map. If you use .lazy, you can even prevent that loop, too!
@LeoDabus It doesn't create an intermediate array of tuples. Try dump(zip([1, 2], [3, 4])).
|
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"

1 Comment

That's perfect! Thanks.
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()
        }
    }
}

11 Comments

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 are you sure that's the case? According to Does Swift implement tail call optimization? and in mutual recursion case?, the compiler does implement tail recursion.
Interesting. I stand corrected. In any case, I would avoid it for the complexity, when such a simpler solution exists
@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.
@Alexander thanks, that does improve readability, updated my answer.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.