この記事は、 Elixir Advent Calendar 2022 その1の19日目の記事です。
Code Golfという遊びをご存知でしょうか?
コードゴルフはコンピュータプログラミング・コンテストの一種。参加者は与えられたアルゴリズムを、可能な限りもっとも短いソースコードで記述することを競う[1]。バイナリサイズではなく、ソースコードの文字数がスコアとなる。
Wikipediaで説明されているように与えられたお題に対してより短い文字数で回答することを競います。
短く記述するためには言語の特性を知り尽くし、動く範囲でできる限りコードを省略することが重要となります。
この記事ではElixirでCode Golfにチャレンジする際のTipsをいくつか紹介したいと思います。
業務では使えないような記述ばっかりですので使わないでくださいね!😂
⛳ どこで遊べるの?
Code Golfにチャレンジできるサイトはいくつかあるみたいですが、僕は以下のサイトで遊んでいます。
問題は84Hole用意されており、対応している言語も45言語あります。
もちろんElixirも対応済みです👍
✨Code Golf Tips in Elixir
🏌️♂️関数はモジュールを使わず無名関数で定義する
Elixirでは基本的にはモジュールを定義し、その中に関数を定義していきます。
ただしCode Golfではモジュールは定義しません。
文字数がかなり増えてしまうので、関数を定義する場合は無名関数で実装します
# 🙄long...
defmodule Hoge do
def fuga(n) do
IO.puts n
end
end
Hoge.fuga("hello")
# 🏌️♂️short!
f = fn n -> IO.puts(n) end
f.("hello")
また無名関数は以下のように記述することでさらに短くできます
f = & IO.puts&1
f.("hello")
🏌️♂️空白や()
はなるべく除去する
Elixirは関数に引数を渡すときに()
を省略することができます。
()
を省略すれば1打スコアを短くできます👍
# 🙄long...
IO.puts("hoge")
Enum.map(1..10, fn i -> i * 2 end)
# 🏌️♂️short!
IO.puts "hoge"
Enum.map 1..10,& &1*2
更に文字列やMap、&1
などの場合は空白も省略することができます
記号があると空白を省略できる場合が多いです
# 🏌️♂️short!
IO.puts"hoge"
&IO.puts&1
f=fn{a,b}->IO.inspect%{"#{a}"=>b}end
🏌️インデントはしない
当たり前ですが、インデントを入れるとその分文字数が増えてしまいます。
Code Golfでは読みやすさは考慮する必要がありません。
最終的にはすべて削ってしまいましょう‼️
また文字数的には変わりませんが改行の代わりに;
を入れるとワンライナーで書けて短くなる気がするのでおすすめです😂
# 🙄long...
fn
0 -> 0
1 -> 1
n -> div(10,n)
end
# 🏌️♂️short!
fn 0->0;1->1;n->div(10,n)end
🏌️♂️条件分岐はifを使わずに論理演算を使う
Code Golfのお題を解くために条件分岐が必要になる場面は多いです。
通常ですと、 case
cond
if
などを使うのが普通ですが、Code Golfでは文字数が増えすぎてしまいます。
その場合は論理演算(&&
||
)を使うと短くすることができます
# 🙄long...
if rem(n, 2) == 0, do: :even, else: :odd
# 🏌️♂️short!
rem(n,2)==0&&:even||:odd
{条件式}&&{真}||{偽}
のような形で記述することができます。
(⚠️プロダクトコードで記述するのは絶対にやめましょうw)
ちなみにElixirでFalseになる値は false
nil
のみで、その他はすべて true
になります。
Code Golf観点だと 0
""
も false
になったほうが嬉しかったですね
🏌️♂️変数は文字列になるべく埋め込む
Code Golfでは文字列を出力する問題がたくさんあります。
変数を文字列に埋め込みたい場面も多いですが、その際は文字列に埋め込んだほうが短くなります。
# 🙄long...
"hogehoge"<>n<>"fugafuga"
# => 25 bytes
# 🏌️♂️short!
"hogehoge#{n}fugafuga"
# => 22 bytes
ただし変数が接頭、接尾に来る場合は埋め込まないほうが1打短くなります。
# 🙄long...
"#{n}hogehoge"
"hogehoge#{n}"
# => 14 bytes
# 🏌️♂️short!
n<>"hogehoge"
"hogehoge"<>n
# => 13 bytes
🏌️♂️無名関数で再帰を行う
再帰が必要になる場面もあります。
再帰はモジュール関数にしないと実装できないかと最初は思っていましたが、無名関数でもやる方法があるようでした。
それが再帰関数として呼び出す関数を一旦変数として持っておいて、自分自身に渡すやり方です。
# 🙄long...
defmodule Fibonacci do
def fib(0), do: 0
def fib(1), do: 1
def fib(n), do: fib(n-1) + fib(n-2)
end
# 🏌️♂️short!
f=fn 0,_ -> 0; 1,_ -> 1; n,f -> f.(n-1, f) + f.(n-2, f) end
f.(10, f) # => 55
この方法なら無名関数で再帰が記述できるので大幅に打数を短縮することができます。
🏌️♂️モジュール関数を複数回使う場合はimportを検討する
Enum
モジュールやString
モジュールの関数を複数回使う場合はimportしたほうが短くなります。
# 🙄long...
1..10|>Enum.map(& &1**2)|>Enum.filter(&rem(&1,2)==0)|>Enum.join(",")
# => 68 bytes
# 🏌️♂️short!
import Enum
1..10|>map(& &1**2)|>filter(&rem(&1,2)==0)|>join(",")
# => 65 bytes
Enum.
が5打、 import Enum
が12打(改行含む)なので、 Enum.
が3回以上ある場合はimport Enum
に切り替えたほうが短くなります 🏌️♂️
ただし、Enum
とString
の両方のモジュールを同時にimportすることはできません。
(一部の関数名がかぶるので)
その場合は使用回数を見てどちらをimportするか決めましょう
🏌️♂️パイプ演算子の使い所
Elixirの人気の一つがパイプ演算子(|>
)ですが、省略したほうが短くなる場合が多いです。
# 🙄long...
[1,2,3]|> Enum.count|>Integer.to_string
# 🏌️♂️short!
Integer.to_string Enum.count [1,2,3]
ただし以下のような引数がある関数の実行結果を引数にとる場合はパイプ演算子を使ったほうが短くなるみたいです
# 🙄long...
f(&1,g(&2,&3))
# 🏌️♂️short!
f&1,&2|>g&3
🏌️♂️100以上の数字は?d
などで置き換える
Elixirでは文字列のコードポイントを ?
で取得することができます。
これを利用することで 100
以上の数字の場合に打数を短くすることができます。
# 🙄long...
1..100
# 🏌️♂️short!
1..?d
Elixirで対応しているUnicodeのcodepointは最大で1114111みたいなので 100..1114111
の数字は置き換えることが可能です
おわりに
明日から業務で使えないようなTipsを紹介いたしました ✨
Code Golfはチャレンジしてみると結構面白く頭の体操になりますので業務の息抜きなどでチャレンジしてみるといいかもしれません 👍
まわりからも仕事しているように見えるのでおすすめです