26

I was wondering how one would go about comparing 2 boolean arrays and listing the non matching booleans.

I have written up a simple example of 2 arrays.

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

How would I compare array1 & array2 and display the non matching. I am trying to do this to check user results for a quiz game.

Thanks!

simlimsd3
  • 599
  • 2
  • 6
  • 14
  • The problem is that it is unclear what you think the answer is. What result do you want / expect for this particular input? Do you want a list of indexes? An array? What? And what should the answer be if one has more booleans than the other? If one is empty? – matt Jun 06 '15 at 16:38
  • And what should that array look like? You must _specify_! What is the "right answer" in your mind? – matt Jun 06 '15 at 16:40
  • Hi sorry Matt! Thank you for your help - I realise I should be more clear. I am very new to programming in general so please bear with me. I was hoping to return a list of indexes but after doing some more research I believe I don`t understand what I`m trying to achieve exactly. Sorry to waste your time. – simlimsd3 Jun 06 '15 at 16:54
  • "I was hoping to return a list of indexes" Perfectly reasonable, but then you have to say so. I'll modify my answer to show that approach too. – matt Jun 06 '15 at 17:13
  • "I don't understand what I'm trying to achieve" Quite so, but that is step one in programming. You must give the computer very precise orders, so you must know exactly what you want it to do, in every possible situation. – matt Jun 06 '15 at 17:22

5 Answers5

42

Here's one implementation, but whether it is the one you are after is completely impossible to say, because you have not specified what you think the answer should be:

let answer = zip(array1, array2).map {$0.0 == $0.1}

That gives you a list of Bool values, true if the answer matches the right answer, false if it does not.

But let's say what you wanted was a list of the indexes of those answers that are correct. Then you could say:

let answer = zip(array1, array2).enumerated().filter() {
    $1.0 == $1.1
}.map{$0.0}

If you want a list of the indexes of those answers that are not correct, just change == to !=.

bandejapaisa
  • 26,576
  • 13
  • 94
  • 112
matt
  • 515,959
  • 87
  • 875
  • 1,141
  • Added another implementation where we assume what you want is a list of the _indexes_ of the right or wrong answers. – matt Jun 06 '15 at 17:21
  • Appreciate it ! Thank you very much. – simlimsd3 Jun 06 '15 at 17:30
  • 7
    The really great thing is that this answer managed to involve `map`, `filter`, `zip`, and `enumerate` all together - the key things you'll need to know about in order to work with arrays in Swift (only `reduce` got omitted - couldn't find a use for it in this question). :) – matt Jun 06 '15 at 20:44
  • must array1 and array2 have the same amount of elements (count), or may them be not equal? – Frank Eno Jul 29 '16 at 08:19
  • 1
    For the hell of it, count the number of differences using reduce....... .reduce(0) { (count, item) -> Int in return count + 1 } – bandejapaisa Feb 16 '17 at 22:37
  • @bandejapaisa can you please update complete code while both array count is not equal. – Sohaib Siddique Feb 18 '20 at 06:55
  • @SohaibSiddique That’s not what the question was so it’s not in the answer. If you have a different question, ask it as a new question, not as a comment. – matt Feb 18 '20 at 13:10
24

Xcode 11 has it supported (only available in iOS 13 or newer)

https://developer.apple.com/documentation/swift/array/3200716-difference

let oldNames = ["a", "b", "c", "d"]
    
let newNames = ["a", "b", "d", "e"]

let difference = newNames.difference(from: oldNames)

for change in difference {
  switch change {
  case let .remove(offset, oldElement, _):
    print("remove:", offset, oldElement)
  case let .insert(offset, newElement, _):
    print("insert:", offset, newElement)
  }
}

Output

remove: 2 c
insert: 3 e
norbDEV
  • 4,795
  • 2
  • 37
  • 28
Ji Fang
  • 3,288
  • 1
  • 21
  • 18
  • 4
    I get this error when try to use difference. **'difference(from:)' is only available in iOS 13 or newer** – GxocT Oct 04 '19 at 07:04
15

the easiest thing to do is use a Set. Sets have a symmetricDifference() method that does exactly this, so you just need to convert both arrays to a set, then convert the result back into an array.

Here’s an extension to make it easier:

extension Array where Element: Hashable {
    func difference(from other: [Element]) -> [Element] {
        let thisSet = Set(self)
        let otherSet = Set(other)
        return Array(thisSet.symmetricDifference(otherSet))
    } }

And here’s some example code you can use to try it out:

let names1 = ["student", "class", "teacher"]
let names2 = ["class", "teacher", "classroom"]
let difference = names1.difference(from: names2)

That will set difference to be ["student", "classroom"], because those are the two names that only appear once in either array.

MGY
  • 7,245
  • 5
  • 41
  • 74
Ravindra_Bhati
  • 1,071
  • 13
  • 28
  • 20
    [Source](https://www.hackingwithswift.com/example-code/language/how-to-find-the-difference-between-two-arrays) – DoesData Feb 25 '19 at 19:25
  • 4
    @DoesData I was about to make the same comment, then I realized this answer is dated prior to the hackingwithswift post, so perhaps _this_ is _their_ source. – monstermac77 Oct 17 '20 at 08:25
  • 2
    @monstermac77 fair enough. However, that link still provides additional details and is helpful. The [Apple Documentation](https://developer.apple.com/documentation/swift/set/3128856-symmetricdifference) also provides a concrete example. – DoesData Oct 17 '20 at 19:32
  • It seems this doesn't do the proper conversion on the return to an array. Using this exact example, I get "Cannot convert value of type 'CollectionDifference' to expected argument type '[ItemObject]'" – Rich Everts Feb 24 '21 at 23:42
2

I found this post looking for a solution to situation very similar to the one posted. However, I found ordered collection diffing using inferringMoves() introduced in Swift 5.1 to be suitable for my use case. In case someone else may find it useful I've added what I used:

let array1 = [true, false, true, true, false, true]
let array2 = [true, true, true, true, true, false]
        
let diff = array2.difference(from: array1).inferringMoves()
    for change in diff {
        switch change {
        case .insert(let offset, let element, _):
            print("insert offset \(offset) for element \(element)")
        case .remove(let offset, let element, _):
            print("remove offset \(offset) for element \(element)")
        }
    }

This prints out the values:

remove offset 4 for element false
remove offset 1 for element false
insert offset 4 for element true
insert offset 5 for element false
originalmyth
  • 129
  • 6
  • 1
    It worked perfectly for me, however I needed to add the extension mentioned here: https://www.fivestars.blog/articles/swift-5-1-collection-diffing/ – Douglas Frari Jan 12 '23 at 13:03
1

Ported from the Swift source code, works with Swift5, iOS 12

usage:

let difference = newArray.differenceFrom(from: originalArray) { (s1: String, s2: String) in
                return s1 == s2
            }
// or

let difference = Array<String>.difference(from: originalArray, to: newArray) { (s1: String, s2: String) in
                return s1 == s2
            }

for change in diff {
                switch change {
                case let .remove(offset, oldElement, _):
                    print("remove:", offset, oldElement)
                case let .insert(offset, newElement, _):
                    print("insert:", offset, newElement)
                }
            }

Source

Array Extension

extension Array {

public func differenceFrom<C: BidirectionalCollection>(
        from other: C,
        by areEquivalent: (C.Element, Element) -> Bool
) -> CollectionDifference<Element>
        where C.Element == Self.Element {
    return Array<Element>.difference(from: other, to: self, using: areEquivalent)
}

public static func difference<C, D>(
        from old: C, to new: D,
        using cmp: (C.Element, D.Element) -> Bool
) -> CollectionDifference<C.Element>
        where
        C: BidirectionalCollection,
        D: BidirectionalCollection,
        C.Element == D.Element {

    // Core implementation of the algorithm described at http://www.xmailserver.org/diff2.pdf
    // Variable names match those used in the paper as closely as possible
    func _descent(from a: UnsafeBufferPointer<C.Element>, to b: UnsafeBufferPointer<D.Element>) -> [_V] {
        let n = a.count
        let m = b.count
        let max = n + m

        var result = [_V]()
        var v = _V(maxIndex: 1)
        v[1] = 0

        var x = 0
        var y = 0
        iterator: for d in 0...max {
            let prev_v = v
            result.append(v)
            v = _V(maxIndex: d)

            // The code in this loop is _very_ hot—the loop bounds increases in terms
            // of the iterator of the outer loop!
            for k in stride(from: -d, through: d, by: 2) {
                if k == -d {
                    x = prev_v[k &+ 1]
                } else {
                    let km = prev_v[k &- 1]

                    if k != d {
                        let kp = prev_v[k &+ 1]
                        if km < kp {
                            x = kp
                        } else {
                            x = km &+ 1
                        }
                    } else {
                        x = km &+ 1
                    }
                }
                y = x &- k

                while x < n && y < m {
                    if !cmp(a[x], b[y]) {
                        break;
                    }
                    x &+= 1
                    y &+= 1
                }

                v[k] = x

                if x >= n && y >= m {
                    break iterator
                }
            }
            if x >= n && y >= m {
                break
            }
        }

        //_internalInvariant(x >= n && y >= m)

        return result
    }

    // Backtrack through the trace generated by the Myers descent to produce the changes that make up the diff
    func _formChanges(
            from a: UnsafeBufferPointer<C.Element>,
            to b: UnsafeBufferPointer<C.Element>,
            using trace: [_V]
    ) -> [CollectionDifference<C.Element>.Change] {
        var changes = [CollectionDifference<C.Element>.Change]()
        changes.reserveCapacity(trace.count)

        var x = a.count
        var y = b.count
        for d in stride(from: trace.count &- 1, to: 0, by: -1) {
            let v = trace[d]
            let k = x &- y
            let prev_k = (k == -d || (k != d && v[k &- 1] < v[k &+ 1])) ? k &+ 1 : k &- 1
            let prev_x = v[prev_k]
            let prev_y = prev_x &- prev_k

            while x > prev_x && y > prev_y {
                // No change at this position.
                x &-= 1
                y &-= 1
            }

            //_internalInvariant((x == prev_x && y > prev_y) || (y == prev_y && x > prev_x))
            if y != prev_y {
                changes.append(.insert(offset: prev_y, element: b[prev_y], associatedWith: nil))
            } else {
                changes.append(.remove(offset: prev_x, element: a[prev_x], associatedWith: nil))
            }

            x = prev_x
            y = prev_y
        }

        return changes
    }

    /* Splatting the collections into contiguous storage has two advantages:
     *
     *   1) Subscript access is much faster
     *   2) Subscript index becomes Int, matching the iterator types in the algorithm
     *
     * Combined, these effects dramatically improves performance when
     * collections differ significantly, without unduly degrading runtime when
     * the parameters are very similar.
     *
     * In terms of memory use, the linear cost of creating a ContiguousArray (when
     * necessary) is significantly less than the worst-case n² memory use of the
     * descent algorithm.
     */
    func _withContiguousStorage<C: Collection, R>(
            for values: C,
            _ body: (UnsafeBufferPointer<C.Element>) throws -> R
    ) rethrows -> R {
        if let result = try values.withContiguousStorageIfAvailable(body) {
            return result
        }
        let array = ContiguousArray(values)
        return try array.withUnsafeBufferPointer(body)
    }

    return _withContiguousStorage(for: old) { a in
        return _withContiguousStorage(for: new) { b in
            return CollectionDifference(_formChanges(from: a, to: b, using: _descent(from: a, to: b)))!
        }
    }
}
}

CollectionDifference

public struct CollectionDifference<ChangeElement> {
/// A single change to a collection.
@frozen
public enum Change {
    /// An insertion.
    ///
    /// The `offset` value is the offset of the inserted element in the final
    /// state of the collection after the difference is fully applied.
    /// A non-`nil` `associatedWith` value is the offset of the complementary
    /// change.
    case insert(offset: Int, element: ChangeElement, associatedWith: Int?)

    /// A removal.
    ///
    /// The `offset` value is the offset of the element to be removed in the
    /// original state of the collection. A non-`nil` `associatedWith` value is
    /// the offset of the complementary change.
    case remove(offset: Int, element: ChangeElement, associatedWith: Int?)

    // Internal common field accessors
    internal var _offset: Int {
        get {
            switch self {
            case .insert(offset: let o, element: _, associatedWith: _):
                return o
            case .remove(offset: let o, element: _, associatedWith: _):
                return o
            }
        }
    }
    internal var _element: ChangeElement {
        get {
            switch self {
            case .insert(offset: _, element: let e, associatedWith: _):
                return e
            case .remove(offset: _, element: let e, associatedWith: _):
                return e
            }
        }
    }
    internal var _associatedOffset: Int? {
        get {
            switch self {
            case .insert(offset: _, element: _, associatedWith: let o):
                return o
            case .remove(offset: _, element: _, associatedWith: let o):
                return o
            }
        }
    }
}

/// The insertions contained by this difference, from lowest offset to
/// highest.
public let insertions: [Change]

/// The removals contained by this difference, from lowest offset to highest.
public let removals: [Change]

/// The public initializer calls this function to ensure that its parameter
/// meets the conditions set in its documentation.
///
/// - Parameter changes: a collection of `CollectionDifference.Change`
///   instances intended to represent a valid state transition for
///   `CollectionDifference`.
///
/// - Returns: whether the parameter meets the following criteria:
///
///   1. All insertion offsets are unique
///   2. All removal offsets are unique
///   3. All associations between insertions and removals are symmetric
///
/// Complexity: O(`changes.count`)
private static func _validateChanges<Changes: Collection>(
        _ changes: Changes
) -> Bool where Changes.Element == Change {
    if changes.isEmpty {
        return true
    }

    var insertAssocToOffset = Dictionary<Int, Int>()
    var removeOffsetToAssoc = Dictionary<Int, Int>()
    var insertOffset = Set<Int>()
    var removeOffset = Set<Int>()

    for change in changes {
        let offset = change._offset
        if offset < 0 {
            return false
        }

        switch change {
        case .remove(_, _, _):
            if removeOffset.contains(offset) {
                return false
            }
            removeOffset.insert(offset)
        case .insert(_, _, _):
            if insertOffset.contains(offset) {
                return false
            }
            insertOffset.insert(offset)
        }

        if let assoc = change._associatedOffset {
            if assoc < 0 {
                return false
            }
            switch change {
            case .remove(_, _, _):
                if removeOffsetToAssoc[offset] != nil {
                    return false
                }
                removeOffsetToAssoc[offset] = assoc
            case .insert(_, _, _):
                if insertAssocToOffset[assoc] != nil {
                    return false
                }
                insertAssocToOffset[assoc] = offset
            }
        }
    }

    return removeOffsetToAssoc == insertAssocToOffset
}

/// Creates a new collection difference from a collection of changes.
///
/// To find the difference between two collections, use the
/// `difference(from:)` method declared on the `BidirectionalCollection`
/// protocol.
///
/// The collection of changes passed as `changes` must meet these
/// requirements:
///
/// - All insertion offsets are unique
/// - All removal offsets are unique
/// - All associations between insertions and removals are symmetric
///
/// - Parameter changes: A collection of changes that represent a transition
///   between two states.
///
/// - Complexity: O(*n* * log(*n*)), where *n* is the length of the
///   parameter.
public init?<Changes: Collection>(
        _ changes: Changes
) where Changes.Element == Change {
    guard CollectionDifference<ChangeElement>._validateChanges(changes) else {
        return nil
    }

    self.init(_validatedChanges: changes)
}

/// Internal initializer for use by algorithms that cannot produce invalid
/// collections of changes. These include the Myers' diff algorithm,
/// self.inverse(), and the move inferencer.
///
/// If parameter validity cannot be guaranteed by the caller then
/// `CollectionDifference.init?(_:)` should be used instead.
///
/// - Parameter c: A valid collection of changes that represent a transition
///   between two states.
///
/// - Complexity: O(*n* * log(*n*)), where *n* is the length of the
///   parameter.
internal init<Changes: Collection>(
        _validatedChanges changes: Changes
) where Changes.Element == Change {
    let sortedChanges = changes.sorted { (a, b) -> Bool in
        switch (a, b) {
        case (.remove(_, _, _), .insert(_, _, _)):
            return true
        case (.insert(_, _, _), .remove(_, _, _)):
            return false
        default:
            return a._offset < b._offset
        }
    }

    // Find first insertion via binary search
    let firstInsertIndex: Int
    if sortedChanges.isEmpty {
        firstInsertIndex = 0
    } else {
        var range = 0...sortedChanges.count
        while range.lowerBound != range.upperBound {
            let i = (range.lowerBound + range.upperBound) / 2
            switch sortedChanges[i] {
            case .insert(_, _, _):
                range = range.lowerBound...i
            case .remove(_, _, _):
                range = (i + 1)...range.upperBound
            }
        }
        firstInsertIndex = range.lowerBound
    }

    removals = Array(sortedChanges[0..<firstInsertIndex])
    insertions = Array(sortedChanges[firstInsertIndex..<sortedChanges.count])
}

public func inverse() -> Self {
    return CollectionDifference(_validatedChanges: self.map { c in
        switch c {
        case .remove(let o, let e, let a):
            return .insert(offset: o, element: e, associatedWith: a)
        case .insert(let o, let e, let a):
            return .remove(offset: o, element: e, associatedWith: a)
        }
    })
}
}

CollectionDifference Extension

extension CollectionDifference: Collection {
public typealias Element = Change

/// The position of a collection difference.
@frozen
public struct Index {
    // Opaque index type is isomorphic to Int
    @usableFromInline
    internal let _offset: Int

    internal init(_offset offset: Int) {
        _offset = offset
    }
}

public var startIndex: Index {
    return Index(_offset: 0)
}

public var endIndex: Index {
    return Index(_offset: removals.count + insertions.count)
}

public func index(after index: Index) -> Index {
    return Index(_offset: index._offset + 1)
}

public subscript(position: Index) -> Element {
    if position._offset < removals.count {
        return removals[removals.count - (position._offset + 1)]
    }
    return insertions[position._offset - removals.count]
}

public func index(before index: Index) -> Index {
    return Index(_offset: index._offset - 1)
}

public func formIndex(_ index: inout Index, offsetBy distance: Int) {
    index = Index(_offset: index._offset + distance)
}

public func distance(from start: Index, to end: Index) -> Int {
    return end._offset - start._offset
}
}

extension CollectionDifference.Index: Equatable {
@inlinable
public static func ==(
        lhs: CollectionDifference.Index,
        rhs: CollectionDifference.Index
) -> Bool {
    return lhs._offset == rhs._offset
}
}

extension CollectionDifference.Index: Comparable {
@inlinable
public static func <(
        lhs: CollectionDifference.Index,
        rhs: CollectionDifference.Index
) -> Bool {
    return lhs._offset < rhs._offset
}
}

extension CollectionDifference.Index: Hashable {
@inlinable
public func hash(into hasher: inout Hasher) {
    hasher.combine(_offset)
}
}

extension CollectionDifference.Change: Equatable where ChangeElement: Equatable {}

extension CollectionDifference: Equatable where ChangeElement: Equatable {}

extension CollectionDifference.Change: Hashable where ChangeElement: Hashable {}

extension CollectionDifference: Hashable where ChangeElement: Hashable {}

extension CollectionDifference where ChangeElement: Hashable {
/// Returns a new collection difference with associations between individual
/// elements that have been removed and inserted only once.
///
/// - Returns: A collection difference with all possible moves inferred.
///
/// - Complexity: O(*n*) where *n* is the number of collection differences.
public func inferringMoves() -> CollectionDifference<ChangeElement> {
    let uniqueRemovals: [ChangeElement: Int?] = {
        var result = [ChangeElement: Int?](minimumCapacity: Swift.min(removals.count, insertions.count))
        for removal in removals {
            let element = removal._element
            if result[element] != .none {
                result[element] = .some(.none)
            } else {
                result[element] = .some(removal._offset)
            }
        }
        return result.filter { (_, v) -> Bool in
            v != .none
        }
    }()

    let uniqueInsertions: [ChangeElement: Int?] = {
        var result = [ChangeElement: Int?](minimumCapacity: Swift.min(removals.count, insertions.count))
        for insertion in insertions {
            let element = insertion._element
            if result[element] != .none {
                result[element] = .some(.none)
            } else {
                result[element] = .some(insertion._offset)
            }
        }
        return result.filter { (_, v) -> Bool in
            v != .none
        }
    }()

    return CollectionDifference(_validatedChanges: map({ (change: Change) -> Change in
        switch change {
        case .remove(offset: let offset, element: let element, associatedWith: _):
            if uniqueRemovals[element] == nil {
                return change
            }
            if let assoc = uniqueInsertions[element] {
                return .remove(offset: offset, element: element, associatedWith: assoc)
            }
        case .insert(offset: let offset, element: let element, associatedWith: _):
            if uniqueInsertions[element] == nil {
                return change
            }
            if let assoc = uniqueRemovals[element] {
                return .insert(offset: offset, element: element, associatedWith: assoc)
            }
        }
        return change
    }))
}
}
norbDEV
  • 4,795
  • 2
  • 37
  • 28