78
\$\begingroup\$

What general tips can you give for golfing in Ruby?

I'm looking for ideas that can be applied to code golf problems in general that are specific to Ruby. (For example, "Remove comments" would not be an answer.)

Please post one tip per answer.

\$\endgroup\$
1
  • \$\begingroup\$ Someone needs to write a language called Rub, which uses a single Unicode character for every Ruby token, kinda like Jelly and Pyth :) \$\endgroup\$ Aug 30, 2017 at 12:28

53 Answers 53

1
2
6
\$\begingroup\$

Use || instead or and && instead and.

Beside the one character from and you can save the spaces (and perhaps the bracket) around the operator.

p true and false ? 'yes' :'no'   #-> true (wrong result)
p (true and false) ? 'yes' :'no' #-> 'no'
p true&&false ? 'yes' :'no'      #-> 'no', saved 5 characters


p true or false ? 'yes' :'no'   #-> true (wrong result)
p (true or false) ? 'yes' :'no' #-> 'yes'
p true||false ? 'yes' :'no'      #-> 'yes', saved 4 characters

If you loop on an array you normally use each. But map loops also over an array and it is one character shorter.

\$\endgroup\$
6
\$\begingroup\$

If the first line starts with #! and contains ruby, then Ruby will take command-line switches from it.

For example, if you require two libraries, you can golf

require'benchmark'
require'prime'

into

#!ruby -rbenchmark -rprime

which saves 7 bytes.

\$\endgroup\$
1
  • 7
    \$\begingroup\$ Or you can just add -rbenchmark -rprime to the character/byte count. That's usual in choice golf. \$\endgroup\$
    – alexia
    Jul 5, 2014 at 12:21
6
\$\begingroup\$

You may be able to save 2 chars and use

[*(...)]

instead of

(...).to_a

For example, suppose we have a range that we want as an array:

(1..2000).to_a

Just do it like this:

[*1..2000]  #  Parentheses around the (ran..ge) is not needed!

And now you have your range as an array.

\$\endgroup\$
1
  • 7
    \$\begingroup\$ I think [*1..2000] works, too? \$\endgroup\$
    – Lynn
    Aug 24, 2015 at 18:38
6
\$\begingroup\$

Re(ab)use predefined globals

There is a whole bunch of predefined global variables that you can use instead of initialising new variables.

A very common example is that you have some golfed one-liner that loops, but you also need to keep a counter for later:

i=0
s.gsub!(/../){i+=1;"foo"}while s=~/.../
p i

So frustrating. Luckily, $. comes to the rescue! It is initially 0, and is incremented each time you read a line from input. This is of course incredibly useful if you actually need to keep track of the amount of lines you have read, but otherwise you can just manually update it:

s.gsub!(/../){$.+=1;"foo"}while s=~/.../
p$.

That's 3 bytes saved.

If you for example need to append your counter to a string s, you may save some more bytes though string interpolation because the variable has a sigil:

i=0;i+=1;s+i.to_s
i=0;i+=1;s+"#{i}" 
$.+=1;s+"#$."

Another interesting global is $:. It's an alias for $LOAD_PATH and is an array full of strings. I haven't actually used this in a golf yet, but I imagine it could come in handy if you need a cache for checking previously visited values or something, and don't care if it's completely empty. Your values are unlikely to crash with any of its initial contents anyways.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Especially for new golfers, it's worth noting that you generally can't use $. as a counter like this in function submissions because of this consensus regarding function reuse. (I was guilty of doing so a couple of times before I became aware of that rule.) \$\endgroup\$
    – Dingus
    Sep 16, 2020 at 14:21
5
\$\begingroup\$

<< trick

a.push x

can be shortened to:

a<<x

for -4 bytes.

\$\endgroup\$
1
  • 5
    \$\begingroup\$ Note: this also works for Strings \$\endgroup\$
    – Cyoce
    Nov 4, 2016 at 1:13
5
\$\begingroup\$

To subtract one (-1), instead of doing this

(i - 1) * 2

do the following

~-i * 2

which will save you 2 bytes in cases where you otherwise have to use brackets (e.g. arithmetics).

\$\endgroup\$
1
  • 6
    \$\begingroup\$ like wise, (i+1) can become -~i \$\endgroup\$
    – Cyoce
    Nov 4, 2016 at 1:16
5
\$\begingroup\$

Array#assoc/rassoc

When you have an array of arrays and want to find the sub-array that starts with a particular value, don't use Enumerable#find, use Array#assoc:

a = [[0,"foo"],[0,"bar"],[1,"baz"],[0,"qux"]]
a.find{|x,|x==1} # => [1,"baz"]
a.assoc(1) # => [1,"baz"]

This is also a good replacement for Enumerable#any? in some situations.

Array#rassoc does the same thing, but checks the sub-arrays' last element:

a = [[123,"good"],[456,"good"]]
a.any?{|*,x|x=="bad"} # => false
a.rassoc("bad") # => nil
\$\endgroup\$
3
  • \$\begingroup\$ For the a.any? line in the rassoc example, what does |x,| do? How is it different from |x|? \$\endgroup\$
    – Cyoce
    Nov 4, 2016 at 1:05
  • \$\begingroup\$ @Cyoce Block parameter destructuring follows the same rules as destructuring assignment, so it's like x=[1,2] vs x,=[1,2]. Using my example above, with |x|, in the first iteration x will be [0,"foo"]. With |x,y|, x will be 0 and y will be "foo". Likewise, with |x,|, x will be 0. In other words, it says "put the first element in x and throw the rest away. \$\endgroup\$
    – Jordan
    Nov 4, 2016 at 1:27
  • \$\begingroup\$ Note that it doesn't work in reverse, e.g. |,y| is a SyntaxError, ergo |_,y|. But I've just now realized that |*,y| works, which is cleaner than using a variable named _ (but no shorter). \$\endgroup\$
    – Jordan
    Nov 4, 2016 at 1:39
5
\$\begingroup\$

Fun with arrays

There are many ways to do the same thing in ruby, and often times the default methods are inefficient - as a case study, a new arrays with set size and initialized to all 10:

Array.new(20,10) # idiomatic
[10]*20

You may already be informed not to use this syntax, however, when you are initializing elements in an array that needs to not be shallow-cloned, for example 2D arrays:

[[]]*20               # beware, all 20 elements points to the one same array
Array.new(20){[]}     # idiomatic
20.times.map{[]}
([0]*20).map{[]}      # free ticket, you can move any line into `0` to save newline
([a,b,c,d]*5).map{[]} # saves a char when you move 4 1-time use expressions
(1..20).map{[]}

Many operations in ruby will create new objects. Sometimes this is to our advantage. For example, to create a new array with the same size as another array, we can get by without even knowing its .size at all:

[0]*a.size # looks kinda nice
a.map{0}   # -2 chars

(Note: the []* version may be preferable in cases where you also want to reuse the value, so [0]*b=a.size might end up helping you cut a char elsewhere)

To shallow clone an array:

b=a[0..-1] # makes sense
b=a.clone
b=a*1      # this returns new_ary
b=*a       # this version for assignments - use [*a] in expressions
\$\endgroup\$
1
  • 1
    \$\begingroup\$ .clone is an alias of .dup ([*a] is still better for arrays) \$\endgroup\$ Mar 1, 2018 at 12:13
5
\$\begingroup\$

Use eval instead of reduce

While array.sum is great and shorter than array.reduce(:+), there are no similar methods for the other operators *, -, / ...

Joining arrays and evaling them saves some space:

a = [1,2,3,4]

a.reduce(:*)   # => 24
eval(a*?*)     # => 24
          ^^   two bytes saved.

It also removes the need to map string input to numbers first:

b = "1 2 3 4".split

b.map(&:to_i).reduce(:*)
eval(b*?*)
          ^^^^^^^^^^^^^^
\$\endgroup\$
4
  • 2
    \$\begingroup\$ eval a*?* to save another char. \$\endgroup\$
    – Unihedron
    Jan 29, 2018 at 6:31
  • 1
    \$\begingroup\$ also for case 2, using .tr' ',?* is way more concise than .split*?*... \$\endgroup\$
    – Unihedron
    Jan 29, 2018 at 6:33
  • 1
    \$\begingroup\$ also, note that a.reduce:* is valid if brackets aren't required though I think it can always be replaced with eval a*?* \$\endgroup\$ Mar 1, 2018 at 12:10
  • \$\begingroup\$ eval(a*?*) is horrible. I like it! \$\endgroup\$ Oct 10, 2020 at 16:31
5
\$\begingroup\$

New features in Ruby 2.5 and 2.6

Ruby keeps getting new features that look like they could be handy for golfing. Here are some that caught my eye. If there's any you think should be added feel free to edit or mention them in the comments.

(See also New features in Ruby 2.3 and 2.4.)

Ruby 2.5

Here's an easy-to-read reference with all of the new features in Ruby 2.5: https://rubyreferences.github.io/rubychanges/2.5.html

Regexp absence operator: (?~foo)

This matches any string that doesn't end with foo. I don't have a specific use for this one off the top of my head, but here's a good article about it.

Enumerable#all?, any?, none?, and one? accept objects that respond to ===

Similar to Enumerable#grep:

%w[Foo bar].one?(/[A-Z]/) # => true
%w[Foo bar].any?("bar") # => true
[1, 2, 3, 5, 7, 11].all?(0..10) # => false

Hash#slice gets a subset of a Hash

I don't see a lot of Hashes in Ruby golf, but it might come in handy:

{foo: 1, bar: 2, baz: 3}.slice(:bar, :baz) # => {foo: 1, bar: 2}

Ruby 2.6

All of the changes in Ruby 2.6: https://rubyreferences.github.io/rubychanges/2.6.html

Endless Range: 1..

Lots of potential uses for this.

%w[a b c].zip("X"..).to_a
# => [["a", "X"], ["b", "Y"], ["c", "Z"]]

[1, 2, 3, 5, 7, 11].any?(10..)
# => true

String#split takes a block

Saves four bytes when iterating over the result of String#split, e.g.:

"foo;bar;baz".split(?;).map {|s| x << s.upcase if ... }

You can do this:

"foo;bar;baz".split(?;) {|s| x << s.upcase if ... }

Note that it still returns the original string, so it won't always be fewer bytes than map.

Proc composition with >> and <<

Procs now have >> and << methods that let them be combined with other procs, or any object that responds to call:

double = ->n{ n * 2 }
square = ->n{ n ** 2 }
(double >> square)[5] # => 100
(double << square)[5] # => 50

% operator for Range

An alias for Range#step:

((1..10) % 2).to_a
# => [1, 3, 5, 7, 9]

Random.bytes

Could be handy.

Random.bytes(10)
# => "'\xB2\xD7\ny"
\$\endgroup\$
5
\$\begingroup\$

Avoid String#sub!

Instead of

a.sub!b,c

save a byte with

a[b]&&=c

Both return nil if no substitution occurs. If a substitution does occur, be aware that the return values differ: a.sub!b,c returns the modified string a whereas a[b]&&=c returns c. However, given that you're using sub! (as opposed to sub) chances are that either:

  • You just need to modify a in place and will be discarding the return value anyway; or
  • You just need the return value to be truthy.

The shorter form meets your needs in both cases.

\$\endgroup\$
3
\$\begingroup\$

When working with arrays, .compact can be replaced with -[nil] to save 2 chars.

\$\endgroup\$
1
  • 4
    \$\begingroup\$ -[p] saves another 2. \$\endgroup\$
    – Doorknob
    Aug 25, 2018 at 16:12
3
\$\begingroup\$

When getting input of fixed length the optional integer parameter to gets may come in handy.

  • Getting a substring:

    gets 10 vs gets[0,10] -- saves 3 bytes

  • Cutting trailing newline:

    gets 10 vs gets.chop -- saves 3 - log10(required_length) bytes (= 2 in this case)

\$\endgroup\$
3
\$\begingroup\$

Cloning strings

Strings are mutable in Ruby so if you have two variables holding the same string (a=b="string") and you modify one (a[0]="S"), the other will also change (b=="String")

To clone a string:

a="string"

b=a.clone # 9 bytes
b=a.dup # 7 bytes
b=+-a # 5 bytes
b=-a # 4 bytes (b will be frozen)
\$\endgroup\$
1
  • 1
    \$\begingroup\$ b=a*1 is yet another way. \$\endgroup\$
    – vrintle
    Dec 12, 2020 at 10:18
3
\$\begingroup\$

Use ~// and !// to match against $_ (last line read)

These can be used with the -n or -p flags.

~/regex/ is equivalent to $_=~/regex/ returning the index of the first match or nil.

Try it online!

!/regex/ equivalent to $_!~/regex/ returning true or false.

Try it online!

Note that !// is weird as it doesn't call the ! method on the regex but only if you use a literal.

!/regex/ matches the expression.

r=/regex/;!r calls the ! (not) method and always returns false.

Also, !r=/regex/ gives an assignment in conditional warning so there's some syntactic sugar going on.

\$\endgroup\$
3
\$\begingroup\$

Filtering a specific class from an Array

Suppose, you've to choose only Integer from

a = [1, 2, "3", 4.0, [5], "6".to_sym, 7r]

then you can do,

a.keep_if{|x|x.kind_of? Integer} # 32 bytes
a.keep_if{|x|x.is_a? Integer} # 29 bytes
a.select{|x|x.is_a? Integer} # 28 bytes
a.select{|x|Integer===x} # 24 bytes
a.grep(Integer) # 15 bytes

Try it online!


You can use Numeric class in case you want numbers only.

\$\endgroup\$
2
\$\begingroup\$

Is it an array?

Originally from histocrat.

Rather than writing

a.class==Array  # 14 bytes

you could write

a.is_a? Array   # 13 bytes

or even

a!=[*a]         # 7 bytes
\$\endgroup\$
1
  • \$\begingroup\$ Alternative a*0==[] \$\endgroup\$
    – G B
    Mar 2 at 20:28
2
\$\begingroup\$

String split function uses $; or space by default; if input is space-seperated, you can omit the argument.

# Reads 3 integers from line 1 of input file
x,y,z=gets.split.map &:to_i

Or, you can configure the behaviour of ARGF with $/ (if there is only one line):

# Reads 3 integers from input file
# Not seperated by newlines, so in case of:
# 1 2
# 3 4
# You will get: 1 2 4 ("2\n3".to_i -> 2)
$/=' '
x,y,z=$<.map &:to_i

Due to the predictable behaviour of .to_i / .to_f, if you only have two numbers, you can use string.to_i(/.to_f) to get the first number and substring after the non-digit (even simpler for space) for the two values. There are tricks if you already know the constraints on the length of the numbers and especially so if the first one is length 1-2:

v=gets           # 1 2 / 11 22 / 11 22222222222222222222 / 123 4567890123456
v.to_i           # 1*    11*     11*                       123*
v[2,9].to_i      # 2*#$  22*#$   22222222                  3
v[2,99].to_i     # 2#$   22#$    22222222222222222222*#    3
v[2..-1].to_i    # 2#    22#     22222222222222222222*#    3
v[/\d+$/].to_i   # 2     22      22222222222222222222      4567890123456
v[~/ /,9].to_i   # 2$    22$     22222222                  45678901
v[~/ /,99].to_i  # 2     22      22222222                  4567890123456*
v[~/ /..-1].to_i # 2     22      22222222                  4567890123456

*: recommend to use in cases as specific than this
#: 1st int in string length is expected to be <=2 (-10 < x < 100)
$: 2nd int in string length is expected to be <=8 (if 1st int is length 1: <=9)
Note: [x,99] will take 99 characters, while ..-1 goes all the way to end of string. Because ~/ / matches the index of the space, this should be accounted for: [~/ /,9] will get length 8 int at most, as first char is the space
Note: If there are three+ numbers, please just use split or map

The above trick can be combined with $_:

p *gets.to_i..$_[2,9].to_i

Handy ways to repeat strings:

# aaaaabcbcbcbcaaaaa,aaaaa,aaaaa
$><<?a*5+'bc'*4+[?a*5]*3*?,

Shorthand for size of input line:

p (eval gets.chars*?+)/~/$/
# is equivalent to:
p (eval gets.chars*?+)/$_.size
\$\endgroup\$
2
\$\begingroup\$
  • To generate symmetric output, you can use a single range like (-x..x) and then map a function on the absolute value: (-8..8).map{|x|x.abs}

  • Use short-circuit evaluation of boolean expressions to avoid if...else: x>1&&s+=x

  • To check if an integer array a contains a number x, you can use a-[x]!=a, especially useful if x is an expression.

  • Defining a variable as an accumulator is often shorter than using map and then reduce.

  • Use string interpolation with % for alignment: "x%12s"%?x is shorter than ?x+11*" "+?x or for base conversions: "%b"%65 vs 65.to_s(2)

  • Use complex number for trigonometric functions: -1.argis PI, 1i.arg is PI/2, (a+b*1i).arg is Math.atan(b/a) and works even when a and b are integers.

\$\endgroup\$
1
  • \$\begingroup\$ If all you need is the absolute value (no other manipulation), (-8..8).map &:abs saves four characters (three if you need to surround the argument in parentheses for additional method chaining afterwards). \$\endgroup\$ Mar 2 at 13:31
1
\$\begingroup\$

To check if a is an Array, instead of doing:

a.kind_of?(Array)

you can do

a.to_s['[']

Won't work if a can be a string that contains [.

\$\endgroup\$
2
  • 5
    \$\begingroup\$ Or use the triple equals thing: Array===a ;) \$\endgroup\$
    – daniero
    Jun 26, 2015 at 23:33
  • 9
    \$\begingroup\$ How about a==[*a]? \$\endgroup\$
    – histocrat
    Nov 4, 2016 at 1:23
1
\$\begingroup\$

Default function arguments

Pre-defining your variables within a function can save you from calling particular arguments if you already know what they are and are calling the function multiple times.

def f(a,b,c)
  return a+b+c
end
puts f(f(3,3,3),f(3,3,3),f(3,3,3))
# => 27

One can shorten this quite easily:

def f(a,b=3,c=3)
  return a+b+c
end
puts f(f(3),f(3),f(3))
# => 27

If you do not assign any second or third arguments, then b=3 and c=3 automatically. One can shorten this further:

def f(a,b=a,c=a)
  return a+b+c
end
puts f(f(3))
# => 27

Note however that you'll need to give it at least one variable, the first one, and I'm not quite sure how you can avoid assigning a value to b while assigning a value to c.


See here for more argument methods.

\$\endgroup\$
1
\$\begingroup\$

The <=> (spaceship) operator compares two expressions and returns -1, 0, or 1. Applies to anything that can be compared with <, >, and ==.

Some example uses:

x<=>0 is equivalent to the sign function.

x.*x<=>0 is the absolute value of x.

\$\endgroup\$
0
\$\begingroup\$

Quick way to check if all items in a Ruby array are unique lists various ways to check whether everything in an array is unique. Unfortunately, none of them are methods that I can send to a receiver, I need to have my array on both sides of an operator. Lame!

Fortunately, uniq! returns nil if no changes are made, and returns the modified array if changes are made, meaning that it can be placed at the end of a pipeline to check for uniqueness;

x.method1.method2.method3.uniq! ? "not unique" : "everything is unique!"

Various other shouty methods have similar behaviour - returning the modified array if changes are made and nil if no changes are made.

\$\endgroup\$
1
2

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.