13

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?

Luca Angeletti
  • 58,465
  • 13
  • 121
  • 148
  • @shim: I have not found anything that works – Luca Angeletti Jul 01 '16 at 20:12
  • @appzYourLife so, what is type `Element`? – pacification Jul 01 '16 at 20:14
  • @pacification: Any type really. It just needs to be `Comparable`. The examples I posted should clarify the concept. – Luca Angeletti Jul 01 '16 at 20:15
  • Is there a subset of types which your array will actually contain? (e.g. will only contain strings, numbers, or bools)? Or no restriction whatsoever? – shim Jul 01 '16 at 20:27
  • @shim: No restrictions. – Luca Angeletti Jul 01 '16 at 20:37
  • 3
    The tricky part here is that Swift cannot know if the value "wrapped" by `Any` is comparable (see [e.g. this gist](https://gist.github.com/dfrib/01e4bf1020e2e39dbe9cfb14f4165656)) without _first performing an attempted type conversion to a concrete type_ and thereafter--given a successful comparison--checking whether that type is `Comparable` or not. This mean you could solve this sorting _if_ you at the same time provided the type "wrapped" by `Any`, but I presume this removes the whole purpose of the generic sorting as per above. – dfrib Jul 01 '16 at 20:57
  • @dfri: Yes, I tried to convert `list` into a `[Element]` using this `let casted = list.flatMap { $0 as? list.first!.dynamicType }` but the compiler doesn't like it – Luca Angeletti Jul 01 '16 at 21:00
  • 3
    I believe that this is a given limitation ("drawback") of the static typing/type safety that Swift is renowned. Possibly you could somewhat circumvent this using Obj-C techniques and runtime introspection, but if possible, I suspect it would limit you to checking `Comparable`-ity of reference types (by Obj-C KVO) by using an `NSObject` subclass wrapper class to `List`, but just reading this sentence again shines light of the messiness of that... :) – dfrib Jul 01 '16 at 21:04

3 Answers3

6

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
    }
}
JMI
  • 2,530
  • 15
  • 26
  • Aaahhh... Now I know whats going on. You meant runtime check, not compiler one. See my updated answer. – JMI Jul 13 '16 at 15:45
  • @JMI: the update doesn't compile. Anyway there are other `Comparable(s)` types that are not supported by your extension. It should work with any type. – Luca Angeletti Jul 13 '16 at 20:55
  • @appzYourLife Are you using swift 2.3 or 3? What kind of error did you get? – JMI Jul 13 '16 at 21:31
  • @JMI: Sorry I missed the extension to make Bool Comparable. But it looks like this code only works for array of String, Bool and Int. I need a solution for every array of `Comparable(s)` as stated in my question. However thanks for trying to find a solution. I appreciated that. – Luca Angeletti Jul 13 '16 at 21:49
  • @appzYourLife See my update 2. It is not possible in runtime... You will have to help to compiler somehow... – JMI Jul 13 '16 at 21:51
  • @JMI: I agree, I also believe it's not possible. – Luca Angeletti Jul 13 '16 at 21:52
5

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
  • It seems that we can check if dynamicType conforms to protocol comparable. – José Roberto Abreu Jul 01 '16 at 20:54
  • Ok, I have seen your update. Now we can know if all the values have the same type and are comparable. Good. But how do we actually compare them into the sort? – Luca Angeletti Jul 01 '16 at 21:03
  • The closure didn't work for me either. Mmm @dfri I also check is returning false for Comparable (the problem is in this line), I will check. – José Roberto Abreu Jul 01 '16 at 21:16
  • 1
    I change the return statement, I tested, now return the expected value. – José Roberto Abreu Jul 01 '16 at 21:27
  • 1
    Huh... when the type being compared is non-optional (i.e `self.first! is Comparable`), then the compiler gives an error that `Comparable` cannot be used as it has Self requirements. I wonder why it works with optionals? – Hamish Jul 01 '16 at 21:30
  • 1
    @originaluser2 Since `Comparable` is heterogenous, I would speculate that a value wrapped in an optional (`.Some` or `.None`) is treated as a generic, in which case it is allowed to type check with `is Comparable`, much like using `Comparable` as a type constraint (`..`), where as concrete unwrapped value is naturally not generic, hence yielding a compile time error. – dfrib Jul 01 '16 at 21:36
  • 1
    @dfri Hmm... although `is Comparable` in this case should really be comparing whether `Optional` *itself* is `Comparable` (which it isn't directly) – rather than simply if `T` is `Comparable`. Although that being said, this does appear to be exactly what it's doing, looks like a special case for `Optional` as I cannot reproduce this with other generic types. Very interesting. – Hamish Jul 01 '16 at 22:09
  • 1
    @originaluser2 Yeah, weird indeed. I summarized these points in [this gist](https://gist.github.com/dfrib/01e4bf1020e2e39dbe9cfb14f4165656), might as well post a question regarding this if I can't find some explanation for it somewhere. But now, sleep! – dfrib Jul 01 '16 at 22:28
  • 1
    @originaluser2 FYI: posted a question regarding this subject [here](http://stackoverflow.com/questions/38160003/type-check-operator-is-for-check-vs-homogenous-protocol-why-can-this-be-done); hopefully someone can shed some light on this. – dfrib Jul 02 '16 at 12:11
  • This won't work in general. You cannot just compare types for equality because it will completely break with subclasses. – Sulthan Jul 03 '16 at 14:18
0

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()
Luke Van In
  • 5,215
  • 2
  • 24
  • 45