对任意类型进行Swift排序
我有一组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) }) } 我使用泛型创建了一个比较函数,并在我的排序例程中调用它.问题是AnyObject!不适用于我的泛型,所以我需要将[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"])
我之前的回答虽然有效,却没有充分利用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"],"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"] 为了实现这一点,我们必须拥有这个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 } } } 显然,整个想法围绕这个Sortable协议: protocol Sortable { func isOrderedBefore(_: Sortable,ascending: Bool) throws -> Bool } ……可以通过我们想要使用的任何类型独立地符合: 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)! 这个throwIfTypeNotEqualTo方法只是Sortable的便利扩展: enum SortableError : ErrorType { case TypesNotEqual } extension Sortable { func throwIfTypeNotEqualTo(other: Sortable) throws { guard other.dynamicType == self.dynamicType else { throw SortableError.TypesNotEqual } } } 就是这样.现在,即使在框架之外,我们也可以将新类型符合Sortable,并且类型检查器在编译时验证我们的[[String:Sortable]]源数据.此外,如果将Thing扩展为符合Hashable,则Set< Thing>也可按钥匙排序…… 请注意,虽然Sortable本身不受约束(这很棒),但是如果需要,可以使用以下协议将源数据和Thing的属性约束为具有NSObject或AnyObject值的字典: protocol SortableNSObjectType : Sortable,NSObjectProtocol { } …或者更直接地通过将数据和Thing的属性声明为: let _: [String : protocol<Sortable,NSObjectProtocol>] (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |