まず初めに、Code Golf とは「できるだけ短いコードで、与えられた問題を解決する競技」である。
実際に例題を交えて説明した方が伝わると思うので、例題を用意した。
以下の文章を出力してください。
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
まずは、単純明快な解法を考えてみる。
print("Hello World!")
print("Hello World!")
print("Hello World!")
print("Hello World!")
print("Hello World!")
print
関数を 5 回使用して問題を解いた。
ここからは Code Golf をしていく。Code Golfはできるだけ短いコードで問題を解決する競技なので、更に短くしてみよう!
今回の場合は、for文を使用することで更に短く出来そうだ。
for i in range(5):
print("Hello World!")
また、 for文の中身が一行なので、以下のように書ける。
for i in range(5):print("Hello World!")
次に、変数 i
は使わない為このように書くと四文字省略できる。
for i in[0]*5:print("Hello World!")
ちなみに、in
と [0]
の間にスペースが無くても動く理由は、記号と文字が隣接している場合、間の空白が省略出来るからである。
そして、おそらくこの例題の最短コードは以下である。
exec("print('Hello World!');"*5)
それでは最初に書いたコードと、出来るだけ短く書いたコードを見比べて見る。
Code Golf がどのようなものか分かったかな?
print("Hello World!")
print("Hello World!")
print("Hello World!")
print("Hello World!")
print("Hello World!")
print("Hello World!\n"*5)
それでは。私が思う、Python の Code Golf で「これはおすすめ!」と思うテクニックを5項目に分けて紹介する。
map(int, input().split())
のような、一般的に競技プログラミングで使われている入力方法を多く使用している。1つめは、1番重要でありとても効果的なテクニック(と呼べるか分からないが)である。
まずは、変数名を1文字にする。これをするだけで、Code Golf感が増し、コードの短縮に繋がる。
また、Python ではアルファベットの大文字と小文字を区別出来るため、一文字でも変数名に困ることは Code Golf において無い。
hello_text = "Hello World!"
for i in range(5):
print(hello_text)
T = "Hello World!"
for i in range(5):
print(T)
一般的なコードでは、可読性(コードの読みやすさ)を上げるために、空白や改行を使い見やすくするが、Code Golf では必要ない。
また、Pythonで間の空白を省略出来る場合は沢山あるので、気になった方は是非色々試してみて欲しい。
T = "Hello World!"
for i in range(5):
print(T)
T="Hello World!"
for i in range(5):
print(T)
if文やfor文などの、中身が1行の場合は、コロン :
の隣に記述できる。
T="Hello World!"
for i in range(5):
print(T)
T="Hello World!"
for i in range(5):print(T)
実は、インデントは1でもプログラムを動かすことが出来る。
可読性がとてもとても悪くなるので普段はしちゃ駄目!!
T="Hello World!"
for i in range(5):
print(T)
T="Hello World!"
for i in range(5):
print(T) # <- 勿論処理が1行なのでfor文の横に書ける
コードを書く上で欠かせないものが変数である。
その中で、初期化や代入の部分で工夫をすることで、コードを短縮できる可能性がある。
同じ値を複数の変数に代入することは1行で簡潔に書ける。
x=0
y=0
z=0
x=y=z=0
基本的にイミュータブルの型であればこのテクニックを使うことができる。
Python のイミュータブルな型は bool
int
str
tuple
などである。詳しくイミュータブルについて知りたい方はこちらの記事がおすすめ。
リストや文字列などのイテラブルなオブジェクトは、複数の変数へ一気に代入することができる。
後ほど紹介するが、open(0)
で入力を受け取る方法と相性が良く覚えておいて損は無い。
A=list(map(int,input().split()))
print(A[1:])
_,*A=map(int,input().split())
print(A)
セイウチ演算子とは「変数の代入と使用」を同時に行える演算子である。あまり使用されていない演算子であるが、Code Golfで使用すると短縮できる場合が存在する。
以下は、配列の長さが5以上の場合に配列の平均値を出力するプログラムである。
A=list(map(int,input().split()))
l=len(A)
s=sum(A)
if(l>=5):print(s/l)
# よりCodeGolfっぽく書くと...
A=list(map(int,input().split()))
if len(A)>=5:print(sum(A)/len(A))
if(l:=len(A:=list(map(int,input().split()))))>=5:print(sum(A)/l)
「変数の代入と使用」を同時に行っている箇所は、:=
がある所である。
if文や内包表記などの、普段は代入が行えない場所で代入が行えるのがとても便利!
「for文を使う入力であり、その入力が最後に来る」時には、そのままfor文に使ったほうが短くなる場合が多い。
N,X=map(int,input().split())
A=map(int,input().split())
c=0
for a in A:
if a<X:c+=1
print(c)
N,X=map(int,input().split())
c=0
for a in map(int,input().split()):
if a<X:c+=1
print(c)
問題によっては、1つの行が完全に必要ない入力の場合がある。
input()
は関数なので代入をせずに呼び出すことができる!
_=input()
A=list(map(int,input().split()))
print(sum(A))
input()
A=list(map(int,input().split()))
print(sum(A))
また、必要の無い入力の後に2行以上入力が来る場合、セイウチ演算子を使うことで更に短縮に繋がる。
(ここまで入力が多いとopen(0)
を使用した場合の方が短い可能性が高い)
(i:=input)()
A=list(map(int,i().split()))
B=list(map(int,i().split()))
print(sum(A+B))
まず eval
関数とは、簡単に言うと「文字列として書いたコードを実行する関数」である。
入力に使われる、list(map(int, input().split()))
を文字列にして eval
関数で実行すると短縮することができる。
A=list(map(int,input().split()))
B=list(map(int,input().split()))
S="list(map(int,input().split()))"
A=eval(S)
B=eval(S)
個人的に好きな短縮方法なので紹介したが、実際は後ほど紹介する open(0)
という方法があるため使うタイミングはあまりない…
先程、input
関数のテクニックを紹介しましたが、大量の入力を受け取る際には open(0)
を使うと更に短く書ける場合が多い。
Pythonでの入力を受け取る方法は、input
sys.stdin
sys.stdin.buffer
io.BytesIO
などあるが、open(0)
というものでも入力を受け取ることが可能である。
では、open(0)
がなぜCode Golfで良く使われているかというと、全ての入力を改行区切りで一度に受け取る事ができるからである。
では、早速 open(0)
で入力した値を受け取ってみよう!以下のコードを実行してみる。
S = open(0)
print(S)
<_io.TextIOWrapper name=0 mode='r' encoding='UTF-8'>
すると、入力ができずにこのような出力になった。
この問題の原因は、open(0)
の戻り値が str
型では無く、 TextIOWrapper
型というものが戻り値として設定されている事が理由である。
では、どのようにしたら入力をした値を利用できるのだろう?
ここで、TextIOWrapper
クラスにはどのような関数が実装されているのか確認してみる。
print(dir(open(0)))
[..., __iter__, ..., __next__, ...]
出力結果が長いので省略するが、__iter__
と __next__
関数が含まれていた。これで、for文やアンパックなどをすることで、入力した値を利用できるということが分かった。
ちなみに、 __iter__
や __next__
があるとfor文が出来るかは、PySight 内の記事で詳しく解説している。
1番使用頻度が高いと思う open(0)
での入力方法は、「1度に入力を受け取ってアンパック代入」である。
N=int(input())
S=input()
print(S[:N])
# input() でも、もう少し短く出来る
N=int(input())
print(input()[:N])
N,S=open(0)
print(S[:int(N)])
open(0)
を使用する際、 Ctrl+D
を押すことで標準入力を終了できる。
もちろん、変数に *
をつけることで、残りの要素を全て受け取ることもできる。
N,*S=open(0)
print(S)
入力
5
abcde
vwxyz
出力
['abcde\n', 'vwxyz']
出力を見るとopen(0)
で入力を受け取ると、改行コード \n
が残ることが分かる。
対象法は色々ありますが良く使う3種類を軽く紹介する。
split()
[:-1]
strip()
もちろん上記の方法が最短になるとは限らないので、色々試してみるのが良いだろう。
全ての入力が、空白 か \n
で区切られてる時、以下の入力方法が使用できる。
N=int(input())
A=list(map(int,input().split()))
print(A[:N])
N,*A=map(int,open(0).read().split())
print(A[:N])
入力
3
1 2 3 4 5
出力
[1, 2, 3]
どのような時に使うかだが、「空白 か \n
で区切られていて、全ての入力を整数に変換したい」時が一番使うべきタイミングだ。
これは入力のコツでは無いが、整数の変数を0で初期化したい時、open(0)
と セイウチ演算子を組み合わせることで、1文字短縮に繋がる。
X,*A=map(int,open(0).read().split())
c=0
for a in A:
if a<X:c+=1
print(c)
X,*A=map(int,open(c:=0).read().split())
for a in A:
if a<X:c+=1
print(c)
入力
5
1 2 3 4 5 6 7
出力
4
実は、Code Golf では条件式を、配列のスライス操作に使う場合がある。
以下は、x
が 3 未満の場合 Yes
を出力し、そうでない場合は No
を出力するコードだ。
if x < 3:
print("Yes")
else:
print("No")
より短く書くと以下の様になる。
(更に空白を削れるが見にくいため、ここではしていない。)
print("Yes" if x<3 else "No")
結構短くなったが、スライス操作を使用することで更に短く書くことが可能である。
print("YNeos"[x>2::2])
「一体、何をしてるんだ!」となるかもしれない。
そこで、 "YNeos"
の0文字目から1つ飛ばしで文字を取り出して出力するコードを書いてみる。
print("YNeos"[0::2])
Yes
Yes
と出力された!
2つのコードを見比べてみると"YNeos"
が何をしていたか分かると思う。
このテクニック( "YNeos"
)は…
条件式が True
なら 1 、False
なら 0 として、スライスの開始位置を変えて、"Yes"
か "No"
を出力させるテクニックである。
ちなみに、なぜ条件式( True
False
)が1や0として扱えるかと言うと、「 bool
クラスが int
クラスのサブクラス」であるからだ。
詳しくは、公式リファレンスにて記述されている。
"YNeos"
の意味について理解できた所で、実際の使用例を見てみる。
与えられた整数が、3 未満なら Yes そうでないなら No と出力してください。
print("YNeos"[int(input())>2::2])
注意点としては、条件式を int(input()) < 3
にしてしまうと、3未満の場合 True
となり、出力結果は No
となってしまう。そのため上記のコードでは、「与えられた整数が 3 以上の場合に、条件式が True
となる」ようにコードを書いている。
次に "YNeos"
が使えない場合にどうするのか、問題を交えて考えてみる。
与えられた整数が、奇数なら Yes 偶数なら No と出力してください。
print("NYoe s"[int(input())%2::2])
まず、奇数偶数の判定は %2
&1
などで行えて、両方とも奇数の場合 1 となる。
この問題では「奇数の場合に Yes
」のため、"YNeos"
と書こうとすると少し冗長になってしまう。
そこで、Yes
と No
の順番を入れ替えた"NYoe s"
を使用することで短く書くことができる。
インデックス指定に条件式を入れるテクニックも存在する。
「P」と入力したら Python と出力し、それ以外は C# と出力してください。
print("CP#y t h o n"[input()=="P"::2])
print(["C#","Python"][input()=="P"])
今回の様に「2つの選択肢の文字列の長さに大きな差がある場合」は、インデックス指定の方が短い場合も存在する。
また、変数に値を代入する際にもインデックス指定が最適な場合がある。
以下のコードは、入力した値が 20 未満の場合 10、それ以外は 20 を変数に代入している。
x=[10,20][int(input())<20]
本記事で参照した資料や参考リンクは各内容に応じて本文中に適宜リンクとして掲載している。