重複データを 1NF に変換 (フラットなリスト化)
1 つのセルに詰めると、あとで数えられない
蔵書の一覧を表計算ソフトで作ると、タグの欄はたいていこうなります。
| 書名 | タグ |
|---|---|
| 深夜特急 | 旅行, エッセイ |
| 銀河鉄道の夜 | 童話 |
| 地球の歩き方 | 旅行 |
眺めるぶんには困りません。困るのは「旅行タグの本は何冊あるか」を数えようとしたときです。第 4 章でやったように辞書で数え上げると、次のようになります。
Python
books = [
["深夜特急", "旅行, エッセイ"],
["銀河鉄道の夜", "童話"],
["地球の歩き方", "旅行"],
]
counts = {}
for title, tags in books:
counts[tags] = counts.get(tags, 0) + 1
# {'旅行, エッセイ': 1, '童話': 1, '旅行': 1}旅行の本は 2 冊あるのに、どちらも 1 と出ます。"旅行, エッセイ" と "旅行" が別のキーとして数えられているからです。検索でも同じことが起きます。tags == "旅行" で絞ると 1 行目が漏れ、部分一致に逃げると 旅行記 のような別のタグまで拾ってしまいます。
原因は 1 か所で、1 つのマスに複数の値が入っていることです。これをやめて 1 行 1 値に開くと、深夜特急の行が 2 行に増えて、タグの欄には 旅行 と エッセイ が 1 つずつ入ります。この形をデータベースの世界では第一正規形(1NF)と呼びます。
分ける道具は split、そのあと strip
カンマで区切られた文字列は split で分けられます。ただし、分けただけでは使えません。
Python
"旅行, エッセイ".split(",")
# ['旅行', ' エッセイ']2 つ目の先頭に空白が残っています。このまま数えると エッセイ と エッセイ が別物として扱われ、最初と同じ失敗をやり直すことになります。前後の空白は strip(JavaScript なら trim)で落とします。
区切り文字を ", " にして空白ごと片づけたくなりますが、やめたほうが無難です。"旅行,エッセイ" のように空白なしで入力された行が分けられなくなります。区切るのはカンマだけにして、空白の始末は strip に任せてください。
消し忘れのカンマが、空のタグを作る
人が手で入力した欄には、"旅行, , エッセイ" のように余ったカンマが残っていることがあります。
Python
[raw.strip() for raw in "旅行, , エッセイ".split(",")]
# ['旅行', '', 'エッセイ']真ん中に空文字が残りました。これを出力に入れると、タグ名が空の行が 1 件生まれます。集計では 1 件として数えられ、画面では何も表示されない行になります。strip した結果が空文字なら捨てる、という判定を必ず通してください。
やってみよう
normalize1NF(rows) を完成させてください。rows は [名前, カンマ区切りの文字列] の組の並びで、返すのは 1 値ずつに開いた組の並びです。
- 外側で 1 組ずつ取り出し、内側でカンマ区切りを 1 つずつ取り出す
- 取り出した値の前後の空白を落とす
- 落とした結果が空文字なら、出力に入れない
1 組の入力から複数の組が生まれるので、map だけでは書けません。ループは 2 段になります。
要件
- カンマで区切られた文字列を 1 値ずつ分解する
- 各値の前後の空白を
trimして取り除く - 空文字(trim 後に長さ 0)は出力に含めない
入出力例
normalize1NF([["Alice","reading, music, hiking"],["Bob","gaming"]]) → [["Alice","reading"],["Alice","music"],["Alice","hiking"],["Bob","gaming"]]
normalize1NF([["Carol","cooking,travel"]]) → [["Carol","cooking"],["Carol","travel"]]
normalize1NF([["Dave"," movies , music "]]) → [["Dave","movies"],["Dave","music"]]
normalize1NF([["Eve","art, , drawing"]]) → [["Eve","art"],["Eve","drawing"]]
normalize1NF([["A","x,y"],["B","z,w"]]) → [["A","x"],["A","y"],["B","z"],["B","w"]]