Daniel Viglione
Daniel Viglione

Reputation: 9427

flat_map in ruby when working with different elements

I think flat_map is great when you have an array of hashes of variable lengths and you want to work with each one individually:

data = [
  {'apple' => 'fruit'},
  {'carrot' => 'vegetable'},
  {'orange' => 'fruit', 'pear' => 'fruit'},
  {'lettuce' => 'vegetable'}
] 

data.flat_map(&:to_a).reduce([]) {|acc, (k,v)| acc << Hash[k,v] }
 => [{"apple"=>"fruit"}, {"carrot"=>"vegetable"}, {"orange"=>"fruit"}, {"pear"=>"fruit"}, {"lettuce"=>"vegetable"}] 

But I don't think I fully understand flat_map. According to docs:

Returns a new array with the concatenated results of running block once for every element in enum.

But look at this example:

[[1], {a: "a"}, {b: "b"}].flat_map(&:to_a)
=> [1, [:a, "a"], [:b, "b"]] 

The first item is stripped from the inner array, the other items are converted to arrays. How does it know to do this?

When I call to_a like this:

[1].to_a
 => [1] 
[1].to_a.flatten
 => [1] 

You see the result is different than what flat_map did. What is flat_map doing in the first example to strip the item of the array?

Upvotes: 3

Views: 4413

Answers (2)

Cary Swoveland
Cary Swoveland

Reputation: 110685

For any array arr having m+n elements, if

arr.map { ... }

returns

[a1, a2,.., am, o1, o2,.., on]

where a1, a2,.., am are arrays and o1, o2,.., on are objects other than arrays, then by changing map to flat_map, leaving the block unchanged, flat_map will return

[*a1, *a2,.., *am, o1, o2,.., on]

Example 1

For

arr = [[1], {a: "a"}, {b: "b"}]

we have

arr.map(&:to_a)
  #=> [[1], [[:a, "a"]], [[:b, "b"]]]

so it follows that

arr.flat_map(&:to_a)

will return

[*[1], *[[:a, "a"]], *[[:b, "b"]]]
  #=> [1, [:a, "a"], [:b, "b"]] 

and indeed it does.

Example 2

Suppose now that we have

arr = [1, 'cat', [2,3], {a: 4}]

then

arr.map(&:itself)
  #=> [1, "cat", [2, 3], {:a=>4}]

so

arr.flat_map(&:itself)

is found to return

[1, "cat", 2, 3, {:a=>4}]

which is the reduction of

[1, "cat", *[2, 3], {:a=>4}]

Upvotes: 3

Amadan
Amadan

Reputation: 198344

[1].flatten is [1] - there is no second level. [[1]].flatten is [1]. This is the difference between your last example and all others.

If you try plain map first,

[[1], {a: "a"}, {b: "b"}].map(&:to_a)
# => [[1], [[:a, "a"]], [[:b, "b"]]]

Notice that each hash arrayifies to an array of arrays (each of which is a pair of a key and a value). When you flatten it by one level:

[[1], {a: "a"}, {b: "b"}].map(&:to_a).flatten(1)
# => [1, [:a, "a"], [:b, "b"]] 

This is exactly what you are getting with flat_map.

Upvotes: 1

Related Questions