I'd like to write an extension for Array which safely returns an unwrapped version of itself.
I can do it with a generic method like so:
func unwrapElements<T>(array: [T?]) -> [T] {
let filtered: [T?] = array.filter{ $0 != nil }
let unwrapped: [T] = filtered.map { $0! }
return unwrapped
}
And I can call it like this:
let sparseNames: [String?] = ["alice", "bob", nil, "doug", nil, nil, "george", "hubert"]
let names: [String] = unwrapElements(sparseNames)
where names ends up being ["alice", "bob", "doug", "george", "hubert"] and is safe to iterate and work with each element.
However, I want to call it like this:
let names = sparseNames.unwrapElements()
I've seen a few similar questions (like this one) but they don't address how to create the method as an extension.
(this is tagged with Xcode6.1 to denote the version of Swift I'm using)
Note: Swift 1.2 Beta 3 has introduced the flatMap function which helps with optionally chaining arrays. See this excellent blog post here
You can't do this right now in Swift. To add that function as an extension to Array, you'd have to mark somehow that it's only callable with certain kinds of arrays: those with optional values as the subtype. Unfortunately, you can't further specialize a generic type, so global functions are the only way possible.
This is the same reason Array has a sort method that takes a comparison function as a parameter, but it doesn't have a sort that "just works" if the array is full of Comparable members - to get that kind of function, you have to look at the top-level sort:
func sort<T : Comparable>(inout array: [T])
Have you tried using filter and map for that?
let array: [String?] = ["Hello", nil, "World"]
let unwrapped = array.map{$0 ?? nil}.filter{$0 != nil}.map{$0!}
println("unwrapped: \(unwrapped)")
// prints "unwrapped: [Hello, World]"
The first map uses the Nil Coalescing Operator to unwrap if possible. Although, I return nil regardless since the following filter removes all nil values. The last map does the actual unwrapping.
You can do this. Here is how:
extension Array {
func catOptionals<A>() -> [A] where Element == A? {
return self.flatMap{ $0 }
}
}
Related
This must be a really basic question. In languages like R you can take an array (swift syntax here)
let x = [1,2,3,4,5]
and extract multiple elements using an array of indices. That is I would like to be able to do something like say (now in a pseudo-Swift syntax because it does not parse)
x[[0,2,3]]
to get a return value of
[1,3,4]
but this does not work directly in Swift. Is there a standard way of doing this? I am currently using Swift4.
I'm not aware of anything built into the Swift Array class that does this.
One possible solution is to define an extension to Array that filters the array to only include the elements at the provided indices.
extension Array {
func elements(at indices: [Int]) -> Array<Element> {
return self.enumerated().filter { indices.contains($0.0) }.map { $0.1 }
}
}
Example usage:
let x = [1,2,3,4,5]
let res = x.elements(at: [0,2,3])
print(res)
Output:
[1, 3, 4]
I have a following code, which copies an array of Rider objects, and appends a new Rider object if it exists.
let riders:[Rider] = getRiders()
let newRider:Rider? = mayGetNewRider()
var ridersPlus = riders
if let rider = newRider {
ridersPlus.append(rider)
}
I am looking for a better (simpler and easier to read) way to write this logic, which also allows me to define ridersPlus as "let" variable.
I am looking for something like below (which is invalid, because I made up the ??? syntax, which produces an empty array of newRider is nil).
let riders:[Rider] = getRiders()
let newRider:Rider? = mayGetNewRider()
let ridersPlus = riders + [newRider???]
How about
let ridersPlus = riders + [newRider].compactMap {$0}
(Note that before Swift 4, compactMap would be called flatMap. You didn't say what Swift version you are using.)
You do it with map and the nil coalescing operator ??:
let ridersPlus = riders + (newRider.map {[$0]} ?? [])
map when called on an Optional value evaluates the given closure when the Optional instance is not nil, passing the unwrapped value as a parameter. If the Optional is nil, the result of the map is nil. Combining that with the nil coalescing operator, the resulting Optional array can be unwrapped or replaced with [] and then added to the riders array.
I have an Array of Image links -
let alamofireSource = [AlamofireSource(urlString: Img1!)!, AlamofireSource(urlString: Img2!)!,
AlamofireSource(urlString: Img3!)!, AlamofireSource(urlString: Img4!)!]
slideshow.setImageInputs(alamofireSource)
some posts have only one image or two or three, and so on. so, sometimes image 2 (for example) is nil, In that case, I don't want it to be added to the array, is that possible?
You can try ( Swift 4 )
let arr = [img1,img2].compactMap{$0}.map{AlamofireSource(urlString:$0)!}
or
let arr = alamofireSource.compactMap{$0}
for Swift 3
let arr = alamofireSource.flatMap{$0}
so, sometimes image 2 (for example) is nil, In that case, I don't want
it to be added to the array, is that possible?
Yes it is. Although I would go with Sh_Khan's suggestion to use the compactMap method to achieve it, but it would be useless for your current case:
Based on your code snippet, I'd assume that alamofireSource of type [AlamofireSource], but not [AlamofireSource?] and that's because you are forcibly unwrap its elements (by adding ! to each of its elements). So far alamofireSource doesn't contain nils (actually it could be more danger than just a declaration, your app might crash!)
So first of all, I would recommend to remove the ! from alamofireSource:
let alamofireSource = [AlamofireSource(urlString: Img1!),
AlamofireSource(urlString: Img2!),
AlamofireSource(urlString: Img3!),
AlamofireSource(urlString: Img4!)]
which means let it be as [AlamofireSource?], therefore you would gain the benefit of using compactMap(_:):
Returns an array containing the non-nil results of calling the given
transformation with each element of this sequence.
As:
let alamofireSourceWihoutNils = alamofireSource.compactMap { $0 }
Assuming you put your Optional url strings into an array, say urlStrings (of type [String?]), you can construct alamofireSource according to (Swift 4):
let alamofireSource = urlStrings.compactMap { $0.map(AlamofireSource.init) }
Which make use of the map(_:) operator of Optional and compactMap(_:) to unwrap the two-level optionality.
Details
Your example contains two levels of optionality:
The optional ImgX arguments of type String? - henceforth referred to and named as img1, ..., img4, as CapitalFirstLetter names are reserved for e.g. types, not type instances.
The failable initilizer init?(urlString: String, placeholder: UIImage? = nil) of AlamofireSource.
First of all, lets gather the optional image links (imgX) into an array
let urlStrings = [url1, url2, url3, url4] // [String?]
Swift 4
You can combine the map(_:) operator of Optional with compactMap(_:) to safely unwrap and make use of the .some entires of urlStrings, thereafter collect the successful invocations of the failable initializer of AlamofireSource:
let alamofireSource = urlStrings.compactMap { $0.map(AlamofireSource.init) }
// or, use a named closure argument
let alamofireSource = urlStrings.compactMap { str in str.map(AlamofireSource.init) }
Swift 3
If using Swift 3, replace the compactMap(_:) invocation above with flatMap(_:):
let alamofireSource = urlStrings.flatMap { $0.map(AlamofireSource.init) }
// or, use a named closure argument
let alamofireSource = urlStrings.flatMap { str in str.map(AlamofireSource.init) }
Say I have the following code.
let myArray = [1,4,5,8,9,13,14,15]
let numbers = [4,8,13,15]
let finalArray = myArray.map({id in numbers.first(where: {$0 == id})!})
But I get an error because sometimes numbers.first(where: {$0 == id}) returns nil sometimes and can't be unwrapped.
I know this isn't the best example but it's the simplest example to explain what I'm trying to do.
My goal in this example is to have finalArray be [4,8,13,15]. So IF numbers.first(where: {$0 == id}) is nil just skip that value.
Is this possible with map in Swift? Or does the returned array length have to equal the array we are running map on?
Quick note. My example is very simple and in practice my problem is more complicated. I have my reasons for wanting to use map because it is an easy way to get a new array based on another array.
To expand on Rashwan L's answer, I believe the Swift 4 version is as follows:
let finalArray = myArray.compactMap({ id in numbers.first(where: {$0 == id}) })
As #LeoDabus pointed out you should use flatMap instead of map in this situation. The reason is because flatMap can return nil since flatMap has the return type U? while map has the return type of U and canĀ“t handle nil.
So this line, can handle nil with flatMap:
let finalArray = myArray.flatMap({ id in numbers.first(where: {$0 == id}) })
How do I make a simple array of say 1000 floats? I have tried this:
var computeArray = Array<Float>(repeating: nil, count:1000)
and get
Type of expression is ambiguous without more context
I also tried this and got the same thing:
var computeArray = [Float](repeating: nil, count:1000)
It's so simple but I can't get it to work. These are basically the same as examples I've found online. Has something changed with the most recent Swift 4?
Try this.
var computeArray: Array<Float> = Array(repeating: 0, count: 1000)
or with nils
var computeArray: Array<Float?> = Array(repeating: nil, count: 1000)
Swift is a type-safe language. This essentially means that you can't store a value of some other type (here nil) in a variable/ constant of a particular type (here Float).
So, if you want to store nil values in an array, declare its element type as optional (here Float?).
var computeArray = [Float?](repeating: nil, count:1000)
or
var computeArray = Array<Float?>(repeating: nil, count:1000)