'How to determine if one array contains all elements of another array in Swift?

I have 2 arrays:

var list:Array<Int> = [1,2,3,4,5]
var findList:Array<Int> = [1,3,5]

I want to determine if list Array contains all findList elements.

By the way, elements might be String as well or other type.

How to do that?

I know that Swift provides contains method that works with one item.



Solution 1:[1]

Instead of iterating through arrays and doing filtering yourself, you can use NSSet to do all the work for you.

var list:Array<Int> = [1,2,3,4,5]
var findList:Array<Int> = [1,3,5]

let listSet = NSSet(array: list)
let findListSet = NSSet(array: findList)

let allElemtsEqual = findListSet.isSubsetOfSet(otherSet: listSet)

NSSet is a lot faster than arrays at checking if it contains any object. In fact it's what it's designed for.

Edit: Using Swift's built-in Set.

let list = [1,2,3,4,5]
let findList = [1,3,5]
let listSet = Set(list)
let findListSet = Set(findList)
//**Swift 4.2 and Above**
let allElemsContained = findListSet.isSubset(of: listSet)

//below versions
//let allElemsContained = findListSet.isSubsetOf(listSet)

Solution 2:[2]

allSatisfy seems to be what you want, assuming you can't conform your elements to Hashable and use the set intersection approach others have mentioned:

let containsAll = subArray.allSatisfy(largerArray.contains)

Solution 3:[3]

Since Swift 4.2 you can write:

extension Array where Element: Equatable {
    func satisfy(array: [Element]) -> Bool {
        return self.allSatisfy(array.contains)
    }
}

Otherwise for Swift 3, Swift 4 you can write this:

extension Array where Element: Equatable {
    func contains(array: [Element]) -> Bool {
        for item in array {
            if !self.contains(item) { return false }
        }
        return true
    }
}

You can see the:

  • contains method here
  • allSatisfy method here

This is just a simple extension that check if the array that you give is in the current array (self)

Solution 4:[4]

You can use the filter method to return all elements of findList which are not in list:

let notFoundList = findList.filter( { contains(list, $0) == false } )

then check if the length of the returned array is zero:

let contained = notFoundList.count == 0

Note that his solution traverses the entire findList array, so it doesn't stop as soon as a non contained element is found. It should be used if you also want to know which elements are not contained.

If you just need a boolean stating whether all elements are contained or not, then the solution provided by Maxim Shoustin is more efficient.

Solution 5:[5]

Consider following generic method:

func arrayContainsArray<S : SequenceType where S.Generator.Element : Equatable>
      (src:S, lookFor:S) -> Bool{

    for v:S.Generator.Element in lookFor{
      if contains(src, v) == false{
        return false
      }
    }
   return true
}

The advantage - method stops after 1st fail and do not continue over findList


Tests

var listAsInt:Array<Int> = [1,2,3,4,5]
var findListAsInt:Array<Int> = [1,3,5]
var result = arrayContainsArray(listAsInt, findListAsInt) // true

listAsInt:Array<Int> = [1,2,3,4,5]
findListAsInt:Array<Int> = [1,3,5,7,8,9]
result = arrayContainsArray(listAsInt, findListAsInt) // false

var listOfStr:Array<String> = ["aaa","bbb","ccc","ddd","eee"]
var findListOfStr:Array<String> = ["bbb","ccc","eee"]
result = arrayContainsArray(listOfStr, findListOfStr) // true

listOfStr:Array<String> = ["aaa","bbb","ccc","ddd","eee"]
findListOfStr:Array<String> = ["bbb","ccc","eee","sss","fff","ggg"]
result = arrayContainsArray(listOfStr, findListOfStr) // false

(tested on Beta7)

Solution 6:[6]

As a complement to Sequence.contains(element) handling multiple elements, add this extension:

public extension Sequence where Element : Hashable {
    func contains(_ elements: [Element]) -> Bool {
        return Set(elements).isSubset(of:Set(self))
    }
}

Used:

list.contains(findList)

Since this uses Set/Hashable it performs much better than Equatable alternatives.

Solution 7:[7]

Right now, I'd probably use something like:

let result = list.reduce(true, { $0 ? contains(findList, $1) : $0 })

...but then I did just read this article, which might be biasing me towards this kind of solution. You could probably make this more efficient without making it completely unreadable, but it's early and I've not had my coffee.

Solution 8:[8]

Extend the Array with the following methods:

extension Array {

    func contains<T where T : Equatable>(obj: T) -> Bool {
        return self.filter({$0 as? T == obj}).count > 0
    }

    func isEqualTo< T : Equatable> (comparingArray : [T]) -> Bool {

        if self.count != comparingArray.count {
            return false
        }

        for e in comparingArray {
            if !self.contains(e){
                return false
            }
        }

        return true
    }
}

An example of how you can use it like this:

if selectedDates.isEqualTo(originalDates) {
    //Arrays the same hide save button
} else {
    //Arrays not the same, show Save & Discard Changes Button (if not shown)
}

Shout out to @David Berry for the contain method.

Solution 9:[9]

None of the previous answers seem to be right.

consider:

let a = [2,2]
let b = [1,2,3]

we wouldn't say that b actually "contains" a, but if your algorithm is based on for-loop & swift's built-in contains(element:) or a set, the above case would pass.

Solution 10:[10]

I use this set of extended methods myself. I hope this code snippet helps:


//  Array + CommonElements.swift


import Foundation

public extension Array where Element: Hashable {

    func set() -> Set<Array.Element> {
        return Set(self)
    }

    func isSubset(of array: Array) -> Bool {
        self.set().isSubset(of: array.set())
    }

    func isSuperset(of array: Array) -> Bool {
        self.set().isSuperset(of: array.set())
    }

    func commonElements(between array: Array) -> Array {
        let intersection = self.set().intersection(array.set())
        return intersection.map({ $0 })
    }

    func hasCommonElements(with array: Array) -> Bool {
        return self.commonElements(between: array).count >= 1 ? true : false
    }
}

Solution 11:[11]

This is Maxim Shoustin's answer updated for Swift 3:

func arrayContainsArray<S : Sequence>
    (src:S, lookFor:S) -> Bool where S.Iterator.Element : Equatable{

    for v:S.Iterator.Element in lookFor{
        if src.contains(v) == false{
            return false
        }
    }
    return true
}

Solution 12:[12]

If you need to determine, that one array is subArray of another.

public extension Array where Element: Equatable {

    func isSuperArray(of array: Array<Element>) -> Bool {

        guard
            count >= array.count,
            let indexes = array.first.flatMap(indexes(of:)),
            !indexes.isEmpty else {
                return false
        }

        let arraysForComparison = indexes
            .compactMap { index -> [Element]? in
                guard index + (array.count - 1) <= count else { return nil }
                return Array(self[index..<(index + array.count)])
        }

        return arraysForComparison.contains(array)
    }

    func isSubArray(of array: Array<Element>) -> Bool {
        array.isSuperArray(of: self)
    }

    private func indexes(of element: Element) -> [Index] {
        enumerated()
            .filter { element == $0.1 }
            .map { index, _ in index }
    }
}

Example of usage:

let array1 = [1, 2, 3, 4]
let array2 = [2, 3]

print(array1.isSuperArray(of: array2)) // true
print(array2.isSubArray(of: array1)) // true

print(array2.isSuperArray(of: array1)) // false
print(array1.isSubArray(of: array2)) // false