Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sorting the [Any] array

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?

like image 590
Luca Angeletti Avatar asked Jul 01 '16 20:07

Luca Angeletti


People also ask

What does it mean to sort an array?

Sorting an array means to arrange the elements in the array in a certain order. Various algorithms have been designed that sort the array using different methods. Some of these sorts are more useful than the others in certain situations.

How do you sort an array alphabetically in Java?

Just like numeric arrays, you can also sort string array using the sort function. When you pass the string array, the array is sorted in ascending alphabetical order. To sort the array in descending alphabetical order, you should provide the Collections interface method reverseOrder () as the second argument.

How to sort the subarray of an array in Java?

To sort the subarray, the Arrays class provides the static method named sort (). It sorts the specified range of the array into ascending order. We can also sort the array of type long, double, float, char, byte, etc. a: An array to be sort.

How to sort a string array in ascending and descending order?

When you pass the string array, the array is sorted in ascending alphabetical order. To sort the array in descending alphabetical order, you should provide the Collections interface method reverseOrder () as the second argument. The following program demonstrates the sorting of a string array in ascending as well as descending order.


2 Answers

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
    }
}
like image 51
JMI Avatar answered Oct 04 '22 03:10

JMI


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
like image 42
José Roberto Abreu Avatar answered Oct 04 '22 04:10

José Roberto Abreu