2017-06-06 40 views
3

我有以下SWIFT代碼:實施`Array`&`ArraySlice`擴展與協議代替

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個錯誤:

Value of type 'Self.Indices' has no member 'groupSplitIndices'

Closure use of non-escaping parameter 'equal' may allow it to escape

(我認爲第二個錯誤是斯威夫特越來越糊塗。)

嘗試2

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

給出錯誤:

Reference to generic type 'CountableRange' requires arguments in <...>

嘗試3

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

給出錯誤:

Type 'Indices' constrained to non-protocol type '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 
    } 
} 
+0

它是否可以實現RandomAccessCollection而不是CountableRange的代碼? - 然後,您可以輕鬆地將來自任意RandomAccessCollection的調用轉發給其索引。 –

+0

@MartinR謝謝 - 我已經增加了一些關於這個問題。 – Benjohn

回答

1

爲了調用indices.groupSplitIndices()你需要收集擴展, 約束
Indices == CountableRange<Index>和要求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 [] 
    } 
} 

這實際上與斯威夫特4編譯(Xcode中9 beta或帶有Swift 4工具鏈的Xcode 8.3.3)。

雖然存在一個問題:當使用「Debug」配置編譯上述代碼時,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<Int>,這意味着我們需要一個同typre要求:

extension RandomAccessCollection where Indices == CountableRange<Int> 

此結果in

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

at self[$0] and 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> 

,這就是它!

+0

謝謝!這看起來不錯。我還沒有機會嘗試,但我會接受,當我得到它的工作。我會問 - 「你怎麼知道這個」!我發現使用Swift確定我需要的約束令人沮喪,並且通常理解標準庫的類型和協議如何組合在一起。從文檔中可以看出,這些錯誤可能會引起誤解,標準庫在2和3之間變化很大(並且會再次發生變化)。 – Benjohn

+1

@Benjohn:我添加了一些解釋。但是也涉及到很多嘗試和錯誤! –

+0

好的,這真是太棒了!謝謝! :-) ...我將通過我自己的代碼跟蹤你的過程並嘗試重新創建。有趣的是,這些錯誤是相當無益的!例如,'error:type'Indices'約束於非協議類型'CountableRange ',更有用的錯誤可能是'錯誤:類型約束只能與協議類型一起使用,不能與結構類型'CountableRange '一起使用。改爲使用相同類型的要求'=='。 - 修復它?'而且,我的工具鏈作爲一個版本返回沒有幫助 - 將升級,一旦我設法解壓縮巨大的Xcode安裝。 – Benjohn