【问题标题】:Array contains a complete subarray数组包含一个完整的子数组
【发布时间】:2016-05-24 10:17:05
【问题描述】:

在 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

【问题讨论】:

    标签: arrays swift


    【解决方案1】:

    您可以使用更高级别的函数来做到这一点,如下所示:

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

    【讨论】:

    • 我认为这可行,但可能太聪明了,人们无法理解。 (这当然适合我)。您能否在其中添加一些 cmets 来解释它在做什么?
    • @Fogmeister 当然可以!不过,这并没有看起来那么可怕——基本上,“reduce”替换了初始索引上的“for”循环,而[String](data[ind..&lt;ind+part.count]) == part 替换了从初始索引开始的各个值的循环。
    • 好的,所以它基本上是......从 0 开始。检查 0 处的子数组是否等于 part 参数。如果它一直返回0。如果不是转到 1 并检查 1 处的子数组是否等于部分参数等等?
    • @Fogmeister 是的,它就是这么做的,但是它将循环折叠成reduce,并使用?? 来避免在找到第一个匹配项后进行进一步检查。
    • 啊,有道理。我没有意识到 reduce 做到了。谢谢。我希望你不介意我的编辑。只需使用您的函数使其扩展任何 Equatable 类型的 Array。
    【解决方案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,感谢您告诉我。我的最后一次编辑应该可以解决所有剩余的问题。
    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

      这个想法可以扩展到所有等价序列。

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

      【讨论】:

        【解决方案5】:

        我想我会添加一个我正在使用的变体,它返回子数组(或 nil)的起始索引。拥有索引会很方便,这更符合最近 Swift 版本中使用的约定(即firstIndex(of:) 而不是contains())。 作为个人调整,在 subarray.count > array.count 的情况下,如果两者以相同的元素开头(否则为零),它将返回 0。如果你不想要这种(有点奇怪)的行为,你可以从 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
            }
        }
        

        【讨论】:

          【解决方案6】:

          数组没有您正在寻找的内置功能,但是您可以使用专为处理此类情况而设计的集合。

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

          【讨论】:

          • 您的方法会错误地将["world", "a"] 评估为“子数组”。
          • 是的,当然,要满足这个条件,我们可能需要使用自定义谓词...
          • @chitnisprasanna 自定义谓词是什么意思?您是在说“好的,我的解决方案不起作用,我必须对此进行编程”?你会如何解决这个问题?
          • 解决方案仍在 Swift 5 中运行。不知道,为什么它被否决了,但这是一个很好的替代方法
          猜你喜欢
          • 2019-09-19
          • 2016-03-13
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-05-06
          • 2012-06-14
          相关资源
          最近更新 更多