whileで繰り返す
このレッスンで作るもの — 「終了」と入力されるまで記録を受け付け続ける
collect_until_endを、whileで作ります。
while で繰り返す
for は「並んでいるものを最後まで回す」ループでした。回る回数は始まる前から決まっています。
ところが、おこづかい帳に何件記録するかは、使う人にしか分かりません。3 件で終わる日もあれば 20 件の日もあります。このように 何回回るか分からない繰り返し を書くのが while です。
forは「全部に対して」、whileは「条件が成り立つあいだ」。どちらを使うか迷ったら、回数が先に決まっているかどうかで選びます。
while の形
while のうしろには 条件 を書きます。条件が True のあいだ、下にインデントした処理を繰り返します。
count = 0
while count < 3:
print(count)
count = count + 1このコードは 0、1、2 を表示して止まります。4 周目に入ろうとしたとき count は 3 になっており、3 < 3 が False なので繰り返しが終わります。
if と形がよく似ていますが、if は条件を 1 回だけ 調べるのに対し、while は 1 周まわるたびに調べ直す ところが違います。
count = count + 1 の行がとても大事です。この行を書き忘れると count は 0 のままで、条件がいつまでも True になり、プログラムが止まらなくなります。これを 無限ループ と呼びます。
whileを書いたら、その場で「この条件はどうやってFalseになるのか」を口に出して確認します。答えられないなら無限ループです。
本来は input を while で回す
このアプリで本当にやりたいのは、下記のような形です。キーボードから入力を受け取り、「終了」と打たれるまで受け付け続けます。
items = []
while True:
text = input("品名を入力してください ")
if text == "終了":
break
items.append(text)
print(items)while True は「条件がいつも成り立つ」ので、そのままでは永遠に回ります。止める役目を break が担っています。break はループを途中で抜ける命令で、次のレッスンで詳しく扱います。
items.append(text) は、リストのうしろに 1 件足す命令です。リストの操作は第 7 章で詳しく扱いますが、ここでは「結果をためる入れ物に 1 件足す」道具として使います。
演習では入力を引数で受け取る
このコースの採点は、関数が return した値を見て正解かどうかを判定します。キーボードは繋がっていないので、input() は使えません。
そこで演習では、入力されるはずだった文字列の並びをリストで受け取る 形にします。input() を 1 回呼ぶ代わりに、リストから 1 件取り出す、と読み替えるわけです。
def collect_until_end(inputs):
items = []
i = 0
while i < len(inputs):
text = inputs[i]
if text == "終了":
break
items.append(text)
i = i + 1
return itemslen(inputs) はリストの件数、inputs[i] は i 番目の 1 件です。どちらも第 7 章で詳しく扱う道具で、ここでは「次に読む位置を i で進めていく」ために借りています。i が件数に届いたら、もう読むものがないのでループが終わります。
collect_until_end(["りんご", "みかん", "終了", "パン"]) を呼ぶと、りんご と みかん を取り込んだところで 終了 に当たって止まります。うしろの パン は読まれないので、返るのは ["りんご", "みかん"] です。
「終了の合図が来たら止める」という考え方は、この先どんなアプリにも出てきます。合図の文字列だけを変えれば、そのまま使い回せます。
よくある間違い
- カウンタを進め忘れる —
i = i + 1を書き忘れると、同じ 1 件を永久に読み続けます。手元で試すときは、止まらなくなったら Ctrl + C で強制終了できます。 i = i + 1をbreakの前に置いてしまう — 位置そのものは動きますが、終了をitemsに入れてしまう順番になっていないか、必ず読み返します。取り込むのは終了ではない行だけです。whileの条件をi <= len(inputs)にしてしまう — 最後の 1 件を読んだあと、存在しない位置を読もうとしてIndexErrorになります。件数が 3 なら読める位置は 0 と 1 と 2 までです。
終了 が 1 件も含まれていない場合は、最後まで読み切って自然にループが終わります。特別な場合分けは要りません。
やってみよう
関数 collect_until_end を実装してください。引数 inputs は文字列のリストで、入力されるはずだった品名が順番に並んでいます。
先頭から順に見ていき、"終了" に当たったらそこで止め、それより前の品名だけを並べたリストを return します。"終了" が含まれていない場合は、全部を並べたリストを返します。空のリストを渡されたときは空のリスト [] を返します。
for でも書けますが、今回は while で書いてみてください。input() を繰り返す形と、頭の中で重ねられるはずです。
要件
- 関数名は collect_until_end で、引数はリスト inputs の1つだけにする
- while を使って先頭から順に見ていく
- "終了" に当たったらそこで止め、それより前の品名だけを返す
- "終了" が無い場合と空のリストの場合も正しく動くようにする
入出力例
collect_until_end(["りんご","みかん","終了","パン"]) → ["りんご","みかん"]
collect_until_end(["りんご","みかん"]) → ["りんご","みかん"]
collect_until_end(["終了","りんご"]) → []
collect_until_end([]) → []
collect_until_end(["パン"]) → ["パン"]