はじめに
CodinGameというコードゴルフで競い合えるサイトがあるのですが、そこで学習した文字数を減らすテクについて解説します。
順番が非常に雑です。
空白
削れる空白は積極的に削っていきましょう。
n = int(input())
print(n * 2)
n=int(input())
print(n*2)
数字と文字の間も削れるところは削れたりします。
print(1if"hoge"==input()else 0)
変数
1文字の変数はa-z,A-Zの52個あります。変数は基本的に1文字にしましょう。
Answer=0
for i in range(5):Answer+=int(input())
print(Answer)
A=0
for i in range(5):A+=int(input())
print(A)
演算子
演算子には優先順位がありますが、優先順位が同じ演算子は基本的に左から計算します。
N=int(input())
print(3*(N//3))
N=int(input())
print(N//3*3)
このようにして括弧を減らしていきましょう。
##入力
###変数に渡さない
入力値が必要ない場合は、単に input()
として無視しましょう。
一回だけ入力値を必要とする場合は、式の中に input()
を入れ込むと変数の長さ分削減できます。
#1行目に文字列の長さが渡されて、2行目の文字列のASCIIコードの値の合計を計算
input()
print(sum(ord(i)for i in input()))
##出力
###リストのアンパック
*
演算子は、リスト内の値を全て出力するときに便利です。
オプションsep
の値で区切ります。
print(*map(len,input().split()),sep="a")
#入力例 think sinking thonking
#[5,7,8]となるので
#出力例 5a7a8
###改行無し
改行したくない場合は、print()
関数のオプションであるend
を使いましょう。
print(end="hoge")
##f文字列(フォーマット済み文字列リテラル)
**f文字列は大変便利です。**ここではコードゴルフで主に用いるものを挙げていきます。
###穴埋め
f文字列の基本です。中括弧{}
で囲ったところに整数、文字列、変数を埋め込みます。
{}
のなかに文字列を入れたいときは、シングルクォーテーション'
とダブルクォーテーション"
を使い分けましょう。
#左の数値が右の数値より大きいかどうか
A,B=map(int,input().split())
print(f"{A} is {'not '*(A<=B)}bigger than {B}")
###ゼロ埋め
#時刻表示
second = int(input())%86400
print(f"{second//3600:02}:{second%3600//60:02}:{second%60:02}")
###進数変換
#先程の0埋めとも組み合わせられます
print(f"{int(input()):b}")#2進数
print(f"{int(input()):o}")#8進数
print(f"{int(input()):08x}")#16進数で8桁まで0埋め
##整数
###1文字の不等号
>=
より >
、<=
より <
を使った方が短いです。
if n<=7:print("Yes")
if n<8:print("Yes")
###+1,-1
n+1
は -~n
、n-1
は ~-n
と表せます。
文字数変わらないのに何が嬉しいんだよというと、-
と ~
は */%
より優先順位が高いので括弧をつける必要がなくなります。
print((n+1)%3)
print(-~n%3)
###切り上げ
n/m
を切り上げた整数は、math.ceil(n/m)
と書くよりも (n+m-1)//m
とした方が短く、さらにmathモジュールをimportする必要がなくなります。
import math
print(math.ceil(n/m))
print((n+m-1)//m)
###bool値も整数
True
は 1
、False
は 0
にあたります。
A=[9,9,8,2,4,4,3,5,3]
print(sum(i>=5for i in A))#4
##文字列
###YNeos
print("YNeos"[0か1::2])
で短くできます。これは
print(["Yes","No"][0か1])
よりも短いです
さらに、この手法が本領発揮するのは3つ以上を場合分けする時です。
例えば、 n
を3で割った余りで think,thonk,sink
を出力仕分けたいときは、
print(["think","thonk","sink"][n%3])
とするよりも、
print("ttshhiionnnkkk"[n%3::3])
とするとかなりの文字数削減です。この場合は、
print("ttshhiio"[n%3::3]+"nk")
とすれば更に短くできます。
似たようなテクで、bool値の足し算を利用した場合分けもあります。
- $N \leq 5$ のとき、"small"
- $5 \lt N \leq 30$ のとき、"medium"
- $30 \lt N$ のとき、"large"
と場合分けしたい時は、
["small","medium","large"]
という配列を作り、indexで場合分けするコードを書きましょう。
N=18
print(["small","medium","large"][(N>5)+(N>30)])
##変数に関数を代入
1文字変数に何回も使う関数を代入することで短くかけたりします。
R = range
N,M,L = map(int,input().split())
for i in R(N):pass
for i in R(M):pass
for i in R(L):pass
変数には組み込み関数だけでなくメンバ関数も代入できます。
C = [3,1,4,1,5,9,2,6,5,3,5,9].count
print(C(3))#2
print(C(5))#3
print(C(7))#0
##ループ
###ループの最後
ループの最後は、下にインデントを開けて書かずに横にかけます。
一行に文を複数書くときは、セミコロンを用います。
また、2重以上のループでもインデントは空白1個分にしましょう。
#while文やif文でも同じです
for i in range(N):
if hoge:print("Yes");break
###execによるループ
for _ in range(N):処理
上のようにループカウンタを用いない場合は、exec()
関数を用いて短くかけます。
exec("処理;"*N)
例えば $N=5$の場合は、次のコードと同じ意味です。
処理;処理;処理;処理;処理;
###重数を減らす
for i in range(N):
for j in range(M):pass
この二重ループを一重ループにするテクです
for k in range(N*M):i,j=k//M,k%M;pass
##open(0)
挙動が少し難しいですがこれを使いこなせると時に数十bytesの削減になります。
まずは単純に
A = open(0)
print(A)
こうすると、標準入力にかかわらず出力は、
<_io.TextIOWrapper name=0 mode='r' encoding='UTF-8'>
と出てきて、は?となります。
ところが左辺(A
)のところを2変数にすると挙動が変わってきます。
A,B = open(0)
print(A)
print(B)
そして標準入力を
attack
guard
とします。
標準出力は次のようになります。
attack
guard
これは、変数Aには "attack\n"
, Bには "guard"
が格納されていたことを表します。
左辺が2変数以上だと、左辺=open(0)
は次のような処理をします。
- 標準入力を全て読み込む (今回は
"attack\nguard"
) - \nで区切ってリストにする。この時末尾の改行は残る。
("attack\nguard"
→["attack\n","guard"]
) - 各変数に順番に代入する (
A = "attack\n", B = "guard"
)
ここで、アスタリスクを用いたアンパック代入を用いることで、
$1$行目に $N$ が与えられ、$2$ ~ $N$+$1$行目に整数が一つづつ与えられる
という超典型標準入力を、
N,*L = map(int,open(0))
で簡単に書き表すことができます。
しかし、次のような入力形式だと困ってしまいます。
$1$行目に $N$ が与えられ、$2$行目に$N$個の整数が空白区切りで与えられる
open(0)
は改行で区切るので同じようには上手くいきません。
それでも、open(0).read().split()
が使えます。
open(0).read()
で標準入力全てをstr型として受け取れるので、split()
で空白and改行区切りできます。
N,*L = map(int,open(0).read().split())
##数式変形
いきなり例題。
$N^0,N^1,\cdots N^M$ の総和を出力せよ。
単純に書くと次のようになります。
N,M=map(int,input().split())
print(sum(N**i for i in range(M)))
しかし、このような単純な総和を簡潔に表せないのは直感的に嫌な気分になります。
そこで、この数式を†Wolfram Alpha†に突っ込みます。
ということで、
N,M=map(int,input().split())
print(~-N**M//~-N)
#おわりに
量が少なすぎる 気が向いたら追加