Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Ruby Tips, Part 5 (globaldev.co.uk)
80 points by timblair on April 22, 2014 | hide | past | favorite | 13 comments


Something in my computer-science-student background feels unsettled when I can use a language for years and still learn new tricks (or often, that I've forgotten that I've learned) about common functionality such as string interpolation...but I do love a lot of Ruby's tricks.

    if path =~ %r{^/assets/mobile/img}
       ...
    end
I knew about `%r` but hadn't realized that it escapes forward-slashes while leaving other regex symbols intact. But with the previous explanation of `%q`, in which arbitrary delimiters can be used, it makes sense...curly braces simply replace `/` as the delimiter, and `/` simply retains its non-specialness in regex...saves me a lot of writing `Regex.escape("/path/to")`.

Another regex notation that I recently learned (elsewhere) and now love to abuse:

     "He is 32-years old"[/\d+/] # => "32"
    
as opposed to:

     "He is 32-years old".match(/\d+/)[0]


The extension of that can also be handy and is often overlooked. Contrived example:

    "28 men are 32 years old"[/(\d+) (year|day|month)/, 1] # => "32"


This explanation of how %r works here is incorrect:

> I knew about `%r` but hadn't realized that it escapes forward-slashes while leaving other regex symbols intact.

The correct explanation can also be found in the next sentence of your comment:

> But with the previous explanation of `%q`, in which arbitrary delimiters can be used, it makes sense...curly braces simply replace `/` as the delimiter, and `/` simply retains its non-specialness in regex.


Not sure arbitrary regex delimiters can be counted as one of "Ruby's tricks" given they've been implemented in sed for maybe 4 decades?


You can also use

   Regexp.new("/foo/bar")
to achieve the desired result but the %r{} shortcut is handier.



I think growing a string with << is an expensive operation. Since once the allocated memory is full, it will cost O(n) to copy the entire string to a new location. If we are carrying out m insertions, this could take O(mn).

The way I have been building strings is to store the m strings in an Array and then join() it at the end (which should be only O(n)). The m small objects need to be allocated in both cases, the only difference being that we hold on to those and join them later on.

Am I missing something? I have never measured the performance on either of the these operations so I could be off the mark here.


For the cases I have benchmarked concatenation with << has been faster than join on an array.

Here's a simple benchmark:

    require "benchmark"

    n = 100_000
    Benchmark.bm(4) do |x|
      x.report("<<") do
        n.times do
          "aaaaa " << "bbbbbb " << "ccccc " << "ddddd " << "eeeee " << "fffff"
        end
      end

      x.report("join") do
        n.times do
          ["aaaaa", "bbbbbb", "ccccc", "ddddd", "eeeee", "fffff"].join(" ")
        end
      end
    end
The results I get for this are:

               user     system      total        real
    <<     0.140000   0.000000   0.140000 (  0.143750)
    join   0.230000   0.000000   0.230000 (  0.228035)
I'd be interested to see if there were any use cases where the relative performance was reversed.


    require "benchmark"
    n = 100_000

    A = "foo " * 20
    B = "bar " * 20
    C = "baz " * 20
    D = "foobar " * 20
    STRINGS = [A, B, C, D]

    Benchmark.bm(4) do |x|
      x.report("<<") do
        n.times do
          "" << A << B << C << D
        end
      end

      x.report("join") do
        n.times do
          STRINGS.join
        end
      end
    end


               user     system      total        real
    <<     0.090000   0.010000   0.100000 (  0.102411)
    join   0.070000   0.010000   0.080000 (  0.071142)


These are some good tips. If you are into the Ruby language and want more, I recommend checking out Ruby Tapas http://www.rubytapas.com/ . I have no affiliation with it, just happy customer.


notice that this is wrong

    x ||= y # equivalent to:
    x || x = y
it's actually something slightly different (or at least it was some years ago).


Here's an example that shows x ||= y behaves like x || x = y, rather than x = x || y

    class Foo
      def foo=(object)
        puts "foo= called with #{object.inspect}"
        @foo = object
      end

      def foo
        puts "foo called"
        @foo
      end
    end

    puts "x || x = y"

    a = Foo.new

    a.foo || a.foo = 1
    a.foo || a.foo = 2

    puts "\nx = x || y"

    b = Foo.new

    b.foo = b.foo || 1
    b.foo = b.foo || 2

    puts "\nx ||= y"

    c = Foo.new

    c.foo ||= 1
    c.foo ||= 2
This outputs:

    x || x = y
    foo called
    foo= called with 1
    foo called

    x = x || y
    foo called
    foo= called with 1
    foo called
    foo= called with 1

    x ||= y
    foo called
    foo= called with 1
    foo called
and you can see that the output for ||= matches the output for x || x = y


I didn't mean that it is "x = x || y", I meant "it's completely ad hoc".

For example, if you try "x || x = y" you will get an exception if x is not defined, but "x ||= y" will work fine. The same is true of constants "X || X = 1" will explode while "X ||= 1" will work fine.




Consider applying for YC's Fall 2025 batch! Applications are open till Aug 4

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: