Why does reducing an array into a hash with the keys as array values and the value as nil give an empty result?
[1,2,3].reduce(into: [Int:Int?](), { $0[$1] = nil })
[1,2,3].reduce(into: [Int:Int?](), { $0[$1] = 1 })
Both of these should have 3 entries, right?
In Swift, setting a dictionary value to nil like you did in the question removes that value from the dictionary entirely. However there are some odd caveats to this.
In your example, you use a Dictionary with type [Int:Int?]:
var dictionary: [Int:Int?] = [1: 1, 2: 1, 3: 1]
Setting a value to nil in the following way removes it:
dictionary[1] = nil
Setting a value to nil with type Int? will assign nil instead:
dictionary[1] = nil as Int?
Likewise, using the updateValue(_:forKey:) function will do the same:
dictionary.updateValue(nil, forKey: 1)
Printing the dictionary after performing either of these operations would show the following:
[1: nil, 2: Optional(1), 3: Optional(1)]
So the behavior is a bit odd but it is possible to achieve your intended result. I don't really recommend it because of how unnatural it is; it is possible, though.
The behaviour is not at all odd actually, and it's a really natural outcome if you understand some fundamental features of Swift's dictionaries and optionals.
Nested optionals
Firstly, optionals are composable. You can have nested optionals, like Optional<Optional<Int>> (a.k.a. Int??). A nested optional like this has possible three values. To illustrate their difference, imagine the result of array.first, where array has type Array<Optional<Optional<Int>>:
Optional.some(Optional.some(someWrappedValue)), which is just the "non-nil" case, with a payload of someWrappedValue. In the context of array.first, that means the array is non-empty, and the first element is non-nil value someWrappedValue (of type Int).
Optional.some(Optional.none), which is a non-nil optional containing a nil. In the context of array.first, that means ten array is non-empty, and the first element is itself nil.
Optional.none, which is just a nil optional. In the context of array.first, that means the array is empty, and there is no first element.
Note the different between case 2 and case 3. "Where" the nil is "found" along the chain has a semantic difference. Optional.none (a.k.a. nil) means there are no elements, which is semantically different to Optional.some(Optional.none), which means there are elements, but the first of them is actually nil.
The precise meaning of nil
Secondly, nil is a shorthand for Optional<T>.none, where T is inferred from the context. For example, that's why let x = nil is illegal, because the type inference system has no clues as to what value of T is appropriate.
Note that nil on its own is inferred to a none at the "earliest" ("outermost") level of optionality. (Case 3, in the list above)
Assign nil through a dictionary subscript
Thirdly, assigning nil to a Dictionary through a subscript erases the value for the given key.
Putting it together
Given these three points, we can consider your example. The subscript of a dictionary is "one layer more optional" than the Value generic type of the dictionary. E.g. for a dict of type [Int: Int], the return type of the subscript operator is V?, and correspondingly, you can assign Int?.
Now consider your dictionary, of type [Int: Int?]. The subscript takes/returns an Int??. Let's consider the three possible cases in context, as before:
Assigning a value of type Optional.some(Optional.some(someWrappedValue)) makes the dict store a value of someWrappedValue for the key, as an Int?. This is what's happening when you write $0[$1] = 1 (Swift is implicitly promoting 1 to Optional.some(Optional.some(1)).
Assigning a value of type Optional.some(Optional.none) makes the dict store a value of nil for the key, as an Int?. This is the part you were missing.
Assigning a value of type Optional.none makes the dict erase any value that might have existed for the key. This is what's happening when you write $0[$1] = nil
So to make this code give your designed outcome, you need to supply a value of the second type. You can do this with:
[1,2,3].reduce(into: [Int:Int?]()) { $0[$1] = Optional.some(nil) }
Related
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) }
I'm wondering about the reversed() method on a swift Array:
var items = ["a", "b", "c"]
items = items.reversed()
the signature of the reversed method from the Apple doc says that it returns a
ReversedRandomAccessCollection<Array<Element>>
could that be assigned back to items without doing what the apple doc say which is
For example, to get the reversed version of an array, initialize a new Array instance from the result of this reversed() method.
or would it give problem in the future? (since the compiler doesn't complain)
There are 3 overloads of reversed() for an Array in Swift 3:
Treating the Array as a RandomAccessCollection,func reversed() -> ReversedRandomAccessCollection<Self> (O(1))
Treating the Array as a BidirectionalCollection,func reversed() -> ReversedCollection<Self> (O(1))
Treating the Array as a Sequence,func reversed() -> [Self.Iterator.Element] (O(n))
By default, reversed() pick the RandomAccessCollection's overload and return a ReversedRandomAccessCollection. However, when you write
items = items.reversed()
you are forcing the RHS to return a type convertible to the LHS ([String]). Thus, only the 3rd overload that returns an array will be chosen.
That overload will copy the whole sequence (thus O(n)), so there is no problem overwriting the original array.
Instead of items = items.reversed(), which creates a copy of the array, reverse that and copy it back, you could reach the same effect using the mutating function items.reverse(), which does the reversion in-place without copying the array twice.
I have to classes: FirstViewController and PlanGenerator.
PlanGenerator has a method called getPlanForIndex (day:Int) -> ([PlanElement], [PlanElement])?. It is returning two different Arrays, both containing Object of Plan Element.
getPlanForIndex (day:Int) -> ([PlanElement], [PlanElement])? {
// Do some work and fill arrays
return (firstArray, secondArray)
}
In the FirstViewController I've created two Properties:
let plan:[PlanElement] = [PlanElement]()
let planGen:PlanGenerator = PlanGenerator()
FirstViewControllerhas a method called:
func prepareView () {
plan = planGen.getPlanForIndex(dayIndex)
}
But when I want to call this, I get the error message:
Cannot assign to 'plan' in 'self'
First, your plan member is a constant (declared with let), and you're assigning to it outside of init. If you want to assign to it after initialization, it needs to be declared with var.
Also, your getPlanForIndex returns a tuple, and you're assigning it to a member of singular (not tuple) type. If you want to assign one part of a tuple only, do it like this:
(plan, _) = planGen.getPlanForIndex(dayIndex)
That assigns a tuple to a tuple, satisfying the compiler, but discards the second element.
If you instead want plan to be a tuple, you'll have to declare and initialize it as such. Looks from the comments like you figured out a way to do that, but here's a way with minimal typing (in either sense of the word):
var plan: ([PlanElement], [PlanElement]) = ([], [])
(Once you've declared the type of array, [] is understood to be an empty array of that type. Just var plan = ([], []) leaves the type ambiguous.)
When I try to use this method the compiler shows the next error: "Type 'UIView' does not conform to protocol 'IntegerLiteralConvertible'"
if find(_views, 1) {
}
That method signature is:
find(domain: C, value: C.Generator.Element) -> C.Index?
Where C is a typed array, C.Generator.Element is the type of the elements in that array, and C.Index? is an optional that will contain the index the element is found at, if found at all.
So the error you are getting is because it looking at the instances in your array UIView and trying to compare them to 1 which is an IntegerLiteral. And UIView is not IntegerLiteralConvertible because it would make no sense to convert a view to an integer.
So find will return the index where some instances can be found in an array of those instances.
var strings: [String] = ["A", "B", "C"]
find(strings, "C")! // 2
But you don't seem to want the index. if find(views, 1) seems to indicate to me that you want to check if index 1 exists in the array. If this is really what you want, you can do this very simply by checking the count.
if _views.count > 1 {
println("index 1 exists in this array")
}