对任意类型进行Swift排序

Jer*_*rry 5 generics 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)
    })
}
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)

mil*_*los 2

我之前的答案虽然有效,但没有充分利用 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它本身不受约束(这很棒),但如果需要,可以使用以下协议将 sourcedataThing'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)