65

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.

1
  • 1
    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. Commented Dec 14, 2017 at 13:04

12 Answers 12

91

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)
Sign up to request clarification or add additional context in comments.

7 Comments

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?
The sets are equal. They are not the same. listSet == findListSet is false.
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.
You can now use Swift's built in Set type for this.
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.
|
44

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)

1 Comment

This won't stand if the wannabe subarray contains duplicated elements that are not duplicated in the larger array.
15

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)

Comments

8

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.

1 Comment

In swift 4 you can do: var ts = findList.filter{ !list.contains($0) }
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.

Comments

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)

1 Comment

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..?
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.

1 Comment

Be careful, if the source array is empty this will return true, which might not be the intended behavior.
3

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.

Comments

3

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
    }
}

Comments

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.

Comments

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
}

Comments

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.