-->

Sorting the [Any] array

2020-02-19 08:57发布

问题:

Given an array defined as follow

let list: [Any]

I want to sort it WHEN

  1. all the values inside it have the same type Element
  2. AND Element is Comparable.

When it should return the sorted array

So I would need a function that when the array is populated in a way like the followings

let list: [Any] = [10, 11, 0, 2, -1]
let list: [Any] = ["Red", "Green", "Blue"]
let list: [Any] = [true, false, true, true]

does return the sorted array.

When it should return nil

On the other hand when list contains one of the following examples

let list: [Any] = [CGPointZero, CGPoint(x:1, y:1)] // CGPoint is not comparable
let list: [Any] = [10, "Hello"] // Values of different types

I want nil as return value.

Any idea?

回答1:

Compile time solution

extension _ArrayType where Generator.Element == Any {
    func sortQ() -> Any? {
        return nil
    }
}

extension _ArrayType where Generator.Element: Comparable {
    func sortQ() -> [Self.Generator.Element] {
        return self.sort(<)
    }
}

// Because Bool is not comparable by default...
extension Bool: Comparable {
}

public func < (lhs: Bool, rhs: Bool) -> Bool {
    return !lhs && rhs // or Int(lhs) < Int(rhs)
}

[10, 11, 0, 2, -1].sortQ()               //[-1, 0, 2, 10, 11]
["Red", "Green", "Blue"].sortQ()         //["Blue", "Green", "Red"]
[true, false, true, true].sortQ()        //[false, true, true, true]
[CGPointZero, CGPoint(x:1, y:1)].sortQ() //nil
[10, "Hello"].sortQ()                    //nil

Runtime solutions:

UPDATE

Here is non final state. The problem is with casting to comparable. IMHO it is not possible. Until now I didn't know about trick with optional type. Anyway even casting of meta type is not possible because type is not known until runtime. My weak workaround is to list supported comparable types:

extension _ArrayType {

    func sortQ() -> [Generator.Element]? {
        var arrayOK = true
        let sortedArray = sort { (firstElement, secondElement) -> Bool in
            guard arrayOK else {
                return false
            }

            let f = Mirror(reflecting: firstElement)
            let s = Mirror(reflecting: secondElement)

            guard f.subjectType == s.subjectType else {
                arrayOK = false
                return false
            }

            switch String(f.subjectType) {
            case "Int":
                return (firstElement as! Int) < (secondElement as! Int)
            case "String":
                return (firstElement as! String) < (secondElement as! String)
            case "Bool":
                return (firstElement as! Bool) < (secondElement as! Bool)
            default:
                arrayOK = false
                return false
            }
        }
        return arrayOK ? sortedArray : nil
    }
}

UPDATE 2

The second option is to have comparable protocol defined differently (AnyComparable). Unfortunately it means to create extensions for all Comparable types. Otherwise there's no way, at compile-time, the compiler can find the correct function/operator (as it doesn't know the types ahead of time).

So you have two options:

  1. if you had some idea of the types you were comparing and define them explicitly (update 1).
  2. Use interface which does not use Self type (update 2).

IMHO there is no other solution

protocol AnyComparable {
    func compareTo(second: Any) -> Bool 
}

extension AnyComparable where Self: Comparable {
    func compareTo(second: Any) -> Bool {
        if let secondSameType = second as? Self {
            return self < secondSameType
        }

        return false
    }
}

extension Int: AnyComparable {
}

extension String: AnyComparable {
}

extension Bool: AnyComparable {
}

extension _ArrayType {

    func sortQ() -> [Generator.Element]? {

        var arrayOK = true
        var wantedType: Any.Type?

        let sortedArray = sort { (firstElement, secondElement) -> Bool in
            guard arrayOK else {
                return false
            }

            if wantedType == nil {
                wantedType = Mirror(reflecting: firstElement).subjectType
            }

            guard let f = firstElement as? AnyComparable where wantedType == Mirror(reflecting: secondElement).subjectType else {
                arrayOK = false
                return false
            }

            return f.compareTo(secondElement)
        }
        return arrayOK ? sortedArray : nil
    }
}


回答2:

For the moment, I wrote a little extension to check if all the elements are of the same type (I will be working on this to check if can get a result):

extension _ArrayType where Generator.Element == Any{

    func hasEqualTypeAndComparable()->Bool{

        if self.count > 0{
            let firstType = self.first?.dynamicType

            for val in self{
                if firstType != val.dynamicType{
                    return false
                }
            }

            return self.first is Comparable
        }

        return false
    }

}

Example:

//Example 1 
var values:[Any] = [2,1,4,3,"Hola"]
values.hasEqualTypeAndComparable() // Print false

//Example 2
var values:[Any] = [2,1,4,3]
values.hasEqualTypeAndComparable() // Prints true


回答3:

If your use case allows you to provide a hint to the compiler, you could specify a filter on the type of output that you want:

extension _ArrayType where Generator.Element == Any {

    func filterByType<T: Comparable>() -> [T] {

        var output = [T]()

        for i in self {
            if let j = i as? T {
                output.append(j)
            }
        }

        return output
    }
}

If the input array does not contain any elements of the specified type then it will just return an empty array. If the type is not a Comparable, then the code won't event compile.

Example:

let list: [Any] = [10, "Hello", 3, false, "Foo", "Bar", 1] // Values of different types

var output = list.filterByType() as [Int]
output.sortInPlace()


回答4:

extension Array{

func wtCompare()->[Any]?{
    if self.count>0 {
        let first = self.first.dynamicType

        for obj in self{
            if obj is Comparable {
                if first.dynamicType != obj.dynamicType {
                    return nil
                }
            }else{
                return nil
            }
        }
        let sorted = self.sorted { (a, b) -> Bool in
            return a < b
        }
        return sorted
    }else{
        return nil
    }
    return nil
}}