我有一组类型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!
不适用于我的通用,因此我需要将从a[sortField]
和b[sortField]
返回的值转换为相同的类型。只要编译器很高兴两个值都属于同一类型并且它实现了Comparable
协议,它甚至不重要。
我认为一个typealias可以做到这一点,但也许有更好的方法?
附带问题:肯定有一种更好的方法可以从集合中创建初始的,未排序的数组,而无需借助NSSet。欢迎一点点暗示。 [解决了那一点!谢谢,奥利弗·阿特金森!]
这是一个可以粘贴到游乐场的大块代码。它在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"])
答案 0 :(得分:1)
我不知道Thingie
的实施情况,但也许您可以提供更多背景信息。
你可以选择这样的东西
func orderBy(sortField: String) -> [Thingie] {
return thingies.allObjects.map { $0 as! Thingie }.sort { $0[sortField] < $1[sortField] }
}
如果您可以提供游乐场示例,我可以提供进一步的帮助。
另外你为什么使用NSSet而不是swift Set?会给你你想要的东西
let thingies: Set = Set<Thingie>()
func orderBy(sortField: String) -> [Thingie] {
return thingies.sort { $0[sortField] < $1[sortField] }
}
编辑
麻烦在于快速的类型安全 - 它需要你知道你正在处理什么类型以便它可以正确编译 - 如果你想要订购字段时指定实际类型你可以得到它按预期工作。
func orderByField<T: Comparable>(sortField: String, type: T.Type) -> [Thingie] {
return thingies.sort { ($0[sortField] as? T) < ($1[sortField] as? T) }
}
var thingieList: Array = orderByField("id", type: Int.self);
print(thingieList[0]["id"])
var thingieList2: Array = orderByField("description", type: String.self);
print(thingieList2[0]["id"])
上面会打印2然后打印11 - 如果你想要解决这个问题,你可以将你的对象存储在不同的结构中,然后你就可以对这些事物进行排序&#39;关于变量。
e.g。
struct Thing {
let id: Int
let description: String
}
var data: [Thing] = [
Thing(id: 2, description: "two"),
Thing(id: 11, description: "eleven")
]
let first = data.sort { $0.id < $1.id }.first?.id
let second = data.sort { $0.description < $1.description }.first?.id
print(first)
print(second)
哪个会实现同样的目标--2和11
我建议不要在可能的情况下使用AnyObject,因为它试图欺骗编译器告诉它你不关心它的帮助。
虽然这是一个有趣的问题,但我希望这可以帮助您解决问题。
答案 1 :(得分:1)
我将从目标API开始(忽略与Hashable
的一致性,因为它的添加不会改变后面的内容)。所以,让我们说我们希望能够写下以下内容:
var thingies = [
["id": 1, "description": "one"],
["id": 2, "description": "two"],
["id": 3, "description": "three"],
["id": 4, "description": "four"]
].map(Thingie.init)
thingies.sortInPlace{ $0["id"] < $1["id"] }
......甚至:
thingies.sortInPlaceBy("id")
thingies
.map{ $0["id"]!.value } // [1, 2, 3, 4]
thingies.sortInPlaceBy("description")
thingies
.map{ $0["description"]!.value } // ["four", "one", "three", "two"]
显然,我们需要MutableCollectionType
协议的扩展:
protocol ThingieDatumSubscriptable {
subscript(_: String) -> ThingieDatum? { get }
}
extension Thingie : ThingieDatumSubscriptable {}
extension MutableCollectionType
where Index : RandomAccessIndexType, Generator.Element : ThingieDatumSubscriptable
{
mutating func sortInPlaceBy(datumName: String, ascending: Bool = true) {
let f: (ThingieDatum?, ThingieDatum?) -> Bool = ascending ? (<) : (>)
sortInPlace{ f($0[datumName], $1[datumName]) }
}
}
这个ThingieDatum
就像是:
import Foundation
struct ThingieDatum : Comparable {
let type: AnyObject.Type
let value: AnyObject
let name: String
init(keyValuePair: (String, AnyObject)) {
name = keyValuePair.0
value = keyValuePair.1
type = keyValuePair.1.dynamicType
}
}
......以及它与某种行人方式实施的Comparable
的一致性如下(除非我们引入更多协议):
func == (lhs: ThingieDatum, rhs: ThingieDatum) -> Bool {
guard lhs.name == rhs.name && lhs.type == rhs.type else {
return false
}
switch lhs.type {
// TODO: implement for other types
case is NSNumber.Type: return lhs.value as! NSNumber == rhs.value as! NSNumber
case is NSString.Type: return (lhs.value as! String) == (rhs.value as! String)
default: break
}
return false
}
func < (lhs: ThingieDatum, rhs: ThingieDatum) -> Bool {
assert(lhs.name == rhs.name && lhs.type == rhs.type)
switch lhs.type {
// TODO: implement for other types
case is NSNumber.Type: return (lhs.value as! NSNumber).doubleValue < (rhs.value as! NSNumber).doubleValue
case is NSString.Type: return (lhs.value as! String) < (rhs.value as! String)
default: break
}
return false
}
有了这样一个ThingieDatum
,我们终于可以找出Thingie
本身:
struct Thingie {
var data: [ThingieDatum]
init(_ data: [String: AnyObject]) {
self.data = data.map(ThingieDatum.init)
}
subscript(datumName: String) -> ThingieDatum? {
for datum in data where datum.name == datumName {
return datum
}
return nil
}
}
虽然这当然是一个有趣的练习,它确实有效(如果你能按照正确的片段顺序复制并粘贴到游乐场)......然而,要进一步理解这个想法,我们可能希望将ThingiDatum
初始化程序限制为自定义协议(而不是AnyObject
),这将保证可比性。然后,我们将使用我们想要使用的每种类型的协议,而不是switch
在一个集中的地方通过这些类型......
答案 2 :(得分:1)
我之前的回答虽然有效,却没有充分利用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"]
为了实现这一点,我们必须拥有这个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
本身不受约束(非常棒),但源data
和Thing
&#39; s properties
可以被NSObject
约束到词典{ {1}}或AnyObject
值,如果需要,可以使用如下协议:
protocol SortableNSObjectType : Sortable, NSObjectProtocol { }
...或更直接宣布data
和Thing
&#39; s properties
为:
let _: [String : protocol<Sortable, NSObjectProtocol>]