Why do two distinct array literals equal each other in Swift? - swift

Why does the expression
import Foundation
["a", "b", "c"] == ["c", "b", "a"]
evaluate to true in a Swift playground?
(The expression evaluates to false when Foundation is not imported.)

Josh's answer is close, but not quite right. Option-click on the equals operator. Your literals are Foundation.CharacterSets.
public static func == (lhs: CharacterSet, rhs: CharacterSet) -> Bool
For literal resolution, the compiler is going to search
The module you're working in.
Your imports.
The Swift standard library. (Which has some special module-scope-disambiguation rule where implicitly-typed literals are Arrays, because that makes working with the language easier for the most part.)
Is this a bug of ambiguity? Yes. Is it resolvable? I doubt it. I bet it's broken because nobody has been able to get the performance to be good enough if it did an exhaustive search. But please, log a bug, find out, and report back!

Swift.Set conforms to ExpressibleByArrayLiteral and it appears that when you compare two ArrayLiterals the compiler does in fact choose to evaluate them as Set's. you can confirm that its true by doing this:
import Foundation
let a = ["a", "b", "c"]
let b = ["c", "b", "a"]
print(["a", "b", "c"] == ["c", "b", "a"])
print(["a", "b", "c"] as Set<String> == ["c", "b", "a"] as Set<String>)
print(["a", "b", "c"] as [String] == ["c", "b", "a"] as [String])
print(a == b)
true
true
false
false
TLDR if you don't annotate the type of an array literal the compiler is free to coerce the type to any type that it chooses to infer as long as it conforms to ExpressibleByArrayLiteral.

Related

How do I duplicate the content of a list in swift

I can't seem to find the answer to this question (I keep getting answers for multiplying a list). I'm new to swift so bear with me.
I have a list say
a = ['a']
in python I can just do a*3 and then I end up with ['a','a','a'] however swift doesn't seem to like this. What is the correct syntax to get the same result in swift?
Note: Suppose that a always has a length of 1.
You can't multiply an array by an Int in Swift, but that isn't hard to add:
func *<T>(_ array: [T], _ count: Int) -> [T] {
return Array(Array(repeating: array, count: count).joined())
}
Examples:
let a = ["a", "b"]
let a3 = a * 3
print(a3)
["a", "b", "a", "b", "a", "b"]
print(["b"] * 5)
["b", "b", "b", "b", "b"]
For people who also have a similar problem, credit goes to matt for finding the solution. Below is the example in the documentation.
let fiveZs = Array(repeating: "Z", count: 5)
print(fiveZs)
// Prints "["Z", "Z", "Z", "Z", "Z"]"

Overloading function in extension produces errors

I tried to add append overload for Swift Array
import Foundation
extension Array {
func append<From>(from: [From], transformer: (From) -> [Element]) {
from.forEach {
self.append(contentsOf: transformer($0))
}
}
}
And it shows me compilation error: Error:(47, 24) extraneous argument label 'contentsOf:' in call
it looks like I can't use other overloads of append in my own overload. It's really strange. Can you help?
That error is misleading. Your append function needs to be marked as mutating:
extension Array {
mutating func append<From>(from: [From], transformer: (From) -> [Element]) {
from.forEach {
self.append(contentsOf: transformer($0))
}
}
}
Alternate Implementation
(This might not be what you need, but it could help another user.)
If you make your transformer to be (From) -> Element, you can simplify this to:
extension Array {
mutating func append<From>(from: [From], transformer: (From) -> Element) {
self.append(contentsOf: from.map(transformer))
}
}
Example call:
var strings = ["a", "b", "c"]
strings.append(from: [1, 2, 3], transformer: String.init)
print(strings) // prints ["a", "b", "c", "1", "2", "3"]

Unwrapping with flatMap

I want to get the weekday int of a date that I know exists like this:
let dayOfWeek = Calendar.current.dateComponents([.weekday], from: row.date).weekday
However, this returns an optional, and I'm trying to figure out how to avoid force unwrapping it.
My though was to do:
let dayOfWeek = (row.date).compactMap( { Calendar.current.dateComponents([.weekday], from: $0).weekday!
})
However this gives me the error "value of type 'Date' has no member 'compactMap'"
Can someone tell me what I'm doing wrong, or how I should go about fixing this?
No optionals are needed. It's simply:
let dayOfWeek = Calendar.current.component(.weekday, from: row.date)
Note, flatMap is not relevant here because row.date is not an optional. But even if it were, it's worth noting that the Optional method flatMap has not had its name changed. It's still flatMap. Only the Sequence method of this name has been changed to compactMap.
See Swift Evolution 0187: Introduce Sequence.compactMap(_:)
So, this is still flatMap:
let foo: Int? = 42
let bar: String? = foo.flatMap { i -> String in
return "The value is \(i)"
}
// Optional("The value is 42")
Note, the returned value is an optional String?. In your example, it looks like you're trying to use flatMap to unwrap your optional, but that's not what flatMap is for. It's for calling the closure if it can unwrap foo, but for returning nil if it can't unwrap it. So it just returns another optional (a String? in my above example).
The flatMap that has been renamed to compactMap in Swift 4.1 is the Sequence rendition:
let baz = ["1", "2", "x", "3"]
let qux: [Int] = baz.compactMap { string -> Int? in
return Int(string) // return integer value if it could convert it, return `nil` if not
}
// [1, 2, 3]
To make it even more confusing, there is still flatMap used with sequences:
let letters = ["a", "b", "c"]
let quux = letters.map { Array(repeating: $0, count: 3) }
// [["a", "a", "a"], ["b", "b", "b"], ["c", "c", "c"]]
let quuz = letters.flatMap { Array(repeating: $0, count: 3) }
// ["a", "a", "a", "b", "b", "b", "c", "c", "c"]
There is no need to avoid forced unwrapping in this case. While weekday is optional, it will never be nil when you specifically request the .weekday component.
let dayOfWeek = Calendar.current.dateComponents([.weekday], from: row.date).weekday!

Difference between flatMap and compactMap in Swift

It seems like in Swift 4.1 flatMap is deprecated. However there is a new method in Swift 4.1 compactMap which is doing the same thing?
With flatMap you can transform each object in a collection, then remove any items that were nil.
Like flatMap
let array = ["1", "2", nil]
array.flatMap { $0 } // will return "1", "2"
Like compactMap
let array = ["1", "2", nil]
array.compactMap { $0 } // will return "1", "2"
compactMap is doing the same thing.
What are the differences between these 2 methods? Why did Apple decide to rename the method?
The Swift standard library defines 3 overloads for flatMap function:
Sequence.flatMap<S>(_: (Element) -> S) -> [S.Element]
Optional.flatMap<U>(_: (Wrapped) -> U?) -> U?
Sequence.flatMap<U>(_: (Element) -> U?) -> [U]
The last overload function can be misused in two ways:
Consider the following struct and array:
struct Person {
var age: Int
var name: String
}
let people = [
Person(age: 21, name: "Osame"),
Person(age: 17, name: "Masoud"),
Person(age: 20, name: "Mehdi")
]
First Way: Additional Wrapping and Unwrapping:
If you needed to get an array of ages of persons included in people array you could use two functions :
let flatMappedAges = people.flatMap({$0.age}) // prints: [21, 17, 20]
let mappedAges = people.map({$0.age}) // prints: [21, 17, 20]
In this case the map function will do the job and there is no need to use flatMap, because both produce the same result. Besides, there is a useless wrapping and unwrapping process inside this use case of flatMap.(The closure parameter wraps its returned value with an Optional and the implementation of flatMap unwraps the Optional value before returning it)
Second Way - String conformance to Collection Protocol:
Think you need to get a list of persons' name from people array. You could use the following line :
let names = people.flatMap({$0.name})
If you were using a swift version prior to 4.0 you would get a transformed list of
["Osame", "Masoud", "Mehdi"]
but in newer versions String conforms to Collection protocol, So, your usage of flatMap() would match the first overload function instead of the third one and would give you a flattened result of your transformed values:
["O", "s", "a", "m", "e", "M", "a", "s", "o", "u", "d", "M", "e", "h", "d", "i"]
So, How did they solve it? They deprecated third overload of flatMap()
Because of these misuses, swift team has decided to deprecate the third overload to flatMap function. And their solution to the case where you need to to deal with Optionals so far was to introduce a new function called compactMap() which will give you the expected result.
There are three different variants of flatMap. The variant of Sequence.flatMap(_:) that accepts a closure returning an Optional value has been deprecated. Other variants of flatMap(_:) on both Sequence and Optional remain as is. The reason as explained in proposal document is because of the misuse.
Deprecated flatMap variant functionality is exactly the same under a new method compactMap.
See details here.
High-order function - is a function which operates by another function in arguments or/and returned. For example - sort, map, filter, reduce...
map vs compactMap vs flatMap
[RxJava Map vs FlatMap]
map - transform(Optional, Sequence, String)
flatMap - flat difficult structure into a single one(Optional, Collection)
compactMap - next step of flatMap. removes nil
flatMap vs compactMap
Before Swift v4.1 three realisations of flatMap had a place to be(without compactMap). That realisation were responsible for removing nil from a sequence. And it were more about map than flatMap
Experiments
//---------------------------------------
//map - for Optional, Sequence, String, Combine
//transform
//Optional
let mapOptional1: Int? = Optional(1).map { $0 } //Optional(1)
let mapOptional2: Int? = Optional(nil).map { $0 } //nil
let mapOptional3: Int?? = Optional(1).map { _ in nil } //Optional(nil)
let mapOptional4: Int?? = Optional(1).map { _ in Optional(nil) } //Optional(nil)
//collection
let mapCollection1: [Int] = [1, 2].map { $0 } //[1, 2]
let mapCollection2: [Int?] = [1, 2, nil, 4].map { $0 } //Optional(1), Optional(2), nil, Optional(4),
let mapCollection3: [Int?] = ["Hello", "1"].map { Int($0) } //[nil, Optional(1)]
//String
let mapString1: [Character] = "Alex".map { $0 } //["A", "l", "e", "x"]
//---------------------------------------
//flatMap - Optional, Collection, Combime
//Optional
let flatMapOptional1: Int? = Optional(1).flatMap { $0 } //Optional(1)
let flatMapOptional2: Int? = Optional(nil).flatMap { $0 } //nil
let flatMapOptional3: Int? = Optional(1).flatMap { _ in nil }
let flatMapOptional4: Int? = Optional(1).flatMap { _ in Optional(nil) }
//Collection
let flatMapCollection1: [Int] = [[1, 2], [3, 4]].flatMap { $0 } //[1, 2, 3, 4]
let flatMapCollection2: [[Int]] = [[1, 2], nil, [3, 4]].flatMap { $0 } //DEPRECATED(use compactMap): [[1, 2], [3, 4]]
let flatMapCollection3: [Int?] = [[1, nil, 2], [3, 4]].flatMap { $0 } //[Optional(1), nil, Optional(2), Optional(3), Optional(4)]
let flatMapCollection4: [Int] = [1, 2].flatMap { $0 } //DEPRECATED(use compactMap):[1, 2]
//---------------------------------------
//compactMap(one of flatMap before 4.1) - Array, Combine
//removes nil from the input array
//Collection
let compactMapCollection1: [Int] = [1, 2, nil, 4].compactMap { $0 } //[1, 2, 4]
let compactMapCollection2: [[Int]] = [[1, 2], nil, [3, 4]].compactMap { $0 } //[[1, 2], [3, 4]]
[Swift Optional map vs flatMap]
[Swift Functor, Applicative, Monad]

Sorting Swift Array according to dynamic list of attributes

I have a dynamic Array of properties for example :
var compareAttributes = ["name", "is_active", "age"]
The items and their count can always be changed.
I need to sort an array according to all the properties in this list (By order) all of them ACCEDING.
I tried the OBJ-C way using NSSortDiscrptors But since my Array contains [JSON] objects I can not convert it to NSArray :
class func sortServerObjectsByCompareAttributes( responseObjects: [JSON]) {
var sortDiscriptors = [NSSortDescriptor]()
for attribute in compareAttributes {
sortDiscriptors.append(NSSortDescriptor(key: attribute, ascending: true))
}
let sortedByAge = (responseObjects as NSArray).sortedArrayUsingDescriptors(sortDiscriptors) // error is [JSON] is not convertible to NSArray
}
I know how to sort this with 1 property :
responseObjects.sortInPlace({$0[compareAttribute] < $1[compareAttribute]})
what I need is a method like this :
fun sortArray(responseObjects: [JSON], sortProperties: [string]) {
//perform the sort
}
How can I achieve that in a swift way ?
Thanks
I'm not sure how far you want to go with this. Finding a Swifty way to do this is not at all easy, because Swift has strict typing and no introspection. Everything about implementing your own sort-by-sort-descriptor method in Objective-C is hard to do in Swift. NSSortDescriptor depends upon key-value coding, which is exactly what is missing from a JSON object because it is a Swift struct. What you need here is an NSObject derivative. If you had stayed with the built-in NSJSONSerialization, that is what you would now have, and the rest would be easy. But you threw that feature away when you went with SwiftJSON instead.
However, just as an example of how to do subsorting by "key" in a special case, let's imagine something like this:
protocol StringSubscriptable {
subscript(which:String) -> String {get}
}
struct Thing : StringSubscriptable {
var p1 : String
var p2 : String
var p3 : String
subscript(which:String) -> String {
switch which {
case "p1": return p1
case "p2": return p2
case "p3": return p3
default: return ""
}
}
}
You see what I've constructed here? It's a pure Swift struct, Thing, that has properties which I've made manually key-value-codable, as it were, by supplying a subscript method that fetches the String value of each of its properties based on the string name of the property.
The StringSubscriptable protocol is then merely a protocol-based way of guaranteeing that Thing has that ability.
Given all that, we can extend Array to do what you're asking for:
extension Array where Element : StringSubscriptable {
mutating func sortByList(list:[String]) {
func sortHelper(a:StringSubscriptable, _ b:StringSubscriptable, _ ix:Int) -> Bool {
let key = list[ix]
if a[key] == b[key] && ix < list.count - 1 {
return sortHelper(a, b, ix + 1)
}
return a[key] < b[key]
}
self.sortInPlace { sortHelper($0, $1, 0) }
}
}
The idea is that you hand sortByList a list of keys and we sort an array of Thing (because it is a StringSubscriptable) based on those keys in order. For each pair, if the first key is enough to determine the outcome, we sort on it, but if the first key gives equal results for this pair, we use the second key instead (that is what subsorting is about).
To illustrate:
let t1 = Thing(p1: "y", p2: "ho", p3: "x")
let t2 = Thing(p1: "z", p2: "ho", p3: "x")
let t3 = Thing(p1: "z", p2: "ho", p3: "a")
var arr = [t1,t2,t3]
arr.sortByList(["p3", "p2", "p1"])
The outcome is
[
Thing(p1: "z", p2: "ho", p3: "a"),
Thing(p1: "y", p2: "ho", p3: "x"),
Thing(p1: "z", p2: "ho", p3: "x")
]
And if you think about it, that's the right answer. First we sort on "p3", so the Thing with p3 value "a" comes first. But for the other two, their p3 values are the same, so we fall back on their p3 values. They are the same as well, so we fall back on their p1 values, and "y" comes before "z".