1

我正在尝试将包含 KeyPath 和排序顺序类型的多个元组传递给应该进行排序的方法。

我有这个方法:

extension Array {
    mutating func sort<T: Comparable>(by criteria: (path: KeyPath<Element, T>, order:OrderType)...) {
        
        criteria.forEach { path, order in
            //...
            sort { first, second in
            order.makeComparator()(
                first[keyPath: path],
                second[keyPath: path]
            )
        }
        }
    }
}

我正在这样使用它:

var posts = BlogPost.examples
        
posts.sort(by:(path:\.pageViews, order: .asc), (path:\.sessionDuration, order: .desc))

现在,因为pageViewssessionDuration属性都是integers,这将起作用。

但是如果我想传递两个不同类型的属性(比如StringInt),我会收到这个错误:

Key path value type 'Int' cannot be converted to contextual type 'String'

这是其余的代码,但我想不是那么相关:

enum OrderType: String {
    case asc
    case desc
}

extension OrderType {
    func makeComparator<T: Comparable>() -> (T, T) -> Bool {
        switch self {
        case .asc:
            return (<)
        case .desc:
            return (>)
        }
    }
}

我应该如何定义排序方法以便它接受异构键路径?

4

1 回答 1

1

没有可变参数泛型(还),所以你需要写一个AnyComparable类型橡皮擦。这个改编自这里的这篇文章。

struct AnyComparable: Equatable, Comparable {
    private let lessThan: (Any) -> Bool
    private let value: Any
    private let equals: (Any) -> Bool

    public static func == (lhs: AnyComparable, rhs: AnyComparable) -> Bool {
        lhs.equals(rhs.value) || rhs.equals(lhs.value)
    }
    
    public init<C: Comparable>(_ value: C) {
        self.value = value
        self.equals = { $0 as? C == value }
        self.lessThan = { ($0 as? C).map { value < $0 } ?? false }
    }

    public static func < (lhs: AnyComparable, rhs: AnyComparable) -> Bool {
        lhs.lessThan(rhs.value) || (rhs != lhs && !rhs.lessThan(lhs.value))
    }
}

这样,您可以将sort方法签名编写为:

mutating func sort(by criteria: (path: KeyPath<Element, AnyComparable>, order:OrderType)...) {
    
    
}

为了让我们更容易传递带有类型AnyComparable的关键路径,我们可以做一个扩展:

extension Comparable {
    // this name might be too long, but I'm sure you can come up with a better name
    var anyComparable: AnyComparable {
        .init(self)
    }
}

现在我们可以这样做:

someArray.sort(by: (\.key1.anyComparable, .asc), (\.key2.anyComparable, .asc))
于 2022-01-07T12:18:05.310 回答