繁体   English   中英

如何使用Swift KeyPaths的异构数组

[英]How to use heterogeneous array of Swift KeyPaths

看起来应该可以使用KeyPath数组作为排序键,使用任意数量的排序键对Swift结构数组进行排序。 从概念上讲,它很简单。 您将KeyPath的数组定义为通用对象,其中唯一的限制是keypath中的属性为Comparable

只要所有KeyPath都指向相同类型的属性,一切都很好。 但是,只要您尝试将指向不同类型的元素的KeyPaths用于数组,它就会停止工作。

请参阅下面的代码。 我创建了一个带有2个Int属性和double属性的简单结构。 我创建了一个实现函数sortedByKeypaths(_:) Array扩展。该函数指定了Comparable的泛型类型PROPERTY。 它需要一些kepath到一些对象Element,它指定了PROPERTY类型的属性。 (可比性质。)

只要您使用KeyPaths数组将该函数调用到所有相同类型的属性,它就可以完美地工作。

但是,如果您尝试将键路径数组传递给不同类型的属性,则会抛出错误“无法将类型'[PartialKeyPath]'的值转换为预期的参数类型'[KeyPath]'”

因为数组包含异构键路径,所以由于类型擦除,数组会转换为输入'[PartialKeyPath]`,并且不能使用PartialKeyPath从数组中获取元素。

有这个问题的解决方案吗? 无法使用各种各样的KeyPaths似乎严重限制了Swift KeyPath的实用性

import UIKit

struct Stuff {
    let value: Int
    let value2: Int
    let doubleValue: Double
}

extension Array {

    func sortedByKeypaths<PROPERTY: Comparable>(_ keypaths: [KeyPath<Element, PROPERTY>]) -> [Element] {
        return self.sorted { lhs, rhs in
            var keypaths = keypaths
            while !keypaths.isEmpty {
                let keypath = keypaths.removeFirst()
                if lhs[keyPath: keypath] != rhs[keyPath: keypath] {
                    return lhs[keyPath: keypath] < rhs[keyPath: keypath]
                }
            }
            return true
        }
    }
}

var stuff = [Stuff]()

for _ in 1...20 {
    stuff.append(Stuff(value: Int(arc4random_uniform(5)),
                       value2: Int(arc4random_uniform(5)),
                 doubleValue: Double(arc4random_uniform(10))))
}

let  sortedStuff = stuff.sortedByKeypaths([\Stuff.value, \Stuff.value2]) //This works
sortedStuff.forEach { print($0) }

let  moreSortedStuff = stuff.sortedByKeypaths([\Stuff.value, \Stuff.doubleValue]) //This throws a compiler error
moreSortedStuff.forEach { print($0) }

使用部分键路径数组的问题是您无法保证属性类型是Comparable 一种可能的解决方案是使用类型擦除包装器来擦除键路径的值类型,同时确保它是Comparable

struct PartialComparableKeyPath<Root> {

  private let _isEqual: (Root, Root) -> Bool
  private let _isLessThan: (Root, Root) -> Bool

  init<Value : Comparable>(_ keyPath: KeyPath<Root, Value>) {
    self._isEqual = { $0[keyPath: keyPath] == $1[keyPath: keyPath] }
    self._isLessThan = { $0[keyPath: keyPath] < $1[keyPath: keyPath] }
  }

  func isEqual(_ lhs: Root, _ rhs: Root) -> Bool {
    return _isEqual(lhs, rhs)
  }

  func isLessThan(_ lhs: Root, _ rhs: Root) -> Bool {
    return _isLessThan(lhs, rhs)
  }
}

然后你可以实现你的排序功能:

extension Sequence {

  func sorted(by keyPaths: PartialComparableKeyPath<Element>...) -> [Element] {
    return sorted { lhs, rhs in
      for keyPath in keyPaths {
        if !keyPath.isEqual(lhs, rhs) {
          return keyPath.isLessThan(lhs, rhs)
        }
      }
      return false
    }
  }
}

然后像这样使用:

struct Stuff {
  let value: Int
  let value2: Int
  let doubleValue: Double
}

var stuff = [Stuff]()

for _ in 1 ... 20 {
  stuff.append(Stuff(value: Int(arc4random_uniform(5)),
                     value2: Int(arc4random_uniform(5)),
                     doubleValue: Double(arc4random_uniform(10))))
}


let sortedStuff = stuff.sorted(by: PartialComparableKeyPath(\.value),
                                   PartialComparableKeyPath(\.value2))
sortedStuff.forEach { print($0) }

let moreSortedStuff = stuff.sorted(by: PartialComparableKeyPath(\.value),
                                       PartialComparableKeyPath(\.doubleValue))
moreSortedStuff.forEach { print($0) }

虽然不幸的是,这需要将每个单独的密钥路径包装在PartialComparableKeyPath值中,以便捕获和擦除密钥路径的值类型,这不是特别漂亮。

实际上我们需要的功能是可变参数泛型 ,它允许您为可变数量的通用占位符定义函数,用于键路径的值类型,每个都限制为Comparable

在此之前,另一种选择是为不同数量的密钥路径编写给定数量的重载以进行比较:

extension Sequence {
  func sorted<A : Comparable>(by keyPathA: KeyPath<Element, A>) -> [Element] {
    return sorted { lhs, rhs in
      lhs[keyPath: keyPathA] < rhs[keyPath: keyPathA]
    }
  }

  func sorted<A : Comparable, B : Comparable>
    (by keyPathA: KeyPath<Element, A>, _ keyPathB: KeyPath<Element, B>) -> [Element] {
    return sorted { lhs, rhs in
      (lhs[keyPath: keyPathA], lhs[keyPath: keyPathB]) <
        (rhs[keyPath: keyPathA], rhs[keyPath: keyPathB])
    }
  }

  func sorted<A : Comparable, B : Comparable, C : Comparable>
    (by keyPathA: KeyPath<Element, A>, _ keyPathB: KeyPath<Element, B>, _ keyPathC: KeyPath<Element, C>) -> [Element] {
    return sorted { lhs, rhs in
      (lhs[keyPath: keyPathA], lhs[keyPath: keyPathB], lhs[keyPath: keyPathC]) <
        (rhs[keyPath: keyPathA], rhs[keyPath: keyPathB], rhs[keyPath: keyPathC])
    }
  }

  func sorted<A : Comparable, B : Comparable, C : Comparable, D : Comparable>
    (by keyPathA: KeyPath<Element, A>, _ keyPathB: KeyPath<Element, B>, _ keyPathC: KeyPath<Element, C>, _ keyPathD: KeyPath<Element, D>) -> [Element] {
    return sorted { lhs, rhs in
      (lhs[keyPath: keyPathA], lhs[keyPath: keyPathB], lhs[keyPath: keyPathC], lhs[keyPath: keyPathD]) <
        (rhs[keyPath: keyPathA], rhs[keyPath: keyPathB], rhs[keyPath: keyPathC], rhs[keyPath: keyPathD])
    }
  }

  func sorted<A : Comparable, B : Comparable, C : Comparable, D : Comparable, E : Comparable>
    (by keyPathA: KeyPath<Element, A>, _ keyPathB: KeyPath<Element, B>, _ keyPathC: KeyPath<Element, C>, _ keyPathD: KeyPath<Element, D>, _ keyPathE: KeyPath<Element, E>) -> [Element] {
    return sorted { lhs, rhs in
      (lhs[keyPath: keyPathA], lhs[keyPath: keyPathB], lhs[keyPath: keyPathC], lhs[keyPath: keyPathD], lhs[keyPath: keyPathE]) <
        (rhs[keyPath: keyPathA], rhs[keyPath: keyPathB], rhs[keyPath: keyPathC], rhs[keyPath: keyPathD], rhs[keyPath: keyPathE])
    }
  }

  func sorted<A : Comparable, B : Comparable, C : Comparable, D : Comparable, E : Comparable, F : Comparable>
    (by keyPathA: KeyPath<Element, A>, _ keyPathB: KeyPath<Element, B>, _ keyPathC: KeyPath<Element, C>, _ keyPathD: KeyPath<Element, D>, _ keyPathE: KeyPath<Element, E>, _ keyPathF: KeyPath<Element, F>) -> [Element] {
    return sorted { lhs, rhs in
      (lhs[keyPath: keyPathA], lhs[keyPath: keyPathB], lhs[keyPath: keyPathC], lhs[keyPath: keyPathD], lhs[keyPath: keyPathE], lhs[keyPath: keyPathF]) <
        (rhs[keyPath: keyPathA], rhs[keyPath: keyPathB], rhs[keyPath: keyPathC], rhs[keyPath: keyPathD], rhs[keyPath: keyPathE], rhs[keyPath: keyPathF])
    }
  }
}

我已经将它们定义为最多6个密钥路径,这对于大多数排序情况应该足够了。 我们正在利用< here的词典元组比较重载,这也在这里展示。

虽然实现不是很好,但现在呼叫站点看起来好多了,因为它可以让你说:

let sortedStuff = stuff.sorted(by: \.value, \.value2)
sortedStuff.forEach { print($0) }

let moreSortedStuff = stuff.sorted(by: \.value, \.doubleValue)
moreSortedStuff.forEach { print($0) }

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM