How do I sort Swift objects by multiple criteria - sorting

I have a list of Swift objects that I'd like to get sorted by multiple criteria. The objects in the list are of type DateRange:
class DateRange {
var from: NSDate?
var to: NSDate?
}
The list contains many of these objects where some from or to fields are nil. I want to have this list sorted by:
First all objects that have dates
Then objects that have at least one date (either from or to)
And at the very end objects without any
The dates itself don't matter, just their existence. In Ruby I could do this (if the date is nil I set it to a very low date):
date_ranges.sort { |a, b|
[fix_nil(a.from), fix_nil(a.to)] <=> [fix_nil(b.from), fix_nil(b.to)]
}.reverse
def fix_nil(val)
val.nil? ? Date.new(0) : val
end
What's the best way to do this with Swift? Thanks in advance.

Seems like it might be a good idea to add a dateCount computed property to your DateRange type. This would be a good time for pattern matching:
extension DateRange {
// returns the number of non-nil NSDate members in 'from' and 'to'
var dateCount: Int {
switch (from, to) {
case (nil, nil): return 0
case (nil, _): return 1
case (_, nil): return 1
default: return 2
}
}
}
Then you can sort your list with a simple closure:
var ranges = [DateRange(nil, nil), DateRange(NSDate(), nil), DateRange(nil, NSDate()), DateRange(nil, nil), DateRange(NSDate(), NSDate())]
ranges.sort { $0.dateCount > $1.dateCount }
If you wanted, you could even make it Comparable with a few more lines:
extension DateRange : Comparable { }
func ==(lhs: DateRange, rhs: DateRange) -> Bool {
return lhs.dateCount == rhs.dateCount
}
func <(lhs: DateRange, rhs: DateRange) -> Bool {
return lhs.dateCount > rhs.dateCount
}
This lets you sort your list properly with an operator argument:
ranges.sort(<)

I presume that by list you mean array, so I am basing my answer on that assumption.
You can use the sort method of the array struct, which takes a closure having this signature:
(lhs: T, rhs: T) -> Bool
returning true if lhs is less than rhs, false otherwise.
I came up with this implementation:
var x: [DateRange]
// ... initialize the array
x.sort { (lhs: DateRange, rhs: DateRange) -> Bool in
if lhs.from != nil && lhs.to != nil {
return true
}
if lhs.from == nil && lhs.to == nil {
return false
}
return rhs.from == nil && rhs.to == nil
}
if lhs has both properties not nil, then it comes first, regardless of rhs
if lhs has both properties nil, then if comes after, regardless of rhs
else lhs has one nil, the other not nil, and in that case it comes first only if rhs has both properties nil
If you plan to reuse the sort in several places, it's better to move the code out of the sort method - the best place is probably an overload of the < operator:
func < (lhs: DateRange, rhs: DateRange) -> Bool {
if lhs.from != nil && lhs.to != nil {
return true
}
if lhs.from == nil && lhs.to == nil {
return false
}
return rhs.from == nil && rhs.to == nil
}
and in that case it can be used as follows:
x.sort(<)
If you don't like the operator overload, you can of course give that function any other name.
Note that sorting is done in place.

Here is how I would approach this. To keep things simple, add a scoring function for the date range. In your scenario, you have 3 possibilities:
nil & nil : 0 points
nil & date : 1 point
date & date : 2 points
import Foundation
class DateRange {
var from: NSDate?
var to: NSDate?
init(from: NSDate?, to: NSDate?)
{
self.from = from
self.to = to
}
func scoreDateRange() -> Int
{
var score = 0
if from != nil
{
score++
}
if to != nil
{
score++
}
return score
}
}
func sortDateRange( d1 : DateRange, d2 : DateRange)-> Bool
{
return d1.scoreDateRange() > d2.scoreDateRange()
}
var date_ranges = [DateRange]()
date_ranges.append(DateRange(from:nil, to:nil))
date_ranges.append(DateRange(from:nil, to:nil))
date_ranges.append(DateRange(from:NSDate(), to:NSDate()))
date_ranges.append(DateRange(from:nil, to:NSDate()))
date_ranges.append(DateRange(from:NSDate(), to:nil))
date_ranges.append(DateRange(from:NSDate(), to:NSDate()))
date_ranges.sort(sortDateRange)

Related

swift function or closure with pre-filled parameter

I have a class that takes a completion handler. Can I pre-fill some the parameters? The completion handler is a function itself rather than a closure.
func completionHandler(value: Int, value2: Int)
{
print(value + value2)
}
func run() {
let handler = completionHandler
handler(9, value2: 7) //runs the handler
someinstance.handler = handler //someinstance will eventually run the handler
let handler2 = completionHandler(9) //is this possible?
someinstance2.handler = handler2 //is this possible?
someinstance3.handler = { a,b in return a+b } //also fine
}
You could, technically, define a handler for you completion handler, for cases when you want to make use of a default value for value or value2 in completionHandler.
func completionHandler(value: Int, _ value2: Int) {
print(value + value2)
}
func defaultValueHandler(defaultValue: Int? = nil, defaultValue2: Int? = nil) -> ((Int) -> ()) {
if let defaultValue2 = defaultValue2 {
return { value in completionHandler(value, defaultValue2) }
}
else if let defaultValue = defaultValue {
return { value in completionHandler(defaultValue, value) }
}
return { _ in print("Invalid use: supply a single non-nil default value.") }
}
var handler = defaultValueHandler(9) // default value for 'value'
handler(5) // calls foo(9, 5), prints 14
handler = defaultValueHandler(nil, defaultValue2: 11) // default value for 'value2'
handler(5) // calls foo(5, 11), prints 16
The use of this is, for your case, probably limited in practice. The handler closure instance above will be of type (Int) -> () as compared to e.g. completionHandler function which is of type (Int, Int) -> (). So if someinstance.handler (in your example) expects the latter, you'll be in trouble.
Now, you could modify defaultValueHandler(..) to return closures of type (Int, Int) -> (), but then you'll need to supply calls to the resulting handling closure with two arguments, where one argument will be ignored in favour of the default value you supplied when assigning a closure to the handler. Again, this technically possibly, but will most likely just confuse whomever codes, and lives near the subject of "troll coding" (e.g. not-so-appriciated 1st april insertion into the code of a colleague, "handler(2,2) == 4 // false ?!?"). Anyway:
func completionHandler(value: Int, _ value2: Int) {
print(value + value2)
}
func defaultValueHandler(defaultValue: Int? = nil, defaultValue2: Int? = nil) -> ((Int, Int) -> ()) {
if let defaultValue2 = defaultValue2 {
if defaultValue == nil {
return { value, _ in completionHandler(value, defaultValue2) }
}
}
else if let defaultValue = defaultValue {
return { _, value2 in completionHandler(defaultValue, value2) }
}
return { _ in print("Invalid use: supply a single non-nil default value.") }
}
var handler = defaultValueHandler(9)
handler(0,5) // ignores first parameter '0', calls foo(9, 5), prints 14
handler = defaultValueHandler(nil, defaultValue2: 11)
handler(5,0) // ignores second parameter '0', calls foo(5, 11), prints 16

Swift: filter protocol array by comparing types

(first post)
usually im able to find answers here or elsewhere but no luck this time =(
Question: in Swift, how do you filter an array that is of a protocol type by an implementing type supplied as a function parameter?
protocol Aprotocol {
var number:Int { get set }
}
class Aclass: Aprotocol {
var number = 1
}
class AnotherClass: Aprotocol {
var number = 1
}
var array:[Aprotocol] = [ Aclass(), AnotherClass(), Aclass() ]
func foo (parameter:Aprotocol) -> Int {
return array.filter({ /* p in p.self == parameter.self */ }).count
}
var bar:Aprotocol = // Aclass() or AnotherClass()
var result:Int = foo(bar) // should return 2 or 1, depending on bar type
maybe this is not the right approach at all?
thanks!
Here is what I think you want:
return array.filter { (element: Aprotocol) -> Bool in
element.dynamicType == parameter.dynamicType
}.count
But I recommend this, which does the same, but without the useless instance of Aclass() which is passed in the answer on the top. Also this way is faster:
func foo <T: Aprotocol>(type: T.Type) -> Int {
return array.filter { (element: Aprotocol) -> Bool in
element.dynamicType == type
}.count
}
var result:Int = foo(Aclass)
The dynamicType will return the Type of an instance
Very easy:
return array.filter({ parameter.number == $0.number }).count
Kametrixoms solution works (if you use "is T" instead of "== type") but in my case, since i didnt know which implementing class was going to call it, had to go with this solution:
protocol Aprotocol: AnyObject {
var number:Int { get set }
}
class func foo(parameter: AnyObject) -> Int {
return array.filter ({ (element: Aprotocol) -> Bool in
object_getClassName(element) == object_getClassName(parameter)
}).count
}

how to compare two optional NSArrays in Swift

Let's have two optional NSArrays. The goal is to check, if they are equal. My solution is
func isArrayEqualToArray(array1:NSArray?, array2:NSArray?) -> Bool {
let areBothEmpty:Bool = array1 == nil && array2 == nil
var areBothEqual:Bool
if !areBothEmpty && array2 != nil {
areBothEqual = array1?.isEqualToArray(array2!) ?? false
} else {
areBothEqual = false
}
let result = areBothEqual || areBothEmpty
return result
}
I feel that it is a little too verbose. It should be doable in a more concise and readable way. Does anyone have a better solution?
It is quite simple:
func isArrayEqualToArray(array1: NSArray?, array2: NSArray?) -> Bool {
return array1 == array2
}
does exactly what you want.
Why does it work? Here == is the operator that compares optionals
func ==<T : Equatable>(lhs: T?, rhs: T?) -> Bool
and that gives true if both operands are nil, or of both
operands are non-nil and the unwrapped operands are equal.
Also NSArray inherits from NSObject which conforms to Equatable,
and comparing NSObjects with == uses the isEqual: method, which is
implemented as isEqualToArray: on NSArray.
Therefore
array1 == array2
gives the same result as
array1.isEqualToArray(array2)
Yes, indeed, you don't need to overcomplicate things:
func isArrayEqualToArray(array1: NSArray?, array2: NSArray?) -> Bool {
if array1 == nil && array2 == nil {
return true;
}
if array1 != nil && array2 != nil {
return array1!.isEqualToArray(array2!);
}
return false;
}
Using a switch statement may be clearer:
func isArrayEqualToArray(a1: NSArray?, a2: NSArray?) -> Bool {
switch (a1,a2) {
case (.Some(a1),.Some(a2)):
return a1.isEqualToArray(a2)
case (.None,.None):
return true
default:
return false
}
}
I liked #The Paramagnetic Croiss answer but I'm still going to shorten it a bit because I see that I can.
```
func isArrayEqualToArray(array1: NSArray?, array2: NSArray?) -> Bool {
if array1 == array2 {
return true;
}
if array1 != nil && array2 != nil {
return array1!.isEqualToArray(array2!);
}
return false;
}
```

debug vs release build?? xcode

Is there any difference between the debug and release build in terms of code / dependancies?
I recently started using test flight (this uses release builds) and have had nothing but crashes. It works perfectly when I build for debug.
Anyone had this problem?
At the moment I get a exc_breakpoint when the app hits "return true"
Which seems very weird. There is nothing to go wrong when all you have to do is return "true"
import UIKit
import CoreLocation
func calculateRelevance (album: AlbumLight , currentLocation: CLLocation, currentCity: String) -> Bool {
let fromLocationLa: CLLocationDegrees = CLLocationDegrees(album.la!)
let fromLocationLo: CLLocationDegrees = CLLocationDegrees(album.lo!)
var fromLocation: CLLocation = CLLocation(latitude: fromLocationLa, longitude: fromLocationLo)
let distance = fromLocation.distanceFromLocation(currentLocation)
if album.isActive == true {
if album.hasRange == true {
if distance < Double(album.range!) {
return true
}
else {
return false
}
}
else {
if currentCity == album.city {
return true
}
else {
return false
}
}
}
else {
return false
}
}
Update :
After a lot of trial and error, I found that adding a println() to get certain values prevents my bugs. For some reason something that isn't nil is turned into nil except for when I call println() just before using the value. Makes no sense to me....
As a guess, I think the optimizer is biting you. fromLocationLa, fromLocationLo, fromLocation, and distance are only used once. This means the following optimization can be done.
…
let fromLocationLa: CLLocationDegrees = CLLocationDegrees(album.la!)
let fromLocationLo: CLLocationDegrees = CLLocationDegrees(album.lo!)
var fromLocation: CLLocation = CLLocation(latitude: fromLocationLa, longitude: fromLocationLo)
let distance = fromLocation.distanceFromLocation(currentLocation)
if album.isActive == true {
if album.hasRange == true {
if distance < Double(album.range!) {
return true
}
else {
return false
}
}
…
optimized to
if album.isActive == true {
if album.hasRange == true {
return CLLocation(latitude: CLLocationDegrees(album.la!), longitude: CLLocationDegrees(album.lo!)).distanceFromLocation(currentLocation) < Double(album.range!)
}
This could explain the odd line number in the optimized code. It also put lots of implicit unwrapping on a single line.
Given the problems you seem to be having, a bit more care might be needed when unwrapping optionals.
let fromLocationLa: CLLocationDegrees? = album.la != nil ? CLLocationDegrees(album.la!) : nil
let fromLocationLo: CLLocationDegrees? = album.la != nil ? CLLocationDegrees(album.lo!) : nil
var fromLocation: CLLocation? = fromLocationLa != nil && fromLocationLo != nil ? CLLocation(latitude: fromLocationLa!, longitude: fromLocationLo!) : nil
let distance = fromLocation?.distanceFromLocation(currentLocation)
if album.isActive == true {
if album.hasRange == true {
if distance != nil && album.range != nil && distance! < Double(album.range!) {
return true
}
else {
return false
}
}

Swift, Equatable protocol bug?

I am building a very simple structure in Swift that contains an array of optional values. This struct must conform to the Equatable protocol. This is the code:
struct MyTable: Equatable {
var values: [Int?] = Array(count: 64, repeatedValue: nil)
}
func == (lhs: MyTable, rhs: MyTable) -> Bool {
return lhs.values == rhs.values
}
Quite simple. I see no mistakes, but the compiler gives error: "'[Int?]' is not convertible to 'MyTable'". Am I doing something stupid? or is this a compiler's bug? Thanks!
(Using Xcode6-Beta5)
The reason why it does not work is there is no == operator defined for arrays with optional elements, only for non-optional elements:
/// Returns true if these arrays contain the same elements.
func ==<T : Equatable>(lhs: [T], rhs: [T]) -> Bool
You can provide your own:
func ==<T : Equatable>(lhs: [T?], rhs: [T?]) -> Bool {
if lhs.count != rhs.count {
return false
}
for index in 0..<lhs.count {
if lhs[index] != rhs[index] {
return false
}
}
return true
}
Another useful option is to use the elementsEqual:isEquivalent: method available on SequenceType. This could allow you to avoid implementing Equatable, but is best used rarely as it is more verbose.
Usage:
let a: [Int?] = []
let b: [Int?] = []
if a.elementsEqual(b, isEquivalent: { $0 == $1 }) {
print("foo") // Works
}

Resources