在Swift中如何确定一个数组是否包含另一个数组的所有元素?

59

我有两个数组:

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

我希望确定list数组是否包含所有findList元素。

顺便说一下,元素也可能是String或其他类型。

如何做到这一点?

我知道Swift提供了一个contains方法,可以用于一个项。


注意,有几个答案建议使用Equatable符合性的解决方案,这会产生>= O(n)的性能。最好使用具有Hashable符合性的Set,在许多情况下比使用Equatable快一个数量级。我已经添加了以下类似的“包含”扩展。 - David James
12个回答

85

不必自己迭代数组并进行筛选,您可以使用NSSet来为您完成所有工作。

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 检查是否包含任何对象比数组快得多,实际上这就是它的设计目的。

编辑: 使用 Swift 的内置 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)

1
这两个集合怎么相等?据我所知,这只是从每个数组生成一个唯一的集合,然后进行比较? - Ben Packard
这些集合是相等的,但它们不完全相同。listSet == findListSet 的值为 false。 - orkoden
2
你是完全正确的。我再次阅读了问题,意识到我误解了它并且使用了错误的方法。正确的方法是使用 isSubsetOfSet(_ otherSet:)。谢谢。 - orkoden
1
现在你可以使用Swift内置的Set类型来实现这个功能。 - Mihai Damian
2
对于 let list = [1,2,3,4,5]let findList = [1,3,3,5],这段代码是否会得到预期结果?当使用 Set 时,findListlist 的子集,但是当仅比较数组时则不是。 - koen
显示剩余2条评论

41

allSatisfy 似乎是你想要的,假设你不能符合元素 Hashable 并使用其他人提到的集合交集方法:

let containsAll = subArray.allSatisfy(largerArray.contains)

如果想要的子数组包含在更大的数组中没有重复的重复元素,那么这是不可行的。 - lazarevzubov

14

自从 Swift 4.2 版本以后,你可以写:

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

否则,对于 Swift 3Swift 4,您可以编写以下代码:

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

您可以在此处查看:

这只是一个简单的扩展,检查您提供的数组是否在当前数组(self)中。


8
作为Sequence.contains(element)处理多个元素的补充,添加以下扩展:
public extension Sequence where Element : Hashable {
    func contains(_ elements: [Element]) -> Bool {
        return Set(elements).isSubset(of:Set(self))
    }
}

使用:

list.contains(findList)

由于使用了Set/Hashable,因此其性能比Equatable的替代方案要好得多。


7
考虑以下通用方法:
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
}

优点-方法在第一次失败后停止,不会继续查找findList

测试

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

(在Beta7上测试过)


我尝试在playground中测试这个...在这一行"if contains(src, v) == false{"。我得到了错误信息"contains不可用:在序列上调用contains()方法"。有什么想法吗..? - Surjeet Rajput

7
你可以使用filter方法返回findList中所有不在list中的元素:
let notFoundList = findList.filter( { contains(list, $0) == false } )

然后检查返回的数组长度是否为零:

let contained = notFoundList.count == 0

请注意,此解决方案遍历整个findList数组,因此它不会在找到非包含元素后立即停止。如果您还想知道哪些元素未包含,则应使用它。
如果您只需要一个布尔值来说明是否包含所有元素,则Maxim Shoustin提供的解决方案更有效。

2
在 Swift 4 中,您可以这样做:var ts = findList.filter{ !list.contains($0) } - bretcj7

3

现在,我可能会使用类似以下的东西:

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

...但我刚刚读了这篇文章,可能会让我对这种解决方案有所偏见。你可能可以在不完全难以理解的情况下使其更加高效,但现在还很早,我还没喝咖啡。


1
请注意,如果源数组为空,则此操作将返回“true”,这可能不是预期的行为。 - vadimtrifonov

2

扩展Array,添加以下方法:

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

一个使用示例如下所示:

您可以像这样使用它:

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

感谢 @David Berry 提供的 contain 方法。


2

之前的答案似乎都不正确。

考虑以下内容:

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

我们不会说 b 实际上“包含”a,但是如果您的算法基于 for-loop 和 Swift 内置的 contains(element:) 或者一个集合,那么上述情况将通过。


2
我自己使用这组扩展方法。我希望这段代码片段能够帮助你:

我使用这组扩展方法。我希望这个代码片段能够帮助你:


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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接