Swift extending Array of specific type - arrays

I'm trying to extend arrays that have dictionaries as it's elements, where the dictionaries' values are comparable. It's basically like an array of custom objects and sorting them by specific property values, only in this case, it's dictionaries, not class instances.
One could see the data structure as Array<Dictionary<Key: Hashable, Value: Comparable>>, although the previous code is unsupported..
I think it comes close to working, but fails for various reasons.
My first attempts were
extension ArrayLiteralConvertible where Element == Dictionary<String, Comparable> {
func sortByKey<T: Comparable>(key: String, ascending: Bool) -> [[String: T]]? {
if let dictType = self as? [[String: T]] {
return ascending ? dictType.sort { return $0[key] < $1[key] } : dictType.sort { return $0[key] > $1[key] }
} else {
return nil
}
}
}
This works in the declaration, but when in use, it throws the error
Dictionary is not convertible to '[String: Int]'
Another attempt:
extension SequenceType where Generator.Element == [String: AnyObject] {
func sortByKey<T: Comparable>(key: String, ascending: Bool) -> [[String: T]]? {
if let dictType = self as? [[String: T]] {
return ascending ? dictType.sort { return $0[key] < $1[key] } : dictType.sort { return $0[key] > $1[key] }
} else {
return nil
}
}
}
gives back
Argument type "[[String: _]]?" does not conform to expected type 'Any' (aka 'protocol<>')
How could I make this work?
ADDED
I made-do with the following, but it's constrained to a specific type, so I have to declare separately for other types like String..
extension ArrayLiteralConvertible where Element == [String: Int] {
func sortByKey(key: String, ascending: Bool) -> [[String: Int]] {
let arraySelf = self as! [[String: Int]]
return ascending ? arraySelf.sort { return $0[key] < $1[key] } : arraySelf.sort { return $0[key] > $1[key] }
}
}
Another alternative:
func sortArray<T: Comparable>(array: [[String: T]], byKey key: String, ascending: Bool) -> [[String: T]] {
return ascending ? array.sort { return $0[key] < $1[key] } : array.sort { return $0[key] > $1[key] }
}

I think you did pretty well. I don't know if you'll like this any better:
func sortedArrayOfDictionaries<T:Comparable>(arr:[[NSObject:T]], byKey key:NSObject) -> [[NSObject:T]] {
return arr.sort({ (d1, d2) -> Bool in
return d1[key] < d2[key]
})
}
To use it, you must cast your initial array so that the key is of type NSObject:
let arr : [[NSObject:Int]] = [["hey":2], ["hey":1]]
let arr2 = sortedArrayOfDictionaries(arr, byKey: "hey")
I arrived at that independently, but on inspection it's not significantly different from your second solution. The only difference, really, is that we are not restricted to a string key.

Related

Swift return object of type from an array containing class elements

Here is a small code I wrote to explain the problem:
class Vehicle{
var name:String = ""
var tyres: Int = 0
}
class Bus:Vehicle{
var make:String = "Leyland"
}
class Car: Vehicle{
var model:String = "Polo"
}
let myVehicles:[Vehicle] = [
Vehicle(),
Car(),
Bus()
]
for aVehicle in myVehicles{
if(aVehicle is Bus){
print("Bus found")
}
}
From the code, I can loop through and get the object of type Bus. However, I need a function to do the same thing and return the element of that type if available. I tried using generics but it does not work. I will need something like this:
func getVehicle(type:T.type)->T?{
// loop through the array, find if the object is of the given type.
// Return that type object.
}
Use foo as? T to try to cast foo as type T.
for aVehicle in myVehicles{
if let bus = aVehicle as? Bus {
print("Bus found", bus.make)
}
}
Your getVehicle can thus be written as:
func getVehicle<T>() -> T? {
for aVehicle in myVehicles {
if let v = aVehicle as? T {
return v
}
}
return nil
}
let bus: Bus? = getVehicle()
or functionally:
func getVehicle<T>() -> T? {
return myVehicles.lazy.flatMap { $0 as? T }.first
}
let bus: Bus? = getVehicle()
(Note that we need to specify the returned variable as Bus? so getVehicle can infer the T.)
You can write it like this :
func getVehicle<T>(type:T)-> [T]{
return myVehicles.filter{ $0 is T }.map{$0 as! T }
}
You can also use this:
func getVehicle<T>(type: T.Type) -> T? {
return myVehicles.filter { type(of: $0) == type }.first as? T
}
Usage:
getVehicle(type: Bus.self)
You can also use this:
let a = array.compactMap({ $0 as? MyTypeClass })
// a == [MyTypeClass] no optional

Sorting the [Any] array

Given an array defined as follow
let list: [Any]
I want to sort it WHEN
all the values inside it have the same type Element
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?
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:
if you had some idea of the types you were comparing and define
them explicitly (update 1).
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
}
}
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
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()

Finding cgpoint in array based only on X

I have an array of CGPoints in Swift.
I'd like to find a point in the array using only the X value (they all have unique X values, so this shouldn't be a problem) and then get the Y value from that.
I also would like a way to only see if it contains a point with the X value that I'm looking for, like a .contains that only cares about the X.
You can use the indexOf: function to find the index of an item in an array using a predicate. From there, you can access that item with the index.
This actually answers both your questions, because it either returns the index of a CGPoint with your X-value, or it returns nil.
let index = pointArray.indexOf {
$0.x == xToFind
}
if let index = index {
let point = pointArray[index]
// Do something with the point that has the x-value you wanted
} else {
// There is no point in the array with the x-value you wanted
}
One overly complicated but sweet way to handle problems like this is with Protocols and Extensions.
If we make a XYType protocol that has a typealias Element and x and y properties of type Element we can extend SequenceType to encapsulate the filter and contains methods.
protocol XYType {
typealias Element
var x : Element { get set }
var y : Element { get set }
}
extension CGPoint : XYType {}
Now the real methods :
Extend SequenceType but use constraints. The Generator.Element should be an XYType and the Generator.Element.Element (the Element of the XYType) should be Equatable.
The actual filter function is a bit complicated. But essentially it uses the function it gets as a parameter includeElement: (Self.Generator.Element.Element) throws -> Bool and if it is true it appends to a copy. In the end it returns that copy.
extension SequenceType where Generator.Element : XYType, Generator.Element.Element : Equatable {
// this function just mimics the regular filter.
func filterByX(#noescape includeElement: (Self.Generator.Element.Element) throws -> Bool) rethrows -> [Self.Generator.Element] {
var copy : [Generator.Element] = []
for element in self where try includeElement(element.x) {
do {
let include = try includeElement(element.x)
if include {
copy.append(element)
}
} catch {
continue
}
}
return copy
}
func filterByY(#noescape includeElement: (Self.Generator.Element.Element) throws -> Bool) rethrows -> [Self.Generator.Element] {
var copy : [Generator.Element] = []
for element in self where try includeElement(element.y) {
do {
let include = try includeElement(element.y)
if include {
copy.append(element)
}
} catch {
continue
}
}
return copy
}
}
Do the same for contains
extension SequenceType where Generator.Element : XYType, Generator.Element.Element : Equatable {
func containsX(#noescape predicate: (Self.Generator.Element.Element) throws -> Bool) rethrows -> Bool {
for element in self {
do {
let result = try predicate(element.x)
if result {
return true
}
} catch {
continue
}
}
return false
}
func containsY(#noescape predicate: (Self.Generator.Element.Element) throws -> Bool) rethrows -> Bool {
for element in self {
do {
let result = try predicate(element.y)
if result {
return true
}
} catch {
continue
}
}
return false
}
}
Tests :
let points = [CGPoint(x: 1, y: 1),CGPoint(x: 2, y: 2),CGPoint(x: 3, y: 3),CGPoint(x: 4, y: 4)]
// CGPoint(2,2)
points.filterByY { (y) -> Bool in
y == 2
}
// false
points.containsX { (x) -> Bool in
x == 5
}
P.S. :
you can also do this, it is a little bit shorter :
let filtered = points.filter {
$0.x == 2
}
let contains = points.contains {
$0.x == 3
}

What type should you use when you want to use index.advanceBy?

I wanted to find some objects near another object in an array. I thought I could write an extension method like this, but I get this error:
// Error: Cannot invoke 'advanceBy' with an argument list of type '(Int)'
The Int type is obviously wrong, but the indexOf method takes a Self.Distance argument and I'm not sure how to use that as a parameter type.
extension CollectionType where Generator.Element : Equatable {
func objectNear(object: Self.Generator.Element, indexModifier: Int) -> Self.Generator.Element? {
if let index = self.indexOf(object) {
let newIndex = index.advancedBy(indexModifier) // this doesn't work
//let newIndex = index.advancedBy(1) // but this this works
if self.indices.contains(newIndex) {
return self[newIndex]
}
}
return nil
}
}
(If there is a more Swifty approach I'd be happy to hear it, but I'd like to understand the above in any case.)
CollectionType has the method
public func indexOf(element: Self.Generator.Element) -> Self.Index?
and conforms to
public protocol Indexable {
typealias Index : ForwardIndexType
// ...
}
Finally, ForwardIndexType has the method
public func advancedBy(n: Self.Distance) -> Self
Therefore the correct type is Index.Distance:
func objectNear(object: Self.Generator.Element, indexModifier: Index.Distance) -> Self.Generator.Element? { ... }
But note that advancing an index beyond endIndex can crash,
e.g. for character collections:
let c = "abc".characters
print(c.objectNear("b", indexModifier: 1)) // Optional("c")
print(c.objectNear("b", indexModifier: 2)) // nil
print(c.objectNear("b", indexModifier: 3)) // fatal error: can not increment endIndex
A safe variant is:
func objectNear(object: Generator.Element, indexModifier: Index.Distance) -> Generator.Element? {
if let index = indexOf(object) {
if indexModifier > 0 && index.distanceTo(endIndex) <= indexModifier {
return nil
}
if indexModifier < 0 && startIndex.distanceTo(index) < -indexModifier {
return nil
}
return self[index.advancedBy(indexModifier)]
}
return nil
}
Alternatively, if you need the method only for collections which
are indexed by an Int (such as Array) then you can define
extension CollectionType where Generator.Element : Equatable, Index == Int {
func objectNear(object: Generator.Element, indexModifier: Int) -> Generator.Element? {
if let index = self.indexOf(object) {
let newIndex = index + indexModifier
if indices.contains(newIndex) {
return self[newIndex]
}
}
return nil
}
}

Swift 2.1 Array Extension objectsAtIndexes

I'm trying to extend an array to return only objects at certain indexes. The map function seemed to be the best choice for me here.
extension Array {
func objectsAtIndexes(indexes: [Int]) -> [Element?]? {
let elements: [Element?] = indexes.map{ (idx) in
if idx < self.count {
return self[idx]
}
return nil
}.filter { $0 != nil }
return elements
}
}
let arr = ["1", "2", "3", "4", "5"]
let idx = [1,3,5]
let x = arr.objectsAtIndexes(idx) //returns: [{Some "2"}, {Some "4"}]
I'm getting a EXC_BAD_INSTRUCTION error when I try to cast the result to a string array:
let x = arr.objectsAtIndexes(idx) as? [String]
Is there any way I can return an array of non-optionals? I've tried to return [Element]? from the extension function.
This throws the same error.
The following code solves the problem for Swift2.1 using the flatmap function.
extension Array {
func objectsAtIndexes(indexes: [Int]) -> [Element] {
let elements: [Element] = indexes.map{ (idx) in
if idx < self.count {
return self[idx]
}
return nil
}.flatMap{ $0 }
return elements
}
}

Resources