我有一组类型的实例Thingie,我想提供在Thingie的任何属性上排序的Thingies数组.例如,某些属性是Int,而其他属性是String,可能还有其他属性.所以我想创建一个排序例程,接受一个字符串作为属性的名称,并比较两个东西的两个属性来确定顺序.
对于仿制药而言,这似乎是一项工作,而且我已经接近了,但是有一个漏洞.
这就是我现在所处的位置:
func compare<T:Comparable>(lft: T, _ rgt: T) -> Bool {
return lft < rgt
}
func orderBy(sortField: String) -> [Thingie] {
let allArray = (self.thingies as NSSet).allObjects as! [Thingie]
//typealias T = the type of allArray[0][sortField]
// or maybe create an alias that conforms to a protocol:
//typealias T:Comparable = ?
return allArray.sort({(a, b) -> Bool in
return self.compare(a[sortField] as! T, b[sortField] as! T)
})
}
Run Code Online (Sandbox Code Playgroud)
我使用泛型创建了一个比较函数,并在我的排序例程中调用它.捕获AnyObject!对于我的泛型不起作用,因此我需要转换从a[sortField]和返回的值b[sortField]相同的类型.只要编译器很高兴两个值都属于同一类型并且它实现了Comparable协议,它甚至不重要.
我认为一个typealias可以做到这一点,但也许有更好的方法?
附带问题:肯定有一种更好的方法可以从集合中创建初始的,未排序的数组而不需要使用NSSet.欢迎一点点暗示.[解决了那一点!谢谢,Oliver Atkinson!]
这里有一大堆代码可以粘贴到游乐场.它在orderBy实现上有三次尝试,每次尝试都有问题.
//: Playground - noun: a place where people can play
import Foundation
class Thingie: Hashable {
var data: [String: AnyObject]
var hashValue: Int
init(data: [String: AnyObject]) {
self.data = data
self.hashValue = (data["id"])!.hashValue
}
subscript(propName: String) -> AnyObject! {
return self.data[propName]
}
}
func ==(lhs: Thingie, rhs: Thingie) -> Bool {
return lhs.hashValue == rhs.hashValue
}
var thingies: Set = Set<Thingie>()
thingies.insert(Thingie(data: ["id": 2, "description": "two"]));
thingies.insert(Thingie(data: ["id": 11, "description": "eleven"]));
// attempt 1
// won't compile because '<' won't work when type is ambiguous e.g., AnyObject
func orderByField1(sortField: String) -> [Thingie] {
return thingies.sort { $0[sortField] < $1[sortField] }
}
// compare function that promises the compiler that the operands for < will be of the same type:
func compare<T:Comparable>(lft: T, _ rgt: T) -> Bool {
return lft < rgt
}
// attempt 2
// This compiles but will bomb at runtime if Thingie[sortField] is not a string
func orderByField2(sortField: String) -> [Thingie] {
return thingies.sort { compare($0[sortField] as! String, $1[sortField] as! String) }
}
// attempt 3
// Something like this would be ideal, but protocol Comparable can't be used like this.
// I suspect the underlying reason that Comparable can't be used as a type is the same thing preventing me from making this work.
func orderByField3(sortField: String) -> [Thingie] {
return thingies.sort { compare($0[sortField] as! Comparable, $1[sortField] as! Comparable) }
}
// tests - can't run until a compiling candidate is written, of course
// should return array with thingie id=2 first:
var thingieList: Array = orderByField2("id");
print(thingieList[0]["id"])
// should return array with thingie id=11 first:
var thingieList2: Array = orderByField2("description");
print(thingieList2[0]["id"])
Run Code Online (Sandbox Code Playgroud)
我之前的答案虽然有效,但没有充分利用 Swift 优秀的类型检查器。它还在可在一个集中位置使用的类型之间进行切换,这限制了框架所有者的可扩展性。
以下方法可以解决这些问题。(请原谅我不忍心删除我之前的答案;让我们说它的局限性是有启发性的......)
和以前一样,我们将从目标 API 开始:
struct Thing : ThingType {
let properties: [String:Sortable]
subscript(key: String) -> Sortable? {
return properties[key]
}
}
let data: [[String:Sortable]] = [
["id": 1, "description": "one"],
["id": 2, "description": "two"],
["id": 3, "description": "three"],
["id": 4, "description": "four"],
["id": 4, "description": "four"]
]
var things = data.map(Thing.init)
things.sortInPlaceBy("id")
things
.map{ $0["id"]! } // [1, 2, 3, 4]
things.sortInPlaceBy("description")
things
.map{ $0["description"]! } // ["four", "one", "three", "two"]
Run Code Online (Sandbox Code Playgroud)
为了实现这一点,我们必须有这个ThingType协议和可变集合的扩展(它适用于集合和数组):
protocol ThingType {
subscript(_: String) -> Sortable? { get }
}
extension MutableCollectionType
where Index : RandomAccessIndexType, Generator.Element : ThingType
{
mutating func sortInPlaceBy(key: String, ascending: Bool = true) {
sortInPlace {
guard let lhs = $0[key], let rhs = $1[key] else {
return false // TODO: nil handling
}
guard let b = (try? lhs.isOrderedBefore(rhs, ascending: ascending)) else {
return false // TODO: handle SortableError
}
return b
}
}
}
Run Code Online (Sandbox Code Playgroud)
显然,整个想法都围绕着这个Sortable协议:
protocol Sortable {
func isOrderedBefore(_: Sortable, ascending: Bool) throws -> Bool
}
Run Code Online (Sandbox Code Playgroud)
...我们想要使用的任何类型都可以独立地遵守它:
import Foundation
extension NSNumber : Sortable {
func isOrderedBefore(other: Sortable, ascending: Bool) throws -> Bool {
try throwIfTypeNotEqualTo(other)
let f: (Double, Double) -> Bool = ascending ? (<) : (>)
return f(doubleValue, (other as! NSNumber).doubleValue)
}
}
extension NSString : Sortable {
func isOrderedBefore(other: Sortable, ascending: Bool) throws -> Bool {
try throwIfTypeNotEqualTo(other)
let f: (String, String) -> Bool = ascending ? (<) : (>)
return f(self as String, other as! String)
}
}
// TODO: make more types Sortable (including those that do not conform to NSObject or even AnyObject)!
Run Code Online (Sandbox Code Playgroud)
此throwIfTypeNotEqualTo方法只是以下方法的方便扩展Sortable:
enum SortableError : ErrorType {
case TypesNotEqual
}
extension Sortable {
func throwIfTypeNotEqualTo(other: Sortable) throws {
guard other.dynamicType == self.dynamicType else {
throw SortableError.TypesNotEqual
}
}
}
Run Code Online (Sandbox Code Playgroud)
就是这样。Sortable现在,我们甚至可以在框架之外使新类型一致,并且类型检查器[[String:Sortable]]在编译时验证我们的源数据。另外,如果Thing扩展为符合HashablethenSet<Thing>也可以按键排序......
请注意,虽然Sortable它本身不受约束(这很棒),但如果需要,可以使用以下协议将 sourcedata和Thing'sproperties限制为带有NSObject或值的字典:AnyObject
protocol SortableNSObjectType : Sortable, NSObjectProtocol { }
Run Code Online (Sandbox Code Playgroud)
...或更直接地通过将dataandThing声明properties为:
let _: [String : protocol<Sortable, NSObjectProtocol>]
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
761 次 |
| 最近记录: |