9

We have an array of the Person objects and each object has another array of String, which is optional. We want the consolidated list of car names in our society.

struct Person {
    let name: String
    let address: String
    let age: Int
    let income: Double
    let cars: [String]?
}
let personsArray = [Person(name:"Santosh", address: "Pune, India", age:34, income: 100000.0, cars:["i20","Swift VXI"]),
                   Person(name: "John", address:"New York, US", age: 23, income: 150000.0, cars:["Crita", "Swift VXI"]),
                   Person(name:"Amit", address:"Nagpure, India", age:17, income: 200000.0, cars:nil)]

let flatmapArray = personsArray.flatMap({$0.cars})
print(flatmapArray)

// Expected Result: ["i20", "Swift VXI", "Crita", "Swift VXI"]

// Result: [["i20", "Swift VXI"], ["Crita", "Swift VXI"]]

Why it's not giving me a single array of string as result?

I did couple of changes in the above code like my code as below, Instead of "nil", we tried to pass the empty array to the 3rd Person object.

Person(name:"Amit", address:"Nagpure, India", age:17, income: 200000.0, cars:Array())

The result was:

[["i20", "Swift VXI"], ["Crita", "Swift VXI"], []]

Still not the expected result.

If I remove the optional from the cars Array like,

let cars: [String]  
Person(name:"Amit", address:"Nagpure, India", age:17, income: 200000.0, cars:Array()) 

then it works as expected.

Result:

["i20", "Swift VXI", "Crita", "Swift VXI"]

I am not sure why it's not giving the above result if the member is of type Collection is optional?

Ozgur Vatansever
  • 49,246
  • 17
  • 84
  • 119
Santosh Botre
  • 549
  • 5
  • 21
  • 2
    FYI: [`flatMap` is Deprecated, Use `compactMap`](https://stackoverflow.com/a/48638302/5638630) – Krunal Feb 15 '18 at 06:30
  • 3
    @Krunal, to be clear, this is in the upcoming version of Swift, and only available currently if you use the Xcode beta. – zneak Feb 15 '18 at 06:34

3 Answers3

8

The issue is that for the purposes of map and flatMap, optionals are collections of 0 or 1 elements. You can directly call map and flatMap on optionals without unwrapping them:

let foo: Int? = 5
foo.map { $0 * $0 } // Int? = 25; "collection of one element"
let bar: Int? = nil
bar.map { $0 * $0 } // Int? = nil; "collection of zero elements"

To illustrate in more familiar terms your current situation, you're looking at the equivalent of this:

class Person {
    let cars: [[String]]
}

If you had a var persons: [Person] and called persons.flatMap { $0.cars }, the resulting type of this operation would unquestionably be [[String]]: you start out with three layers of collections and you end up with two.

This is also effectively what is happening with [String]? instead of [[String]].

In the case that you are describing, I would advise dropping the optional and using empty arrays. I'm not sure that the distinction between a nil array and an empty array is truly necessary in your case: my interpretation is that a nil array means that the person is incapable of owning a car, whereas an empty array means that the person is capable of owning a car but doesn't have any.

If you cannot drop the optional, then you will need to call flatMap twice to flatten two layers instead of only one:

persons.flatMap { $0.cars }.flatMap { $0 }
zneak
  • 134,922
  • 42
  • 253
  • 328
  • Of course. If you feel that this adequately answered your question, don't forget to click the checkmark underneath the score. – zneak Feb 16 '18 at 05:32
3

Why let cars: [String] is different from let cars: [String]? with flatMap ?

There are two flavours of flatMap on sequences;

1) one that flattens out sequences returned from the closure passed to it,

2) one that filters out nils from the closure passed to it (note this overload is soon to be renamed to compactMap to avoid confusion as @krunal mentioned on comment)

In your code you’re using the nil filtering overload, so the nils in your array are filtered out. It won’t however do any flattening of the nested arrays. You could call flatMap again to achieve that.

like

let flatmapArray = personsArray.flatMap{$0.cars}.flatMap{$0}
print(flatmapArray)
Prashant Tukadiya
  • 15,838
  • 4
  • 62
  • 98
  • 1
    Nope it doesn't explain that fact that why `let cars: [String]` is working and `let cars: [String]?` not. – Bista Feb 15 '18 at 06:39
  • If you remove the optional/'?' then it works perfectly fine. The @zneak answer seems correct to me. – Santosh Botre Feb 15 '18 at 07:24
  • @SantoshBotre Then what you get why [String] is working and String]? not. ? i edited answer and remove that first line – Prashant Tukadiya Feb 15 '18 at 07:29
  • @SantoshBotre Check now the why factor. – Prashant Tukadiya Feb 15 '18 at 11:11
  • @Mr.Bista Check now why factor – Prashant Tukadiya Feb 15 '18 at 11:11
  • @SantoshBotre I have already added the reason too. Flat map is used for two things 1) flattens out sequences 2) filters out nils ; this is two functionalities having same name ; there is no special reason why apple has choose flatMap for two things – Prashant Tukadiya Feb 16 '18 at 11:49
  • @PrashantTukadiya Thanks a lot. flatMap do have two flavors which i am aware. Here, we are trying to understand the different behavior of flatMap function which varies on optional and no optional collection. And not the how to get single collection or code to get the single collection using the Channing of the higher-order function. Hope you understand our intent. – Santosh Botre Feb 16 '18 at 11:52
  • @SantoshBotre Good luck with that. What you are asking is you have already tried . i can see in your question with different inputs in flatmap and which is clearly shows behaviours with different output and I explain why this output is different ;. My suggestion is to you should focus on what type of functions you have and what is its output on different scenarios. And of-course you are most welcome :) have a great day :) – Prashant Tukadiya Feb 16 '18 at 12:00
0

You need to call flatMap once more for array containing Optionals: personsArray.flatMap { $0.cars }.flatMap { $0 }

Ilya Kharabet
  • 4,203
  • 3
  • 15
  • 29