A 2:1
packer
For chars scoring, it can reduce code by a factor of two.
Spoiler
Decompressor
Note: the overhead is 29 characters so your uncompressed code must be at least 60 characters to get a benefit.
Code.eval_string<<"..."::utf16>>
Compressor (in Python)
Note: the code size must be divisible by 2 for the compressor to work. If not, adding a trailing space should suffice.
print('CODE'.encode().decode('utf-16be'))
Try it online!
Integer Constants
The ? operator gets the unicode codepoint for a character. So, integer literals between 100 and 1114111 can be replaced by a ? followed by the character the unicode codepoint represents to save characters and sometimes bytes as well.
Conditionals
It is always shorter to use the boolean operators &&
and ||
instead of if
and else
.
Recursive Functions
The usual way to define a recursive function is:
defmodule,M do: def f(x),do: x<1&&0||x+f(x-1)
However, by making the function a parameter to itself this can be rewritten as an anonymous function like this:
f=fn f,x->x<1&&0||x+f.(f,x-1)end
Or if you use the shorter syntax for anonymous functions you can write it like this:
f=& &2<1&&0||&2+&1.(&1,&2-1)
Modules
Modules can be imported to use function names without the corresponding module name. This saves bytes with 3 or more uses of Enum, List, or String. Also, modules are symbols so in very specific circumstances this can be helpful. If you use exactly two functions from the String module, the binding s=String
saves one byte.
Looping
Using list comprehensions is sometimes shorter than mapping over a range. Also, remember all of the features comprehensions provide. You can do nested loops, bind variables, and use filters.
Enum.map 0..99,& &1>5&&IO.puts&1
for n<-0..99,n>5,do: IO.puts n
Mapping has an advantage if you are already importing Enum
, though you have the disadvantage of not being able to nest function comprehensions using &
. List also return a list of values, so they can be used within other maps as well.
Enum.map 0..99,&Enum.sum(for(n<-0..&1,do: f.(&1)))
Since Elixir doesn't allow mutable variables, you can abuse scan
and reduce
in order to pass a changing value in a loop.
Enum.scan 0..9,0,fn _,n->IO.puts n
f.(n)
end
Multiple "variables" may need a tuple or list structure to be passed along e.g. {x,y}
or [x|y]
. You can also leave out the initial value and the scan will start with the first value of the enumerable. List comprehensions also have a reduce:
option, though it is quite verbose.
Erlang
Elixir has access to all of Erlang's functions (documented here), some of which do not have equivalent functions in Elixir's standard library. These are accessible through symbols. Some good modules are:
:io
, with :io.format
allows formatting strings, (:io_lib.format
returns the string rather than printing it)
:math
with a lot of trig functions
:string
mostly works with arrays or charlists but has some unexpected benefits. For example, :string.chars
is one byte shorter than List.duplicate
with a single character and also has the option of adding a terminating sublist. :string.chr
and :string.str
are shorter alternatives to finding the first indexes of elements or sublists of lists (along with their find last alternatives rchr
and rstr
)
Charlists
Use charlists over strings when appropriate. List.duplicate
is shorter than String.duplicate
for example. Both display the same when printed using IO.puts
. While to_charlist
is available in the Kernel and works on most things, it is usually shorter (though slower) to interpolate into single quotes, e.g. '#{99}'. Symbols are printed and interpolated as normal, so may save a byte over a normal string, ('a'
vs :a
)
Function Call Syntax
Remember all of the ways you can call functions.
f(&1,g(&2,&3))
vs.
f&1,&2|>g&3
Blocks
Blocks can be created in parenthesis and execute multiple statements returning the last value in the block. This is very useful for executing side effects in anonymous functions or assigning variables (though they are only within the scope of the block). Note that you can't convert a block on its own to an anonymous function. You can also do this within a string interpolation ('#{&1;&2}'
)
Enum.map 0..9,&IO.puts(&1)&&2*&1 # Good
Enum.map 0..9,&(IO.puts&1;2)*&1 # Good
Enum.map 0..9,&(IO.puts&1;2*&1) # Error