Pythonプログラミング(ステップ7・辞書・集合・タプル)

このページでは、リスト(list)の「親類」である、Pythonの辞書(dict)と集合(set)、およびタプル(tuple)について学ぶ。

1. 辞書:拡張版リスト

リストは、複数のデータに0から順に通し番号をつけて管理する仕掛けであった。 これは、0以上の整数からデータへの写像と考えることができる。 この考えをさらに進めて、整数に限らず、有限個のデータからデータへの写像を考えるのは、ごく自然であろう。 こうした「拡張されたリスト(配列)」は、ハッシュ(hash)と呼ばれて、Pythonに限らず、複数のプログラミング言語に実装されている。 特にPythonではこれを「辞書(dictionary)」と呼んでいる。

Pythonの辞書は以下のように作成する:

dict = {"リンゴ":"赤", "バナナ":"黄", "ミカン":"橙", "イチゴ":"赤"}

[ ] の代わりに { } 記号を用いることと、それぞれの要素が キー(key)と値(value)のペアで構成される点が、リストと異なる。

上の例では、果物の名称がキーで、色が値、という対応になっている。

このように辞書を作ると、dict["リンゴ"]は"赤"を, dict["バナナ"]は"黄"を与える。 定義されていないキー(例えば、dict["キウイ"])を使うと、エラーになる。 また、ひとつのキーに複数の値を対応づけることはできない。

辞書に新しく「語彙」を加えたり、値を更新するには

dict["キウイ"] = "緑"

としても良いし、

dict.update(キウイ = "緑")

でもよい。後者の場合、キーの指定にクォート記号を付けるとエラーになるので注意。

辞書から特定のキーを持つデータを削除するには、

dict.pop("バナナ")

のように、辞書から取り出せば(popすれば)よい。

2. 集合:順序を気にせず、重複を許さないリスト

数学で登場する「集合」を自然にコーディングする仕掛けがPythonには備わっている。 例えば、果物の集合(set)を定義してみよう。

fruit = {"リンゴ", "バナナ", "ミカン"}

このように、集合の表現には、辞書と同様、{ } 記号を用いる。 辞書の要素はキーと値のペアで構成されていたが、集合は値が列挙されるのみである。 するとリストの括弧を変えただけのようにも思えるが、集合では重複する要素を持てない(持たない)。 例えば、

fruit = {"リンゴ", "バナナ", "ミカン"}
fruit.add("ミカン") # 要素の追加
print(fruit)

を実行すると、出力結果は {'ミカン', 'バナナ', 'リンゴ'} となる。

以下のように、list(集合)、または set(リスト) を使って、集合とリストは相互に変換可能である。

fruit = {"リンゴ", "バナナ", "ミカン"}
list_of_fruit = list(fruit)

set_of_number=set([1,2,3,4,5])

ここまでに登場した3つの型について、基本的な操作方法をまとめておく:

リスト, 辞書, 集合の比較

リスト(list) 辞書(dict) 集合(set)
作成(空)
x=[ ]
d={ }
s=set()
作成(要素あり)
x=[1,2,3]
d={"one":1,"two":2,"three":3}
s={1,2,3}
参照・確認
# ..番目を指定
n=x[0] 
# キーを指定
n=d["one"] 
# in 演算子を使用
# 1がsに含まれるか(TrueまたはFalse)
if 1 in s:
  ... 
要素追加
x.append(4)
d["four"]=4
s.add(4)
要素削除
x.remove(1)
# キーを指定して削除
# 削除したキーに対応する値が返る
v=d.pop("one")
# 値が不要の場合は
del d["one"]
# も可
s.remove(1)
連結
x.extend([4,5,6])
d.update({"four":4,"five":5,"six":6})
# 和集合を生成
s2 = s.union({4,5,6}
# または
s2 = s | {4,5,6}

# 積集合(交差)を生成
s3 = s.intersection({2,3})
# または
s3 = s & {2,3}

差分
# 直接的な手段なし
# (集合に変換して処理)
# 直接的な手段なし
# (集合に変換して処理)
diff={1,2,3,4,5,6} - {2,4,6}
# diffは{1,3,5}になる
		
値からキーを求める
x=[1,2,3,4,5]
i=x.index(3)
# iには2がセットされる
			
# 値を検索する
d={"one":1,"two":2,"three":3}
for k,v in d.items():
	if k==3:
		print(v)
			
# 集合の要素に順序等は無い

3.タプル:更新できないリスト

タプル(tuple)は「順序付きのワンセットのデータ」で、丸括弧を使って

digit = (0,1,2,3,4,5,6,7,8,9)
choice = ("リンゴ","ミカン","バナナ")

のように表現する。ここで、タプルの要素に choice[2] のようにアクセスすることはできるが、変更は許されない(代入するとエラーになる)。

タプルはプログラム中で「定数」として利用できるほか、関数の戻り値が複数ある場合によく使われる。関数の最後に

def func():
   ...
   return x,y,z

のように記述すると、タプル (x,y,z) が戻り値となる。呼び出し側で、これを

  a,b,c = func()

のように受けることで、aにx、bにy、cにzの値がそれぞれセットできる。

4.相互変換

リストXを集合Yに変換するには Y = dict(X), 集合YをリストXに変換するには X = list(Y) のように書けばよい。

同様に、tuple( )関数を使って、リストや集合をタプルに変換できる。

文字列をリスト等に変換する場合、list('ABC')は、['A','B','C']を表すので、注意が必要である。

5.辞書とタプルを用いたコードの例

辞書を使った例として、置換 $$ \begin{eqnarray}\left( \begin{array}{ccccc} \textrm{brown} & \textrm{red} & \textrm{orange} & \textrm{yellow} & \textrm{green} & \textrm{blue} & \textrm{purple} & \textrm{grey} & \textrm{white} & \textrm{black} \\ \textrm{brown} & \textrm{blue} & \textrm{red} & \textrm{purple} & \textrm{orange} & \textrm{grey} & \textrm{yellow} & \textrm{white} & \textrm{green} & \textrm{black} \end{array}\right)\end{eqnarray} $$ を辞書で表現し、それを互換に分解するコードを示す。 そのアルゴリズムについてはあみだくじの説明の箇所を参照のこと。

このように、辞書を使うことで、整数以外のデータも直感的に取り扱えるようになる。

辞書.items() は、辞書のキーと値をペアをタプルとして返す。

# coding: utf-8

sigma={'brown':'brown', 'red':'blue', 'orange':'red', 'yellow':'purple', 'green':'orange', 
       'blue':'grey', 'purple':'yellow', 'grey':'white', 'white':'green', 'black':'black'}

L=[ ]

for k,c in sigma.items():
    s = k
    for x1,x2 in L:
        if x1==s:
            s=x2
        elif x2==s:
            s=x1
    if s==c:
        continue
    L.append([s,c])

print(L)

コードを実行すると、互換のリストが表示されるが、教科書などの流儀とは逆順になっている(左から右の順に作用)ので注意。

類似の例として、参考書の『4.1 カードマジック』で述べられている巡回置換の分解アルゴリズム(Algorithm 16, p.114)を 辞書を使ってコーディングしたのが以下である。 この例でも、辞書を使って置換$\sigma$を表現している。

Algorithm 16 のPythonコードの例

# coding: utf-8

sigma={1:1, 2:6, 3:2, 4:7, 5:3, 6:8, 7:4, 8:9, 9:5, 10:10}

X=[1,2,3,4,5,6,7,8,9,10]
L=[]

while len(X)>0:
    x = X[0]
    C = [x]
    y = sigma[x]
    while y != x:
        C.append(y)
        y = sigma[y]
    L.append(C)
    for z in C:
        X.remove(z)

print(L)

icon-pc 練習:要素の組み合わせの列挙

10個の要素からなるPythonの集合

s = {'brown', 'red', 'orange', 'yellow', 'green', 'blue', 'purple', 'grey', 'white', 'black'}

の中から5個の要素を取り出す方法を全て列挙するコードを書いてみなさい。

icon-hint ヒント

アルゴリズムによっては、集合をリストに変換してから処理したほうが効率的かもしれない(例:X = list(s))。 リストの添字を$i_1, i_2, \cdots, i_5$とすれば、$0 \le i_1 \lt i_2 \lt \cdots \lt i_5 \lt 10$ であるような $\{ X_{i_1}, X_{i_2}, \cdots, X_{i_5} \}$ を出力すればよいはず。

文字列をリストや集合に変換する場合は注意が必要である。set('apple'){'a','p','l','e'}となる。 set(['apple']){'apple'}を表す。

集合$X$から$n$個の要素を取り出す組み合わせを得る関数を $\textrm{comb}(X,n)$は、$X$から要素を一つだけ取り除いた集合$Y$から$n-1$個を取り出す組み合わせ $\textrm{comb}(Y,n-1)$を使って表現できるので、再帰的な関数を用いると、 すっきりとコーディングできるかもしれない。


icon-pc 練習:部分集合の列挙

10個の要素からなるPythonの集合

s = {'brown', 'red', 'orange', 'yellow', 'green', 'blue', 'purple', 'grey', 'white', 'black'}

のすべての部分集合(べき集合)をプリントするコードを書いてみなさい。

icon-hint ヒント

基本的には、各要素を「含む」「含まない」のパターンをすべて列挙するしかない。

itertoolsライブラリには、リストや集合の要素の組み合わせを求める機能(combination)が含まれているので、それを用いることもできる。以下は具体的なコードの例である。

# coding: utf-8
import itertools

s = {'brown', 'red', 'orange', 'yellow', 'green', 'blue', 'purple', 'grey', 'white', 'black'}
powerset = [ ]
for n in range(len(s)+1):
    res = itertools.combinations(s,n)
    for subset in res:
        powerset.append(set(subset))

print(powerset)

なお、Pythonでは集合を要素とする集合(集合の集合)は表現できないため、上の例では、べき集合を集合のリストとして表現した。

icon-pc 練習:辞書を用いた簡易データベース

各都道府県の郵便番号と住所のCSVデータが https://www.post.japanpost.jp/zipcode/download.htmlからダウンロードできる。

表の中から出身地等のデータ選んでダウンロードし、Pythonの実行環境から読み取れる場所にCSVデータをコピーの上、下記のサンプルコード(郵便番号から住所を検索)の動作を確認する(この例では、宮城県のデータ04MIYAGI.CSVを用いているが、適宜、変更すること)。

Google Colabを使っている場合は、こちらのページの説明を確認の上、ランタイムの作業ディスクにCSVファイルをアップロードする必要がある。

郵便番号は半角で、途中にハイフン等は入れず、 9800852のように入力する

# coding:utf-8

import pandas as pd

data = pd.read_csv('04MIYAGI.CSV', dtype={2:object},
                   header=None, encoding='Shift_JIS')

yubin_db = dict()

for c,x in data.iterrows():
    zip = x[2]
    addr = x[6]+x[7]+x[8]
    yubin_db[zip] = addr

while True:
    zip = input('ZIP CODE:')
    addr = yubin_db[zip]
    print(addr)

このコードは、存在しない郵便番号を入力するとエラー(例外)で停止してしまう。 そこで、存在しない番号が入力された場合は "見つかりません" と表示できるよう、コードを改良してみなさい。