对任意类型

时间:2015-12-03 19:53:43

标签: swift generics

我有一组类型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"])

3 个答案:

答案 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本身不受约束(非常棒),但源dataThing&#39; s properties可以被NSObject约束到词典{ {1}}或AnyObject值,如果需要,可以使用如下协议:

protocol SortableNSObjectType : Sortable, NSObjectProtocol { }

...或更直接宣布dataThing&#39; s properties为:

let _: [String : protocol<Sortable, NSObjectProtocol>]