Reputation: 1757
Given [Int?], need to build string from it.
This code snippet works
let optionalInt1: Int? = 1
let optionalInt2: Int? = nil
let unwrappedStrings = [optionalInt1, optionalInt2].flatMap({ $0 }).map({ String($0) })
let string = unwrappedStrings.joined(separator: ",")
But I don't like flatMap
followed by map
. Is there any better solution?
Upvotes: 9
Views: 4605
Reputation: 2296
Swift 3.1:
[1,2,3].flatMap({String(describing: $0)}).joined(separator: " ")
// "1 2 3" (without quotes sure)
Upvotes: 1
Reputation: 154731
The other answers use flatMap
to eliminate the nil
elements and require a call to joined()
to combine the elements and add the commas.
Using joined()
to add the commas is of course a second pass through the
array. Here's a way to use reduce
to do this in one pass:
let arr: [Int?] = [nil, nil, 1, 2, 3, nil, 4, nil, 5, nil]
let result = arr.reduce("") { $1 == nil ? $0 : $0.isEmpty ? "\($1!)" : $0 + ",\($1!)" }
print(result)
Output:
1,2,3,4,5
Explanation
This example uses reduce
to construct the result string element by element.
reduce
is a method called on a sequence. It takes an initial value (""
in this example) and a closure that combines the next element in the sequence with the partial result to create the next partial result.
The closure is called on each element in the sequence (i.e. array arr
). The closure first checks if the element is nil
and if it is, it returns the partial result unmodified. If the element is not nil
, it then checks if the partial result is still the empty string. If it is empty, this is the first element in the result so it returns a string made up of just the element. If the partial result is not empty, it adds the new element preceded by a ,
to the partial result.
reduce(into:)
:Rewriting this to use reduce(into:)
and append
results in an implementation that is twice as fast as the other answers when generating the final comma separated string.
let result = arr.reduce(into: "") { (string, elem) in
guard let elem = elem else { return }
if string.isEmpty {
string = String(elem)
} else {
string.append(",\(elem)")
}
}
Upvotes: 2
Reputation: 63399
Here's another approach:
[optionalInt1, optionalInt2].flatMap { $0 == nil ? nil : String($0!) }
Edit: You probably shouldn't do this. These approaches are better, to avoid the !
[optionalInt1, optionalInt2].flatMap {
guard let num = $0 else { return nil }
return String(num)
}
or:
[optionalInt1, optionalInt2].flatMap { $0.map(String.init) }
Upvotes: 6
Reputation: 398
Avoiding flatMap and map altogether. However creating a local var.
let optionalInt1: Int? = 1
let optionalInt2: Int? = nil
let optionalInts = [optionalInt1, optionalInt2]
var strings = [String]()
for optInt in optionalInts {
if let integer = optInt {
strings.append(String(integer))
}
}
let string = strings.joined(separator: ",")
Upvotes: 0
Reputation: 59536
If you don't like the flatMap
and the map
together you can replace this
[optionalInt1, optionalInt2].flatMap({ $0 }).map({ String($0) })
with this
[optionalInt1, optionalInt2].flatMap { $0?.description }
let optionalInt1: Int? = 1
let optionalInt2: Int? = nil
let result = [optionalInt1, optionalInt2]
.flatMap { $0?.description }
.joined(separator: ",")
Since:
description
is discouraged by the Swift teamflatMap
+ map
concatenation because of the double loopI propose another solution
let result = [optionalInt1, optionalInt2].flatMap {
guard let num = $0 else { return nil }
return String(num)
}.joined(separator: ",")
Upvotes: 3
Reputation: 73236
You can make use of the map
method of Optional
within a flatMap
closure applied to the array, making use of the fact that the former will return nil
without entering the supplied closure in case the optional itself is nil
:
let unwrappedStrings = [optionalInt1, optionalInt2]
.flatMap { $0.map(String.init) }
Also, if you don't wrap the trailing closures (of flatMap
, map
) in paranthesis and furthermore make use of the fact that the initializer reference String.init
will (in this case) non-ambigously resolve to the correct String
initializer (as used above), a chained flatMap
and map
needn't look "bloated", and is also a fully valid approach here (the chained flatMap
and map
also hold value for semantics).
let unwrappedStrings = [optionalInt1, optionalInt2]
.flatMap{ $0 }.map(String.init)
Upvotes: 4