Close
Glad You're Ready. Let's Get Started!

Let us know how we can contact you.

Thank you!

We'll respond shortly.

LABS
Ruby Enumerable and string

Did you know that you can call map() and each() on a Ruby string? Do you know how they behave? I hope I’m not the only one that thought they understood it but was proven mistaken.

What would you expect the following code to do?

"hello".map{|char| "Char#{char}"  }

I thought it would return me an array with a bunch of “Char” where x is each letter in “hello”. Nope…it returns:

["Charhello"]

What would you expect the following code to do?

"hello".each{|char| puts "Char#{char}"}

Yeah I thought it would write out a bunch of puts statements “Char” where x is each letter in “hello”. Nope it prints:

Charhello

Ok so maybe my thoughts on strings being enumerable were a little off…I’ve been wrong before and will probably be wrong again.

What really threw me for a loop was that I can access the characters in a string by index:

"hello"[1]

returns

101

which is the ASCII representation of “e”.

So what is the moral of this story? Sometimes things aren’t as the seem at first glance. And sometimes you need to step back, fire up irb and see what really is happening.

Update: Thanks all for the responses below.

Comments
  1. irb(main):008:0> String.instance_methods.each.select {|m| m if m.to_s.include?(‘each’)}
    => [:each_line, :each_byte, :each_char, :each_codepoint]

    irb(main):009:0> “test”.each {|c| puts c}
    NoMethodError: undefined method `each’ for “test”:String
    from (irb):9
    from C:/Ruby19/bin/irb:12:in `

    irb(main):010:0> “test”.map {|c| c}
    NoMethodError: undefined method `map’ for “test”:String
    from (irb):10
    from C:/Ruby19/bin/irb:12:in `

    What version of Ruby is this on? Also is it possible that you’ve got another library which has monkey patched MethodMissing in the String class? Is this in a straight irb session or maybe from a ./script/console or rails console session? I think .each_byte or .each_char would do what you’re looking for but probably there is some kind of 3rd party intervention going on here. Good luck!

  2. Mike Gehard says:

    Here’s an example console output:

    xxxx:~ xxxx$ gem list

    *** LOCAL GEMS ***

    rake (0.8.7)
    rdoc (2.5.9)
    xxxx:~ xxxx$ irb
    1.9.2-p0 > “hello”.each{|char| puts “Char#{char}”}
    Charhello
    => “hello”
    1.9.2-p0 >

    Also, the Ruby documentation says that [String](http://ruby-doc.org/core/classes/String.html) includes the Enumerable module so you’d expect to get each() and map(). Interesting that your version doesn’t behave the same way.

  3. JGeiger says:

    He’s using 1.8.x, you’re using 1.9.x

    String does a lot of different things when going to 1.9.x.

  4. JGeiger says:

    $ irb
    ruby-1.9.2-p0 > “hello”.each{|char| puts “Char#{char}”}
    NoMethodError: undefined method `each’ for “hello”:String
    from (irb):1
    from /Users/jgeiger/.rvm/rubies/ruby-1.9.2-p0/bin/irb:17:in `


    ruby-1.9.2-p0 >

    Dunno why your ruby is doing that, but 1.9 doesn’t include each.

    http://blog.grayproductions.net/articles/ruby_19s_string

    “In Ruby 1.8, String’s each() method iterated over lines of data. I imagine that was done because it’s a common way to process data, but the question is what made lines the correct choice? What about iterating by bytes or characters? You could iterate by bytes in Ruby 1.8 using each_byte(), but you needed to resort to Regexp tricks to get characters.

    In the Ruby 1.9 realm of all encoded data, blessing one type of iteration just doesn’t make sense. Instead, each() has been removed from String and it is no longer Enumerable. This is probably one of the biggest changes to the core API that code will need to adapt to.”

  5. Mike Gehard says:

    Looks like 1.8.7-p299 behaves the same way:

    info: Using ruby 1.8.7 p299
    xxxx:~ xxxx$ gem list

    *** LOCAL GEMS ***

    rake (0.8.7)
    rdoc (2.5.9)
    xxxx:~ xxxx$ irb
    ruby-1.8.7-p299 > “hello”.each{|char| puts “Char#{char}”}
    Charhello
    => “hello”
    ruby-1.8.7-p299 >

  6. Mike Gehard says:

    Very interesting indeed…

    JGeiger…it is very interesting that my 1.9.2-p0 seems to include each(). Thanks for the link. I’ll do a little more poking around and see what I find…

  7. C:>ruby -v
    ruby 1.9.1p378 (2010-01-10 revision 26273) [i386-mingw32]

    C:>ruby -e “p String.included_modules”
    [Comparable, Kernel]

    I’ll try on 1.9.2 on a Linux box in a bit and post the results.

  8. Got to know through RubyKoans that Ruby 1.8 and 1.9 differ in the way how single characters in strings are represented. Here is the excerpt from koans/about_strings.rb

    ====================================
    in_ruby_version(“1.8″) do
    def test_in_ruby_1_8_single_characters_are_represented_by_integers
    assert_equal __, ?a
    assert_equal __, ?a == 97

    assert_equal __, ?b == (?a + 1)
    end
    end

    in_ruby_version(“1.9″) do
    def test_in_ruby_1_9_single_characters_are_represented_by_strings
    assert_equal , ?a
    assert_equal __, ?a == 97
    end
    end
    ====================================

    Found this page with differences between 1.8 and 1.9:
    http://eigenclass.org/hiki.rb?Changes+in+Ruby+1.9#l112

    String: No longer an Enumerable

    String is not Enumerable anymore. Use #each_line instead of #each, and #lines (see below) to iterate over the lines.

  9. Mike Gehard says:

    Not sure what was up this morning with my install of 1.9.2-p0 but now I get the following:

    ruby-1.9.2-p0 > ‘this thing’.each{|a| puts a}
    NoMethodError: undefined method `each’ for “this thing”:String
    from (irb):1
    from /Users/pivotal/.rvm/rubies/ruby-1.9.2-p0/bin/irb:17:in `


    ruby-1.9.2-p0 > ‘this’.is_a? Enumerable
    => false

    Maybe my machine was already checked out for the weekend already.

    Final verdict is that in 1.8.x, each() on a String breaks on a line and in 1.9.x, String is no longer Enumerable so it is no longer an issue.

    Thanks all for the comments and for keeping tabs on Pivotal Blabs.

  10. The code from RubyKoans – with MarkDown

    in_ruby_version(“1.8″) do
    def test_in_ruby_1_8_single_characters_are_represented_by_integers
    assert_equal 97, ?a
    assert_equal true, ?a == 97

    assert_equal true, ?b == (?a + 1)
    end
    end

    in_ruby_version(“1.9″) do
    def test_in_ruby_1_9_single_characters_are_represented_by_strings
    assert_equal a, ?a
    assert_equal __, ?a == 97
    end
    end

  11. pmacek says:

    Matthew Closson, I liked your post but I wanted to give it a little poke.

    You can drop the `m if `from your statement and write `String.instance_methods.each.select {|m| m.to_s.include?(‘each’)}`

    `Array#select` returns an array of elements where the block evaluates to true :)

  12. pete says:

    @Matthew Closson, pmacek:

    Even shorter…

    > String.instance_methods.grep /each/
    => [:each_line, :each_byte, :each_char, :each_codepoint]

  13. pmacek & pete — nice calls on both of those statement improvements.

    Mike — glad to see that its figured out now, and look forward to future blog posts.

Post a Comment

Your Information (Name required. Email address will not be displayed with comment.)

* Copy This Password *

* Type Or Paste Password Here *