Maiaux
Maiaux

Reputation: 975

Or-ing arrays of bools in Swift

I have two arrays of bools in Swift, not necessarily of the same size, and would like to do an element-wise OR between them. Consider, e.g.,

var array1 = [false, true, false, true, false, true]
var array2 = [true, true, false, false]

What I would like to do is apply array2 as an OR-mask to a portion of array1.

If I were to transcribe array2 into a portion of array1, I would simply do this

array1[0..<array2.count] = array2[0..<array2.count]

But I would like to be able to write something like this:

array1[0..<array2.count] ||= array2[0..<array2.count]

which of course does not compile because ||= is not available.

An explicit solution is to iterate through the elements in a classical way:

for i in 0..<array2.count {
    array1[i] = array1[i] || array2[i]
}

but I wonder if there is a more elegant, Swift-like (maybe faster?) solution.

Upvotes: 1

Views: 253

Answers (3)

dfrib
dfrib

Reputation: 73216

You can make use of the fact that the global zip(_:_:) function creates a truncated zipped sequence with a the length that is the shorter on of the two supplied sequences.

If the two sequences passed to zip(_:_:) are different lengths, the resulting sequence is the same length as the shorter sequence.

To zip the indices of the array being transcribed into with the elements of the array being transcribed from. E.g. to construct a custom in-place replacement infix operator:

infix operator ||=: AssignmentPrecedence

func ||=(_ lhs: inout [Bool], rhs: [Bool]) {
    zip(0..<lhs.count, rhs).forEach { if !lhs[$0] { lhs[$0] = $1 } }
    //or: zip(0..<lhs.count, rhs).forEach { lhs[$0] = lhs[$0] || $1 }
}

Used as follows:

// array to be transcribed _into_ longer than the array
// being transcibed from
var array1 = [false, true, false, true, false, true]
var array2 = [true, true, false, false]

array1 ||= array2
print(array1) // [true, true, false, true, false, true]

// array to be transcribed _into_ shorter than the array
// being transcibed from
array2 = [true, true, false, false, true, true, false, false]
array1 ||= array2
print(array1) // [true, true, false, true, true, true]

Note also the following valuable comment from @zneak:

"The behavior for || on arrays of different size isn't obvious to me, so I think that it could be valuable to use a function with a representative name instead of an operator."

So you might want to considering implementing the logic above into a function with a semantically sound name rather than an operator.

Upvotes: 1

Sulthan
Sulthan

Reputation: 130201

I see no reason why you can't define the operator by yourself, e.g:

let array1 = [false, true, false, true, false, true]
let array2 = [true, true, false, false]

func ||(lhs: Array<Bool>, rhs: Array<Bool>) -> Array<Bool> {
    let shorter: Array<Bool>
    let longer: Array<Bool>

    if lhs.count < rhs.count {
        shorter = lhs
        longer = rhs
    } else {
        longer = lhs
        shorter = rhs
    }

    let shorterProlonged = shorter + longer.suffix(from: shorter.count)

    return zip(shorterProlonged, longer).map { $0 || $1 }
}

print(array1 || array2)

Upvotes: 3

akashivskyy
akashivskyy

Reputation: 45210

If by "Swift-like" you mean a more declarative solution, you can:

  1. zip the arrays
  2. map the zipped tuples to produced an "or-ed" array
  3. replace the slice in the first array

Here's how it would look like:

let oredArray = zip(array1, array2).map { $0 || $1 }
array1[0 ..< min(oredArray.endIndex, array2.endIndex)] = ArraySlice(oredArray)

This does, however, require array1 to be mutable. Also, notice the use of min() – it's a safeguard against array2 being longer than array1.

Upvotes: 2

Related Questions