函数接受泛型Collection并返回索引数组

时间:2017-07-06 15:39:42

标签: swift generics

我正在尝试编写一个接受泛型Collection和单个元素的函数,然后将该元素的索引或索引作为数组返回。但是,我在return语句中收到错误。

enter image description here

func findAll<T: Collection, U: Equatable>(_ items: T, _ find: U) -> [U] where T.Iterator.Element == U {
    var found = [Int]()
    for (index, item) in items.enumerated() {
        if item == find {
        found.append(index)
        }
    }
    return found
}

2 个答案:

答案 0 :(得分:3)

Ken的答案很好,但Collection可以有一个除Int之外的索引。如果你试图使用你得到的索引,那么很多人就无法用它们下标。

而不是[Int],您需要[Self.Index]。因此,您需要更通用的enumerated

,而不是zip
extension Collection where Self.Element: Equatable {
    func indicesOfElements(equalTo element: Self.Element) -> [Self.Index] {
        return zip(self.indices, self)    // Zip together the index and element
            .filter { $0.1 == element }   // Find the ones that match
            .map { $0.0 }                 // Return the elements
    }
}

[1,2,3,4,1].indicesOfElements(equalTo: 1) // => [0,4]

那就是说,一种更简单的方法是:

extension Collection where Self.Element: Equatable {
    func indicesOfElements(equalTo element: Self.Element) -> [Self.Index] {
        return indices.filter { self[$0] == element }
    }
}

要查看与下标有关的问题,请考虑以下事项:

let s = "abcabc"
let i = s.indicesOfElements(equalTo: "a").first!
s[i] // "a"

let j = findAll(s, "a").first!
s[j] // error: 'subscript' is unavailable: cannot subscript String with an Int, see the documentation comment for discussion

虽然协议扩展是在Swift中执行此操作的首选方法,但这可以直接转换为以下通用函数语法:

func indicesOfElements<C: Collection>(in collection: C, equalTo element: C.Element) -> [C.Index]
    where C.Element: Equatable {
        return collection.indices.filter { collection[$0] == element }
}

以下样式也是等效的,并且学习曲线略浅(不需要filter

func simplerIndicesOfElements<C: Collection>(in collection: C, equalTo element: C.Element) -> [C.Index]
    where C.Element: Equatable {

        var results: [C.Index] = []

        for index in collection.indices {
            if collection[index] == element {
                results.append(index)
            }
        }

        return results
}

我相信即使是相当新的Swift开发人员也应该学会阅读简单的filtermap表达式(尽管这些都应该保持简单,即使是专家!)但是学习简单绝对没有错首先for迭代。

如果您刚开始使用,请注意此处的命名方式。您的初始示例包含两个未命名的参数。在许多(大多数)情况下,这是糟糕的Swift。在Swift中,我们通常会尝试命名我们的参数,以便他们在英语中自然地阅读。在findAll(xs, x)中,不清楚参数是什么或返回值是什么。在indicesOfElements(in: xs, equalTo: x)中,所有信息都可以在呼叫站点获得。

在Swift 3中,这需要比你预期的语法更多的语法:

func indicesOfElements<C: Collection>(in collection: C, equalTo element: C.Iterator.Element) -> [C.Index]
    where C.Iterator.Element: Equatable, C.Indices.Iterator.Element == C.Index {
    // ... Same body ...
}

在Swift 3中,关联类型不能对它们施加额外的约束。这似乎是一件小事,但它是巨大的。这意味着没有办法说Collection.Indices实际上包含Collection.Index。而且没有办法创建一个Collection.Element(由订阅集合返回的类型),它被承诺与Collection.Iterator.Element相同(迭代通过集合返回的类型)。这会导致很多C.Iterator...where条款显得像C.Indices.Iterator.Element == C.Index一样明显。

如果你接近Swift旅程的开始,我可能会完全跳过泛型,并以[Int]的形式写出来。 Swift程序员(新的和“旧的”)的一个常见错误是在他们需要它之前跳转到泛型编程。但是如果你正处于你正在处理泛型的阶段,那么你经常需要在Swift 3中编写它们。(Swift 4是对泛型编程的一个显着改进。)

答案 1 :(得分:1)

既然你想返回找到的元素的索引,你应该返回一个Ints数组而不是[U]。这是更新后的代码:

func findAll<T: Collection, U: Equatable>(_ items: T, _ find: U) 
  -> [Int] where T.Iterator.Element == U {

    var found = [Int]()
    for (index, item) in items.enumerated() { 
        if item == find {
            found.append(index)
        }
    }
    return found
}

这是一个测试用例:

let numbers = [1,0,1,0,0,1,1]
let indicesOfOnes = findAll(numbers, 1)
print(indicesOfOnes)

将打印:

[0, 2, 5, 6]