絶対に競プロ以外では書いてはいけない!書いたら○されると思え!なテクニック集
※初心者向けじゃないと言う指摘を受けたためタイトルをまんま採用させて頂きました。
はじめに
Pythonで競技プログラミングを始めてみると基本的な文法以外にそれそう使うんだ!?という常識が存在したりします。標準入力やif, for などの構文を一通り扱えるようになったけどまだA問題やB問題もスマートに解けないよ、(ワンライナーに憧れている人)、という方の参考になればいいかなと思って書きました。
また、以下の記事がわかりやすいのですが、こちらに載っていないようなテクニック、を判断基準にしています。
具体的にはpython的に基本の文法というよりも基本の文法を実際にはこんな使い方もあるよ!といった内容です。
競技プログラミングを始めたら色々ある中でもAtcoderに登録する方はかなり多いと思うので、今回はAtcoderのAtcoder Beginner Contest、特にA問題とB問題について取り扱い、各問題のショーテスト回答を参考にしています。
Atcoderって何?と言う方はこちらの記事を参考にしてください。
AtCoder に登録したら次にやること ~ これだけ解けば十分闘える!過去問精選 10 問 ~
Atcoderとは、から何をすればいいかなど様々なことに触れています。
というかKensuke Otsukiさんという方がこの手のわかりやすい記事をいくつか書いていらっしゃるのでそちらを参考にしてください。
!注意!
普通の答え、とは一般的な可読性や再利用可能かなどを加味してのプログラム
スマートな答え、とはプログラムを書くスピードを意識し、可読性は無視したプログラム
というような考え方です。競技プログラミングにおいては、という点にあらかじめご了承ください。
スペース区切りの値の受け取りとYes,Noの表示
解説
普通の答え
N = int(input())
L = list(map(int, input().split()))
max_edge = max(L)
sum_of_edge_without_max = sum(L) - max_edge
if max_edge > sum_of_edge_without_max:
print('Yes')
else:
print('No')
スマートな答え
_ = int(input())
L = list(map(int,input().split()))
print('YNeos'[max(L) >= sum(L) - max(L)::2])
まず一行目でデータの個数を受け取りますが、一行にスペース区切りでデータが入力される場合、データの個数は必要ないため、慣例的に使わない変数としてアンダースコアを用いています。
some_nums = list(map(int,input().split()))
# 1 2 3 4 5 を入力とする。
print(some_nums)
# [1, 2, 3, 4, 5]
some_nums2 = list(map(int,input().split()))
# 1 2 3 4 5 6 7 を入力とする。
print(some_nums2)
# [1, 2, 3, 4, 5, 6, 7]
#データの個数により任意長リストが生成される
そしてmap関数。これは任意の引数に対してそれぞれにある処理を施すというもの。
ただここで気を付けなければならないのがその返り値。
従来ならリストを返していたが、今はmapオブジェクトを返す仕組みになっています。
nums = map(int,input().split())
# 1 2 3と入力
print(nums)
# <map object at 0x0000026E48071400> と出力される
mapオブジェクトが返されてしまうため、ここにlist関数を使ってリストにします。
L = list(map(int,input().split()))
そして最後の部分
print('YNeos'[max(L) >= sum(L) - max(L)::2])
'YNeos'、不思議な文字列ですね、、、YesなのかNoなのか、、、
答えはどっちも。いったいどういうことか、順番に見ていきましょう。
print('qiita'[0])
# q
pythonでは文字列をリストに見立てて添え字から文字を呼び出すことができるのです!
なので上記の例だと一文字目のqが表示されていますね。更にここにリストのスライス機能を合わせるとどうなるでしょうか。
print('abcde'[1::2])
#bd
もうお分かりですね。1文字目(0文字目から数えて)2文字おきずつ取得するのでbdと出力されます。リストから偶数要素(奇数)だけ取り出すときなんかにも使うスライス方法です。これを先ほどの文字列に使うと、、、
print('YNeos'[0::2])
# Yes
print('YNeos'[1::2])
# No
こうしてYesとNoを添え字の数字を変えるだけで切り替えて表示することができるんですね。
もうif文で条件分岐しながらYesだのNoだのうつ必要がなくなりましたね。
print('NYOE S'[1::2])
# YES
print('NYOE S'[0::2])
# NO
また、文字列を工夫すれば添え字が1のときにYESを表示させることも可能です。
あとはこの部分に0もしくは1を最初から入れておかないで条件分岐を書くだけです。
この部分ですね。
max(L) >= sum(L) - max(L)
もしこの値が真なら添え字内ではtrueは整数1として扱われ、もし偽なら添え字内ではfalseは0として扱われます。これで条件を満たせばNo, 満たさなければYesと表示されるプログラムが書けました。
まとめ
a = list(map(int, input().split()))
print('YNeos'[何か条件式::2])
整数のリストの取得はアンパック代入で行う。
普通にやろう。
YesかNoの表示プログラムなら文字列操作を工夫してif文なしで書くことができる。
文字列の繰り返し、加算(*, +)
解説
例題:155-A-Christmas Eve Eve Eve
普通の答え
string = "Christmas"
for i in range(25-int(input())):
string += " Eve"
print(string)
これでもif文を繰り返しまくるよりはまあまあスマートですがワンライナーならもっとスマートです。
スマートな答え
引用:ransewhaleさん、43Byte
print("Christmas"+" Eve"*(25-int(input())))
これは普通の回答でも使っているのですが文字列の加算を用いています。
string = "christmas" + " eve"
print(string)
# christmas eve
pythonはやっぱり便利ですね。更にpythonでは加算だけでなく文字列の乗算もできます。
string = "christmas" + " eve"*3
print(string)
# christmas eve eve eve
単純ですげえ便利。
これでいちいち文字列を用意しなくても済みます。
str1 = "christmas eve"
str2 = "christmas eve eve"
str3 = "christmas eve eve eve"
こんなことにならなくてよかったですね。笑
まとめ
似たような文字列の回答を表示する際は文字列の加算と乗算をおこないましょう。
string = "christmas" + " eve"*3
print(string)
# christmas eve eve eve
in演算子~文字列にある文字がふくまれているか~
解説
例題: 114-A-753
普通の答え
n = input()
if (n == '3') or (n == '5') or (n == '7'):
print('YES')
else:
print('NO')
if文のベタなタイプです。
スマートな答え
引用:yayo256さん、34Byte
print('NYOE S'[input()in'357'::2])
非常にシンプルな問題です。まずYESとNOの表示は初めに解説した方法を利用していますね。
一方で文字列に3か5か7が含まれているかの判断には in を用いていますね。
print('1' in '123')
# True
in演算子を用いることによって'1'が'123'に含まれているか?がわかります。
nums = [1, 2, 3, 4]
print(1 in nums)
# True
print 5 not in nums
# True
in演算子は文字列をリストのように扱えることからもわかる通り、そのままリストにも使えます。また、not演算子と組み合わせることによって、含まれていないときにTrueを返す、といったこともできます。
print([7, 5, 3].count(3))
# 1
また、count関数によってリストの中に対象のオブジェクトがいくつ含まれているかを数えることもできます。これによって一桁の数値なら[3, 5, 7]の中に最大でも一つしか含まれないので、返り値は必ず0か1となり、以下のようにして同様の回答を得ることもできます。
print(['NO', 'YES'][[7, 5, 3].count(int(input()))])
まとめ
対象がリストや文字列にふくまれるかどうかを確かめる場合in演算子を使います。
print("NYOE S"[対象 in リストか文字列::2])
# または
print(['NO', 'YES'][リスト.count(対象)])
evalとreplace関数
解説
普通の答え
t, x = map(int, input().split())
print(t/x)
スマートな答え
string = input().replace(' ', '/')
print(eval(string))
ここでポイントになる文法は二つあります。replaceとevalです。
まずeval関数ですが、簡単に言うと文字列の式評価を行う関数です。
print(eval("1 + 6 / 3"))
# 実行結果: 3.0
上の通り文字列でもしっかり値を計算してくれます。
続いてreplace関数です。文字列の置き換えをやってくれる関数です。
print('あばりびがばとぼう'.replace('ば', '').replace('び', '').replace('ぶ', '').replace('べ', '').replace('ぼ', ''))
# ありがとう
オブジェクトに対して第一引数(に一致する)文字列を第二引数に置換します。
上の例だと置換済みの文字列に対してもreplaceをチェーンしているので複数の置換も簡単に行えます。
簡易的なバビ語解読プログラムができあがる訳ですね。
(元々の単語にば行を含んでいても置換してしまうため完全ではないですが)
まとめ
入力の文字列をそもまま実行することで解が得られそうな場合にはreplace関数によって入力文字列を加工、それをeval関数を使って実行、という流れにすることでかなり簡単に解を求める事が出来ます。
# 入力:a b
# a + b を出力する
print(eval(input().replace(' ', '+')))
続く
今回はほんの一部のテクニックのみ紹介しましたがまだまだ紹介したい物があります。
もうすでにちょっと長いような気がしますがまだまだ徐々に増やしていこうと思います。
総まとめ
また、ここの説明よくわからないとかこのコード解説して欲しいということであればじゃんじゃん質問して下さい。
あとここおかしいとかこっちのがいいという意見もめちゃくちゃウェルカムです!
読んで下さりありがとうございました!
そしてなにより素晴らしいワンライナーコード達をお書きになったAtcoderの方達に感謝です!!
本当にありがとうございました!!