2

I need help figuring out why there are differences between map and each in my following code.

j = "This is an awesome string"

j.split('').map do |char|
    char.next! if char =~ /[A-Za-z]/
end

This returns:

["U", "i", "j", "t", nil, "j", "t", nil, "b", "o", nil, "b", "x", "f", "t", "p", "n", "f", nil, "t", "u", "s", "j", "o", "h"]

but

j.split('').each do |char|
    char.next! if char =~ /[A-Za-z]/

returns:

["U", "i", "j", "t", " ", "j", "t", " ", "b", "o", " ", "b", "x", "f", "t", "p", "n", "f", " ", "t", "u", "s", "j", "o", "h"]

Why does the change from map to each remove the nil?

theamateurdataanalyst
  • 2,794
  • 4
  • 38
  • 72
  • As always, there's a oneliner for this in ruby: `"This is an awesome string".gsub(/[A-z]/) { |c| c.next }` Not strictly relevant to the question, but worth knowing nonetheless (I think, anyway). – amnn Jul 09 '14 at 08:41
  • 1
    @asQuirreL On your way.. `"This is an awesome string".enum_for(:gsub, /[a-z]/i).map(&:next)` ... Hope you would like. It actually #map, which will show you what it has done, during iteration. :-) – Arup Rakshit Jul 09 '14 at 10:01
  • Similarly, there is `This is an awesome string".gsub(/[A-z]/, &:next)` (The problem with your solution is that it actually removes non alphabetic characters, rather than ignoring them). – amnn Jul 09 '14 at 10:18
  • @asQuirreL It is #gsub.. Not #gsub! ..No worry. :-) – Arup Rakshit Jul 09 '14 at 12:43

1 Answers1

5

Because Array#each returns the receiver itself. But Array#map, maps, each current character, you passed to the block, with the current computation result.

With #map, the condition if char =~ /[A-Za-z]/ evalutated as falsy, for each passed element like " ". Thus block mapped nil while it founds " " or any element for which if condition evaluated as falsy. But #each forgets about the block result after each iteration is completed, But #map doesn't.

If you look at the output, you can see all nil(s) are in the same position of " ". This observation also dictates about the behaviour I just mentioned above.

In Ruby, every block of code, like definition of class, method definitions, block definitions returns its last statement. In your #map block, there is no statements, after the if modifier, so it reruns nil.

In Ruby >= 2.1.1 I saw method to returns its names as a symbol. But when you will invoke it, then it will return its last statement. If the method body is empty, then nil.

Look below :-

[1] pry(main)> def foo
[1] pry(main)* end  
=> :foo
[2] pry(main)> foo
=> nil
[3] pry(main)> def baz
[3] pry(main)*   12
[3] pry(main)* end  
=> :baz
[4] pry(main)> baz
=> 12

update

From the comment of @asquirrel, I got an idea. You could write your code a more Ruby way :-

"This is an awesome string".gsub(/[a-z]/i).map(&:next)
# => ["U", "i", "j", "t", "j", "t", "b", "o", "b", 
# "x", "f", "t", "p", "n", "f", "t", "u", "s", "j", "o", "h"]

update1

j = "This is an awesome string"

array = j.split('').map do |char|
  char =~ /[A-Za-z]/ ? char.next : char
end

p array 

# >> ["U", "i", "j", "t", " ", "j", "t", " ", "b", "o", " ", "b", 
# "x", "f", "t", "p", "n", "f", " ", "t", "u", "s", "j", "o", "h"]
Community
  • 1
  • 1
Arup Rakshit
  • 116,827
  • 30
  • 260
  • 317