3

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?

1
  • 3
    Note that your code calls two different flatMap() methods. Commented Nov 14, 2016 at 14:55

4 Answers 4

2

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!

Sign up to request clarification or add additional context in comments.

4 Comments

omg, this is just a flawless explanation, you are incredible. Btw, how can you tell which flatMap() did I call, since both works on arrays?
@DCDC: Command-click it in the Xcode source code editor – or just "see" it :)
yeah ok, but why different methods are called ? :P
@DCDC: It depends on the receiver and on the closure. The second closure {Double($0)} returns an optional. The first closure {$0} returns an array.
2

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

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

Comments

1

Try like this.

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

One suggestion variable name always start with lower case so instead of Doubles and Strings write like doubles and strings .

Comments

1

Given this

let strings = [["1.1", "1.2"], ["2.1", "2.2"]]

You can write

let doubles: [[Double]] = strings.map { elms -> [Double] in
    return elms.flatMap { Double($0) }
}

Shorter version

If you want a shorter version (slightly different from the other answers) then

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.