如何将一个返回一个符合Equatable的值的闭包传递给一个函数

时间:2017-04-26 16:40:55

标签: arrays swift protocols

我尝试创建Array的扩展名,该扩展名会根据应用了闭包的项目返回一个新的唯一项目数组。

例如:如果我有一个Apple数组,其中apple具有属性名称和来源,为了得到每个来源的一个Apple,我会调用apple.uniqued(on: { $0.origin })

这是我到目前为止的代码:

extension Array where Element: Equatable {
    func uniqued(on extract: (Element) -> Equatable) { // A
        let elementsAndValues = map { (item: $0, extracted: extract($0)) } // 1

        var uniqueValues: [Element] = []
        var uniqueExtracts: [Equatable] = [] // A

        elementsAndValues.forEach { (item, extracted) in
            if !uniqueExtracts.contains(extracted) { // 3, B
                uniqueValues += [item]
                uniqueExtracts += [extracted]
            }
        }

        return uniqueValues
    }
}

这应该如下工作:

  1. 创建一个元组数组,其中包含原始元素(item)和应用了闭包的元素(提取)
  2. 如果uniqueExtracts不包含该项,请添加该项并将该项添加到uniqueItems数组中。
  3. 我得到的错误是:

    A)"协议' SomeProtocol'只能用作通用约束,因为它具有Self或相关的类型要求" (两次)

    B)"缺少参数标签'其中:'在电话"

    我使用的是最新版本的Xcode。任何建议都会有很多帮助。非常感谢。

2 个答案:

答案 0 :(得分:5)

您有多个问题混合在一起以创建您所看到的错误。你应该做的是使用通用。

extension Array
{
    func uniqued<T:Equatable>(on extract:(Array.Element) -> T) -> [Array.Element]
    {
        let elementsAndValues = self.map{ (item: $0, extracted: extract($0)) }

        var uniqueValues:[Element] = []
        var uniqueExtracts:[T] = []

        for (item, extracted) in elementsAndValues
        {
            if !uniqueExtracts.contains(extracted)
            {
                uniqueValues.append(item)
                uniqueExtracts.append(extracted)
            }
        }

        return uniqueValues
    }
}

<T:Equatable>声明符合T的通用类型参数Equatable。然后函数签名可以期望一个闭包,它从尖括号中的类型约束返回一些我们知道符合T的泛型类型Equatable。您还必须将Equatable的每次出现更改为通用参数T,因为Equatable不是真实类型;见my answer here。如果你这样做,代码应该编译。

你还应该改变一些其他的事情:

  • 不使用elementsAndValues.forEach(:),而是使用for <pattern> in list {}循环。

  • 虽然这是有争议的,但在向数组添加一个元素时,您应该使用Array().append(:)方法而不是+=连接。在+=的情况下,与+相反,这纯粹是为了传达意图。

  • 您没有为函数声明返回类型,因此编译器假定它返回Void,因此return uniqueValues语句将导致编译器错误。在函数中添加-> [Array.Element]以解决此问题。

  • where Element:Equatable作为Array本身的约束是多余的。您正在使用键函数来确定均衡性,因此元素本身是否相等是无关紧要的。

  • 您可能希望使用Set或其他散列数据结构而不是uniqueExtracts数组。测试数组中的成员资格是O(n)操作。

答案 1 :(得分:1)

我会用group(by:)函数执行此操作,该函数将按给定键(例如origin)对序列中的每个元素进行分组,从而产生一个字典映射键到组(元素数组)群组)。从那里,我只是映射字典,只是得到每个组中的第一个元素。

public extension Sequence {
    public typealias Element = Iterator.Element
    public typealias Group = [Element]

    public func group<Key: Hashable>(by deriveKey: (Element) -> Key) -> [Key: Group] {
        var groups =  [Key: Group]()

        for element in self {
            let key = deriveKey(element)

            if var existingArray = groups[key] { // Group already exists for this key
                groups[key] = nil //performance optimisation to prevent CoW
                existingArray.append(element)
                groups[key] = existingArray
            }
            else {
                groups[key] = [element] // Create new group
            }
        }

        return groups
    }
}

struct Apple {
    let name: String
    let origin: String
}

let apples = [
    Apple(name: "Foo", origin: "Origin 1"),
    Apple(name: "Bar", origin: "Origin 1"),
    Apple(name: "Baz", origin: "Origin 2")
]

let firstAppleInEachOrigin = apples.group(by: {$0.origin}).flatMap{ _, group in group.first }


firstAppleInEachOrigin.forEach{ print($0) }