I'm looking for tips for golfing in the R statistical language. R is perhaps an unconventional choice for Golf. However, it does certain things very compactly (sequences, randomness, vectors, and lists), many of the built-in functions have very short names, and it has an optional line terminator (;). What tips and tricks can you give to help solve code golf problems in R?
23 Answers
Some tips:
In R, it's recommended to use
<-
over=
. For golfing, the opposite holds since=
is shorter...If you call a function more than once, it is often beneficial to define a short alias for it:
as.numeric(x)+as.numeric(y) a=as.numeric;a(x)+a(y)
Partial matching can be your friend, especially when functions return lists which you only need one item of. Compare
rle(x)$lengths
torle(x)$l
Many challenges require you to read input.
scan
is often a good fit for this (the user ends the input by entring an empty line).scan() # reads numbers into a vector scan(,'') # reads strings into a vector
Coercion can be useful.
t=1
is much shorter thant=TRUE
. Alternatively,switch
can save you precious characters as well, but you'll want to use 1,2 rather than 0,1.if(length(x)) {} # TRUE if length != 0 sum(x<3) # Adds all the TRUE:s (count TRUE)
If a function computes something complicated and you need various other types of calculations based on the same core value, it is often beneficial to either: a) break it up into smaller functions, b) return all the results you need as a list, or c) have it return different types of values depending on an argument to the function.
As in any language, know it well - R has thousands of functions, there is probably some that can solve the problem in very few characters - the trick is to know which ones!
Some obscure but useful functions:
sequence
diff
rle
embed
gl # Like rep(seq(),each=...) but returns a factor
Some built-in data sets and symbols:
letters # 'a','b','c'...
LETTERS # 'A','B','C'...
month.abb # 'Jan','Feb','Mar'...
month.name # 'January','February','March'...
T # TRUE
F # FALSE
pi # 3.14...
Instead of importing a package with
library
, grab the variable from the package using::
. Compare the followings:library(splancs);inout(...) splancs::inout(...)
Of course, it is only valid if one single function is used from the package.
This is trivial but a rule of thumb for when to use @Tommy's trick of aliasing a function: if your function name has a length of
m
and is usedn
times, then alias only ifm*n > m+n+3
(because when defining the alias you spendm+3
and then you still spend 1 everytime the alias is used). An example:nrow(a)+nrow(b) # 4*2 < 4+3+2 n=nrow;n(a)+n(b) length(a)+length(b) # 6*2 > 6+3+2 l=length;l(a)+l(b)
Coercion as side-effect of functions:
instead of using
as.integer
, character strings can be coerced to integer using:
:as.integer("19") ("19":1)[1] #Shorter version using force coercion.
integer, numeric, etc. can be similarly coerced to character using
paste
instead ofas.character
:as.character(19) paste(19) #Shorter version using force coercion.
-
12\$\begingroup\$ Re: 3rd tip,
el("19":1)
is even shorter by one byte. \$\endgroup\$– JayCeCommented Aug 22, 2018 at 22:37
Some very specific golfing tips:
if you need to extract the length of a vector,
sum(x|1)
is shorter thanlength(x)
as long asx
is numeric, integer, complex or logical.if you need to extract the last element of a vector, it may be cheaper (if possible) to initialise the vector backwards using
rev()
and then callingx[1]
rather thanx[length(x)]
(or using the above tip,x[sum(x|1)]
) (ortail(x,1)
--- thanks Giuseppe!). A slight variation on this (where the second-last element was desired) can be seen here. Even if you can't initialise the vector backwards,rev(x)[1]
is still shorter thanx[sum(x|1)]
(and it works for character vectors too). Sometimes you don't even needrev
, for example usingn:1
instead of1:n
.(As seen here). If you want to coerce a data frame to a matrix, don't use
as.matrix(x)
. Take the transpose of the transpose,t(t(x))
.if
is a formal function. For example,"if"(x<y,2,3)
is shorter thanif(x<y)2 else 3
(though of course,3-(x<y)
is shorter than either). This only saves characters if you don't need an extra pair of braces to formulate it this way, which you often do.For testing non-equality of numeric objects,
if(x-y)
is shorter thanif(x!=y)
. Any nonzero numeric is regarded asTRUE
. If you are testing equality, say,if(x==y)a else b
then tryif(x-y)b else a
instead. Also see the previous point.The function
el
is useful when you need to extract an item from a list. The most common example is probablystrsplit
:el(strsplit(x,""))
is one fewer byte thanstrsplit(x,"")[[1]]
.(As used here) Vector extension can save you characters: if vector
v
has lengthn
you can assign intov[n+1]
without error. For example, if you wanted to print the first ten factorials you could do:v=1;for(i in 2:10)v[i]=v[i-1]*i
rather thanv=1:10:for(...)
(though as always, there is another, better, way:cumprod(1:10)
)Sometimes, for text based challenges (particularly 2-D ones), it's easier to
plot
the text rather thancat
it. the argumentpch=
toplot
controls which characters are plotted. This can be shortened topc=
(which will also give a warning) to save a byte. Example here.To take the floor of a number, don't use
floor(x)
. Usex%/%1
instead.To test if the elements of a numeric or integer vector are all equal, you can often use
sd
rather than something verbose such asall.equal
. If all the elements are the same, their standard deviation is zero (FALSE
) else the standard deviation is positive (TRUE
). Example here.Some functions which you would expect to require integer input actually don't. For example,
seq(3.5)
will return1 2 3
(the same is true for the:
operator). This can avoid calls tofloor
and sometimes means you can use/
instead of%/%
.The most common function for text output is
cat
. But if you needed to useprint
for some reason, then you might be able to save a character by usingshow
instead (which in most circumstances just callsprint
anyway though you forego any extra arguments likedigits
)don't forget about complex numbers! The functions to operate on them (
Re
,Im
,Mod
,Arg
) have quite short names which can occasionally be useful, and complex numbers as a concept can sometimes yield simple solutions to some calculations.for functions with very long names (>13–15 characters), you can use
get
to get at the function. For example, in R 3.4.4 with no packages loaded other than the default,get(ls(9)[501])
is more economical thangetDLLRegisteredRoutines
. This can also get around source code restrictions such as this answer. Note that using this trick makes your code R-version-dependent (and perhaps platform dependent), so make sure you include the version in your header so it can be reproduced if necessary.
-
1\$\begingroup\$
tail(v,1)
is the same length asrev(v)[1]
for the "last element of an array" golfing tip as well. \$\endgroup\$– GiuseppeCommented May 1, 2018 at 19:58 -
\$\begingroup\$
read.csv(t="a,b,c",,F)
is shorter thanel(strsplit("a,b,c",","))
. \$\endgroup\$– J.DoeCommented Sep 24, 2018 at 15:10 -
4\$\begingroup\$ An equivalent to
sum(x|1)
issum(1^x)
. When we have a shorthand forsum
, this can be useful due to operator precedence, as in"!"=sum;!1^x
. \$\endgroup\$ Commented Feb 4, 2020 at 22:30
- Abuse the builtins
T
andF
. By default, they evaluate toTRUE
andFALSE
, which can be automatically converted to numerics1
and0
, and they can be re-defined at will. This means that you don't need to initialize a counter (e.g.i=0
...i=i+1
), you can just useT
orF
as needed (and jump straight toF=F+1
later). - Remember that functions return the last object called and do not need an explicit
return()
call. Defining short aliases for commonly used functions is great, such as
p=paste
. If you use a function a lot, and with exactly two arguments, it is possible that an infixing alias will save you some bytes. Infixing aliases must be surrounded by%
. For example:`%p%`=paste
And subsequently
x%p%y
, which is 1 byte shorter thanp(x,y)
. The infixing alias definition is 4 bytes longer than the non-infixingp=paste
though, so you have to be sure it's worth it.
-
15\$\begingroup\$ You can use primitive functions and you save many bytes:
`+`=paste; x+y
\$\endgroup\$– MasclinsCommented May 4, 2017 at 9:53
You can assign a variable to the current environment while simultaneously supplying it as an argument to a function:
sum(x <- 4, y <- 5) x y
If you are subseting a
data.frame
and your condition depends on several of its columns, you can avoid repeating thedata.frame
name by usingwith
(orsubset
).d <- data.frame(a=letters[1:3], b=1:3, c=4:6, e=7:9) with(d, d[a=='b' & b==2 & c==5 & e==8,])
instead of
d[d$a=='b' & d$b==2 & d$c==5 & d$e==8,]
Of course, this only saves characters if the length of your references to the
data.frame
exceeds the length ofwith(,)
if...else
blocks can return the value of the final statement in which ever part of the block executes. For instance, instead ofa <- 3 if (a==1) y<-1 else if (a==2) y<-2 else y<-3
you can write
y <- if (a==1) 1 else if (a==2) 2 else 3
-
7\$\begingroup\$ Only caution about (1) is that when you do that you're passing it in by order not by named arguments. If
f <- function(a,b) cat(a,b)
, thenf(a <- 'A', b <- 'B')
is not the same asf(b <- 'B', a <- 'A')
. \$\endgroup\$ Commented Oct 15, 2013 at 13:11
Using if
, ifelse
, and `if`
There are several ways to do if-statements in R. Golf-optimal solutions can vary a lot.
The basics
if
is for control flow. It is not vectorized, i.e. can only evaluate conditions of length 1. It requireselse
to (optionally) return an else value.ifelse
is a function. It is vectorized, and can return values of arbitrary length. Its third argument (the else value) is obligatory.*`if`
is a function, with the same syntax asifelse
. It is not vectorized, nor are any of the return arguments obligatory.
* It's not technically obligatory; ifelse(TRUE,x)
works just fine, but it throws an error if the third argument is empty and the condition evaluates to FALSE
. So it's only safe to use if you are sure that the condition is always TRUE
, and if that's the case, why are you even bothering with an if-statement?
Examples
These are all equivalent:
if(x)y else z # 13 bytes
ifelse(x,y,z) # 13 bytes
`if`(x,y,z) # 11 bytes
Note that the spaces around else
are not required if you are using strings directly in the code:
if(x)"foo"else"bar" # 19 bytes
ifelse(x,"foo","bar") # 21 bytes
`if`(x,"foo","bar") # 19 bytes
So far, `if`
looks to be the winner, as long as we don't have vectorized input. But what about cases where we don't care about the else condition? Say we only want to execute some code if the condition is TRUE
. For one line of code alone, if
is usually best:
if(x)z=f(y) # 11 bytes
ifelse(x,z<-f(y),0) # 19 bytes
`if`(x,z<-f(y)) # 15 bytes
For multiple lines of code, if
is still the winner:
if(x){z=f(y);a=g(y)} # 20 bytes
ifelse(x,{z=f(y);a=g(y)},0) # 27 bytes
`if`(x,{z=f(y);a=g(y)}) # 23 bytes
There's also the possibility where we do care about the else condition, and where we want to execute arbitrary code rather than return a value. In these cases, if
and `if`
are equivalent in byte count.
if(x)a=b else z=b # 17 bytes
ifelse(x,a<-b,z<-b) # 19 bytes
`if`(x,a<-b,z<-b) # 17 bytes
if(x){z=y;a=b}else z=b # 22 bytes
ifelse(x,{z=y;a=b},z<-b) # 24 bytes
`if`(x,{z=y;a=b},z<-b) # 22 bytes
if(x)a=b else{z=b;a=y} # 22 bytes
ifelse(x,a<-b,{z=b;a=y}) # 24 bytes
`if`(x,a<-b,{z=b;a=y}) # 22 bytes
if(x){z=y;a=b}else{z=b;a=y} # 27 bytes
ifelse(x,{z=y;a=b},{z=b;a=y}) # 29 bytes
`if`(x,{z=y;a=b},{z=b;a=y}) # 27 bytes
Summary
Use
ifelse
when you have input of length > 1.If you're returning a simple value rather than executing many lines of code, using the
`if`
function is probably shorter than a fullif
...else
statement.If you just want a single value when
TRUE
, useif
.For executing arbitrary code,
`if`
andif
are usually the same in terms of byte count; I recommendif
mainly because it's easier to read.
-
3\$\begingroup\$ Nice! Very good comparisons, +1! \$\endgroup\$– BillywobCommented Oct 28, 2016 at 11:13
Save values in-line: Others have mentioned that you can pass values in-order and assign them for use elsewhere, i.e.
sum(x<- 1:10, y<- seq(10,1,2))
However, you can also save values inline for use in the same line!
For instance
n=scan();(x=1:n)[abs(x-n/2)<4]
reads from stdin
, creates a variable x=1:n
, then indexes into x
using that value of x
. This can sometimes save bytes.
Alias for the empty vector You can use
{}
as the empty vectorc()
as they both returnNULL
.Base Conversion For integer digits of
n
in base 10, usen%/%10^(0:nchar(n))%%10
. This will leave a trailing zero, so if that is important to you, usen%/%10^(1:nchar(n)-1)%%10
otn%/%10^(0:log10(n))
. This can be adapted to other bases, using0:log(n,b)
instead oflog10(n)
.Using
seq
and:
: Rather than using1:length(l)
(or1:sum(x|1)
), you can useseq(l)
as long asl
is alist
orvector
of length greater than 1, as it defaults toseq_along(l)
. Ifl
is numeric and could potentially be length1
,seq(!l)
orseq(a=l)
will do the trick.Additionally,
:
will (with a warning) use the first element of its arguments.Removing attributes Using
c()
on anarray
(ormatrix
) will do the same asas.vector
; it generally removes non-name attributes.Factorial Using
gamma(n+1)
is shorter than usingfactorial(n)
andfactorial
is defined asgamma(n+1)
anyway.Coin Flipping When needing to do a random task 50% of the time, using
rt(1,1)<0
is shorter thanrunif(1)<0.5
by three bytes.Extracting/Excluding elements
head
andtail
are often useful to extract the first/last few elements of an array;head(x,-1)
extracts all but the last element and is shorter than using negative indexing, if you don't already know the length:head(x,-1) x[-length(x)] x[-sum(x|1)]
-
1\$\begingroup\$ @J.Doe worthy of its own post, I think! Perhaps with a title of "alternatives to
rep
". Other tips questions have a restriction of one tip per answer, which I wholeheartedly endorse for this question, too! Also,1:n*0
is shorter thanIm(1:n)
by two bytes, which means your second trick can bex+0*-n:n
as well :-) \$\endgroup\$– GiuseppeCommented Sep 7, 2018 at 17:42 -
2\$\begingroup\$ @J.Doe Or even better,
!1:n
is also an array ofn
zeros depending on use case; credit to the MATL/MATLAB tips question (probably Luis Mendo) for that one, though. \$\endgroup\$– GiuseppeCommented Sep 7, 2018 at 17:43 -
\$\begingroup\$ Thanks, @Giuseppe! Can I suggest you create this post, as I don't want to take reputation from your good ideas. \$\endgroup\$– J.DoeCommented Sep 7, 2018 at 17:48
-
\$\begingroup\$ @J.Doe oh, I don't mind. Always good to have other R golfers getting more visibility; I think it's fair to say I'm a pretty known entity at this point! You've been going around suggesting quite impressive improvements, so take the rep (pun not intended) and keep up the good work golfing :-) \$\endgroup\$– GiuseppeCommented Sep 7, 2018 at 17:51
-
1\$\begingroup\$ not
(log(i,b)%/%1):0)
instead offloor(log(n,b))+1
? \$\endgroup\$ Commented Feb 24, 2019 at 2:10
Implicit type conversion
The functions as.character
, as.numeric
, and as.logical
are too byte-heavy. Let's trim them down.
Conversion to logical from numeric (4 bytes)
Suppose x
is a numeric vector. Using the logical not operator !
implicitly recasts the numeric to a logical vector, where 0
is FALSE
and nonzero values are TRUE
. !
then inverts that.
x=!x
x=0:3;x=!x
returns TRUE FALSE FALSE FALSE
.
Conversion to character from numeric or logical (7 bytes)
This is a fun one. (From this tweet.)
x[0]=''
R sees that you're updating the vector x
with ''
, which is of class character
. So it casts x
into class character
so it's compatible with the new data point. Next, it goes to put ''
in the appropriate place... but the index 0
doesn't exist (this trick also works with Inf
, NaN
, NA
, NULL
, and so on). As a result, x
is modified in class only.
x=1:3;x[0]=''
returns "1" "2" "3"
, and x=c(TRUE,FALSE);x[0]=''
returns "TRUE" "FALSE"
.
If you have a character object already defined in your workspace, you can use that instead of ''
to save a byte. E.g., x[0]=y
!
Conversion to character from numeric or logical under certain conditions (6 bytes)
J.Doe pointed out in the comments a six-byte solution:
c(x,"")
This works if x
is atomic and if you intend to pass it to a function which requires an atomic vector. (The function may throw a warning about ignoring elements of the argument.)
Conversion to numeric from logical (4 bytes)
You can use the funky indexing trick from above (e.g. x[0]=3
), but there's actually a quicker way:
x=+x
The positive operator implicitly recasts the vector as a numeric vector, so TRUE FALSE
becomes 1 0
.
-
\$\begingroup\$ Your last trick could be
x=+x
to keepTRUE
as1
. \$\endgroup\$– GiuseppeCommented Apr 18, 2018 at 9:16 -
\$\begingroup\$ @Giuseppe Oh, duh, of course! Thanks, updated now. \$\endgroup\$ Commented Apr 19, 2018 at 7:37
-
\$\begingroup\$ Conversion from numeric or logical to character. You can use
c(x,"")
ifx
is atomic, provided that you're then going to usex
in a function that only cares about the first element (it may complain). This is 1 byte cheaper thanx[0]="";
. \$\endgroup\$– J.DoeCommented Sep 5, 2018 at 12:01
Do-while loops in R
Occasionally, I find myself wishing R had a do-while
loop, because:
some_code
while(condition){
some_code # repeated
}
is far too long and very un-golfy. However, we can recover this behavior and shave off some bytes with the power of the {
function.
{
and (
are each .Primitive
functions in R.
The documentation for them reads:
Effectively,
(
is semantically equivalent to the identityfunction(x) x
, whereas{
is slightly more interesting, see examples.
and under Value,
For
(
, the result of evaluating the argument. This has visibility set, so will auto-print if used at top-level.For
{
, the result of the last expression evaluated. This has the visibility of the last evaluation.
(emphasis added)
So, what does this mean? It means a do-while loop is as simple as
while({some_code;condition})0
because the expressions inside {}
are each evaluated, and only the last one is returned by {
, allowing us to evaluate some_code
before entering the loop, and it runs each time condition
is TRUE
(or truthy). The 0
is one of the many 1-byte expressions that forms the "real" body of the while
loop.
Additionally, this can be combined with print
(which invisibly returns its argument) to repeatedly print an intermediate result until (and including) it reaches 0
. An example of this can be found here.
Abuse
outer
to apply an arbitrary function to all the combinations of two lists. Imagine a matrix with i, j indexed by the first args, then you can define an arbitrary function(i,j) for each pair.Use
Map
as a shortcut formapply
. My claim is thatmapply
is cheaper than a for loop in situations where you need to access the index. Abuse the list structure in R.unlist
is expensive.methods::el
allows you to cheaply unlist the first element. Try to use functions with list support natively.Use
do.call
to generalize function calls with arbitrary inputs.The accumulate args for
Reduce
is extremely helpful for code golf.Writing to console line by line with
cat(blah, "\n")
is cheaper withwrite(blah, 1)
. Hard coded strings with "\n" may be cheaper in some situations.If a function comes with default arguments, you can use function(,,n-arg) to specify the n-th argument directly. Example:
seq(1, 10, , 101)
In some functions, partial argument matching is supported. Example:seq(1, 10, l = 101)
.If you see a challenge involving string manipulation, just press the back button and read the next question.
strsplit
is single handily responsible for ruining R golf.
Now for some newly discovered tips from 2018
A[cbind(i,j)] = z
can be a good way to manipulate matrices. This operation is very byte efficient assuming you designi, j, z
as vectors with correct lengths. You may save even more by calling the actual index/assign function"[<-"(cbind(i,j), z)
. This way of calling returns the modified matrix.Use a new line instead of
\n
for line breaks.Squeezing down line counts can save you bytes. In-line assignment
lapply(A<-1:10,function(y) blah)
and function args assignmentfunction(X, U = X^2, V = X^3)
are ways of doing this.So
"[<-"
is a function in R (and is related to my ancient question on SO)! That is the underlying function responsible for operations such asx[1:5] = rnorm(5)
. The neat property of calling the function by name allows you to return the modified vector. In order words"[<-"(x, 1:5, normr(5))
does almost the same thing as the code above except it returns the modified x. The related "length<-", "names<-", "anything<-" all return modified output
-
5\$\begingroup\$ I think using
"[<-"
is worthy of its own "Tips" answer, as it will return the modified array/matrix/whatever. \$\endgroup\$– GiuseppeCommented Mar 15, 2018 at 15:05 -
\$\begingroup\$ Caution that the function given to
outer
has to be vectorized, i.e., it gets called just once with a vector (in column-major order) of alli
s as the first argument and a vector of allj
s as the second, rather than getting called once for eachi
,j
pair. You can useVectorize()
on an arbitrary function, but that's expensive. Also remember thatx %o% y
is short forouter(x, y, `*`)
. \$\endgroup\$– EonemaCommented Dec 14 at 4:48
Change the meaning of operators
R operators are just functions that get special treatment by the parser. For example <
is actually a function of two variables. These two lines of code do the same thing:
x < 3
`<`(x, 3)
You can reassign another function to an operator, and the parser will still do it's thing, including respecting operator precedence, but the final function call will be the new one rather than the original. For example:
`<`=rep
now means these two lines of code do the same thing:
rep("a", 3)
"a"<3
and precedence is respected, resulting in things like
"a"<3+2
#[1] "a" "a" "a" "a" "a"
See for example this answer, and also the operator precedence page. As a side effect, your code will become as cryptic as one written in a golf language.
Some operators like +
and -
can accept either one or two parameters, so you can even do things like:
`-`=sample
set.seed(1)
-5 # means sample(5)
#[1] 2 5 4 3 1
5-2 # means sample(5, 2)
#[1] 5 4
See for example this answer.
See also this answer for using [
as a two-byte, three-argument operator.
-
2\$\begingroup\$ This is a comment on rturnbull's tips but I think we need to start enforcing a "one tip per answer" rule because it's so freakin' hard to find the one I need when I come here. \$\endgroup\$– GiuseppeCommented May 16, 2018 at 16:24
-
1\$\begingroup\$ also depending on the precedence of the operators, you can do some funky stuff that might help; like
<
has lower precedence than+
, but*
has higher precedence than+
so you could potentially chain them together! \$\endgroup\$– GiuseppeCommented May 16, 2018 at 16:27 -
1\$\begingroup\$ @Giuseppe you know what I tried to find before posting and couldn't find it. Thanks for pointing it out. I'm planning to add more details on operator precedence with examples as I start using this trick more and more. \$\endgroup\$– JayCeCommented May 16, 2018 at 16:28
-
4\$\begingroup\$ Here's a fun one: if you bind
?
topaste
or some other function that can take two arguments, the precedence order means you can still use inline assignments viaa<-b?d<-e
. \$\endgroup\$– J.DoeCommented Oct 2, 2018 at 9:40 -
2\$\begingroup\$ You should add
[
as a three-element alias (that's two bytes); I often find it helpful for things likeouter
(and consistently forget about it!), although of course you need to ensure you don't actually need to use[
. It would also likely be helpful to link to the operator precedence page to help with alias selection. \$\endgroup\$– GiuseppeCommented May 2, 2019 at 19:48
Scenarios where you can avoid paste(...,collapse="")
and strsplit
These are a pain in the usual string challenges. There are some workarounds.
Reduce(paste0,letters)
for -5 bytes frompaste0(letters,collapse="")
A 2-byte golf where you have a list containing two vectors
c(1,2,3)
andc(4,5,6)
and want to concatenate them element-wise to a string"142536"
. Operator abuse gives youp=paste0;"^"=Reduce;p^p^r
which saves two bytes on the usualpaste0
call.Instead of
paste0("(.{",n,"})")
to construct (eg) a regex for 20 bytes, consider a regex in a regex:sub(0,"(.{0})",n)
for 17 bytes.
Sometimes (quite often, actually) you'll need to iterate through a vector of characters or strings, or split a word into letters. There are two common use cases: one where you need to take a vector of characters as input to a function or program, and one where you know the vector in advance and need to store it in your code somewhere.
a. Where you need to take a string as input and split it into either words or characters.
If you need words (including characters as a special case):
If a newline
0x10
(ASCII 16) separating the words is OK,x=scan(,"")
is preferred to wrapping your code infunction(s,x=el(strsplit(s," ")))
.If the words can be separated by any other whitespace, including multiple spaces, tabs, newlines etc, you can use @ngm's double scan trick:
x=scan(,"",t=scan(,""))
. This gives the scanned in string toscan
as thetext
arg and separates it by whitespace.The second argument in
scan
can be any string so if you have created one, you can recycle it to save a byte.
If you need to turn an input string into a vector of characters:
x=el(strsplit(s,""))
is the shortest general solution. Thesplit
argument works on anything of length zero includingc()
,{}
etc so if you happen to have created a zero length variable, you could use it to save a byte.If you can work with the ASCII character codes, consider
utf8ToInt
, sinceutf8ToInt(x)
is shorter than thestrsplit
call. To paste them back together,intToutf8(utf8ToInt(x))
is shorter thanReduce(paste0,el(strsplit(x,"")))
.If you need to split arbitrary strings of numbers like
"31415926535"
as input, you can useutf8ToInt(s)-48
to save 3 bytes onel(strsplit(s,""))
, provided you can use the integer digits instead of the characters, as is often the case. This is also shorter than the usual recipe for splitting numbers into decimal digits.
b. Where you need a fixed vector of either words or characters in advance.
If you need a vector of single characters that have some regular pattern or are in alphabetic order, look at using
intToUtf8
orchartr
applied to a sequence viaa:b
or on the built in letters setsletters
orLETTERS
. The pattern language built intochartr
is especially powerful.For 1 to 3 characters or words,
c("a","b","c")
is the only general shortest solution.If you need a fixed vector of between 4 and 10 non whitespace characters or words, use
scan
withstdin
as thefile
arg:
f(x=scan(,""))
q
w
e
r
t
y
u
If
scan
fromstdin
isn't possible, for 6 or more non whitespace characters or words, usescan
with thetext
argumentscan(,"",t="a b c d e f")
.If you need a vector of (a) 6 or more characters of any type or (b) 10 or more non-whitespace characters ,
strsplit
viax=el(strsplit("qwertyuiop",""))
is probably the way to go.You may be able to get away with the following quote trick:
quote(Q(W,E,R,T,Y))
, which creates that expression. Some functions likestrrep
, andgrep
will coerce this to a vector of strings! If you do, this is good for any length of word or character vector from 3 to 11.There's no good reason to use
strsplit
on words viax=el(strsplit("q w e r t y"," "))
. It always loses toscan(,"",t="q w e r t y"))
by a fixed overhead of 5 bytes.
Here's a table of the byte counts used by each approach to read in a vector of single characters of length n
. The relative ordering within each row is valid for characters or words, except for strsplit
on ""
which only works on characters.
| n | c(...) | scan | scan | strsplit | quote |
| | |+stdin|+text | on "" | hack |
| | | | | CHAR ONLY| |
|----|--------|------|------|----------|-------|
| 1 | 3 | 11 | 15 | 20 | 8 |
| 2 | 10 | 13 | 17 | 21 | 11 |
| 3 | 14 | 15 | 19 | 22 | 13 |
| 4 | 18 | 17 | 21 | 23 | 15 |
| 5 | 22 | 19 | 23 | 24 | 17 |
| 6 | 26 | 21 | 25 | 25 | 19 |
| 7 | 30 | 23 | 27 | 26 | 21 |
| 8 | 34 | 25 | 29 | 27 | 23 |
| 9 | 38 | 27 | 31 | 28 | 25 |
| 10 | 42 | 29 | 33 | 29 | 27 |
| 11 | 46 | 31 | 35 | 30 | 29 |
| 12 | 50 | 33 | 37 | 31 | 31 |
c. If you need to input text as a character matrix, a few recipes that seem short are
s="hello\nworld\n foo"
# 43 bytes, returns "" padded data frame
# If lines > 5 are longer than lines <= 5, wraps around and causes error
read.csv(t=gsub("(?<=.)(?=.)",",",s,,T),,F)
# 54 bytes with readLines(), "" padded matrix
sapply(p<-readLines(),substring,p<-1:max(nchar(p)),p))
# plyr not available on TIO
# 58 bytes, returns NA padded matrix, all words split by whitespace
plyr::rbind.fill.matrix(Map(t,strsplit(scan(,"",t=s),"")))
# 61 bytes, returns NA padded matrix
plyr::rbind.fill.matrix(Map(t,(a=strsplit)(el(a(s,"\n")),"")))
-
2\$\begingroup\$
scan
has atext
argument, which is more competitive thanel(strsplit(x," "))
if you only need strings! Try it online! As opposed to your last suggestion ofread.csv
. \$\endgroup\$– GiuseppeCommented Oct 5, 2018 at 18:13 -
\$\begingroup\$ If you just want characters, your call of
scan
is better up to 5 characters,el(strsplit(x,""))
is more competitive thanscan
for 6 or more. Try it online! I haven't yet found a good use forread.csv
, but maybe it would be useful if you needed a data table for some reason? \$\endgroup\$– J.DoeCommented Oct 5, 2018 at 18:20 -
\$\begingroup\$ I've never found a use for a
data.frame
but maybe we need to find / create a challenge where it would be helpful! Maybe adplyr
stylegroup_by()
andsummarize()
type of manipulation? IDK. \$\endgroup\$– GiuseppeCommented Oct 5, 2018 at 18:26 -
\$\begingroup\$ And for reading in strings
scan(,"")
still seems better? Try it online! \$\endgroup\$– J.DoeCommented Oct 5, 2018 at 18:26 -
\$\begingroup\$ Yeah for sure, although if you interpret an input format strictly as ngm does here then double
scan
is handy. \$\endgroup\$– GiuseppeCommented Oct 5, 2018 at 18:28
Some basic concepts but should be somewhat useful:
In control flow statements you can abuse that any number not equal to zero will be evaluated as
TRUE
, e.g.:if(x)
is equivalent toif(x!=0)
. Conversely,if(!x)
is equivalent toif(x==0)
.When generating sequences using
:
(e.g.1:5
) one can abuse the fact that the exponentiation operator^
is the only operator that has precedence over the:
-operator (as opposed to+-*/
).1:2^2 => 1 2 3 4
which saves you two bytes on the parentheses that you would normally have to use in case you wanted to e.g. loop over the elements of an
n x n
matrix (1:n^2
) or any other integer that can be expressed in a shorter manner using exponential notation (1:10^6
).A related trick can of course be used on the vectorized operations as well
+-*/
, although most commonly applicaple to+-
:for(i in 1:(n+1)) can instead be written as for(i in 0:n+1)
This works because
+1
is vectorized and adds1
to each element of0:n
resulting in the vector1 2 ... n+1
. Similarly0:(n+1) == -1:n+1
saves you one byte as well.When writing short functions (that can be expressed on one line), one can abuse variable assignment to save two bytes on the enclosing curly brackets
{...}
:f=function(n,l=length(n))for(i in 1:l)cat(i*l,"\n") f=function(n){l=length(n);for(i in 1:l)cat(i*l,"\n")}
Note that this might not always comply to rules of certain challenges.
Alternatives to rep()
Sometimes rep()
can be avoided with the colon operator :
and R's vector recycling.
For repeating
n
zeroes, wheren>0
,0*1:n
is 3 bytes shorter thanrep(0,n)
and!1:n
, an array ofFALSE
, is 4 bytes shorter, if the use case allows it.To repeat
x
n
times,x+!1:n
is 2 bytes shorter thanrep(x,n)
. Forn
ones, use!!1:n
if you can use an array ofTRUE
.To repeat
x
2n+1
times, wheren>=0
,x+0*-n:n
is 4 bytes shorter thanrep(x,2*n+1)
.The statement
!-n:n
will give aTRUE
flanked on both sides byn
FALSE
. This can be used to generate even numbers of characters in calls tointToUtf8()
if you remember that a zero is ignored.
Modular arithmetic can be useful. rep
statements with the each
argument can sometimes be avoided using integer division.
To generate the vector
c(-1,-1,-1,0,0,0,1,1,1)
,-3:5%/%3
is 5 bytes shorter thanrep(-1:1,e=3)
.To generate the vector
c(0,1,2,0,1,2,0,1,2)
,0:8%%3
saves 4 bytes onrep(0:2,3)
.Sometimes nonlinear transformations can shorten sequence arithmetic. To map
i in 1:15
toc(1,1,3,1,1,3,1,1,3,1,1,3,1,1,3)
inside a compound statement, the obvious golfy answer is1+2*(!i%%3)
for 11 bytes. However,3/(i%%3+1)
is 10 bytes, and will floor to the same sequence, so it can be used if you need the sequence for array indexing.
-
1\$\begingroup\$ I don't know if you're still around, but you can use
a%x%!!1:n
in place ofrep(a,e=n)
for arbitrary numerica
. \$\endgroup\$– GiuseppeCommented Mar 11, 2022 at 14:18
R 4.1.0: new shorthand function syntax
I just found out the very new release R 4.1.0 comes with some new syntax. https://www.r-bloggers.com/2021/05/new-features-in-r-4-1-0/
The new shorthand syntax \(arglist) expr
performs exactly as function(arglist) expr
and makes writing R functions 7 bytes shorter:
\(x,y)x+y
instead of
function(x,y)x+y
There's also the new native pipe |>
, but this isn't shorter than just using parentheses (and the pipe has to be used with parentheses, unlike magrittr.)
When you do need to use a function, use pryr::f()
instead of function()
.
Example:
function(x,y){x+y}
is equivalent to
pryr::f(x,y,x+y)
or, even better,
pryr::f(x+y)
Since If there is only one argument, the formals are guessed from the code.
-
1\$\begingroup\$ Unless you can get it down to one argument (like in the third example), this isn't a golf, for
function(x,y){x+y}
can be written asfunction(x,y)x+y
for the same bytecount aspryr::f(x,y,x+y)
but with more readability. \$\endgroup\$ Commented Mar 1, 2018 at 23:20 -
1
-
3\$\begingroup\$ Note that this is obsolete now that R 4.1 has new shorthand function syntax
\(arglist) expr
. \$\endgroup\$– qwrCommented Dec 29, 2021 at 9:07 -
\$\begingroup\$ Actually writing functions like
pryr::f(x+y)
isn't obsolete yet \$\endgroup\$– qwrCommented Jan 2, 2022 at 23:14
Surviving challenges involving strings
As mentioned in another answer, unlist(strsplit(x,split="")
and paste(...,collapse="")
can be depressing. But don't just walk away from these, there are workarounds!
utf8ToInt
converts a string to a vector,intToUtf8
does the reverse operation. You're getting a vector ofint
, not a vector ofchar
but sometimes this is what you're looking for. For instance to generate a list of-
, better useintToUtf8(rep(45,34))
thanpaste(rep("-",34),collapse="")
gsub
is more useful than other function of thegrep
family when operating on a single string. The two approaches above can be combined as in this answer which benefited from the advice of ovs, Giuseppe and ngm.- Choose a convenient I/O format as in this answer taking input as lines of text (without quotes) or this one taking a vector of chars. Check with the OP when in doubt.
- As pointed out in the comments,
<
compares strings lexicographically as one would expect.
-
1\$\begingroup\$
intToUtf8
also has a second argumentmultiple = FALSE
which will convert fromint
s to individual characters (length-one strings) rather than a single string if set toTRUE
. \$\endgroup\$– GiuseppeCommented Oct 17, 2018 at 16:58 -
\$\begingroup\$ Also, starting in 3.5.0, there's a third argument
allow_surrogate_pairs = FALSE
, but I don't know what it does; the docs say something about reading two-bytes as aUTF-16
but I barely know whatUTF-8
is so I'll just ignore it until someone else finds a way to golf with it. \$\endgroup\$– GiuseppeCommented Oct 17, 2018 at 16:59
Know your apply
functions
- As already mentioned, use
Map
instead ofmapply
(and possiblylapply
, depending on whether you want vectorisation also on further arguments to the function). - Use
sapply
when you want the result simplified when possible. - Use
apply
on matrices to apply a function rowwise or columnwise. But remember of functions likecolSums
andcolMeans
. - Use
tapply(x,y,f)
instead ofsapply(split(x,y),f)
orMap(f,split(x,y))
- but you may want to usesimplify=FALSE
in the second case sometimes.
Remember that sometimes the function you're using can apply another function to the results (through the FUN
argument), e.g. combn
.
Numeric Tricks
Ignoring precision error:
x%/%1
instead offloor(x)
(integer part for positive x)x%%1
instead ofx-floor(x)
(fractional part for positive x)!x%%1
,x%%1
to check if integer or notx^.5
instead ofsqrt(x)
gamma(x+1)
instead offactorial(x)
Constants: \$\pi\$ pi
, \$e\$ exp(1)
, \$1/e\$ exp(-1)
(if you need these for some reason) \$1 / \sqrt{2\pi}\$ dnorm(0)
, \$\gamma\$ -digamma(1)
-
4\$\begingroup\$ I would note that
x%/%1
is not integer part for negativex
. \$\endgroup\$– GiuseppeCommented Jan 18 at 17:53
Some ways to find the first non-zero element of an array.
If it has a name x
:
x[!!x][1]
Returns NA
if no non-zero elements (including when x
is empty, but not NULL
which errors.)
Anonymously:
Find(c, c(0,0,0,1:3))
Returns NULL
if no non-zero elements, or empty or NULL
.
-
\$\begingroup\$ This will return
NA
if all elements ofx
are zero, I believe, so use it with caution! \$\endgroup\$– GiuseppeCommented Aug 22, 2018 at 23:40 -
-
\$\begingroup\$
Find
is also a little safer as it works onNULL
, as long as nothing else needs to happen to the result, in which case I'm not sure if returningNA
orNULL
is safer. \$\endgroup\$– ngmCommented Aug 23, 2018 at 20:36 -
\$\begingroup\$ oh that's right. the issue with returning NULL is errors... in the version comparison question I first tried
sign(Find(c,w))
which caused errors - had to doFind(c,sign(w))
to get it not to error. I think both ways have their uses. \$\endgroup\$– JayCeCommented Aug 23, 2018 at 20:42
all
and any
on lists
Apparently, all
and any
both accept lists of depth 1 and work just as well as on vectors (with a warning).
So, if you used all(sapply(list,function))
in your code, in most places you may replace it with all(Map(function,list))
for -3 bytes.
I didn't know that before the answer (linked above) by @Dominic van Essen.
Tips for restricted source challenges :
Characters in R literals constants can be replaced by hex codes, octal codes and unicodes.
e.g. the string
"abcd"
can be written :# in octal codes "\141\142\143\144" # in hex codes "\x61\x62\x63\x64" # in unicodes "\u61\u62\u63\u64" # or "\U61\U62\U63\U64"
We can also mix characters with octal/hex/unicode and use some oct codes and some hex codes together, as long as unicode characters are not mixed with octal/hex e.g. :
# Valid "a\142\x63\x64" # Valid "ab\u63\U64" # Error: mixing Unicode and octal/hex escapes in a string is not allowed "\141\142\x63\u64"
See the end of this section for further details.
Since functions can be written using string literals, e.g.
cat()
can be written alternatively :'cat'() "cat"() `cat`()
we can use octal codes, hex codes and unicode for function names as well :
# all equal to cat() "\143\141\164"() `\x63\x61\x74`() '\u63\u61\u74'() "ca\u74"()
with the only exception that unicode sequences are not supported inside backticks ``
Round brackets can be avoided abusing operators e.g. :
cat('hello') # can be written as `+`=cat;+'hello'
An application of all the three tricks can be found in this answer
-
2\$\begingroup\$ Also, numbers can be written in hexadecimal:
0xB
and0xb
return11
(no need for backticks or quotes). \$\endgroup\$ Commented Sep 16, 2019 at 7:14 -
1
Searching datasets
R's builtin datasets can be a trove, in particular for restricted-source challenges, but searching through them is not easy.
For instance, for this answer, creating the value 100
thanks to a dataset was useful. It could be that the value was there as is, or as the sum of a column, or as the size of the dataset.
Here is the code I used; credit goes to Giuseppe in the chat:
x = data(package = "datasets")
res = x$results
for(dataset_name in paste0(res[,1],"::",res[,3])){
curr = try(eval(parse(t=dataset_name)), T)
if(length(curr) == 100) print(c("Length found in", dataset_name))
if(100 %in% unlist(curr)) print(c("Value found in", dataset_name))
try(if(try(sum(curr),T) == 100) print("Sum found in", dataset_name), T)
}
This searches all the datasets which are included by default; you can search datasets in other packages by changing the first line to x <- data(package = .packages(all.available = TRUE))
.
You will need to make lavish use of try
to avoid issues with different types of data. Remember that some datasets are vectors and not matrices or data frames. Other functions to try instead of sum
include min
, max
, mean
, prod
, sd
and var
.