X - 3.02.組み込みコンテナ Editorial /

Time Limit: 0 msec / Memory Limit: 0 KiB

前のページ | 次のページ

キーポイント

  • Python は多くのデータ型を備えており、特に代表的なものに list, tuple, set, dict が存在する
  • 扱いたいデータや処理の特徴に応じて使い分けることが望ましい

list / tuple

データの列を扱うことのできるデータ構造

  • list / tuple の主要な操作
操作 記法 計算量 list tuple
値のアクセス l[i] O(1)
値の変更 l[i] = x O(1)
末尾への値の挿入 l.append(x) O(1)
末尾以外への値の挿入 l.insert(i, x) O(N)
末尾の値の削除 l.pop() O(1)
末尾以外の値の削除 l.pop(i) O(N)
列の長さの取得 len(l) O(1)
値の存在判定 x in l O(N)
値の出現回数 l.count(x) O(N)
  • コード例
    # list
    l = [3, 1, 4, 1, 5]

    print(l[2])  # 4

    l[2] = 7
    print(l)  # [3, 1, 7, 1, 5]

    l.append(9)
    print(l)  # [3, 1, 7, 1, 5, 9]

    l.insert(3, 8)
    print(l)  # [3, 1, 7, 8, 1, 5, 9]

    l.pop()
    print(l)  # [3, 1, 7, 8, 1, 5]

    l.pop(0)
    print(l)  # [1, 7, 8, 1, 5]

    print(len(l))  # 5

    print(1 in l)  # True
    print(3 in l)  # False

    print(l.count(1))  # 2
    
    # tuple
    t = (3, 1, 4, 1, 5)

    print(t[2])  # 4

    print(len(t))  # 5

    print(1 in t)  # True
    print(2 in t)  # False
    print(t.count(3))  # 1
    

set

集合を扱うことのできるデータ構造

  • set の主要な操作
操作 記法 計算量 1
値の追加 s.add(x) O(1)
値の削除 s.remove(x) O(1)
集合の要素数の取得 len(s) O(1)
値の存在判定 x in s O(1)
  • コード例
    s = {1, 3, 5}

    s.add(2)
    print(s)  # {1, 2, 3, 5}

    s.remove(3)
    print(s)  # {1, 2, 5}

    print(len(s))  # 3

    print(2 in s)  # True
    print(3 in s)  # False
    

dict

辞書や連想配列と呼ばれるデータ構造

  • dict の主要な操作
操作 記法 計算量 1
値のアクセス d[k] O(1)
値の追加・更新 d[k] = v O(1)
キーの削除 d.pop(k) O(1)
辞書の項目数 len(d) O(1)
キーの存在判定 k in d O(1)
  • コード例
    d = {3: 31, 1: 31, 4: 30}

    d[11] = 30
    print(d)  # {3: 31, 1: 31, 4: 30, 11: 30}

    d.pop(4)
    print(d)  # {3: 31, 1: 31, 11: 30}

    print(len(d))  # 3

    print(11 in d)  # True
    print(4 in d)  # False
    print(31 in d)  # False
    

list

1.10.リスト で扱ったように、list はデータの列を扱うデータ構造です。

キーポイントで触れたように値のアクセス・変更・挿入・削除などができます。
これらは詳しくは 1.10.リスト で扱っていますので、ぜひ復習してみてください。

tuple

tuplelist と同様にデータの列を扱うデータ構造です。list との最大の違いは、(文字列 (str) などと同様に)変更ができないことです。

t = (3, 1, 4, 1, 5)
# エラーになる
t[0] = 9
エラー出力
Traceback (most recent call last):
  File "/judge/Main.py", line 3, in <module>
    t[0] = 9
    ~^^^
TypeError: 'tuple' object does not support item assignment

(意訳:tuple の要素への代入は行えません)

ただ、あくまで tuple の中身の変更ができないだけで、その変数に代入されている tuple 自体を別のものに変更することはできます。

t = (3, 1, 4, 1, 5)
# これは OK
t = (9, 8, 7)

中身を変更するような操作を除き、tuple でも list と同じような操作を行うことができます。

tuple を作る

  • tuple() で空の tuple を作ることができます。
    • tuplelist とは異なり要素を追加することができないので、使うことはほとんどないと思います。
  • ( 要素1, 要素2, 要素3, ... )tuple を作ることができます。
  • tuple( for 文に入れられるもの )tuple を作ることができます。
    • tuple([1, 2, 3, 4, 5]) などといったように、listtuple に変換することもできます。
  • tuple( 内包表記 )tuple を作ることができます。
# tuple (3, 1, 4, 1, 5) を作り、t に代入する
t = (3, 1, 4, 1, 5)

# いろいろな型を持つデータを 1 つの tuple に入れることができる
t = ("Hello", "AtCoder", 123, 4.5, [])

# 括弧は省略することもできる
t = "Hello", "AtCoder", 123, 4.5, []

# tuple (0, 1, 2, 3, 4) を作る
t = tuple(range(5))

# tuple (0, 1, 4, 9, 16) を作る
t = tuple(i * i for i in range(5))

tuple を作るときの注意点

1 要素の tuple

  • 1 要素の tuple を作るときは、ただ括弧でくくっているだけと区別するため、明示的にカンマをつける必要があります。
t = (3)  # カンマをつけないとただ括弧でかこっているだけとされてしまい tuple にならない
print(type(t))  # <class 'int'>
t = (3,)  # 1 要素の tuple を作るときは明示的に最後のカンマをつける必要がある
print(type(t))  # <class 'tuple'>
t = 3,  # 1 要素の tuple を作るときでも括弧は省略することもできる
print(type(t))  # <class 'tuple'>

内包表記での初期化

  • 内包表記で tuple を初期化するとき、() でくくるだけだと generator という別のものを作る記法になってしまうため、tuple() でくくる必要があります。
t = [i for i in range(5)]  # list ならこの書き方で OK
print(type(t))  # <class 'list'>

t = (i for i in range(5))  # 上と同じように括弧だけ変えると tuple にならない
print(type(t))  # <class 'generator'>

t = tuple(i for i in range(5))  # 内包表記で tuple を作るにはこのように書く必要がある
print(type(t))  # <class 'tuple'>

tuple の長さ

  • tuple t に対し、len(t)t の長さ(要素数)を取得できます。

i 番目の要素

  • tuple t と整数 i に対し、t[i]ti 番目の要素を取得できます。
    • この i のこと(リストの何番目の要素かを表す番号)を添字(そえじ)と言います。
    • 添字は 0 から始まることに注意しましょう。先頭の要素は t[0] で、末尾の要素は t[len(t)-1](または t[-1])で取得することができます。
  • tuplet[i] = x などといったような 要素の変更はできない ことに注意しましょう。

for 文で要素を列挙する

  • tuple t に対し、for x in tt の要素を順に x に代入して繰り返し処理を行うことができます。
t = (3, 1, 4, 1, 5)

for x in t:
    print(x)
出力
3
1
4
1
5

出力する

  • tuple t に対し、print(*t)t の要素を空白区切りで出力できます。
t = (3, 1, 4, 1, 5)

# t の要素を空白区切りで出力する
print(*t)

# t の要素を改行区切りで出力する
for x in t:
    print(x)
出力
3 1 4 1 5
3
1
4
1
5

tuple のさらなる機能

tuple においても、1.10.リスト - リストのさらなる機能 で紹介した機能の多くを使うことができます(ただし、append, insert, pop などのような中の要素を変更するような機能は使えません)。

packing, unpacking

tuple は、複数の値をひとつの変数に格納していると見ることもできますが、逆に tuple の各要素を別々の変数に格納することもできます。これを、それぞれ packing, unpacking と呼びます。

# 複数の値をひとつの変数に代入 (packing)
t = 3, 1, 4, 1, 5

# tuple の各要素を別々の変数に代入 (unpacking)
a, b, c, d, e = t

# これも OK
a, b, c, d, e = (3, 1, 4, 1, 5)
# もちろんこれも OK
a, b, c, d, e = 3, 1, 4, 1, 5

なので、1.04.変数と型 - 複数の変数への代入 で紹介した x, y = 2, 3 というコードは、(2, 3) という tuple を作り、それを xy に unpacking していたということになります。
また、x, y = y, x についても同様です。

なお、特に unpacking については list などの他のコンテナでも同様に行えます。

set

set はデータの集合を扱うデータ構造です。集合とは、 重複する要素をもたない、順序づけられていない 要素の集まりです。
Python では、データに対し「ハッシュ値」というものを定義することで集合を扱うデータ構造を実現しています(set を使う上で、基本的には「ハッシュ値」の仕組みについて理解する必要はありません)。

set のメリット

  • 追加・削除・存在判定などの操作が高速に行える

set のデメリット

  • 同じ要素を複数格納することができない
  • 順序を保持することができない
    • つまり「i 番目に追加した要素を求める」といったことは基本的にはできない
    • また、「i 番目に大きい要素を求める」といったこともできない

例えば、以下のようなコードを書くことができます。

# 30, 10, 40 という 3 要素から set を作り、s に代入する
s = {30, 10, 40}

# s に 50 という要素を追加
s.add(50)
# s に 50 という要素が含まれるかどうかを出力
print(50 in s)

# s から 10 という要素を削除
s.remove(10)
# s に 10 という要素が含まれるかどうかを出力
print(10 in s)

# s の要素を出力
for x in s:
    print(x)
出力
True
False
40
50
30

set を作る

  • set() で空の set を作ることができます。
    • {} だと空の dict になってしまうことに注意してください(dict についてはこの次に説明します)。
  • { 要素1, 要素2, 要素3, ... }set を作ることができます。
  • set( for 文に入れられるもの )set を作ることができます。
    • set([1, 2, 3, 4, 5]) などといったように、list から set を作ることもできます。
  • { 内包表記 }set を作ることができます。
# 30, 10, 40, 10, 50 という 5 要素から set を作り、s に代入する
# このとき、10 が重複しているので実際には 4 要素の set ができる
s = {30, 10, 40, 10, 50}

# set の中身は順序が保持されるとは限らない
print(s)  # {40, 10, 50, 30}

set の要素数

  • set s に対し、len(s)s の要素数を取得できます。
# 10 が重複しているので実際には 4 要素の set ができる
s = {30, 10, 40, 10, 50}

print(len(s))  # 4

要素の追加・削除・存在判定

  • set s と要素 x に対し、s.add(x)s に要素 x を追加できます。
    • すでに s に存在する要素を add しても何も起こりません。
  • set s と要素 x に対し、s.remove(x)s から要素 x を削除できます。
    • s に存在しない要素を remove するとエラーになります。
    • なお、s.discard(x) というメソッドもあり、こちらは存在しない要素を指定してもエラーにはなりません(何も起こらない)。
  • set s と要素 x に対し、x in ss に要素 x が存在するか判定できます(存在するならば True、存在しないならば False が返る)。
    • 逆に存在しないか判定したいときは x not in s と書けます(not (x in s) と同じ)。
# 空の set s を作成
s = set()

# s に 100, 200, 300 を追加
s.add(100)
s.add(200)
s.add(300)
print(s)  # {200, 100, 300}

# すでに s に存在する要素を add しても何も起こらない
s.add(100)
print(s)  # {200, 100, 300}

# s に存在するかどうか判定
print(100 in s)  # True
print(404 in s)  # False

# s から 100 を削除
s.remove(100)

# s から 100 を削除したので `100 in s` が False になる
print(100 in s)  # False

# 存在しない要素を remove するとエラーになるので、条件分岐を挟むとよい
if 404 in s:
    s.remove(404)

for 文で要素を列挙する

  • set s に対し、for x in ss の要素をひとつずつ x に代入して繰り返し処理を行うことができます。
    • このとき、要素の列挙される順序は、追加された順や小さい順とは限らないことに注意してください。
s = set()

s.add(100)
s.add(200)
s.add(300)

for x in s:
    print(x)
出力
200
100
300

(この出力結果は、実行する環境や Python のバージョンなどで変わる可能性があります)

出力する

  • set s に対し、print(*s)s の要素を空白区切りで出力できます。
s = {100, 200, 300}

print(*s)

for x in s:
    print(x)
出力
200 100 300
200
100
300

set の注意点

list は set に入れられない

  • set の要素には list を含めることはできません。
    • これは、list は整数や str, tuple などと違い中身を変更できてしまうからです。
    • set は中で「ハッシュ値」という値を用いて検索しています。中身が勝手に変わるとこの検索方法が使えないので、Python では「list にはハッシュを定義しない」=「set に入れられない」としています。
# エラーにならない
ok = {"Hello", "AtCoder", 123}

# エラーになる
ng = {"Hello", "AtCoder", 123, [4, 5]}
エラー出力
Traceback (most recent call last):
  File "/judge/Main.py", line 5, in <module>
    ng = {"Hello", "AtCoder", 123, [4, 5]}
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: unhashable type: 'list'

(意訳:list はハッシュ化できません)

set を使ったテクニック

list の重複を除去する

  • ある list を一度 set に変換してからもう一度 list に変換すると、重複が除去できます。
    • ただし、順序はバラバラになります。
  • 特に、list l の重複を除去しつつソートしたいときは sorted(set(l)) などと書くことができます。
l = [30, 10, 40, 10, 50]

# 重複を除去する
# ただし順序は保持されない
l = list(set(l))

print(l)  # [40, 10, 50, 30]
l = [30, 10, 40, 10, 50]

# 重複を除去しつつソートする
l = sorted(set(l))

print(l)  # [10, 30, 40, 50]

set のさらなる機能

set 同士の演算

set では和集合、積集合、差集合などのより高度な集合演算もサポートされています。
なお、これらの演算の計算量は集合の大きさに比例することに注意してください。

集合 (set) が返る演算

返り値 記法(一例)
和集合(st の少なくともどちらかに含まれる要素からなる集合) s | t
積集合(st のどちらにも含まれる要素からなる集合) s & t
差集合(s に含まれて t に含まれない要素からなる集合) s - t
対称差集合(st の片方だけに含まれる要素からなる集合) s ^ t
s = {1, 2}
t = {1, 3}

# 和集合(s と t の少なくともどちらかに含まれる要素からなる集合)
print(s | t)  # {1, 2, 3}

# 積集合(s と t のどちらにも含まれる要素からなる集合)
print(s & t)  # {1}

# 差集合(s に含まれて t に含まれない要素からなる集合)
print(s - t)  # {2}

# 対称差集合(s と t の片方だけに含まれる要素からなる集合)
print(s ^ t)  # {2, 3}

真偽値が返る演算

判定する内容 記法(一例)
st は集合として一致? (s = t) s == t
st は集合として異なる? (s \neq t) s != t
st の部分集合? (s \subseteq t) s <= t
st の真部分集合? (s \subset t) s < t
st の上位集合? (s \supseteq t) s >= t
st の真上位集合? (s \supset t) s > t

dict

dict は連想配列や辞書と呼ばれるデータ構造です。
dict を用いると、「要素 (key) に値 (value) が紐づいている」といったようなデータを簡単に扱うことができます。
set と同様、Python では、要素 (key) に対し「ハッシュ値」というものを定義することで連想配列を実現しています(なので、set と同様、key に list を用いることはできません)。

例えば、以下のようなコードを書くことができます。

# 各人の氏名にテストの点数を紐づけたデータ
d = {"Alice": 100, "Bob": 89, "Charlie": 95}

# データへのアクセス
print(d["Alice"])
print(d["Bob"])
print(d["Charlie"])

# データの追加
d["Dave"] = 77

print(d)

# データの変更
d["Charlie"] = 59

print(d)

# データの削除
d.pop("Bob")

print(d)

# データを順に出力
for name, score in d.items():
    print(name, score)
出力
100
89
95
{'Alice': 100, 'Bob': 89, 'Charlie': 95, 'Dave': 77}
{'Alice': 100, 'Bob': 89, 'Charlie': 59, 'Dave': 77}
{'Alice': 100, 'Charlie': 59, 'Dave': 77}
Alice 100
Charlie 59
Dave 77

dict を作る

  • {} で空の dict を作ることができます。
  • { key_1: value_1, key_2: value_2, ... }dict を作ることができます。
# 同じキーがある場合、より後ろに書かれたものが格納される
d = {"Alice": 100, "Bob": 89, "Charlie": 95, "Alice": -1}

print(d)  # {'Alice': -1, 'Bob': 89, 'Charlie': 95}

また、内包表記でも { k: v for ... } という形で書くことで dict を作ることができます

square_root = {i * i: i for i in range(5)}

print(square_root)  # {0: 0, 1: 1, 4: 2, 9: 3, 16: 4}

dict の要素数

  • dict d に対し、len(d)d の要素数を取得できます。
# "Alice" というキーが重複しているので、要素数 3 の dict が作られる
d = {"Alice": 100, "Bob": 89, "Charlie": 95, "Alice": -1}

print(len(d))  # 3

要素の取得・追加・更新・削除・キーの存在判定

  • dict d とキー k に対し、d[k] でキー k に割り当てられた値を取得できます。
    • d に含まれないキーを指定するとエラーになります。
  • dict s とキー k、値 v に対し、d[k] = v でキー kv を割り当てます。
    • すでに d に存在するキーに対して行った場合、割り当てる値を更新する操作になります。
  • dict d とキー k に対し、d.pop(k)d からキー k を削除できます。
    • d に存在しないキーを pop するとエラーになります。
  • dict d とキー k に対し、k in dd にキー k が存在するか判定できます(存在するならば True、存在しないならば False が返る)。
    • 逆に存在しないか判定したいときは k not in d と書けます(not (k in d) と同じ)。
# 空の dict d を作成
d = {}

# d に要素を追加
d[200] = "OK"
d[400] = "Bad Request"
d[418] = "I'm a teapot"

print(d[418])  # I'm a teapot

# すでに存在するキーを指定した場合、上書きされる
d[418] = "(Unused)"

print(d[418])  # (Unused)

# d にキーが存在するかどうか判定
print(200 in d)  # True
print(404 in d)  # False

# あくまでキーとして存在するかなので、値側のみに存在していても False となる
print("OK" in d)  # False

d.pop(418)

# d からキー 418 を削除したので `418 in d` が False になる
print(418 in d)  # False

# 存在しない要素にアクセスしたり pop したりするとエラーになるので、条件分岐を挟むとよい
if 404 in d:
    print(d[404])
    d.pop(404)

また、値が存在しないときに初期化をしてくれる defaultdict というものもあり、これを使用すると存在するかどうかの条件分岐を省略することもできます。defaultdict については 4 章で詳しく説明します。

dict を使う場合
l = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3]
counter = {}

for x in l:
    # counter に要素 x が存在しない状態で counter[x] += 1 とするとエラーになるので、
    # if 文で判定し、あらかじめ代入しておく
    if x not in counter:
        counter[x] = 0
    counter[x] += 1

for k, v in counter.items():
    print(k, v)
defaultdict を使う場合
from collections import defaultdict

l = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3]
counter = defaultdict(int)

for x in l:
    # defaultdict の場合エラーにならないので条件分岐が不要
    counter[x] += 1

for k, v in counter.items():
    print(k, v)
出力(どちらも同じ)
3 2
1 2
4 1
5 2
9 1
2 1
6 1

for 文で要素を列挙する

  • dict d に対し、for k in d(または for k in d.keys())で dキーを追加された順に k に代入して繰り返し処理を行うことができます。
  • dict d に対し、for v in d.values()dを追加された順に v に代入して繰り返し処理を行うことができます。
  • dict d に対し、for k, v in d.items()dキーと値を追加された順にそれぞれ k, v に代入して繰り返し処理を行うことができます。
d = {"Alice": 100, "Bob": 89, "Charlie": 95}

for k in d:
    print(k)

for v in d.values():
    print(v)

for k, v in d.items():
    print(k, v)
出力
Alice
Bob
Charlie
100
89
95
Alice 100
Bob 89
Charlie 95

問題

リンク先の問題を解いてください。

EX20.最頻値

前のページ | 次のページ


  1. setdict はハッシュを用いて実装されているため、多くの操作の期待時間計算量は O(1) ですが、悪意ある入力が与えられると遅くなります。