I'd like to convert nested array of strings into nested array of doubles
example:
let Strings = [["1.1", "1.2"],["2.1", "2.2"]]
to
let Doubles = [[1.1, 1.2],[2.1, 2.2]]
I tried
let Doubles = Strings.flatMap(){$0}.flatMap(){Double($0)}
but in this case I obtain one array of double values, how to keep this array nested?
EDIT:
Could you also elaborate why not using map() twice nor flatMap() twice? Why the right way to do this is to use map, then flatMap?
Let's try to sort things out. Array has a map() method
/// Returns an array containing the results of mapping the given closure
/// over the sequence's elements.
public func map<T>(_ transform: (Element) throws -> T) rethrows -> [T]
which creates a new array by transforming each element, and a
flatMap() method
/// Returns an array containing the non-`nil` results of calling the given
/// transformation with each element of this sequence.
public func flatMap<ElementOfResult>(_ transform: (Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult]
which is similar but ignores the elements which are mapped to nil
by the closure.
And there is another flatMap() method
/// Returns an array containing the concatenated results of calling the
/// given transformation with each element of this sequence.
public func flatMap<SegmentOfResult : Sequence>(_ transform: (Element) throws -> SegmentOfResult) rethrows -> [SegmentOfResult.Iterator.Element]
which transforms each element to a sequence and concatenates the results.
Your code calls the second flatMap() method to flatten the
array to one dimension, and then the first flatMap() method to
convert the strings to numbers.
The first candidate for transforming arrays (or sequences in general) is map(), and that
works for nested arrays as well:
let strings = [["1.0", "2.0"],["3.0", "4.0"]]
let doubles = strings.map { $0.map { Double($0) }}
print(doubles) // [[Optional(1.0), Optional(2.0)], [Optional(3.0), Optional(4.0)]]
The result is a nested array of optionals because the conversion to a floating point value can fail. How to fix that?
Forcefully unwrap (usually not recommended):
let strings = [["1.0", "2.0"],["3.0", "4.0"]]
let doubles = strings.map { $0.map { Double($0)! }}
print(doubles) // [[1.0, 2.0], [3.0, 4.0]]
That is fine if you have fixed data and can guarantee that each string is a valid floating point value. If not, the program will crash:
let strings = [["1.0", "2.0"],["3.0", "wot?"]]
let doubles = strings.map { $0.map { Double($0)! }}
// fatal error: unexpectedly found nil while unwrapping an Optional value
Provide a default value:
let strings = [["1.0", "2.0"],["3.0", "wot?"]]
let doubles = strings.map { $0.map { Double($0) ?? 0.0 }}
print(doubles) // [[1.0, 2.0], [3.0, 0.0]]
Here the invalid string is mapped to 0.0, so that the "shape"
of the array is preserved. But 0.0 is a "magic number" now,
and we cannot see from the result if it comes from a valid
or an invalid string.
Ignore invalid strings. And now flatMap() comes into play
(the first version):
let strings = [["1.0", "2.0"],["3.0", "wot?"]]
let doubles = strings.map { $0.flatMap { Double($0) }}
print(doubles) // [[1.0, 2.0], [3.0]]
The inner flatMap() returns an array of the non-nil
Double($0) values, i.e. all invalid strings are ignored.
Advantage: The code cannot crash due to invalid input, and no "magic numbers" are used. Possible disadvantage: The array shape is not preserved.
So pick your choice!
In the first transformation you should use map instead of flatMap like the following:
let doubles = Strings.map { $0.flatMap {Double($0)} }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With