Map with Optional Unwrapping in Swift - swift

Say I had the below api :
func paths() -> [String?] {
return ["test", nil, "Two"]
}
And I was using this in a method where I needed [String], hence I had to unwrap it using the simple map function. I'm currently doing :
func cleanPaths() -> [String] {
return paths.map({$0 as! String})
}
Here, the force-cast will cause an error. So technically I need to unwrap the Strings in the paths array. I'm having some trouble doing this and seem to be getting silly errors. Can someone help me out here?

compactMap() can do this for you in one step:
let paths:[String?] = ["test", nil, "Two"]
let nonOptionals = paths.compactMap{$0}
nonOptionals will now be a String array containing ["test", "Two"].
Previously flatMap() was the proper solution, but has been deprecated for this purpose in Swift 4.1

You should filter first, and map next:
return paths.filter { $0 != .None }.map { $0 as! String }
but using flatMap as suggested by #BradLarson is a just better

Perhaps what you want to is a filter followed by a map:
func cleanPaths() -> [String] {
return paths()
.filter {$0 != nil}
.map {$0 as String!}
}
let x = cleanPaths()
println(x) // ["test", "two"]

let name = obj.value
name.map { name in print(name)}

Related

unable to infer complex closure return type

unable to build swift project because of this error.
// showing error with inputs.flatmap
fileprivate func makeShippingAddressDictWith(inputs: [TextFieldData]) -> [String: String] {
var shippingDict: [String: String] = [:]
let _ = inputs.flatMap { input in
if let shippingFieldType = input.type as? ShippingDictKeyable.Type {
shippingDict[shippingFieldType.shippingDictKey] = input.text
}
return nil
}
// FIXME: these empty values are the result of a poorly designed request in GDKECommerce
shippingDict["email"] = ""
shippingDict["second_name"] = ""
shippingDict["suffix"] = ""
shippingDict["title"] = ""
shippingDict["salutation"] = ""
shippingDict["company_name"] = ""
return shippingDict
}
}
You could use .forEach instead of .flatMap. Then you would not have to worry about a return type that you are ignoring anyway (with let _ =).
Combining this with a filter would produce a cleaner functional statement if that's what you're after:
inputs.map{ ( $0.text, $0.type as? ShippingDictKeyable.Type) }
.filter{ $1 != nil }
.forEach{ shippingDict[$1!.shippingDictKey] = $0 }
// FIXME: these empty values are the result of a poorly designed request in GDKECommerce
let blankAttributes = ["email", "second_name", "suffix", "title", "salutation", "company_name"]
blankAttributes.forEach{ shippingDict[$0] = "" }
Or use a for loop as suggested by Hamish.
If performance is a factor, the compiler will produce faster code with the for loop than with map/filter/forEach.
Note that, if you want to go crazy with functional style, Swift 4 will let you return the whole dictionary in a single line:
return [String:String]( uniqueKeysWithValues:
inputs.map{ ($0.type as? ShippingDictKeyable.Type, $0.text) }
.filter{ $0.0 != nil }
.map{($0!.shippingDictKey,$1)}
+ ["email", "second_name", "suffix", "title", "salutation", "company_name"]
.map{($0,"")}
)
This may only work in the playground though cause real projects tend to complain about expressions being too complex more often.

Swift String to Array

I have a string in Swift that looks like this:
["174580798","151240033","69753978","122754394","72373738","183135789","178841809","84104360","122823486","184553211","182415131","70707972"]
I need to convert it into an NSArray.
I've looked at other methods on SO but it is breaking each character into a separate array element, as opposed to breaking on the comma. See: Convert Swift string to array
I've tried to use the map() function, I've also tried various types of casting but nothing seems to come close.
Thanks in advance.
It's probably a JSON string so you can try this
let string = "[\"174580798\",\"151240033\",\"69753978\",\"122754394\",\"72373738\",\"183135789\",\"178841809\",\"84104360\",\"122823486\",\"184553211\",\"182415131\",\"70707972\"]"
let data = string.dataUsingEncoding(NSUTF8StringEncoding)!
let jsonArray = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions(), error: nil) as! [String]
as the type [String] is distinct you can cast it forced
Swift 3+:
let data = Data(string.utf8)
let jsonArray = try! JSONSerialization.jsonObject(with: data) as! [String]
The other two answers are working, although SwiftStudiers isn't the best regarding performance. vadian is right that your string most likely is JSON. Here I present another method which doesn't involve JSON parsing and one which is very fast:
import Foundation
let myString = "[\"174580798\",\"151240033\",\"69753978\",\"122754394\",\"72373738\",\"183135789\",\"178841809\",\"84104360\",\"122823486\",\"184553211\",\"182415131\",\"70707972\"]"
func toArray(var string: String) -> [String] {
string.removeRange(string.startIndex ..< advance(string.startIndex, 2)) // Remove first 2 chars
string.removeRange(advance(string.endIndex, -2) ..< string.endIndex) // Remote last 2 chars
return string.componentsSeparatedByString("\",\"")
}
toArray(myString) // ["174580798", "151240033", "69753978", ...
You probably want the numbers though, you can do this in Swift 2.0:
toArray(myString).flatMap{ Int($0) } // [174'580'798, 151'240'033, 69'753'978, ...
which returns an array of Ints
EDIT: For the ones loving immutability and functional programming, have this solution:
func toArray(string: String) -> [String] {
return string[advance(string.startIndex, 2) ..< advance(string.endIndex, -2)]
.componentsSeparatedByString("\",\"")
}
or this:
func toArray(string: String) -> [Int] {
return string[advance(string.startIndex, 2) ..< advance(string.endIndex, -2)]
.componentsSeparatedByString("\",\"")
.flatMap{ Int($0) }
}
Try this. I've just added my function which deletes any symbols from string except numbers. It helps to delete " and [] in your case
var myString = "[\"174580798\",\"151240033\",\"69753978\",\"122754394\",\"72373738\",\"183135789\",\"178841809\",\"84104360\",\"122823486\",\"184553211\",\"182415131\",\"70707972\"]"
var s=myString.componentsSeparatedByString("\",\"")
var someArray: [String] = []
for i in s {
someArray.append(deleteAllExceptNumbers(i))
}
println(someArray[0]);
func deleteAllExceptNumbers(str:String) -> String {
var rez=""
let digits = NSCharacterSet.decimalDigitCharacterSet()
for tempChar in str.unicodeScalars {
if digits.longCharacterIsMember(tempChar.value) {
rez += tempChar.description
}
}
return rez.stringByReplacingOccurrencesOfString("\u{22}", withString: "")
}
Swift 1.2:
If as has been suggested you are wanting to return an array of Int you can get to that from myString with this single concise line:
var myArrayOfInt2 = myString.componentsSeparatedByString("\"").map{$0.toInt()}.filter{$0 != nil}.map{$0!}
In Swift 2 (Xcode 7.0 beta 5):
var myArrayOfInt = myString.componentsSeparatedByString("\"").map{Int($0)}.filter{$0 != nil}.map{$0!}
This works because the cast returns an optional which will be nil where the cast fails - e.g. with [, ] and ,. There seems therefore to be no need for other code to remove these characters.
EDIT: And as Kametrixom has commented below - this can be further simplified in Swift 2 using .flatMap as follows:
var myArrayOfInt = myString.componentsSeparatedByString("\"").flatMap{ Int($0) }
Also - and separately:
With reference to Vadian's excellent answer. In Swift 2 this will become:
// ...
do {
let jsonArray = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions()) as! [String]
} catch {
_ = error // or do something with the error
}

Swift 1.2 cast [AnyObject?] to [AnyObject]

If you look at this function:
typealias JSONDictionary = [String: AnyObject]
private func allIdentifiersInJson(json: [JSONDictionary], key: String?) -> [AnyObject] {
if let identifier = key {
let identifiers = json.map{ $0[identifier] } //this returns [AnyObject?]
return identifiers as? [AnyObject] ?? [] // this crashes the compiler in Release with Optimizations
}
return []
}
How would you cast an [AnyObject?] to an [AnyObject]
In Release config with optimizations enabled, I get this error during compilation:
Bitcast requires both operands to be pointer or neither
%548 = bitcast i64 %547 to %objc_object*, !dbg !10033
LLVM ERROR: Broken function found, compilation aborted!
One way would be to use flatMap:
let l: [AnyObject?] = [1, 2, 3, 4, 5, 6, nil, 7]
let m = l.flatMap { $0 } // Returns [1, 2, 3, 4, 5, 6, 7] as [AnyObject]
(tested only in Swift 2)
Expanding on what is in my comment above, if you use the following, your app will crash if identifier is not a key in the dictionary:
let identifiers = json.map { $0[ identifier ]! }
So, what you might want to do is filter that first, then perform the mapping knowing that you can force unwrap because you've already checked for that key:
let identifiers = json.filter({ $0[ identifier ] != nil }).map { $0[ identifier ]! }
But if it were me, I'd want to get rid of the force unwrap altogether for total safety, even when someone else or even yourself comes back to this code a week later and monkeys with it by taking out the filter:
let identifiers = json.map({ $0[ identifier ] ?? NSNull() }).filter { !($0 is NSNull) }
Now you are safely unwrapping with the nil coalescing operator (??) and providing a default object (NSNull), then filtering out those default objects.
You probably want to be using something like this instead:
private func allIdentifiersInJson(json: [JSONDictionary], key: String?) -> [AnyObject] {
if let key = key {
return json.map { $0[key] }.filter { $0 != nil } as! [AnyObject]
}
return []
}
Because we are using filter to remove all instances of nil, we can use the forced cast to [AnyObject] without fear of a crash.
Or, as pointed out by #MirekE in their answer, flatMap is even more succinct:
return json.flatMap { $0[key] }
Which only returns non-nil results, and removes the need for any casting.
This function will not crash the compiler:
private func allIdentifiersInJson(json: [JSONDictionary], key: String?) -> [AnyObject] {
if let key = key {
let identifiers = json.map{ $0[key]! } //this returns [AnyObject]
return identifiers ?? []
}
return []
}

Array transform having failable initialiser

I am using Swift 1.2 in Xcode 6.3.1
Following is my Person struct
struct Person {
let age: Int
init?(age: Int) { //Failable init
if age > 100 { return nil }
self.age = age
}
}
I am having a list of ages against which I have to make Person Objects.
I have made playground file.
let arr = Array(1...150) //Sample set of ages against which Person is created
var personList: [Person]!
and
personList = arr.map({ (val: Int) -> Person? in
return Person(age: val) //Makes object of type Person?
}).filter {
$0 != nil
}.map {
return $0!
}
Here I have uses map - filter - map because the first map invokes failable intializer, (hence it returns Person?) and personList is of type [Person].
Hence second function filters all the non nil objects and third map forcefully opens to optional therby making Person? to Person.
Is there a more easy/readable way out ? Chaining map-filter-map definitely seems to be an overkill for this
You can use flatMap to get rid of any nils in the array, this tutorial discusses the method in length, but the following will work best:
let personList = arr.flatMap { Person(age: $0) }
Note: This answer was given for Swift 1.2, the current
version at the time the question was posted. Since Swift 2 there is a better solution, see #Jeremie's answer.
I don't know of a built-in function that combines filter()
and map(). You can write the code slightly more compact using
the shorthand argument $0 in all closures:
let personList = arr.map { Person(age: $0) }
.filter { $0 != nil }
.map { $0! }
Of course you can define your own extension method which maps the
array elements and keeps only the non-nil results:
extension Array {
func optmap<U>(transform: T -> U?) -> [U] {
var result : [U] = []
for elem in self {
if let mapped = transform(elem) {
result.append(mapped)
}
}
return result
}
}
and then use it as
let personList = arr.optmap { Person(age: $0) }
You can use compactMap which is better that flatMap in this case to remove any nils in the array:
let personList = arr.compactMap { Person(age: $0) }
The Swift document declared:
Returns an array containing the non-nil results of calling the given
transformation with each element of this sequence.

Swift: shortcut unwrapping of array of optionals

Assume we have an array of optionals defined:
var arrayOfOptionals: [String?] = ["Seems", "like", "an", nil, "of", "optionals"]
I can force unwrap it in a short way: var arrayForCrash = arrayOfOptionals.map { $0! }
But that will make app to crash, is there any other short way(without explicitly unwrapping) how I can unwrap an array of optional?
This solution will get you a new array with all values unwrapped and all nil's filtered away.
Swift 4.1:
let arrayOfOptionals: [String?] = ["Seems", "like", "an", nil, "of", "optionals"]
let arrayWithNoOptionals = arrayOfOptionals.compactMap { $0 }
Swift 2.0:
let arrayOfOptionals: [String?] = ["Seems", "like", "an", nil, "of", "optionals"]
let arrayWithNoOptionals = arrayOfOptionals.flatMap { $0 }
Swift 1.0:
let arrayOfOptionals: [String?] = ["Seems", "like", "an", nil, "of", "optionals"]
let arrayWithNoOptionals = arrayOfOptionals.filter { $0 != nil }.map { $0! }
Since it is an array of optionals, it is possible some of the entries are nil. Instead of force unwrapping with !, use the nil coalescing operator to turns nils into empty strings.
let arrayOfOptionals: [String?] = ["This", "array", nil, "has", "some", "nils", nil]
let array:[String] = arrayOfOptionals.map{ $0 ?? "" }
// array is now ["This", "array", "", "has", "some", "nils", ""]
Although you can use flatMap { $0 } to remove nils, flatMap is actually a much more powerful function, and has an overloaded version which does something completely different (e.g. flatten [[Int]] to [Int]). If you're not careful, you may accidentally invoke the wrong function.
I would recommend using an extension on SequenceType to remove nils. If you use removeNils(), you'll be able to essentially do the following:
[1, nil, 2].removeNils() == [1, 2]
It works by making Optional conform to an OptionalType protocol which allows extending SequenceTypes that contain Optional values.
For more information see the original answer I posted.
I took #Cenny's answer and decided to make an operator out of it:
prefix operator <!> {}
prefix func <!> <T>(array: [T?]) -> [T] {
return array.filter{ $0 != nil }.map{ $0! }
}
I'm using it to parse an array of JSON objects and filter the ones that failed:
static func parse(j: JSONArray) -> [Agency]? {
return <!>j.map { self.parse($0) }
}
Update for Swift 2+:
Use flatMap operator and it'll only return non-nil objects
Swift 4
Easy to read and safe approach to filter nils of any sequence
protocol OptionalProtocol {
associatedtype Wrapped
var optional: Wrapped? { get }
}
extension Optional: OptionalProtocol {
var optional: Wrapped? {
return self
}
}
extension Sequence where Element: OptionalProtocol {
var removingOptionals: [Element.Wrapped] {
return self.compactMap { $0.optional }
}
}
Usage
let array: [Int?] = [1, 2, nil, 3, 4, nil]
print(array.removingOptionals) // prints [1, 2, 3, 4], has type [Int]
The more interesting, how to unwrap an optional array of optional values. It is important to deal when we are working with JSON, because JSON can potentially contain null value for an array of something.
Example:
{ "list": null }
// or
{ "list": [null, "hello"] }
To fill a Swift struct we may have a model:
struct MyStruct: Codable {
var list: [String?]?
}
And to avoid working with String?? as a first item we could:
var myStruct = try! JSONDecoder.init().decode(MyStruct.self, from: json.data(using: .utf8)!)
let firstItem: String? = s1.list?.compactMap { $0 }.first
With compactMap { $0 } we can avoid
let i2: String?? = s1.list?.first
compactMap { $0 } is an equivalent of filter { $0 != nil }. map { $0! }
How about:
import Foundation
var test: [String!] = ["this","is","a",nil,"test"]
for string in test {
if string != nil {
print(string)
}
}
Output is thisisatest.
In your case use [String!], if I understood you correctly.