3-1. 辞書 (dictionary)

キーと値を対応させるデータ構造である辞書について説明します。

参考

辞書は、キー (key) と (value) を対応づけるデータです。 キーとしては、文字列・数値・タプルなどの変更不可能なデータを使うことができますが、 変更可能なデータであるリスト・辞書を使うことはできません。 (辞書も変更可能なデータです。) 一方、値としては、変更の可否にかかわらずあらゆる種類のデータを指定できます。

たとえば、文字列 'apple' をキーとし値として数値 3 を、'pen' をキーとして 5 を対応付けた辞書は、 次のように作成します。

[1]:
ppap = {'apple' : 3, 'pen' : 5}
ppap
[1]:
{'apple': 3, 'pen': 5}
[2]:
type(ppap)
[2]:
dict

辞書の キー1 に対応する値を得るには、リストにおけるインデックスと同様に、

辞書[キー1]

とします。

[3]:
ppap = {'apple' : 3, 'pen' : 5}
ppap['apple']
[3]:
3

辞書に登録されていないキーを指定すると、エラーになります。

[4]:
ppap['orange']
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
/tmp/ipykernel_3729/2828625380.py in <module>
----> 1 ppap['orange']

KeyError: 'orange'

キーに対する値を変更したり、新たなキーと値を登録するには代入文を用います。

[5]:
ppap = {'apple' : 3, 'pen' : 5}
ppap['apple'] = 10
ppap['pinapple'] = 7
ppap
[5]:
{'apple': 10, 'pen': 5, 'pinapple': 7}

上のようにキーから値は取り出せますが、値からキーを直接取り出すことはできません。 また、リストのようにインデックスを指定して値を取得することはできません。

[6]:
ppap[1]
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
/tmp/ipykernel_3729/2923433636.py in <module>
----> 1 ppap[1]

KeyError: 1

キーが辞書に登録されているかどうかは、演算子 in を用いて調べることができます。

[7]:
ppap = {'apple': 3, 'pen': 5}
'apple' in ppap
[7]:
True
[8]:
'banana' in ppap
[8]:
False

組み込み関数 len によって、辞書に登録されている要素、キーと値のペア、の数が得られます。

[9]:
ppap = {'apple': 3, 'pen': 5}
len(ppap)
[9]:
2

del 文によって、登録されているキーの要素を削除することができます。具体的には、次のように削除します。

del 辞書[削除したいキー]
[10]:
ppap = {'apple' : 3, 'pen' : 5}
del ppap['pen']
ppap
[10]:
{'apple': 3}

空のリストと同様に空の辞書を作ることもできます。このような空のデータは繰り返し処理でしばしば使われます。

[11]:
empty_d = {}
empty_d
[11]:
{}

練習

リスト list1 が引数として与えられたとき、list1 の各要素 value をキー、valuelist1 におけるインデックスをキーに対応する値とした辞書を返す関数 reverse_lookup を作成してください。

以下のセルの ... のところを書き換えて reverse_lookup(list1) を作成してください。

[12]:
def reverse_lookup(list1):
    ...

上のセルで解答を作成した後、以下のセルを実行し、実行結果が True になることを確認してください。

[13]:
print(reverse_lookup(['apple', 'pen', 'orange']) == {'apple': 0, 'orange': 2, 'pen': 1})
False

辞書のメソッド

辞書のメソッドを紹介しておきます。

キーを指定して値を得るメソッド

get メソッドは、引数として指定したキーが辞書に含まれてる場合にはその値を取得し、 指定したキーが含まれていない場合には None を返します。 get を利用することで、エラーを回避し、登録されているかどうかわからないキーを使うことができます。 先に説明したキーを括弧、[...]、で指定する方法では、 辞書にキーが存在しないとエラーとなりプログラムの実行が停止してしまいます。

[14]:
ppap = {'apple' : 3, 'pen' : 5}
print('キーappleに対応する値 = ', ppap.get('apple'))
print('キーorangeに対応する値 = ', ppap.get('orange'))
print('キーorangeに対応する値(エラー) = ', ppap['orange'])
キーappleに対応する値 =  3
キーorangeに対応する値 =  None
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
/tmp/ipykernel_3729/2572363094.py in <module>
      2 print('キーappleに対応する値 = ', ppap.get('apple'))
      3 print('キーorangeに対応する値 = ', ppap.get('orange'))
----> 4 print('キーorangeに対応する値(エラー) = ', ppap['orange'])

KeyError: 'orange'

また、get に2番目の引数を与えると、その引数の値を「指定したキーが含まれていない場合」に get が返す値とすることができます。

[15]:
ppap = {'apple' : 3, 'pen' : 5}
print('キーappleに対応する値 = ', ppap.get('apple', -1))
print('キーorangeに対応する値 = ', ppap.get('orange', -1))
キーappleに対応する値 =  3
キーorangeに対応する値 =  -1

▲キーがない場合に登録を行う

setdefault メソッドは、 指定したキーが辞書に含まれてる場合には、対応する値を返します。 キーが含まれていない場合には、2番目の引数として指定した値を返すと同時に、キーに対応する値として登録します。

[16]:
ppap = {'apple' : 3, 'pen' : 5}
print('キーappleに対応する値 = ', ppap.setdefault('apple', 7))
print('setdefault("apple", 7)を実行後の辞書 = ', ppap)
print('キーorangeに対応する値 = ', ppap.setdefault('orange', 7))
print('setdefault("orange", 7)を実行後の辞書 = ', ppap)
キーappleに対応する値 =  3
setdefault("apple", 7)を実行後の辞書 =  {'apple': 3, 'pen': 5}
キーorangeに対応する値 =  7
setdefault("orange", 7)を実行後の辞書 =  {'apple': 3, 'pen': 5, 'orange': 7}

上のような setdefault を用いた手続きを、[...] を用いて書き換えるとたとえば次のようになります。

[17]:
ppap = {'apple' : 3, 'pen' : 5}
if 'apple' not in ppap:
    ppap['apple'] = 7
print('キーappleに対応する値 = ', ppap['apple'])
print('実行後の辞書 = ', ppap)
if 'orange' not in ppap:
    ppap['orange'] = 7
print('キーorangeに対応する値 = ', ppap['orange'])
print('実行後の辞書 = ', ppap)
キーappleに対応する値 =  3
実行後の辞書 =  {'apple': 3, 'pen': 5}
キーorangeに対応する値 =  7
実行後の辞書 =  {'apple': 3, 'pen': 5, 'orange': 7}

▲キーを指定した削除

pop メソッドは指定したキーおよびそれに対応する値を削除し、削除されるキーに対応付けられた値を返します。

[18]:
ppap = {'apple' : 3, 'pen' : 5}
print(ppap.pop('pen'))
print(ppap)
5
{'apple': 3}

▲全てのキーと値の削除

clear メソッドは全てのキーと値を削除します。その結果、辞書は空となります。

[19]:
ppap = {'apple' : 3, 'pen' : 5}
ppap.clear()
ppap
[19]:
{}

キーの一覧を得る

keys メソッドはキーの一覧を返します。これはリストのようなものとして扱うことができ、 for ループと組み合わせて繰り返し処理で利用されます(3-2を参照してください)。 以下のように、keys メソッドが返した結果に関数 list を適用すると、 通常のリストになります。

[20]:
ppap = {'apple' : 3, 'pen' : 5}
list(ppap.keys())
[20]:
['apple', 'pen']

値の一覧を得る

values メソッドはキーに対応する全ての値の一覧を返します。これもリストのようなものとして扱うことができます。

[21]:
list(ppap.values())
[21]:
[3, 5]

キーと値の一覧を得る

items メソッドはキーとそれに対応する値をタプルにした一覧を返します。 これもタプルを要素とするリストのようなものとして扱うことができ、forループなどで活用します(3-2を参照してください)。

[22]:
list(ppap.items())
[22]:
[('apple', 3), ('pen', 5)]

▲辞書を複製する

copy メソッドは辞書を複製します。リストの場合と同様に一方の辞書を変更してももう一方の辞書は影響を受けません。

[23]:
ppap = {'apple': 3, 'pen': 5, 'orange': 7}
ppap2 = ppap.copy()
ppap['banana'] = 9
print(ppap)
print(ppap2)
{'apple': 3, 'pen': 5, 'orange': 7, 'banana': 9}
{'apple': 3, 'pen': 5, 'orange': 7}

keys, values, items の返値

keys, values, items メソッドの一連の説明では、返値を「リストのようなもの」と表現してきました。 通常のリストとどう違うのでしょうか?

次の例では、ppapkeys, values, items メソッドの返値をそれぞれ ks, vs, itms に代入し、 print でそれぞれの内容を表示させています。

次いで、ppap に新たな要素を加えたのちに、同じ変数の内容を表示させています。 1, 2回目の print で内容が異なることに注意してください。 もとの辞書が更新されると、これらの内容も動的に変わります。

[24]:
ppap = {'apple': 3, 'pen': 5, 'orange': 7}
ks = ppap.keys()
vs = ppap.values()
itms = ppap.items()
print(list(ks))
print(list(vs))
print(list(itms))
ppap['kiwi'] = 9
print(list(ks))
print(list(vs))
print(list(itms))
['apple', 'pen', 'orange']
[3, 5, 7]
[('apple', 3), ('pen', 5), ('orange', 7)]
['apple', 'pen', 'orange', 'kiwi']
[3, 5, 7, 9]
[('apple', 3), ('pen', 5), ('orange', 7), ('kiwi', 9)]

辞書とリスト

冒頭で述べたように、辞書では値としてあらゆる型のデータを使用できます。 すなわち、次のように値としてリストを使用する辞書を作成可能です。 リストの要素を参照するには数字インデックスをさらに指定します。

[25]:
numbers = {'dozens': [10, 20, 40], 'hundreds': [100, 101, 120, 140]}
print(numbers['dozens'])
print(numbers['dozens'][1])
[10, 20, 40]
20

逆に、辞書を要素とするリストを作成することもできます。

[26]:
ppap = {'apple': 3, 'pen': 5}
pets = {'cat': 3, 'dog': 3, 'elephant': 8}
ld = [ppap, pets]
print(ld[1])
print(ld[1]['dog'])
{'cat': 3, 'dog': 3, 'elephant': 8}
3

練習

辞書 dic1 と文字列 str1 が引数として与えられたとき、 以下のように dic1 を変更する関数 handle_collision を作成してください。 ただし、dic1 のキーは整数、キーに対応する値は文字列を要素とするリストとします。

  1. dic1str1 の長さ n がキーとして登録されていない場合、str1 のみを要素とするリスト ls を作成し、 dic1 にキー nn に対応する値 ls を登録します。

  2. dic1str1 の長さ n がキーとして登録されている場合、そのキーに対応する値(リスト)に str1 を追加します。

以下のセルの ... のところを書き換えて handle_collision(dic1, str1) を作成してください。

[27]:
def handle_collision(dic1, str1):
    ...

上のセルで解答を作成した後、以下のセルを実行し、実行結果が True になることを確認してください。

[28]:
dic1_orig = {3: ['ham', 'egg'], 6: ['coffee', 'brandy'], 9: ['port wine'], 15: ['curried chicken']}
dic1_result = {3: ['ham', 'egg', 'tea'], 6: ['coffee', 'brandy'], 9: ['port wine'], 15: ['curried chicken']}
handle_collision(dic1_orig, 'tea')
print(dic1_orig == dic1_result)
False

練習の解答

[29]:
def reverse_lookup(list1):
    dic1 = {}  # 空の辞書を作成する
    for value in list1:
        dic1[value] = list1.index(value)
    return dic1
#reverse_lookup(['apple', 'pen', 'orange'])
[30]:
def handle_collision(dic1, str1):
    if dic1.get(len(str1)) is None:
        ls = [str1]
    else:
        ls = dic1[len(str1)]
        ls.append(str1)
    dic1[len(str1)] = ls
#handle_collision({3: ['ham', 'egg'], 6: ['coffee', 'brandy'], 9: ['port wine'], 15: ['curried chicken']}, 'tea')
[ ]: