0

How to enumerate collections(like [1,2,3,4]) with every two elements, not [[1, 2], [3, 4]], but [[1,2], [2, 3], [3, 4]] in Swift.

Prefer a method like forEach, but forEachTwo in this question:

collections.forEachTwo { (first, second) in
    // do something.
}
Devlinlin
  • 1
  • 2
  • Please provide some information, your question is quite bare. What is collection? An array [1, 2, 3, 4]? An array of arrays [[1, 2], [3, 4]]? A Set? What have your tried so far, what issues did you find? – HunterLion Apr 09 '22 at 17:22
  • I updated my question, it's [1,2,3,4]. – Devlinlin Apr 13 '22 at 13:54

1 Answers1

0
for (first, second) in zip(elements, elements.dropFirst()) {
    print(first, second)
}

forEach (or a derivative) is bad here for iteration. It is better to use a for-in. forEach has side-effects (breaking FP) and subtle weirdnesses with Swift (breaking good Swift). for-in is more powerful and cleaner. But the creation of the actual sequence is classic FP: zip with dropFirst.

Note that this is a sequence of tuples, not arrays as you wrote. I don't think you want arrays here, given the syntax you suggest (though you could achieve that with a map if you did).

You can get the syntax you suggest with this, but it's not as flexible as the for-in, and has subtle edge cases (for example if the closure includes a return it behaves like a continue):

extension Sequence {
    func forEachTwo(_ f: ((Element, Element) -> Void)) {
        for element in zip(self, dropFirst()) {
            f(element.0, element.1)
        }
    }
}
Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • 1
    Consider using AdjacentPairs from swift algorithms package over zip with the dropFirst – Shadowrun Apr 09 '22 at 19:23
  • Rob, can you elaborate on the problems with forEach? – Duncan C Apr 09 '22 at 19:37
  • 2
    @DuncanC `forEach` gives the illusion of being "functional" while failing the basic requirements (it is only useful if it creates side effects). It cannot easily be aborted early, but trying to (by using `return`) is not an error; it just behaves surprisingly (it translates into `continue`). To return early, the developer is pushed to a misleading (and tricky) use of throws. It does not scale to async/await. It creates a redundant, but less-flexible, syntax for the same operation whose primary benefit is "it looks more clever" rather than expressing a unique concept (like `map` does). – Rob Napier Apr 09 '22 at 19:53
  • 1
    If `forEach` hadn't been added to stdlib very early-on, it would never be accepted as an addition today. https://twitter.com/jckarter/status/710487229093715968 There was discussion of creating language features that would make `forEach` useful, but they never really went anywhere, and all the future work went into improving `for-in`. https://forums.swift.org/t/remove-foreach/322/30 `forEach` is a barnacle, begging to confuse developers with its "consistent if you constantly remember the implementation details, but silly if you think of it as a looping construct" semantics. – Rob Napier Apr 09 '22 at 19:57
  • All good points, and I never noticed the odd handling of return. – Duncan C Apr 09 '22 at 20:04
  • I never realized any of that stuff about `forEach`! – matt Apr 13 '22 at 15:08