数组包含完整的子数组

9

在Swift中,我应该如何检查一个数组是否完全包含给定的子数组?例如,是否有一个contains函数可以这样使用:

let mainArray = ["hello", "world", "it's", "a", "beautiful", "day"]
contains(mainArray, ["world", "it's"])   // would return true
contains(mainArray, ["world", "it"])   // would return false
contains(mainArray, ["world", "a"])   // would return false - not adjacent in mainArray
6个回答

3
您可以使用更高级的函数来完成此操作,例如:
func indexOf(data:[String], _ part:[String]) -> Int? {
    // This is to prevent construction of a range from zero to negative
    if part.count > data.count {
        return nil
    }

    // The index of the match could not exceed data.count-part.count
    return (0...data.count-part.count).indexOf {ind in
        // Construct a sub-array from current index,
        // and compare its content to what we are looking for.
        [String](data[ind..<ind+part.count]) == part
    }
}

如果找到了匹配项,此函数将返回第一个匹配项的索引;否则将返回nil

您可以按照以下方式使用它:

let mainArray = ["hello", "world", "it's", "a", "beautiful", "day"]
if let index = indexOf(mainArray, ["world", "it's"]) {
    print("Found match at \(index)")
} else {
    print("No match")
}

作为通用数组的扩展进行编辑...

现在可以将其用于任何具有相同类型的Equatable数组中。

extension Array where Element : Equatable {
    func indexOfContiguous(subArray:[Element]) -> Int? {

        // This is to prevent construction of a range from zero to negative
        if subArray.count > self.count {
            return nil
        }

        // The index of the match could not exceed data.count-part.count
        return (0...self.count-subArray.count).indexOf { ind in
            // Construct a sub-array from current index,
            // and compare its content to what we are looking for.
            [Element](self[ind..<ind+subArray.count]) == subArray
        }
    }
}

我认为这个方案可行,但可能对人们来说过于聪明而难以理解(至少对我来说是这样)。你能否添加一些注释来解释它正在做什么? - Fogmeister
@Fogmeister 当然可以!这其实比看起来的要简单得多 - 基本上,“reduce”替换了初始索引上的“for”循环,而[String](data[ind..<ind+part.count]) == part则替换了从初始索引开始的各个值的循环。 - Sergey Kalinichenko
@Fogmeister非常感谢您的编辑!将其更通用化是完全有道理的。 - Sergey Kalinichenko
@dasblinkenlight 你会如何优化这个代码,使得当子数组的第一个实例被找到时,reduce函数可以“跳出”循环? - Daniel
@dasblinkenlight 希望你不介意,我已经更新了你的答案,使用 indexOf 而不是 reduce,这样函数在找到匹配项时可以短路。感觉不需要单独回答。 - Hamish
显示剩余3条评论

2
据我所知,这样的功能并不存在。但是您可以通过以下扩展来添加此功能:
extension Array where Element: Equatable {
    func contains(subarray: [Element]) -> Bool {
        guard subarray.count <= count else { return false }
    
        for idx in 0 ... count - subarray.count {
            let start = index(startIndex, offsetBy: idx)
            let end = index(start, offsetBy: subarray.count)
            if Array(self[start ..< end]) == subarray { return true }
        }        
        return false
    }
}

一旦将扩展添加到您的项目中,您只需调用:

mainArray.contains(["world", "it's"]) // true
mainArray.contains(["world", "it"])   // false
mainArray.contains(["it's", "world"]) // false

let array2 = ["hello", "hello", "world"]
array2.contains(["hello", "world"]) // true
[1, 1, 1, 2].contains(subarray: [1, 1, 2]) // true

即使进行编辑,对于数组 [1, 1, 1, 2][1, 1, 2],该代码仍将失败。这并没有正确地重新开始搜索。 - andras
嗨@andras,谢谢你让我知道。我的最后一次编辑应该解决所有剩余的问题。 - Daniel

0

这个想法可以扩展到所有可比较的序列。

public extension Sequence where Element: Equatable {
  /// The iterators of all subsequences, incrementally dropping early elements.
  /// - Note: Begins with the iterator for the full sequence (dropping zero).
  var dropIterators: AnySequence<AnyIterator<Element>> {
    .init(
      sequence(state: makeIterator()) {
        let iterator = $0
        return $0.next().map { _ in .init(iterator) }
      }
    )
  }

  /// - Note: `false` if `elements` is empty.
  func contains<Elements: Sequence>(inOrder elements: Elements) -> Bool
  where Elements.Element == Element {
    elements.isEmpty
      ? false
      : dropIterators.contains {
        AnySequence(zip: ($0, elements))
          .first(where: !=)?.1 == nil
      }
  }
}

public extension Sequence {
  /// The first element of the sequence.
  /// - Note: `nil` if the sequence is empty.
  var first: Element? {
    var iterator = makeIterator()
    return iterator.next()
  }

  /// Whether the sequence iterates exactly zero elements.
  var isEmpty: Bool { first == nil }
}

public extension AnySequence {
  /// Like `zip`, but with `nil` elements for the shorter sequence after it is exhausted.
  init<Sequence0: Sequence, Sequence1: Sequence>(
    zip zipped: (Sequence0, Sequence1)
  ) where Element == (Sequence0.Element?, Sequence1.Element?) {
    self.init(
      sequence(
        state: (zipped.0.makeIterator(), zipped.1.makeIterator())
      ) { iterators in
        Optional(
          (iterators.0.next(), iterators.1.next())
        )
        .filter { $0 != nil || $1 != nil }
      }
    )
  }
}

public extension Optional {
  /// Transform `.some` into `.none`, if a condition fails.
  /// - Parameters:
  ///   - isSome: The condition that will result in `nil`, when evaluated to `false`.
  func filter(_ isSome: (Wrapped) throws -> Bool) rethrows -> Self {
    try flatMap { try isSome($0) ? $0 : nil }
  }
}

0

simpleBob的第一次尝试似乎在最小修改下就能够工作:

extension Array where Element: Equatable {
    func contains(subarray: [Element]) -> Index? {
        var found = 0
        var startIndex:Index = 0
        for (index, element) in self.enumerate() where found < subarray.count {
            if element != subarray[found] {
                found = 0
            }
            if element == subarray[found]  {
                if found == 0 { startIndex = index }
                found += 1
            }
        }

        return found == subarray.count ? startIndex : nil
    }
}

0

我想分享一下我正在使用的变体,它返回子数组的起始索引(或nil)。拥有索引可能很方便,并且这更符合最近Swift版本中使用的约定(即firstIndex(of:)而不是contains())。 作为个人调整,在子数组.count > array.count的情况下,如果两个元素以相同的元素开头,则会返回0(否则为nil)。如果您不想要这种(有点奇怪的)行为,可以从guard块中返回nil。

extension Array where Element: Equatable {
    func firstIndex(ofSubarray subarray: [Element]) -> Int? {
        guard subarray.count <= count else {
            return self == Array(subarray[0 ..< self.count]) ? 0 : nil
        }
        
        for idx in 0 ... count - subarray.count {
            let start = index(startIndex, offsetBy: idx)
            let end = index(start, offsetBy: subarray.count)
            if Array(self[start ..< end]) == subarray { return idx }
        }
        return nil
    }
}

-1

数组没有您要寻找的内置功能,但是您可以使用集合来处理这种情况。

let mainSet:Set = ["hello", "world", "it's", "a", "beautiful", "day"]
let list2:Set = ["world", "it's"]
let list3:Set = ["world","a"]
list2.isSubsetOf(mainSet)

4
你的方法会错误地将 ["world", "a"] 评估为一个“子数组”。 - Jakub Vano
是的,当然,为了满足这个条件,我们可能需要使用自定义谓词... - chitnisprasanna
@chitnisprasanna 你所说的自定义谓词是什么意思?你是在说“好吧,我的解决方案不起作用,我必须编写这个”吗?你会如何解决这个问题? - Daniel
解决方案仍在Swift 5中运行。 不知道为什么会被投反对票,但这是一个很好的替代方式。 - Viktor

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