Check CGSE Julia golfing tips
Imperative/functional/array
Julia syntax is flexible enough to allow different programming styles. A simple task as printing the numbers from 1 to 10 each on a new line can be written in several ways.
The classic imperative style reads
# 29 bytes
for i=1:10
println(i)
end
This can be easily golfed by removing all the indentation
# 24 bytes
for i=1:10;println(i)end
Note that no space is required between the closing bracket and end
.
Julia supports list comprehension as well, therefore we may write also
# 23 bytes
[println(i) for i=1:10]
A more functional approach would use map
as
# 23 bytes
map(i->println(i),1:10)
where ->
is used to define an anonymous function. The previous can be even shorter:
# 20 bytes
map(println,1:10)
However, julia really shines when we start broadcasting functions over arrays. Every function can be applied to all the elements of an array by postpending a .
, hence
# 14 bytes
println.(1:10)
Broadcasting
You can use the broadcast operator + pipe operator to save parentheses.
(x->x^2+1).([1,2,3])
=> [1,2,3].|>x->x^2+1
Comparison chaining
Julia supports comparison chaining like Python, so this also means you can sometimes save a character by replacing &&
with ≠
or similar:
x>0&&println(x) # 15 chars, 15 bytes
x>0≠println(x) # 14 chars, 16 bytes
Symbols instead of string literals
Using a symbol instead of a string literal can save one byte:
println("FizzBuzz")
println(:FizzBuzz)
Python-like operators
Julia uses C-like %
/
÷
, to get Python-like results use mod
and fld
.
Using first
When applying some transformation on every value, it can be beneficial to use a longer function and broadcasting:
first.(a)
a.|>n->n[1]
Getting the first value of a length-1 list
When getting the first value from a list of one element, list[]
saves one byte instead of list[1]
.
Grouping expressions
You can use (...;...)
as a way to group expressions that return the value of the last expression. Example: sum(j->(x+=...;b+=...;s),list)
to sum s
(x
and b
is ignored, but you can do calculations inside without counting towards that sum).
Case study
Here is a weird program to demonstrate julia's strangest features.
n<* =0n,10>n+1<*
1<println
What's going on? Even though we redefined *
, julia will still understand 0n
as 0*n
. Julia can use symbols as arguments when defining a function. We need a space between *
and =
. The symbol ~
is usually more useful, since no space is required after ~
.
Renaming *
to p
:
n<p=p(0,n),10>n+1<p
1<println
Next we can rewrite that chained comparison as 10>n+1 && n+1<p
, and rename <
to f
. Even though we have redefined <
, the rules of comparison chains still apply!
f(n,p)=p(0,n),10>n && f(n+1,p)
f(1,println)
So, the initial program defines a recursive function, which we use to iterate from 1 to 9, printing each number with a leading 0. Redefining a comparison operator in this fashion is often the shortest way to iterate in julia. Additionally passing auxiliary functions as symbols can save bytes in many situations.
!
and ~
These are useful operators to re-define as you don't need to specify their argument. For example, say we have the following
my_long_string = "this is a long string I want to split in words"
s(n)=split(n)
words = s(my_long_string)
this is equivalent to
! =split # note the space before =
words = !my_long_string
or
~=split
words = ~my_long_string
But it gets better as ~
is both a unary and binary operator. Hence
another_long_string = "this_time_I'm_using_undescore_as_a_delimiter"
~=split
words = ~(another_long_string,"_") # meh...
words = another_long_string~"_" # better
Finally, say you have two functions to rename, split
and join
, you can pack the assignment as
~=split;! =join # 15B
!,~=join,split # -1B
various
f=popfirst!(l)
f=popat!(l,1) # -1B
f,l...=l # -5B
e=pop!(l)
l...,e=l # -1B