【Python】新卒の子に出した問題、自分ができないと恥ずかしいのでやった

タイトル通りです。
できるだけ初学者にもわかるように書きたい。

問題(て～れん):
下記のような辞書があります。
この辞書のvalueをすべて文字列にした辞書を作成しなさい。
また、ネストされてる辞書やリストの場合、それ自体を文字にするのではなく、その中のvalueを文字列に変換しなさい。

```json:input.json
{
    "key1": "val1",
    "key2": 2,
    "key3": ["test", "test", "test"],
    "key4": ["test", 4, "test"],
    "key5": "val5",
    "key6": 6,
    "key7": {"key8": "val8","key9": 9, "key10": {"key11": 11}}
}
```

という問題。

一応、出した後に再起呼び出し必要で難しそうなのでいったん汎用性なしでいいよ、と言っておいたけど…
問題出したからには汎用的なの作りたいよね。

# 必用そうな知識
- 型について
- リストについて
- 辞書について
- if文について
- for文について
- 型の変更について
- 関数について
- 再起呼び出しについて


一通りこんな感じかな。
これらについて、今回利用しているものについてだけ説明していきたいと思います。

## 型について
型、というのはデータの形式のようなものです。
`765`のような整数の場合は整数型(`int`)
`3.141592`のような少数の場合、浮動小数点型(`float`)
`"test"`であれば文字データなので型は文字列型(`str`)
などがあります。

python内でとあるデータや変数がどんな型なのか知りたいときは
```python
zimusyo = 283
type(zimusyo)
```
のように`type`でくくってあげると型を取得することができます。
確認したいときはこれをprintしたりするといいと思います。

## リストについて
リスト(list)です。
```python
["test","test"]
```
のように`[]`でくくり、データとデータの間を`,`で区切ったものになります。
中に入れるデータは何でもよく、複数の型を混同させることもできます。
データを取り出す際は
```python
Vtuber = ["OuroKronii", "大代真白", "兎田ぺこら", "宇宙あんず"]
print(Vtuber[1])
```
のようにデータの後ろに`[]`をつけ、その中に何番目のデータが欲しいかを入れてあげます。
注意点として、何番目のデータが欲しいか数える際に、0から始めて何番目かとなる点に注意して下さい。
上記のように`[1]`と指定した場合に取得できるのは`大代真白` でちゅ ~~推せ~~
データを更新する際には
```python
Vtuber[1] = "音霊魂子"
```
のようにデータの場所を指定して上書きすることで更新できます。


## 辞書について
辞書(dict)です。
```python
{"key1": "value1", "key2": "value2"}
```
のように`{}`でくくったデータです。
1つのデータは`key`と`value`の組で構成されており、この二つは`:`を挟んで組になります。
複数のデータを入れる場合はそれぞれの組の間に`,`を挟んで区切ります。
valueにはどのようなデータを入れてもいいので、辞書が入れ子になることもあります。
```python
{"key1": {"key2": "value2"}}
```
こんな感じで辞書の中に辞書、という状況もあり得るということです。

辞書型のデータを取得するには

```python
aisatu = {"suisei": "すいちゃんは～？", "usada": "こんぺここんぺここんぺこ～", "YMD": "かつもく～"}
print(aisatu["suisei"])
```
のようにデータの後ろに`[]`をつけ、その中にほしいデータのkeyを入れると取得できます。
上記のように`["suisei"]`と指定した場合に取得できるのは`すいちゃんは～？` ~~きょうもちいさーい~~
データを更新する際には
```python
aisatu["suisei"] = "きょうもかわいー"
```
のようにデータのkeyを指定して上書きすることで更新できます。


## if文について
if文はいわゆる条件分岐というやつです。
○○の時は～～、××の時は～～
のように場合分けしたいときに使う文です。
```python
if a == b:
  print("a == b")
elif a == c:
  print("a == c")
else:
  print("False")
print("これはelseの外")
```
のように書きます。
if の後ろに判定したい条件文を入れ`:`で命令を終わり、その下の行から分岐後にしたい内容を書きます。
この時、ifの中で実行したいものはインデント(文頭の隙間)を開ける必要があり、同じ中身のインデントはすべて等しくある必用があります。
ifの後、さらにifで条件を入れたい場合`elif`を使ってつなげることができます。
この時インデントはいったん元に戻す必要があります。
また、条件がいらない(それ以外の場合～)ときは、`else`を使ってつなげるとできます。
こちらもいったんインデントを戻してあげましょう。
pythonはインデントで命令範囲を定めているのでしっかりインデントを意識して書いていきましょう。

ここでよく使う条件式を書いておきます。
```python
a == b # 同じ
a != b # 同じじゃない、違う
a <= b # bはa以上(同じ値を含む)
a <  b # bはaより大きい(同じ値は含まない)
a >= b # bはa以下(同じ値を含む)
a >  b # bはaより小さい(同じ値は含まない)
a in b # bの中にaがある 
a or b # aまたはb(論理和)aかbどちらか一方でもあってる場合
a and b# aかつb(論理積)aとbどちらもあってる場合
a      # aの中身が存在する(True)
!a     # aの中身が存在しない(False)
```
こんなところかと。おおざっぱなので気になったら調べましょう。


## for文について
pythonのfor文は他の言語ではよく`forEach`と呼ばれているものの挙動に近いと思います。
```python
for value in ["やって見せろよ、マフティー!", "何とでもなるはずだ!!", "ガンダムだと?!"]:
  print(value)
```
としたとき、iにはlistの中身が先頭から一つ取り出された値が入ることになります。
一週目だと、`やって見せろよ、マフティー!`が入るということですね。

何週目なのかを知りたいときもあると思います。
その場合は`enumalate`というものを利用します。
```python
for i, value in enumerate(["やって見せろよ、マフティー!", "何とでもなるはずだ!!", "ガンダムだと?!"]):
  print(i, value)
```
こうすることで`i`(forの前の方の変数)に何週目なのかという情報が入ってくれます。
何週目かは0週目から数えられる点に注意しましょう。

また、辞書もforで回すことができます。
```python
kabotya = {"ガウマン": "やって見せろよ、マフティー!", "ハサウェイ": "何とでもなるはずだ!!", "レーン": "ガンダムだと?!"}
for key, value in kabotya.items() :
  print(key, value)
```
のようにデータの後ろに`items()`とつけることでkeyとvalueを一つずつ取り出してくれます。
どちらか片方で良い場合は`keys()`, `values()`のどちらかをつけるとよいでしょう。


## 型の変更について
最初の方で型についてはなしたとおもいます。
型は、ものによっては変換な能なことがあります。
例えば`"346"`という文字列は数字のみでできているので数値の型に変換ができるのです。
この型の返還を`cast変換`といいます。
```python
zimusyo = "346"
suuzi = int(zimusyo)
```
このように変更したい型でくくってあげると変更することができます。
すべてのデータでできるわけではないので注意しておきましょう。



## 関数について
関数とは、特定の動作をするもの、というイメージでいいと思います。
```python
def _add(arg1, arg2):
  return arg1 + arg2
```
上記のようにして関数は定義されます。
`def`の後に任意の名前を付け、`()`の中に必要な情報を入れるための引数を用意します。
その後、関数の中にやりたい動作等を書いていきます。
今回は引数を２つ用意して、それを足した答えを教えてくれる関数にしました。

returnとすると、関数の実行を終了し、関数を呼び出したところにreturnの後ろの値を返してくれます、
```python
ans = _add(1, 2)
```
このように呼び出します。
一つ目の引数に1、二つ目の引数に2を入れました。
関数内では一つ目と二つ目を足して、その答えを返してくれるので、ansには`3`という値が入ります。

引数にはデフォルトの値を入れておくことができます。
これは、引数に何も入れられなかったときに何を入れておくかを決めて置けるものです。
```python
def _add(arg1, arg2=100):
  return arg1 + arg2
```
このように引数の後ろに`=`をつけて値を入れておくことで、arg2に何も入らなかったときはarg2の値を100として計算してくれるようになります。


おおざっぱですが関数については以上にしておきます。


## 再帰呼び出しについて

**ほんぺんかいし**

最初にも言いましたが、ここが難しいと思うので、よくわからんというひとは調べることを推奨します。

再帰呼び出しとは`関数の中で自分の関数を呼び出すこと`です。

無限ループしちゃうじゃん、と思ったあなた。正解です。
これは書き方によっては無限ループするようなものです。
どういうときに有効か、とかはちゃんと調べてください。私は雰囲気で再帰している。。。。

forやwileとの違いとしては、`ずっと同じ操作を繰り返したい`場合は`forやwile文`、`処理結果に対して同じ処理をしたい`場合は`再帰`だと思ってます。わからんけど。

```python
def _sub(arg1):
  if arg1 < 0:
    return arg1
  else:
    _sub(arg1-2)
```

こうすることで_subを実行するとarg1の値が0より小さくなるまで無限に1引き続ける関数ができました。
今回の例だと再帰呼び出しの恩恵がよくわからんと思いますが、本題は問題の方なので、そういうものだと思ってください。




# やっと問題を解くぞ

ではまず何がやりたいかを再確認。
ネストされたvalue含め、すべてのvalueを文字列に変換する。
ネストとは入れ子、つまり辞書の中の辞書の中の...みたいになってるやつってこと。

単純に考えてみれば

1. key, valueを一個ずつ取得
2. 文字列に変換
3. valueを置き換える
ですね。
まずこれ作っちゃいましょう。


```python 

def change_val_to_str(input_data):
  for key, val in input_data.items():
    change_data = str(val)
    input_data[key] = change_data 

```

すると結果が…
```json
{
    'key1': 'val1', 
    'key2': '2', 
    'key3': "['test', 'test', 'test']", 
    'key4': "['test', 4, 'test']", 
    'key5': 'val5', 
    'key6': '6', 
    'key7': "{'key8': 'val8', 'key9': 9, 'key10': {'key11': 11}}"
}
```
このように辞書やリストの形のまま文字列になっちゃいました。

ので、
1. key, valueを一個ずつ取得
  1. 辞書の場合
    1. そのkey, valueを一個ずつ取得
    2. 文字列に変換
    3. valueを置き換える
  2. リストの場合
    1. そのvalueを一個ずつ取得
    2. 文字列に変換
    3. valueを置き換える
  3. そうじゃない場合
    1. 文字列に変換
    2. valueを置き換える

これで辞書やlistの中身を文字列にできるはず！

```python
def change_str(input_data):
    for key, val in input_data.items():
        if type(val) == dict:
            for k, v in val.items():
                change_data = str(v)
                val[k] = change_data
        elif type(val) == list:
            for i, v in enumerate(val):
                change_data = str(v)
                val[i] = change_data
        else:
            change_data = str(val)
            input_data[key] = change_data
```
これでいいでしょうか
listを更新するために、場所の情報が必要になるので、enumalateが必要な点に注意しましょう。
```json
{
    'key1': 'val1', 
    'key2': '2', 
    'key3': ['test', 'test', 'test'], 
    'key4': ['test', '4', 'test'], 
    'key5': 'val5', 
    'key6': '6', 
    'key7': {
        'key8': 'val8', 
        'key9': '9', 
        'key10': "{'key11': 11}"
    }
}
```
よさそう、、でしたが最後の`key10`の値がまだちゃんとなってないようです。

<details><summary>じゃあ...?</summary><div>

```markdown

1. key, valueを一個ずつ取得
  1. 辞書の場合
    1. そのkey, valueを一個ずつ取得
      1. 辞書の場合
        1. そのkey, valueを一個ずつ取得
        2. 文字列に変換
        3. valueを置き換える
      2. リストの場合
        1. そのvalueを一個ずつ取得
        2. 文字列に変換
        3. valueを置き換える
      3. そうじゃない場合
        1. 文字列に変換
        2. valueを置き換える
    2. 文字列に変換
    3. valueを置き換える
  2. リストの場合
    1. そのvalueを一個ずつ取得
      1. 辞書の場合
        1. そのkey, valueを一個ずつ取得
        2. 文字列に変換
        3. valueを置き換える
      2. リストの場合
        1. そのvalueを一個ずつ取得
        2. 文字列に変換
        3. valueを置き換える
      3. そうじゃない場合
        1. 文字列に変換
        2. valueを置き換える
    2. 文字列に変換
    3. valueを置き換える
  3. そうじゃない場合
    1. 文字列に変換
    2. valueを置き換える
```
</div></details>


いやいやいや
長すぎなんよ。
でもこれ欲見てみると、
辞書の場合... の中でまた辞書の場合... と同じことを繰り返してるように見えますね？
繰り返し実行結果に処理したい場合は?そう、再帰処理ですね。

どうやら同じ処理の内容は
1. 辞書の場合
    1. そのkey, valueを一個ずつ取得
    2. 文字列に変換
    3. valueを置き換える
2. リストの場合
    1. そのvalueを一個ずつ取得
    2. 文字列に変換
    3. valueを置き換える
3. そうじゃない場合
    1. 文字列に変換
    2. valueを置き換える

ぽいですが、もうちょい考えてみましょう。
入力が辞書だった場合で考えてみましょう。
どこで再帰呼び出しするのがいいでしょうか。
辞書かリストの場合中身をいじりたいので、再帰呼び出しをするのは辞書かリストの時でよさそうです。
そして辞書かリスト以外の時は文字列に変えて代入する、でよさそうですね。

1. 入力が辞書だった場合
    1. そのkey, valueを一個ずつ取得
        1. それが辞書かリストだった場合
            1. 再帰
        1. それ以外の時
            1. 文字列変換
            1. valueを置き換える

では、入力がリストだった場合はどうでしょうか。
同じように再帰呼び出しするのは辞書かリストの時でよさそうです。
また、辞書かリスト以外の時も同様に文字列に変えて代入するだけでよさそうです。

1. 入力がlistだった場合
    1. その繰り返しの回数(i), valueを取得
        1. それが辞書かリストだった場合
            1. 再帰
        1. それ以外の時
            1. 文字列変換
            1. valueを置き換える


これでよさそうです。
最後に辞書とリスト以外についてですが、これが関数に入ることはありません。
というのも、再帰呼び出しの条件分岐で、辞書かリストのどちらか、としているので、これについての処理はいりませんね。

ではこれを踏まえてコードを書きましょう。

```python
def change_val_to_str(input_data, i="none"):
    if type(input_data) == dict:
        for k, v in input_data.items():
            if type(v) == dict or type(v) == list:
                change_val_to_str(v)
            else:
                input_data[k] = str(v)
    elif type(input_data) == list:
        for i, v in enumerate(input_data):
            if type(v) == dict or type(v) == list:
                change_val_to_str(v, i)
            else:
                input_data[i] = str(v)
```

このようになりました。
実行すると...

```json
{
    'key1': 'val1', 
    'key2': '2', 
    'key3': ['test', 'test', 'test'], 
    'key4': ['test', '4', 'test'], 
    'key5': 'val5', 
    'key6': '6', 
    'key7': {
        'key8': 'val8', 
        'key9': '9', 
        'key10': {
            "key11": "11"
        }
    }
}
```

できました！！



# まとめ
何も考えず再帰使うような課題出してごめんねゆるしてね。
でも再帰呼び出しを使えるようになれば複雑なデータの処理を行えるようになるので覚えておくといいと思います。
まずは関数を作って使い、使い方に慣れていってからでもいいと思いますが、必要な時に再帰使えるかな？
と思えるといいかもしれませんね。

ではでは。
大代真白はいいぞ。