加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

对任意类型进行Swift排序

发布时间:2020-12-14 02:23:52 所属栏目:百科 来源:网络整理
导读:我有一组Thingie类型的实例,我想提供Thingie的任何属性排序的Thingies数组.例如,某些属性是Int,而其他属性是String,可能还有其他属性.所以我想创建一个排序例程,接受一个字符串作为属性的名称,并比较两个东西的两个属性来确定顺序. 对于仿制药而言,这似乎是
我有一组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>]

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读