How do I check in Swift if two arrays contain the same elements regardless of the order in which those elements appear in?
Asked Answered
S

11

75

Let's say there are two arrays...

var array1 = ["a", "b", "c"]
var array2 = ["b", "c", "a"]

I'd like the result of the comparison of these two arrays to be true, and the following...

var array1 = ["a", "b", "c"]
var array2 = ["b", "c", "a", "d"]

...to be false. How can I achieve that in Swift? I tried to convert both arrays to sets but for some reason Set() keeps removing some (usually duplicated) objects that the array contains.

Any help would be appreciated.

Saltine answered 19/4, 2016 at 9:40 Comment(3)
What about ["a","b"] and ["a", "a", "b"], should they compare true or false?Buckler
False, but ["a", "a", "b"] and ["a", "b", "a"] as true.Hedjaz
Just to comment - "... but for some reason Set() keeps removing some (usually duplicated) objects" - yes, that's because, by definition, sets do not contain duplicates.Preclinical
Q
119

Swift 3, 4

extension Array where Element: Comparable {
    func containsSameElements(as other: [Element]) -> Bool {
        return self.count == other.count && self.sorted() == other.sorted()
    }
}

// usage
let a: [Int] = [1, 2, 3, 3, 3]
let b: [Int] = [1, 3, 3, 3, 2]
let c: [Int] = [1, 2, 2, 3, 3, 3]

print(a.containsSameElements(as: b)) // true
print(a.containsSameElements(as: c)) // false

Quach answered 19/4, 2016 at 9:51 Comment(8)
Very nice, most probably I would not come up with the idea to sort the elements first. Thanks to everyone that responded.Hedjaz
Thank-you for an elegant and easily reusable solution but the count comparison appears to be unnecessary.Astrakhan
@Astrakhan no but if count fails you avoid having to sort two arrays and then compare them, so it's more efficient.Wamble
@Wamble Performance measures with and without the count clause have no discernible performance difference.The == operation on arrays (Swift source code) first checks the counts before comparing arrays element by element.Astrakhan
@Astrakhan I didn't know that, well then in that case I can't think of a reason to compare countWamble
If the count clause is evaluated first using a guard statement and exiting early when false, this could avoid sorting both arrays.Penick
@Astrakhan @Izi in that case, is it then a bit more efficient than the answer and still clean to use return self == other || self.sorted() == other.sorted() as this will only compare element by element if the count is the same, and only sort if the element by element compare succeeds?Horsehair
@Neil Smith && is short circuiting. If the left side evaluates to false, it won't bother evaluating the right side because it knows the entire expression will be falseHorsehair
S
13

Swift Compare arrays

input:

let array1 = ["a", "b", "c"]
let array2 = ["b", "c", "a", "c"]

Case 1: Duplicates are important, then use built-in Array.sort() function which uses Introsort under the hood with O(n log n) complexity

let array1Sorted = array1.sorted() //a, b, c
let array2Sorted = array2.sorted() //a, b, c, c

if (array1Sorted.count == array2Sorted.count && array1Sorted == array2Sorted) {
    //they are identical
}

Case 2: Duplicates are not important - use Set, with O(n) complexity. The Set implements Hashable so the task is to implement the hash function for element

let set1 = Set(array1) //b, c, a
let set2 = Set(array2) //b, c, a
    
if (set1.count == set2.count && set1 == set2) {
    //they are identical
}

[Swift Collections]

Salonika answered 7/8, 2019 at 16:57 Comment(5)
It was described in the question that using Set doesn't work since the arrays might contain duplicates.Ireland
You also have to compare the count.Vermicide
The sets won't work for arrays that can contain duplicates, but whenever the programmer assumes there are no duplicates - Set's speed is just magical in comparison going through the hassle of sorting both arraysNicholnichola
This solution doesn't work if there are duplicates. The solution needs to compare count of the arrays, not the sets. In the current example, they should not be identical because array2 has a duplicate. If comparison is done on Array counts then it correctly reports they do not have the same elements. Saying they're identical is also misleading as order makes them not identical.Clericalism
Comparing counts and contents of the Sets isn't enough. The following two arrays would be seen as identical: ["a", "a", "b"] and ["a", "b", "b"].Fluxmeter
S
11

Swift 5.2 Solution

var array1 = ["a", "b", "c"]
var array2 = ["b", "c", "a"]

if array1.sorted() == array2.sorted() {
    print("array 1 & array 2 are same")
}
Schofield answered 27/5, 2020 at 7:44 Comment(0)
G
8

you can do something like this:

  array1.sortInPlace()
  array2.sortInPlace()

  print(array1,array2)

  if array1 == array2 {
    print("equal")
  } else {
  print("not equal") 
  }

and if don't want change origional array we can do

 let sorted1 = array1.sort()
 let sorted2 = array2.sort()

  if sorted1 == sorted2 {
    print("equal")
  }else {
    print("not equal")
  }
Goidelic answered 19/4, 2016 at 9:48 Comment(0)
R
3

I know this question is old, and it also didn't want to determine if array1 was a subset of array2. However, This works in Swift 5.3 and Xcode 12.3:

var array1 = ["a", "b", "c"]
var array2 = ["b", "c", "a", "d"]

print("array1 == array2? \(Set(array1) == Set(array2))")
print("array1 subset to array2? \(Set(array1).isSubset(of: Set(array2)))")
Reputed answered 27/1, 2021 at 2:55 Comment(0)
S
2

Create function to compare them:

func containSameElements(var firstArray firstArray: [String], var secondArray: [String]) -> Bool {
    if firstArray.count != secondArray.count {
        return false
    } else {
        firstArray.sortInPlace()
        secondArray.sortInPlace()
        return firstArray == secondArray
    }
}

Then:

var array1 = ["a", "a", "b"]
var array2 = ["a", "b", "a"]

var array3 = ["a", "b", "c"]
var array4 = ["b", "c", "a", "d"]

print(containSameElements(firstArray: array1, secondArray: array2)) //true
print(containSameElements(firstArray: array3, secondArray: array4)) //false
print(array1) //["a", "a", "b"]
print(array2) //["a", "b", "a"]
print(array3) //["a", "b", "c"]
print(array4) //["b", "c", "a", "d"]
Saum answered 19/4, 2016 at 9:52 Comment(5)
Typically a boolean check shouldn't mutate its arguments.Snot
Oh, I remember now, Swift has mental calling conventions for arrays.Snot
I don't know about this. Can you give me an example.Saum
I think they are (or at least used to be) copy-on-write but what constituted a write was sometimes surprising. Anyway your code is fine - you mutate a copy, not the original.Snot
You mean var firstArray firstArray: [String] should be You mean inout firstArray: [String]?Saum
P
2

Here is a solution that does not require the element to be Comparable, but only Equatable. It is much less efficient than the sorting answers, so if your type can be made Comparable, use one of those.

extension Array where Element: Equatable {
    func equalContents(to other: [Element]) -> Bool {
        guard self.count == other.count else {return false}
        for e in self{
          guard self.filter{$0==e}.count == other.filter{$0==e}.count else {
            return false
          }
        }
        return true
    }
}
Pennsylvanian answered 6/5, 2018 at 21:18 Comment(1)
If the element is Hashable, then you can iterate through only Set(self).Pennsylvanian
K
1

Solution for Swift 4.1/Xcode 9.4:

extension Array where Element: Equatable {
    func containSameElements(_ array: [Element]) -> Bool {
        var selfCopy = self
        var secondArrayCopy = array
        while let currentItem = selfCopy.popLast() {
            if let indexOfCurrentItem = secondArrayCopy.index(of: currentItem) {
                secondArrayCopy.remove(at: indexOfCurrentItem)
            } else {
                return false
            }
        }
        return secondArrayCopy.isEmpty
    }
}

The main advantage of this solution is that it uses less memory than other (it always creates just 2 temporary arrays). Also, it does not require for Element to be Comparable, just to be Equatable.

Keir answered 17/7, 2018 at 9:5 Comment(0)
K
1

If elements of your arrays are conforming to Hashable you can try to use the bag (it's like a set with the registration of each item amount). Here I will use a simplified version of this data structure based on Dictionary. This extension helps to create bag from array of Hashable:

extension Array where Element: Hashable {
    var asBag: [Element: Int] {
        return reduce(into: [:]) {
            $0.updateValue(($0[$1] ?? 0) + 1, forKey: $1)
        }
    }
}

Now you need to generate 2 bags from initial arrays and compare them. I wrapped it in this extension:

extension Array where Element: Hashable {
    func containSameElements(_ array: [Element]) -> Bool {
        let selfAsBag = asBag
        let arrayAsBag = array.asBag
        return selfAsBag.count == arrayAsBag.count && selfAsBag.allSatisfy {
            arrayAsBag[$0.key] == $0.value
        }
    }
}

This solution was tested with Swift 4.2/Xcode 10. If your current Xcode version is prior to 10.0 you can find the function allSatisfy of ArraySlice in Xcode9to10Preparation. You can install this library with CocoaPods.

Keir answered 11/10, 2018 at 16:15 Comment(0)
T
1

if I have

    array1 = ["x", "y", "z"]
    array2 = ["a", "x", "c"]

I can do

    array1.filter({array2.contains($0})

to return ["x"]

and likewise

    array1.filter({!array2.contains($0)})

to return ["y", "z"]

Threegaited answered 25/8, 2022 at 1:45 Comment(1)
helped me filter one array from another so thank you. Only doesn't return a BOOL as Q asks.Laveen
A
0

For determining if an array is a subset of another array, and duplicate elements are not relevant.

public extension Sequence where Element: Equatable {
    func contains(allOf sequence: [Element]) -> Bool {
        return sequence.allSatisfy { self.contains($0) }
    }
}

let a: [Int] = [1, 2, 3, 4]
let b: [Int] = [1, 2, 3]
let c: [Int] = [1, 2, 3, 4, 5]
let d: [Int] = [1, 1, 2, 2, 3, 3, 4, 4]

a.contains(allOf: b) // true
a.contains(allOf: c) // false
a.contains(allOf: d) // true
Anticlastic answered 7/5, 2024 at 1:40 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.