「退屈なことはPythonにやらせよう」の演習を解く。（ [3.11.1], [3.11.2], [4.10.1], [4.10.2] ）

<table><td>
本記事は下書きリストから掘り出された個人的アーカイヴの1つです．
公開時期と執筆時期の間には，ある程度の開きがあります．
</td></table>

---

オライリー本「退屈なことはPythonにやらせよう 初版第1刷」の各演習プロジェクトを自分なりに解いたものです．

かなり前に書いたやつです．下書きリストに残ってたのですが，誰かの役に立つかも知れないので，一応公開しておきます

---

# ルール
演習時に使用する文法等は，基本的にその演習ページまでの内容に沿って書くものとしています．
例えば「演習の時点でfor文は説明されたけれど，while文と累算代入演算子は説明されていない」という場合，

```python
x = 100
y = 0
while y > -131071:
    x -= 1
    y = (x ** 3) - (3 * x ** 2) + (x - 1)
print('when x=' + str(x) + ', y=' + str(y))
```

と書きたいところを

```python
x = 100
y = 0
for i in range(10000):  # edited
    x = x - 1           # edited
    y = (x ** 3) - (3 * x ** 2) + (x - 1)
    if y <= -131071:    # inserted
        break           # inserted
print('when x=' + str(x) + ', y=' + str(y))
```

と書く，といった具合です．

---

#演習【3.11.1】コラッツ数列 (p.78)

■　要件

- `number`というパラメータを1つ持つ`collatz()`という名前の関数を書く
- `number`が偶数なら，`collatz()`は`number / 2`を戻り値にする
- `number`が奇数なら，`collatz()`は`3 * number  + 1`を戻り値にする
- ユーザーに整数を入力してもらい，その数を`collatz()`に渡して呼び出す
- `collatz()`が1を返すまで，その戻り値を`collatz()`自身に渡し，呼び出し続ける
- 入力を促す文章，入力値，collatz()の戻り値，を表示する  
e.g.  
![image.png](https://qiita-image-store.s3.amazonaws.com/0/253921/b8850c08-0c3e-835e-44e2-0f8e0fc9a6ba.png)


■　実装例

```python
def collatz(number):             # まず関数を定義
    if number % 2 == 0:          # 入力が偶数なら実行
        number /= 2              # number = number / 2 を累算代入演算子で
    else:                        # 入力が奇数(number % 2 != 0)なら実行
        number = 3 * number + 1  # そのまま
    return int(number)           # 2で割ったときにfloat型になったのをint型に直す


print('input a number: ')        # ここから実行される
n = int(input())                 # 入力の受け付け＋文字列から整数に変換
while n != 1:                    # 入力が1か，collatz()の返り値が1になるまでループ
    n = collatz(n)               # collatz()自身に値を返す
    print(n)                     # 毎回返り値を表示させる


input()                          # コマンドプロンプトを閉じさせない(Windows)
                                 # 空行(lintを静かにさせる)
```
最後の`input()`は，.pyファイルを開いて実行させるときのTipsです．（input()があれば，入力があるまで，プログラムが一時停止状態になる-->Enterを押すと，input()の行が実行されますが，値を返す先がないので，プログラムへの影響なしに，次の行へ移り，そのままプログラムが終了します）
IDLEなどの即時実行できる環境がある人は気にしなくても大丈夫です．


#演習【3.11.2】入力の妥当性検証(p.78)

■　要件（3.11.1への追加）

- ユーザーの入力が整数でない場合を判定するよう，try - except文を追加
- 尚，int()関数に文字列を渡すと，ValueError例外が起こる
- except節では，整数値の入力を促す文章を表示する

■　実装例

```python
def collatz(number):
    if number % 2 == 0:
        number /= 2
    else:
        number = 3 * number + 1
    return int(number)


print('input a number: ')
while True:                      #常にTrue: breakが出るまで無限ループ
    try:
        n = int(input())         #ここでエラーが出たら，即時except節へ（ループする）
        break                    #エラーが出なかったら，ループを抜ける
    except ValueError:
        print('input a integer!')#ループ終端なので，実行後自動でcontinue(while文の先頭へ)
while n != 1:
    n = collatz(n)
    print(n)


input()     # PAUSE

```
不正な入力がないかをチェックするための`try`&`except`を，`while`で囲み，エラーがなくなるまでループするようにしました．最初から入力が正常なら1回目で検証用ループを脱出(`break`)します．

# 演習【4.10.1】カンマ付け (p.107)

■　要件

- `spam = ['apple', 'bananas', 'tofu', 'cats']`のようなリストがあるとき，
- `'apple, bananas, tofu and cats'`と返すような関数
- 関数には，任意のリスト値を渡しても良いようにする

■　実装例

```python
def f(arg):                                # display like [0], [1], ..., [-2] and [-1]
    a = len(arg)                           # あとでループ幅に使うために，受け取ったリストの長さを代入
    o = ''                                 # 返り値に使うstr型変数を用意
    for i in range(a):                     # arg[0]からarg[-1]までを操作
        if i == (a - 1):                   # 最後の入力分
            o += arg[i]                    # --> そのまま出力文字列に追加
        elif i == (a - 2):                 # 最後から2番目の入力分
            o += arg[i] + ' and '          # --> andを付けて出力文字列に追加
        else:                              # 最後から3番目までは
            o += arg[i] + ', '             # --> カンマを付ける
    return o


print('Input something and press "Enter" to continue\n\
or input "end" if the list is over.')
spam = []                                  # 入力データを受け取るリストを用意
while True:
    print('\n' + '- input: ', end='')
    w = input()
    if w == 'end':                          # endが入力されたら，リストの追加をやめる
        print('\nThe list you made: ' + str(spam) + '\n')
        break                               # 作成したリストを表示したあと，ループから抜ける
    spam.append(w)                          # 随時，入力をリストに追加
print('\n--> ' + f(spam))                   # 関数f()にspamを渡し，返り値を表示させる

input()                                     # PAUSE

```
f(arg)のif文とelif文は入れ替わっても問題ないですが，if文が，処理を上から順番に，飛ばさずに実行するということを踏まえると，「`arg[-1]`->`arg[-2]`->その他」の順番が気持ちいいです．
入力が1単語のときはそれをそのまま返し，2単語だけのときはA and Bの形で表示するようになっています．

ちなみに，`\n`は改行文字です．時々出てくる`end=''`は，出力文字列の後ろに何もつけない指定をしています（デフォルトだと改行が入る）．あと`', '`とか`' and '`というように，空白文字を前後につけたり...`sep=','`を今回使わなかったのは，問題文に`apple, bananas, tofu and cats`ではなく，`'apple, bananas, tofu and cats'`(:1つの文字列)を返せと書いてあったからです．

関数の内部状態が分かるように，関数内に適宜`input()`を入れたものがこちら：
![image.png](https://qiita-image-store.s3.amazonaws.com/0/253921/18032ea6-639a-669e-71a5-4b81dfb92666.png)
`', '`や`' and '`が適宜挿入されている流れが分かると思います．


# 演習【4.10.2】文字絵グリッド (p.107)

■　要件

- 左のようなリストから，右のような表示をせよ(行列の転置みたいなやつ)  
![image.png](https://qiita-image-store.s3.amazonaws.com/0/253921/fecb10d5-b533-f9a2-e7bf-44033b24410a.png)
- ※実際の問題ではドットでしたが，見ずらかったのでハイフンにしています．

■　実装例

```python
grid = [['_', '_', '_', '_', '_', '_'],
        ['_', '0', '0', '_', '_', '_'],
        ['0', '0', '0', '0', '_', '_'],
        ['0', '0', '0', '0', '0', '_'],
        ['_', '0', '0', '0', '0', '0'],
        ['0', '0', '0', '0', '0', '_'],
        ['0', '0', '0', '0', '_', '_'],
        ['_', '0', '0', '_', '_', '_'],
        ['_', '_', '_', '_', '_', '_']]

i = 0                                 #---------┐
for j in range(len(grid[i])):         #         |
    for n in range(len(grid)):        #---┐     |
        print(grid[n][j], end='')     #   ├※1  ├※2
    print('\n')                       #---┘     |
    i += 1                            #---------┘

input()                               # PAUSE

```
grid[i][j]とおき，

※1の発想...

1. `grid[0][j]`~`grid[-1][j]`というふうに，  
`j`を固定し，`i`を動かすループが必要
1. (`i`を動かす=行のインデックスを回す)
1. `len(grid)`をrange()に入れたfor文を回す：  
`j`は固定（外ループに依存）しつつ，`i`を回す（グリッドの行数分，縦移動）
1. 横移動なしで縦移動し，一番したについたら，改行文字を入れる

※2の発想...

1. `i`は固定（外ループ？に依存）しつつ，`j`を回す（グリッドの列数分，横移動）
2. `i`も回さないといけないけど，`for i in range(len(grid))`を一番外のループに書くと，同じようなループが３重になって見た目がすっきりしないので，`j`ループ内のカウンタ`i += 1`で`i`を回し，その外に初期値`i = 0`を置く

というように，内側から順番に考えるとやりやすかったです．
休憩を入れてから取り組むとスムーズに解けました．詰んだときは，とりあえず別のことをしましょう！

---

今回は以上です．

気が向いたら続き書きます