56

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.

Zack Braksa
  • 1,628
  • 18
  • 26
Maxim Shoustin
  • 77,483
  • 27
  • 203
  • 225
  • Beware, several answers suggest solutions using `Equatable` conformance which yield >= O(n) performance. Prefer using `Set` with `Hashable` conformance which in many cases is an order of magnitude faster than using `Equatable`. I have added a "contains" extension below along these lines. – David James Dec 14 '17 at 13:04

12 Answers12

83

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)
BharathRao
  • 1,846
  • 1
  • 18
  • 28
orkoden
  • 18,946
  • 4
  • 59
  • 50
  • 1
    How are these two sets equal? As far as I can tell this is only generating a unique set from each array and then comparing them? – Ben Packard Mar 23 '15 at 19:14
  • The sets are equal. They are not the same. `listSet == findListSet` is false. – orkoden Mar 24 '15 at 11:48
  • "Two sets have equal contents if they each have the same number of members and if each member of one set is present in the other." Maybe I'm missing something obvious but how do these two sets have the same number of members? – Ben Packard Mar 24 '15 at 14:10
  • 2
    You are completely correct. I read the question again and realized I had misunderstood it and was using the wrong method. The correct method to use is `isSubsetOfSet(_ otherSet:)`. Thank you. – orkoden Mar 25 '15 at 11:47
  • 1
    You can now use Swift's built in Set type for this. – Mihai Damian Jun 10 '15 at 09:31
  • Sets are great, but they have disadvantages. One is that they can only contain elements that conform to `Hashable`. Another is that in the case of Swift's `Set` and the older `NSSet` types, they do not maintain element ordering. (There is an `NSOrderedSet` type that can do this, though) My answer below is more practical in the case where you don't have enough information to correctly conform the element's type to `Hashable`. – Jacob Relkin Sep 26 '19 at 08:10
  • 2
    Will this give the expected result for `let list = [1,2,3,4,5]` and `let findList = [1,3,3,5]`? When using `Set`, `findList` is a subset of `list`, but it is not when just comparing the arrays. – koen Feb 10 '20 at 13:08
40

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)
Jacob Relkin
  • 161,348
  • 33
  • 346
  • 320
14

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)

Julien Kode
  • 5,010
  • 21
  • 36
8

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.

David James
  • 2,430
  • 1
  • 26
  • 35
7

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)

Maxim Shoustin
  • 77,483
  • 27
  • 203
  • 225
  • i tried to test this in playground...at this line "if contains(src, v) == false{" .I am getting error "contains is unavailable : call the contains() method on the sequence" any idea..? – Surjeet Rajput Oct 14 '16 at 07:03
7

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.

Antonio
  • 71,651
  • 11
  • 148
  • 165
3

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.

Matt Gibson
  • 37,886
  • 9
  • 99
  • 128
2

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.

Community
  • 1
  • 1
Ibrahim Yildirim
  • 2,731
  • 2
  • 19
  • 31
2

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.

pkamb
  • 33,281
  • 23
  • 160
  • 191
Gocy015
  • 208
  • 3
  • 14
2

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
    }
}
Pranav Kasetti
  • 8,770
  • 2
  • 50
  • 71
Sean
  • 59
  • 2
1

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
}
Community
  • 1
  • 1
Rem-D
  • 627
  • 6
  • 15
0

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