Basic tips
A list of short tips for easy small byte saves:
'g'
vs. ?g
for single-length strings
n.times{}
for looping from 0
to n-1
, inclusive
(a..b).map{}
vs. a.upto(b)
for looping from a
to b
, inclusive
a.upto(b-1)
vs. (a...b).map{}
for looping from a
to b-1
, inclusive
p(n)
vs. p n
, if precedence allows it (applies to most Ruby functions)
print
vs. $><<
to output without a newline
ARGV
vs. $*
to access arguments
a if b
vs. b&&a
for conditionals (b||a
for the inverse)
a.join
vs. a*''
for joining arrays (''
can be replaced with any delimiter)
a.uniq
vs. a|[]
vs. a&a
for removing repeated elements
s.index(/REGEX/)
vs. /REGEX/=~s
for getting the first occurrence of a regex
"#{$_}"
vs. "#$_"
for string interpolation of dollar variables
'foo'
vs. :foo
for shortening string literals (limited use)
['what','is','code','golf']
vs. %w(what is code golf)
for hardcoded string arrays
a=[0]
vs. *a=0
for initializing a 1-element array
a=[0,1,2]
vs. a=0,1,2
for initializing a multi-element array
x.map{_1.abs}
vs. x.map(&:abs)
(or x.map &:abs
) for simple method calls
Numbered parameters
This feature introduced in Ruby 2.7 allows you to drop the block parameter:
[1,2,3].map{|x|p x}
[1,2,3].map{p _1}
[[1,2],[3,4]].map{|x,y|p x+y}
[[1,2],[3,4]].map{p _1+_2}
Beware, however, as they have certain limitations in nested loops:
a.map{|x|b.map{|y|p x+y}} # OK
a.map{|x|b.map{p x+_1}} # OK
a.map{b.map{|y|p _1+y}} # NOT OK
a.map{b.map{p _1+_2}} # (VERY) NOT OK
Chaining expressions
Ruby has a strange feature that allows you to chain expressions in an obfuscated way. This allows for things like:
b^=3;a-=1|b;a&3
# ->
3&a-=1|b^=3
Magic variables
Certain predefined variables like $.
or $*
can be abused to omit the initialization of an extra variable:
# `$.` is always initialized to 0
n=0;9.times{p n+=_1}
9.times{p$.+=_1}
# `$*` (a.k.a. ARGV) is an empty list if no arguments are given
a=[];9.times{puts (a<<_1)*' '}
9.times{puts ($*<<_1)*' '}
Two other variables that are less used, but can still be useful are $/
, which is "\n"
by default, and $0
, which is "-"
by default (on code.golf).
Subscripting integers
To get the i
th bit of an integer, simply use the subscript operator:
n>>i&1
n[i]
Subscripts can be extended to work with ranges as well. For example, n[2..5]
gives the binary bits from 2
to 5
, as a decimal integer.
Splatting
Splatting is very flexible, and can be used in a multitude of ways. It is particularly good for grabbing the first/last/middle elements of array:
a,*b,c=*1..5 # a=1, b=[2,3,4], c=5
a,*,c=*1..5 # a=1, c=5
a,=*1..5 # a=1
*,c=*1..5 # c=5
You can also use it to print an array of numbers, each on its own line, with
p *a
Note that you would use puts
on an array of strings, but it doesn't require splatting:
# Functionally identical
puts *a
puts a
Operator methods
Operators can in fact be used as methods. For example, 1+2*3
may be written as 1.+(2.*(3))
. The precedence changes when using it is used this way, which sometimes saves a byte:
a*(b+c)
a.*b+c
String conversion
There are some neat ways to do string conversion, involving array multiplication. For example, say that you wanted to concatenate a number to a string:
"#{n}"+s
n.to_s+s
[n,p]*s
If you simply want to convert a number to a string, there is also a shorter way, but it involves an already-present string variable:
"#{n}"
n.to_s
[n]*''
[n]*s # Assumes a defined string variable `s`
filter
vs grep
By passing a lambda into grep
, it can behave like a filter
. This allows for a 2-byte save:
(1..n).filter{|n|}
(1..n).grep->n{}
The downside is that the precedence of the grep
version is pretty wacky, which makes it less useful.
Looping a constant amount
In general, n.times
or eval''*n
is the shortest way to loop n
times. But with clever use of predefined variables, shorter alternatives exist that loop a predetermined amount:
$:.map{} # 8
$:.max{} # 7
$:.sort{} # 16
$".map{} # 45
$".max{} # 44
$".sort{} # 506
# These two hold a block parameter, which contains the current index, like `.times`
$:.fill{} # 8 (same length as `9.times`, so what's the point?)
$".fill{} # 45
Note that the amount of iterations illustrated above are specific to code.golf. In the case of max
and sort
, the return value within the block must be a number, otherwise it will cause an error.
Obscuring 1.upto
A rather specific (and rare) example in which combining eval
and $.
can match the length of 1.upto
:
1.upto(n){pred(_1)&&p(_1)}
eval'p$.if pred($.+=1);'*n
It should be noted that there are situations where the latter is shorter, such as if the space between if
and pred
can be reused.
Packers
2:1 packer
Spoiler
Compressor
Note: the code size must be divisible by 2 for the compressor to work. If not, adding a trailing space should suffice.
puts CODE.force_encoding('utf-16le').encode('utf-8')
puts CODE.encode('utf-8', 'utf-16le')
Decompressor
Note: the overhead is 27 characters so your uncompressed code must be over 54 characters to get a benefit.
eval'...'.encode('utf-16le').b
3:1 packer
Spoiler
Compressor
Use the code in the Python tips (change [101, 102, 103]
to [97, 98, 99]
)
Decompressor
r="";"...".chars{|c|97.upto(99){r<<c.ord%_1+32}};eval r