AutomA’s blog

Pythonキチガイを目指すブログ

【Python3】競技プログラミングの回答方法がわからないときに見るもの

研修でJavaばっかりやってるので全然Pythonの勉強ができない。

いや時間はあるんですが、いかんせん体力がね...

ジム通いでも始めたいところですが、今度はジムの会費が高すぎて払えないという。貧乏社会人のつらいところです。


さて、今回はPython初心者の方向けの記事を書きます。

Pythonの基本的な文法は入門書や学習サイトで勉強したし、いっちょ競技プログラミングで腕試しするか!という方が、陥りがちな落とし穴があります。

paizaとかCodeIQとか、そのへんは難易度的にもとっつきやすいんですが、この落とし穴を乗り越えない限り、解法は分かるのになぜか不正解になる...ということになってしまいます。

・標準入力

よくあるつまづきポイントがこちら。

Nが与えられ、それをうんたらかんたらした結果を出力して下さい。
なお、Nは標準入力から渡されます。

標準入力?
なにいってんだこいつ。

この言葉を初めて見た方はこう思うことでしょう。
私もこれが分からず不正解を出し続け、ついに「与えられた整数をすべて足し合わせた数を出力してください」なんていうクッソ簡単な問題で時間切れになってしまい、ショックでその後3日間パソコンに触らなかった悲しい過去があります。

標準入力を受け取る

これは要は「キー入力を受け付ける」操作をして値を受け取ってね、ということなのです。
input()関数を使いましょう。

>>> n = input()

これで変数nに入力された値、さっきの問題でいうところのNが代入されます。簡単だな!
なお、input()で得られた値は問答無用でstr型になるので、計算問題の場合は型変換しましょう。

>>> # 1を入力
>>> n = input()
>>> print(n, type(n))

 1 <class 'str'>
>>> # 1を入力
>>> n = int(input())
>>> print(n, type(n))

 1 <class 'int'>

スペース区切りの標準入力

変則パターンとして、「スペースで区切られたN、Mが与えられる」というものもあります。
input()は与えられた入力を一行まるまる取得するので、行の中で要素を区切りたいときはinputに加えひと手間必要になります。
split()関数を使います。

>>> # 「1 2」を入力
>>> n = input().split(" ")
>>> print(n)

['1', '2']

これでsplit()で指定された文字列で区切られた値を、配列に格納された形で得ることができました。
「指定された文字列」なので、例えばカンマで区切られているときはinput(",")とすることもできます。

配列の型変換

ここで注意したいのが、int()型変換は配列に対して使うことができないということです。
さっきと同じノリで型変換をしようとするとエラーを出します。

>>> # 「1 2」を入力
>>> n = int(input().split(" "))

int() argument must be a string, a bytes-like object or a number, not 'list'
>>> # 「1 2」を入力
>>> n = int(input()).split(" ")

invalid literal for int() with base 10: '1 2'

※ 配列にする前に型変換をしようとしても、文字列(スペース)を含む値をintに変換することはできません。

配列の要素を型変換したい場合は、map()関数を使いましょう。

>>> # 「1 2」を入力
>>> n = input().split(" ")
>>> n_int = list(map(int,n))
>>> print(n_int)

[1, 2]

mapとlistという見慣れない関数が出てきましたが、一つ一つ解説していきます。まずmap()関数から。

map関数は、第一引数(intの部分)に任意の関数を、第二引数(nの部分)以降に任意の配列を指定することで、「配列の要素全てに、指定した関数の処理を加える」というものです。
応用範囲が広そうでゾクゾクしませんか?

つまり、前述のコードは、このコードと同じことをしていることになります。

>>> # 「1 2」を入力
>>> n = input().split(" ")

>>> for i in range(len(n)):
...    n[i] = int(n[i])

>>> print(n)

先ほど、配列全体に対してint()をかけることができないことを確認しました。
ならば、配列の要素ひとつひとつはただのstr型なので、それらひとつひとつにint()をかけてやればいいじゃん、ということです。

次にlist()関数ですが、これは引数を配列型に変換する関数です。int()やstr()の仲間ですね。
なぜこの関数が必要かというと、map()関数は引数に配列を要求しておいて戻り値にはイテレータを返す、というとんだアナーキストだからです。
戻り値として返されたイテレータを、もう一度配列型に変換する必要があるんですね。

>>> # 「1 2」を入力
>>> n = input().split(" ")
>>> n_iter = map(int,n)
>>> n_int = list(map(int,n))
>>> print(n_iter)
>>> print(n_int)

<map object at 0x7fb604c48518>
[1, 2]

map()関数は便利なので覚えておくといいでしょう。

・標準出力

さて、無事標準入力を受け取り、アルゴリズムを生み出し、みごと配列型に格納された回答を導き出すことができました。あとはprint()関数を使い回答を出力するだけです。
問題を見ると、「スペースで区切って一行で出力してください」とあります。
改行をなくすならend = "" を使えばいいな...せや!

>>> results = [1, 2, 3]
>>> for i in results:
...    print(i, end = " ")

1 2 3 

これだと不正解になります。なんでだよ!

わかりやすいように、値を出力した後に"|"を入れてみましょう。

>>> results = [1, 2, 3]
>>> for i in results:
...    print(i,end = " ")
...    print("|",end = "")

 1 |2 |3 |

うまく伝われば幸いですが、この回答だと3の後にスペースが入っていますね。
問題の指定は「スペースで区切る」なので、区切る必要のない最後の要素の後についているスペースは不要なスペースと判定されます。

これこそが、陥りがちな落とし穴、「余分なスペース」です。
計算結果がint型やstr型だけ、という問題なら大丈夫ですが、計算が複雑になってくると配列を使う場面がでてきます。
そんなときにfor文をまわして要素を出力すると、このように余分なスペースも一緒についてきてしまいます。

余分なスペースを出力しない

じゃあどうするかというと、join()関数を使いましょう。

>>> results = [1, 2, 3]
>>> results_int = map(str, results)
>>> answer = " ".join(results_int)
>>> print(answer)

1 2 3

join()関数は、指定した文字列を、指定した配列の要素の間に割り込ませて一つの文字列にする、という処理をします。
「要素の間」なので、間の無い最後の要素にスペースが入る心配はありませんね。
join()はstr型の配列にしか使えませんので、map()関数を使いresults配列の要素をstr型に変えてから使います。

これで余分なスペースの無い、すっきりした文字列を出力することができました。

map()やjoin()を知らないと、初期の私のようにこんなクソコードを書く羽目になってしまうので、知らなかった方はぜひ覚えていってください!

>>> results = [1, 2, 3]
>>> for i in results:
...    print(i,end = "")
...    if i != results[len(results)-1]:
...        print(" ",end = "")

1 2 3

入力と出力をマスターして、良い競技プログラミングライフを!