23

Is there a way to do something similar to break from a for loop but in array's reduce() function?

E.g. consider I have an array:

var flags = [false, false, true, false, false, true, false]

... and I need to get a cumulative || on them. With a for loop, the following would be possible:

var resultByFor = false

for flag in flags {
    if flag {
        resultByFor = true
        break
    }
}

... i.e. at the moment we get our first true there is no need to finish the loop as the result will be true anyway.

With reduce(), the following looks quite neat and tidy:

var resultByReduce = flags.reduce(false) { $0 || $1 }

However, with the array given in the example, the for loop body would be executed only 3 times, while reduce() function closure would get triggered full 7 times.

Is there a way to make reduce() to bail out on 3rd iteration as well (just like it can be done in for loop)?

[UPD]

I oversimplified the question. The original problem was more like this:

extension Int {
    func isWholeMultiplesOf(base: Int) -> Bool {
        return (self % base) == 0
    }
}

var numbers = [3, 5, 6, 7, 2, 3, 8]

var resultByFor = false

// The loop body will be triggered only 3 times
for number in numbers {
    if number.isWholeMultiplesOf(2) {
        resultByFor = true
        break
    }
}

// The closure of reduce() will be triggered 7 times
var resultByReduce = numbers.reduce(false) {
    $0 || $1.isWholeMultiplesOf(2)
}

... i.e. I have an array of objects and I want to know if there is at least one of them that has certain method evaluating to true.

0x416e746f6e
  • 9,872
  • 5
  • 40
  • 68
  • I don't think so. `reduce` like `contains` and `filter` iterates over all items. I think 1st example is a proper one – Maxim Shoustin Apr 27 '15 at 22:11
  • you could write that as extension. That would look like reduce but break early. – qwerty_so Apr 27 '15 at 22:25
  • 3
    Why don't you just use contains? – Fogmeister Apr 27 '15 at 22:44
  • 5
    Generally, if you find yourself needing to break out of a reduce, you shouldn’t be using a reduce. It is _much_ better to use a `for` loop than contort yourself into avoid one. If you don’t like the look of it in your code, wrap it in a generic function along the lines of `contains` or `find`, with an early `return` in the body of the loop. – Airspeed Velocity Apr 27 '15 at 23:21

3 Answers3

12

As others have suggested, you can use contains for this purpose:

var flags = [false, false, true, false, false, true, false]
contains(flags, true) //--> true

Another option is to use find to search for the first instance of what you're looking for, in this case true:

var flags = [false, false, true, false, false, true, false]    
find(flags, true) // --> 2, returns nil if not found
let containsTrue = (find(flags, true) != nil)

Edit: Newer versions of Swift expose these functions on the related collection protocols instead of global functions.

var flags = [false, false, true, false, false, true, false]
flags.contains(where: { $0 == true })
flags.contains(true) // if checking for a specific element
let index = flags.firstIndex(where: ${ $0 == true }) // --> 2
Eric Amorde
  • 1,068
  • 9
  • 19
  • That's not the new equivalent syntax. You don't need a closure for a `Bool` sequence. `Bool` is `Equatable`. `flags.contains(true)` –  Mar 01 '22 at 19:31
  • Yeah that's true, but the original question seemed to be simplifying the use case and they were actually interested in providing the block, at least based on the edit made. I'll update mine though – Eric Amorde Mar 01 '22 at 19:36
4

It's not available out of the box in the Swift Standard Library, but you can make it. In my blog post I have described my proposed solution. In your case it'll look like this on the call side:

flags.reduce(false, { $0 || $1 }, until: { $0 })

You can give it a try in that Playground

Maciek Czarnik
  • 5,950
  • 2
  • 37
  • 50
0

The modern way of tackling your "original problem" is

[3, 5, 6, 7, 2, 3, 8].contains {
  $0.isMultiple(of: 2)
}

There is no break for iterated closures, just as there is no break allowed in the main body of a function. To mimic it, you need to throw an error.

for number in numbers {
  guard !number.isMultiple(of: 2) else {
    break
  }

  print(number)
}
try? numbers.forEach {
  guard !$0.isMultiple(of: 2) else {
    struct Error: Swift.Error { }
    throw Error()
  }

  print($0)
}