11
9

More than 3 years have passed since last update.

Python コードゴルフテクニック集

Last updated at Posted at 2021-06-08

はじめに

CodinGameというコードゴルフで競い合えるサイトがあるのですが、そこで学習した文字数を減らすテクについて解説します。
順番が非常に雑です。

空白

削れる空白は積極的に削っていきましょう。

old
n = int(input())
print(n * 2)
new
n=int(input())
print(n*2)

数字と文字の間も削れるところは削れたりします。

print(1if"hoge"==input()else 0)

変数

1文字の変数はa-z,A-Zの52個あります。変数は基本的に1文字にしましょう。

old
Answer=0
for i in range(5):Answer+=int(input())
print(Answer)
new
A=0
for i in range(5):A+=int(input())
print(A)

演算子

演算子には優先順位がありますが、優先順位が同じ演算子は基本的に左から計算します。

old
N=int(input())
print(3*(N//3))
new
N=int(input())
print(N//3*3)

このようにして括弧を減らしていきましょう。

入力

変数に渡さない

入力値が必要ない場合は、単に input() として無視しましょう。
一回だけ入力値を必要とする場合は、式の中に input() を入れ込むと変数の長さ分削減できます。

example
#1行目に文字列の長さが渡されて、2行目の文字列のASCIIコードの値の合計を計算
input()
print(sum(ord(i)for i in input()))

出力

リストのアンパック

*演算子は、リスト内の値を全て出力するときに便利です。
オプションsepの値で区切ります。

example
print(*map(len,input().split()),sep="a")
#入力例 think sinking thonking
#[5,7,8]となるので
#出力例 5a7a8

改行無し

改行したくない場合は、print()関数のオプションであるendを使いましょう。

example
print(end="hoge")

f文字列(フォーマット済み文字列リテラル)

f文字列は大変便利です。ここではコードゴルフで主に用いるものを挙げていきます。

穴埋め

f文字列の基本です。中括弧{}で囲ったところに整数、文字列、変数を埋め込みます。
{}のなかに文字列を入れたいときは、シングルクォーテーション'とダブルクォーテーション"を使い分けましょう。

example
#左の数値が右の数値より大きいかどうか
A,B=map(int,input().split())
print(f"{A} is {'not '*(A<=B)}bigger than {B}")

ゼロ埋め

example
#時刻表示
second = int(input())%86400
print(f"{second//3600:02}:{second%3600//60:02}:{second%60:02}")

進数変換

example
#先程の0埋めとも組み合わせられます
print(f"{int(input()):b}")#2進数
print(f"{int(input()):o}")#8進数
print(f"{int(input()):08x}")#16進数で8桁まで0埋め

整数

1文字の不等号

>= より ><= より < を使った方が短いです。

old
if n<=7:print("Yes")
new
if n<8:print("Yes")

+1,-1

n+1-~nn-1~-n と表せます。
文字数変わらないのに何が嬉しいんだよというと、-~*/% より優先順位が高いので括弧をつける必要がなくなります。

old
print((n+1)%3)
new
print(-~n%3)

切り上げ

n/m を切り上げた整数は、math.ceil(n/m) と書くよりも (n+m-1)//m とした方が短く、さらにmathモジュールをimportする必要がなくなります。

old
import math
print(math.ceil(n/m))
new
print((n+m-1)//m)

bool値も整数

True1False0 にあたります。

example
A=[9,9,8,2,4,4,3,5,3]
print(sum(i>=5for i in A))#4

文字列

YNeos

example
print("YNeos"[0か1::2])

で短くできます。これは 

other
print(["Yes","No"][0か1])

よりも短いです
さらに、この手法が本領発揮するのは3つ以上を場合分けする時です。
例えば、 nを3で割った余りで think,thonk,sink を出力仕分けたいときは、

old
print(["think","thonk","sink"][n%3])

とするよりも、

new
print("ttshhiionnnkkk"[n%3::3])

とするとかなりの文字数削減です。この場合は、

newofnew
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で場合分けするコードを書きましょう。

example
N=18
print(["small","medium","large"][(N>5)+(N>30)])

変数に関数を代入

1文字変数に何回も使う関数を代入することで短くかけたりします。

code
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

変数には組み込み関数だけでなくメンバ関数も代入できます。

code
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個分にしましょう。

code
#while文やif文でも同じです
for i in range(N):
 if hoge:print("Yes");break

execによるループ

old
for _ in range(N):処理

上のようにループカウンタを用いない場合は、exec()関数を用いて短くかけます。

new
exec("処理;"*N)

例えば $N=5$の場合は、次のコードと同じ意味です。

処理;処理;処理;処理;処理;

重数を減らす

old
for i in range(N):
 for j in range(M):pass

この二重ループを一重ループにするテクです

new
for k in range(N*M):i,j=k//M,k%M;pass

open(0)

挙動が少し難しいですがこれを使いこなせると時に数十bytesの削減になります。
まずは単純に

code
A = open(0)
print(A)

こうすると、標準入力にかかわらず出力は、

output
<_io.TextIOWrapper name=0 mode='r' encoding='UTF-8'>

と出てきて、は?となります。
ところが左辺(A)のところを2変数にすると挙動が変わってきます。

code
A,B = open(0)
print(A)
print(B)

そして標準入力を

input
attack
guard

とします。
標準出力は次のようになります。

output
attack

guard

これは、変数Aには "attack\n", Bには "guard" が格納されていたことを表します。

左辺が2変数以上だと、左辺=open(0)は次のような処理をします。

  1. 標準入力を全て読み込む (今回は "attack\nguard" )
  2. \nで区切ってリストにする。この時末尾の改行は残る。
    ( "attack\nguard"["attack\n","guard"] )
  3. 各変数に順番に代入する (A = "attack\n", B = "guard")

ここで、アスタリスクを用いたアンパック代入を用いることで、

$1$行目に $N$ が与えられ、$2$ ~ $N$+$1$行目に整数が一つづつ与えられる

という超典型標準入力を、

code
N,*L = map(int,open(0))

で簡単に書き表すことができます。

しかし、次のような入力形式だと困ってしまいます。

$1$行目に $N$ が与えられ、$2$行目に$N$個の整数が空白区切りで与えられる

open(0) は改行で区切るので同じようには上手くいきません。
それでも、open(0).read().split()が使えます。
open(0).read()で標準入力全てをstr型として受け取れるので、split()で空白and改行区切りできます。

code
N,*L = map(int,open(0).read().split())

数式変形

いきなり例題。

$N^0,N^1,\cdots N^M$ の総和を出力せよ。

単純に書くと次のようになります。

old
N,M=map(int,input().split())
print(sum(N**i for i in range(M)))

しかし、このような単純な総和を簡潔に表せないのは直感的に嫌な気分になります
そこで、この数式を†Wolfram Alpha†に突っ込みます。
スクリーンショット 2021-06-08 21.20.29.png

ということで、

new
N,M=map(int,input().split())
print(~-N**M//~-N)

おわりに

量が少なすぎる 気が向いたら追加

11
9
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
9