62
\$\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\$
  • \$\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\$ – Mark Thomas Aug 30 '17 at 12:28

50 Answers 50

4
\$\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\$
4
\$\begingroup\$

On looping

while...end

If you need to break out of the loop, while condition;code;end will probably be shorter than loop{code;condition||break}.

The ; before end is not always required, eg. while condition;p("text")end

until c;...;end is equivalent to while !c;...;end and 1 byte shorter.

Note that in most cases code while condition and code until condition are significantly shorter as they don't require the end keyword and can often drop semicolons. Also, i+=1 while true is equivalent to i+=1while true and 1 byte shorter.

redo

When run, the redo command jumps back to the beginning of the block it's in.

When using redo in a lambda, you will have to move any setup variables to the arguments to avoid them being reset at every iteration (see examples).

recursion

Recursion can be shorter is some cases. For instance, if you're working on an array element by element, something like f=->s,*t{p s;t[0]&&f[*t]} can be shorter than the alternatives depending on the stuff.

Note that per the current consensus, if you're calling your function by name, you need to include the assignment (f=) in the byte count making all recursive lambdas 2 bytes longer by default.

eval

If you need to run some code n times, you can use eval"code;"*n.

This will concatenate code; n times and run the whole thing.

Note that in most cases you need to include a ; after your code.

Examples

A lambda to print all numbers from 1 to a inclusive:

->n{i=0;loop{p i+=1;i<n||break}} # 32 bytes
f=->n,i=1{i>n||p(i)&&f[n,i+1]}   # 30 bytes
->n,i=0{p(i+=1)<n&&redo}         # 24 bytes
->n{i=0;p i+=1while i<n}         # 24 bytes
->n{i=0;eval"p i+=1;"*n}         # 24 bytes
->n{n.times{|i|p i+1}}           # 22 bytes # thanks to @benj2240

In this case, since the end-point is defined (n), the n.times loop is the shortest.

The redo loop works because i+=1 modifies i and returns its new value and p(x) returns x (this is not true of print and puts).

Given a function g and a number n, find the first number strictly larger than n for which g[n] is truthy

->g,n{(n+1..1/0.0).find{|i|g[i]}} # 33 bytes
->g,n{loop{g[n+=1]&&break};n}     # 29 bytes
f=->g,n{n+=1;g[n]?n:f[g,n]}       # 27 bytes
->g,n{1until g[n+=1];n}           # 23 bytes
->g,n{g[n+=1]?n:redo}             # 21 bytes

In this case, with an unknown end-point, redo is the best option.

The (n+1..Inf) loop is equivalent to simply looping indefinitely but more verbose.

A 1 (or anything else) is required before the until keyword to complete the syntax, using a number allows you to drop a space.

The eval method is not viable in this case because there is neither a defined end-point nor an upper bound.

\$\endgroup\$
  • 1
    \$\begingroup\$ .times can often be even shorter for fixed loops, eg ->n{n.times{|i|p i+1}} for 22 bytes \$\endgroup\$ – benj2240 Mar 16 '18 at 15:07
4
\$\begingroup\$

New features in Ruby 2.7 (experimental)

Ruby 2.7 is in prerelease (as of 17 Jun 2019) and has some features that look great for golfing. Note that some of them might not make it into the final 2.7 release.

All changes in Ruby 2.7-preview1: https://github.com/ruby/ruby/blob/v2_7_0_preview1/NEWS

Numbered block parameters

This is my favorite. It lets you finally drop the |a,b| in a block:

%w[a b c].zip(1..) { puts @1 * @2 }
# => a
#    bb
#    ccc

Method reference operator: .:

.: is syntactic sugar for the .method method, e.g.:

(1..5).map(&1r.:/)
# => [(1/1), (1/2), (1/3), (1/4), (1/5)]

Pattern matching

I'm not sure how much use this will see in golf, but it's a great feature for which I only have a contrived example:

def div(*a)
  case a
    in [0, 0] then nil
    in [x, 0] if x > 0 then Float::INFINITY
    in [x, 0] then -Float::INFINITY
    in [x, y] then x.fdiv(y)
  end
end

div(-3, 0) # => -Infinity

The pattern matching syntax has lots of features. For a complete list, check out this presentation: https://speakerdeck.com/k_tsj/pattern-matching-new-feature-in-ruby-2-dot-7

This is also the feature most likely to change before 2.7 is finished; it even prints a warning when you try to use it, which you should heed:

warning: Pattern matching is experimental, and the behavior may change in future versions of Ruby!

Beginless Range: ..3

Analogous to the endless Range introduced in 2.6, it may or may not have much use in golfing:

%w[a v o c a d o].grep(..?m)
# => ["a", "c", "a", "d"]

Enumerable#tally to count like elements

This could be useful in golfing:

%w[a v o c a d o].tally
# => {"a"=>2, "v"=>1, "o"=>2, "c"=>1, "d"=>1}

Enumerable#filter_map to filter+map in one

(1..20).filter_map {|i| 10 * i if i.even? }
# => [20, 40, 60, 80, 100]

If the block returns nil or false the element will be omitted from the result.

Integer#[] takes a second argument or range:

You've long been able to get a specific bit from an integer with with subscript notation:

n = 77 # (binary 01001101)
n[3] # => 1

Now you can get the value of a range of bits by a second length argument or a range.

n = 0b01001101
n[2, 4] # => 3 (0011)
n[2..5] # => 3

Note that bits are indexed from least- to most-significant (right to left).

\$\endgroup\$
3
\$\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\$
  • 4
    \$\begingroup\$ Or you can just add -rbenchmark -rprime to the character/byte count. That's usual in choice golf. \$\endgroup\$ – nyuszika7h Jul 5 '14 at 12:21
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\$

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\$
  • 2
    \$\begingroup\$ like wise, (i+1) can become -~i \$\endgroup\$ – Cyoce Nov 4 '16 at 1:16
3
\$\begingroup\$

Save a byte when printing a word with symbols

This is a bit situational, but every byte counts!

puts"thing" # before
puts:thing  # after
          ^
\$\endgroup\$
3
\$\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\$
  • \$\begingroup\$ .clone is an alias of .dup ([*a] is still better for arrays) \$\endgroup\$ – Asone Tuhid Mar 1 '18 at 12:13
3
\$\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\$
2
\$\begingroup\$

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

\$\endgroup\$
  • 2
    \$\begingroup\$ -[p] saves another 2. \$\endgroup\$ – Doorknob Aug 25 '18 at 16:12
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\$
2
\$\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\$
  • 2
    \$\begingroup\$ eval a*?* to save another char. \$\endgroup\$ – Unihedron Jan 29 '18 at 6:31
  • \$\begingroup\$ also for case 2, using .tr' ',?* is way more concise than .split*?*... \$\endgroup\$ – Unihedron Jan 29 '18 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\$ – Asone Tuhid Mar 1 '18 at 12:10
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\$
  • 4
    \$\begingroup\$ Or use the triple equals thing: Array===a ;) \$\endgroup\$ – daniero Jun 26 '15 at 23:33
  • 6
    \$\begingroup\$ How about a==[*a]? \$\endgroup\$ – histocrat Nov 4 '16 at 1:23
1
\$\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)

\$\endgroup\$
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\$

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
\$\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\$
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\$
0
\$\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\$
0
\$\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\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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