【问题标题】:Implement `Array` & `ArraySlice` extension with a protocol instead使用协议实现 `Array` 和 `ArraySlice` 扩展
【发布时间】:2017-06-15 03:43:53
【问题描述】:

我有以下 Swift 代码:

extension Array {
  typealias EqualTest = (Iterator.Element, Iterator.Element) -> Bool

  func groupSplitIndices(withEqualTest equal: EqualTest) -> [Index] {
    return indices.groupSplitIndices(withEqualTest: {equal(self[$0], self[$1])})
  }
}

extension ArraySlice {
  typealias EqualTest = (Iterator.Element, Iterator.Element) -> Bool
  
  func groupSplitIndices(withEqualTest equal: EqualTest) -> [Index] {
    return indices.groupSplitIndices(withEqualTest: {equal(self[$0], self[$1])})
  }
}

extension CountableRange {
  typealias EqualTest = (Element, Element) -> Bool
  
  func groupSplitIndices(withEqualTest equal: EqualTest) -> [Element] {
    // Implementation omitted here.
    // For details see  "Background" at the end of the question.
  }
}

除了使用相同的代码扩展 ArrayArraySlice 之外,是否有可以扩展的协议来实现相同的结果?

基本上,我想扩展关联类型IndicesCountableRange 的任何集合。

尝试通用实现

我尝试了很多方式来表达这一点,但我还没有找到让它编译的方法。

尝试 1

extension RandomAccessCollection {
  typealias EqualTest = (Iterator.Element, Iterator.Element) -> Bool

  func groupSplitIndices(withEqualTest equal: EqualTest) -> [Index] {
    // Error on next line…
    return indices.groupSplitIndices(withEqualTest: {equal(self[$0], self[$1])})
  }
}

此尝试出现 2 个错误:

“Self.Indices”类型的值没有成员“groupSplitIndices”

闭包使用非转义参数 'equal' 可能会允许它转义

(我认为第二个错误是 Swift 变得混乱。)

尝试 2

extension RandomAccessCollection where Indices: CountableRange {
  // Implementation omitted.
}

给出错误:

对泛型“CountableRange”的引用需要 <...>

中的参数

尝试 3

extension RandomAccessCollection where Indices: CountableRange<Int> {
  // Implementation omitted.
}

给出错误:

类型“Indices”被限制为非协议类型“CountableRange”


背景

这是上面省略的实现groupRanges(withEqualTest:)CountableRange 的扩展。该算法、它的作用以及它的大 O 成本在 in this question 讨论。

我曾尝试实现类似RandomAccessCollection 的扩展,但运气不佳。

extension CountableRange {
  typealias EqualTest = (Element, Element) -> Bool
  
  func groupRanges(withEqualTest equal:EqualTest) -> [CountableRange] {
    let groupIndices = groupSplitIndices(withEqualTest: equal)
    return groupIndices.indices.dropLast().map {groupIndices[$0]..<groupIndices[$0+1]}
  }
  
  func groupSplitIndices(withEqualTest equal: EqualTest) -> [Element] {
    var allIndexes = [lowerBound]
    allIndexes.append(contentsOf: interiorGroupSplitIndices(withEqualTest: equal))
    allIndexes.append(upperBound)
    
    return allIndexes
  }
  
  func interiorGroupSplitIndices(withEqualTest equal: EqualTest) -> [Element] {
    var result = Array<Element>()
    var toDo = [self]
    
    while toDo.count > 0 {
      let range = toDo.removeLast()
      
      guard
        let firstElement = range.first,
        let lastElement = range.last,
        firstElement != lastElement,
        !equal(firstElement, lastElement) else {
          continue;
      }
      
      switch range.count {
      case 2:
        result.append(lastElement)
      default:
        let midIndex = index(firstElement, offsetBy: range.count/2)
        toDo.append(range.suffix(from: midIndex))
        toDo.append(range.prefix(through: midIndex))
      }
    }
    
    return result
  }
}

【问题讨论】:

  • 是否可以选择实现 RandomAccessCollection 而不是 CountableRange 的代码? – 然后您可以轻松地将调用从任意 RandomAccessCollection 转发到其索引。
  • @MartinR 谢谢——我已经在这个问题中添加了更多内容。

标签: swift generics


【解决方案1】:

为了调用indices.groupSplitIndices(),您需要集合扩展上的约束
Indices == CountableRange&lt;Index&gt;, 这要求IndexStrideable

extension RandomAccessCollection where Index: Strideable, Indices == CountableRange<Index> {

    typealias EqualTest = (Iterator.Element, Iterator.Element) -> Bool

    func groupSplitIndices(withEqualTest equal: EqualTest) -> [Index] {
        return indices.groupSplitIndices(withEqualTest: {
            equal(self[$0], self[$1])
        })
    }
}

extension CountableRange {
    typealias EqualTest = (Element, Element) -> Bool

    func groupSplitIndices(withEqualTest equal: EqualTest) -> [Element] {
        // Dummy implementation:
        return []
    }
}

这实际上是用 Swift 4 编译的(Xcode 9 beta 或 Xcode 8.3.3 与 Swift 4 工具链)。

不过有一个问题:Xcode 8.3.3 中的 Swift 3 编译器在编译 上面带有“调试”配置的代码。这似乎是一个编译器 错误,因为它在“发布”配置中编译没有问题, 以及 Xcode 9 或 Xcode 8.3.2 和 Swift 4 工具链。


这是我如何找出上述解决方案的粗略描述。 让我们从你的“尝试 3”开始:

extension RandomAccessCollection where Indices: CountableRange<Int>

// error: type 'Indices' constrained to non-protocol type 'CountableRange<Int>

Indices不能是CountableRange&lt;Int&gt;的子类或采用CountableRange&lt;Int&gt;的类型,这意味着我们需要一个same-typre要求:

extension RandomAccessCollection where Indices == CountableRange<Int>

这会导致

// error: cannot subscript a value of type 'Self' with an index of type 'Int'

self[$0]self[$1]Collectionsubscript方法 接受Self.Index 参数,所以我们将其更改为

extension RandomAccessCollection where Indices == CountableRange<Index>

// error: type 'Self.Index' does not conform to protocol '_Strideable'

所以Index 必须是Strideable

extension RandomAccessCollection where Index: Strideable, Indices == CountableRange<Index>

就是这样!

【讨论】:

  • 谢谢!这看起来不错。我还没有机会尝试,但是当我让它工作时我会接受。我正要问——“你怎么知道的”!我发现使用 Swift 来确定我需要的约束以及通常理解标准库的类型和协议如何组合在一起是令人沮丧的。从文档中似乎不清楚,错误可能会产生误导,并且标准库在 2 和 3 之间发生了很大变化(并且会再次更改?)。
  • @Benjohn:我已经添加了一些解释。但是也涉及到很多尝试和错误!
  • 好吧,这真是太棒了!谢谢! :-) ...我将按照您的流程完成我自己的代码并尝试重新创建。有趣的是,这些错误毫无帮助!例如error: type 'Indices' constrained to non-protocol type 'CountableRange&lt;Int&gt;,一个更有帮助的错误可能是error: Type constraint can only be used with protocol type, not with a struct type 'CountableRange&lt;Int&gt;'. Use the same-type requirement '==' for a struct instead. – fix it? 另外,我的工具链作为一个版本回来并没有帮助 - 一旦我设法解压庞大的 xcode 安装,将升级。
  • 还有一个问题,如果可以的话——有什么方法可以给RandomAccessCollection where Index: Strideable, Indices == CountableRange&lt;Index&gt; 起一个名字/别名,比如CountableRangeIndicesRandomAccessCollection?这样我就可以写extension CountableRangeIndicesRandomAccessCollection {…?能够隔离具有可数范围的索引的集合的想法会很好。或者,它们的索引的整数等。
  • @Benjohn:我不知道,但我想不会。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-10-03
  • 1970-01-01
  • 1970-01-01
  • 2011-12-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多