Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Convert nested array of strings into nested array of doubles using functional programming

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?

like image 598
theDC Avatar asked Dec 29 '25 08:12

theDC


2 Answers

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!

like image 156
Martin R Avatar answered Dec 30 '25 22:12

Martin R


In the first transformation you should use map instead of flatMap like the following:

let doubles = Strings.map { $0.flatMap {Double($0)} }

like image 37
Bobj-C Avatar answered Dec 30 '25 21:12

Bobj-C



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!