【问题标题】:Function accepting a generic Collection and returning an array of indices接受泛型集合并返回索引数组的函数
【发布时间】:2017-07-06 15:39:42
【问题描述】:

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

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
}

【问题讨论】:

  • 您将返回类型声明为泛型find 类型的数组,但您返回Int 的数组。这是典型的类型不匹配
  • @vadian 我在实验时忘记删除它。我用 find 替换了它,现在收到一个错误,我“无法使用类型为 '(Int)' 的参数列表调用 'append'”

标签: swift generics


【解决方案1】:

Ken 的回答很好,但是 Collection 可以有一个不是 Int 的索引。如果您尝试使用返回的索引,您将无法使用它们进行下标。

你想要[Self.Index],而不是[Int]。所以你想要更通用的zip,而不是enumerated

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.Iterator.Element (通过迭代集合返回的类型)相同的 Collection.Element(通过下标集合返回的类型)。这导致很多C.Iterator...where 子句看起来很明显,例如C.Indices.Iterator.Element == C.Index

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

【讨论】:

  • 有没有办法在没有扩展的情况下做到这一点?我正在学习的书还没有到那些。还有,我从来没见过 zip,真的很有趣!
  • 感谢您的更新!这有很大帮助!但是在使用您的最后一个示例“无法调用非函数类型'[T.Index.Type]'的值”时我仍然遇到错误
  • 哪一行?我无法重现那个。 (我假设您使用的是最新的带有 Swift 4 的 beta 版本,否则根本无法编译……)这种错误通常意味着您在某处创建了命名冲突;通常使用与变量或属性具有相同基本名称的函数。
  • 我没有运行 Beta,我想这有很大的不同。我正在运行 Xcode 8.3.3 并学习使用 Swift 3 的 Big Nerd Ranch Swift 书。imgur.com/a/GJzt4
【解决方案2】:

因为你想返回找到的元素的索引,你应该返回一个 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]

【讨论】:

  • 非常感谢。我想知道为什么作业(Gold)说要将返回类型从 Int 更改为其他类型? imgur.com/a/DlEX6
  • @CompEng 查看 Rob 的回答,它包含的内容比我的回答要多
猜你喜欢
  • 1970-01-01
  • 2015-11-11
  • 2012-02-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-09-19
  • 1970-01-01
  • 2018-06-12
相关资源
最近更新 更多