Pythonのリスト型を基本に立ち戻ってまとめてみた

スポンサーリンク
Python基礎
スポンサーリンク

Pythonのリスト型を使いこなし方をまとめてみました。最近始めた競技プログラミングでリスト型でデータを整理する機会が増えたのですが、NumpyやPandasの知識とごちゃごちゃになって頭の整理ができていません。そこで今回、リスト型について、自分用にまとめてました。

参考:Pythonチュートリアル(日本語版)5.データ構造

リスト型の詳細まとめ

リストの作成

空のリストを作る
l = []
リストを作る:各括弧のカンマ区切り
l = [1, 2, 3]
m = [[1, 2, 3],[4, 5, 6]]
リストを作る:リスト内包表記
l = [x for x in range(5)]
リストを作る:list()で型変換
# タプルからリストへ
t = (1 ,2 ,3)
l = list(t) # [1, 2, 3]
# 文字列からリストへ
s = 'abcde'
l = list(s) # ['a', 'b', 'c', 'd', 'e']

リストの演算

元のリスト
s = list('abcde') 
s
['a', 'b', 'c', 'd', 'e']
要素の入れ替え
# インデックス[3]に'x'を入れる
s[3] = 'x'
['a', 'b', 'c', 'x', 'e']
# インデックス[0]と[2]の値の入れ替え
s[0], s[2] = s[2], s[0]
['c', 'b', 'a', 'd', 'e']
# スライス[1:3]を'o'に入れ替え
s[1:3] = 'o'
['a', 'o', 'd', 'e']
要素の追加
# 末尾に値を追加
s.append('f')
['a', 'b', 'c', 'd', 'e', 'f']
# 末尾にシーケンスを追加
s.extend(['f','g'])
['a', 'b', 'c', 'd', 'e', 'f', 'g']
# シーケンスの足し合わせ
s += ['f' ,'g']
m = s + s
['a', 'b', 'c', 'd', 'e', 'f', 'g']
シーケンスの足し合わせ
t = ['x', 'y', 'z']
s += t
['a', 'b', 'c', 'd', 'e', 'x', 'y', 'z']
# インデックス[3]に値を挿入
s.insert(3,'x')
['a', 'b', 'c', 'x', 'd', 'e']
# インデックス[2]に値を挿入
s[2:2] = 'x'
['a', 'b', 'x', 'c', 'd', 'e']
# 要素の繰り返し
s = s * 3
['a', 'b', 'c', 'd', 'e', 'a', 'b', 'c', 'd', 'e', 'a', 'b', 'c', 'd', 'e']

スライスの活用

リストの一部の取り出し(スライス)

スライスは [開始位置:終了位置]で示します。スライス位置と指定されるインデックスの関係を図示すると下のようになります。例えば、[2:4]のスライスで指定されるのは、[2]と[3]になります。開始位置と終了位置は省略できます。例えば、[2:]ならインデックス[2]以上をすべて指定します。[:4]なら、インデックス[3]以下をすべて指定します。[:]はすべてのインデックスを指定します。

# インデックス[1]と[2]を取り出し
t = s[1:3]
['b', 'c']
# インデックス[2]より前を取り出し
t = s[:3]
['a', 'b', 'c']
# インデックス[3]以降を取り出し
t = s[3:]
['d', 'e']
値を取り出して削除
# pop(i)でインデックスiの値を取り出して削除する
s = list('abcde') 
print('pop実行前のs = {}'.format(s))
a = s.pop(3)
print('取り出した値 = {}'.format(a))
print('pop実行後のs = {}'.format(s))
pop実行前のs = ['a', 'b', 'c', 'd', 'e']
取り出した値 = d
pop実行後のs = ['a', 'b', 'c', 'e']
# インデックスを指定しないと末尾が削除されて、取り出される
s.pop()
'e'
区間を指定して削除
# [2:4]の区間を削除
del s[2:4]
['a', 'b', 'e']
# [2:4]の区間を削除
s[2:4] = []
['a', 'b', 'e']
区間を指定して値やシーケンスに置換
# [1:4]の区間を'x'の置き換える
s[1:4] = 'x'
['a', 'x', 'e']
# [1:4]の区間を'x'5つに置き換える
s[1:4] = 'x'*5
['a', 'x', 'x', 'x', 'x', 'x', 'e']

リスト全体への操作

リストの全削除
# 全削除されて空のリストになります
s.clear()
[]
リストの反転
s.reverse()
['e', 'd', 'c', 'b', 'a']

リストのコピー:コピーの深さ

リストのコピーには3種類あります。
1.参照渡し:IDが同じ
2.浅いコピー(Shallow Copy):IDは違う、多次元構造のときは、内部のオブジェクトは元のリストと同じところを参照している
3.深いコピー(Deep Copy):IDは違う、完全に別のコピーとして内部のオブジェクトを生成

参照渡し

あまり使うことはないと思うのですが、初心者のうちは知らないと、参照渡しでコピーを作ってしまうことがあるかもしれません。下の例だと、t = sとリストを複製しましたが、tを編集した結果がsにも影響を及ぼしています。idを確認すると、同じidで同一のオブジェクトだと判断されてしまうので、複製しようとして、このようにするのは非常に危険です。

s = ['a', 'b', 'c', 'd', 'e']
t = s
print(s)
print(t)
t[2] = 'x'
print(s)
print(t)
print(id(s))
print(id(t))
['a', 'b', 'c', 'd', 'e']
['a', 'b', 'c', 'd', 'e']
['a', 'b', 'x', 'd', 'e']
['a', 'b', 'x', 'd', 'e']
1830781435968
1830781435968
Shallow Copy(浅いコピー)

以下のように作るとShallow Copyができます。Shallow Copyの方法で複製したリストは相互に影響をうけません。

# コピーの元リスト
s = ['a', 'b', 'c', 'd', 'e']

# list()を使った新たなリスト生成
u = list(s)

# スライス全指定でのリスト複製
w = s[:]

# copyモジュールを使ったリスト複製
import copy
c = copy.copy(s)

print('変更前リスト')
print(s)
print(u)
print(w)
print(c)

s[3] = 'x'

print('変更後のリスト')
print(s)
print(u)
print(w)
print(c)
変更前リスト
['a', 'b', 'c', 'd', 'e']
['a', 'b', 'c', 'd', 'e']
['a', 'b', 'c', 'd', 'e']
['a', 'b', 'c', 'd', 'e']
変更後のリスト
['a', 'b', 'c', 'x', 'e']
['a', 'b', 'c', 'd', 'e']
['a', 'b', 'c', 'd', 'e']
['a', 'b', 'c', 'd', 'e']
深いコピー(Deep Copy)

一方で、Shallow Copyも多次元構造の時、内部のリストは元のリストの影響を受けます。そこで、先ほども使ったcopyモジュールには、まったく別のオブジェクトとして、リストを複製する関数が用意されています。copy.deepcopy()で完全に別の複製リストが作れます。下の例では2次元のリストを扱っていますが、Shallow Copyは元のリストの影響をうけます。一方、Deep Copyは他のリストの影響をうけません。

import copy

# コピーの元リスト
s = [['a','b'],['c','d']] 

# Shallow Copy
u = copy.copy(s)

# Deep Copy
d = copy.deepcopy(s)

print('変更前リスト')
print(s)
print(u)
print(d)

s[1][1] = 'x'

print('変更後のリスト')
print(s)
print(u)
print(d)
変更前リスト
[['a', 'b'], ['c', 'd']]
[['a', 'b'], ['c', 'd']]
[['a', 'b'], ['c', 'd']]
変更後のリスト
[['a', 'b'], ['c', 'x']] 
[['a', 'b'], ['c', 'x']] 
[['a', 'b'], ['c', 'd']] 

リストのソート

sortメソッドと、sorted関数

リストのソートはsortメソッドかsorted関数でできます。それぞれの違いを下の表に示します。

元のリスト出力使い方
sortメソッドソートされるNone元のリスト.sort()
sorted関数ソートされないソートされたリストa = sorted(元のリスト)
# sortメソッドの場合
s = [10, 9, 7, 4, 3, 6, 8]
print(s)
s.sort()
print(s)
# メソッドはリストをソートするが出力はしない
print(s.sort())
[10, 9, 7, 4, 3, 6, 8]
[3, 4, 6, 7, 8, 9, 10]
None
# sorted関数の場合
s = [10, 9, 7, 4, 3, 6, 8]
print(s)
a = sorted(s)
print(s)
print(a)
[10, 9, 7, 4, 3, 6, 8]
[10, 9, 7, 4, 3, 6, 8]
[3, 4, 6, 7, 8, 9, 10]
降順に並び替える

デフォルトではsortやsortedでは昇順に並び変えられます。降順に並び替えるときはreverse=Trueと指定します。

s = [10, 9, 7, 4, 3, 6, 8]
print(s)
# 昇順
s.sort()
print(s)
# 降順
s.sort(reverse = True)
print(s)
[10, 9, 7, 4, 3, 6, 8]
[3, 4, 6, 7, 8, 9, 10]
[10, 9, 8, 7, 6, 4, 3]
keyを指定して並び替える

ソートの条件をkeyで指定できます。keyで指定する方法に従ってソートを実行します。

s = ['banana', 'Apple', 'Egg']
# 普通にソート
s.sort()
print(s)
# 小文字で比較してソート
s.sort(key = str.lower)
print(s)
# 文字列の長さで比較してソート
s.sort(key = len)
print(s)
['Apple', 'Egg', 'banana']
['Apple', 'banana', 'Egg']
['Egg', 'Apple', 'banana']

2次元のリストの並び替えなどではkeyは重宝します。下記の2次元のリストで、普通にソートすると各リスト内のリスト各行の先頭の数字で比較することになりますが、keyで比較対象を[1]列や[2]列を指定することで、意図した列で並び替えることができます。lamda関数は、各行からxにリストを取り出し、x[1]をkeyとして出力し、それをもとに並び替えを実施しています。

n = [[3, 2, 5], [4, 1, 3], [1, 5, 4]]
a = sorted(n)
print(a)
# リストの2番目の数字でソート
a = sorted(n, key = lambda x: x[1])
print(a)
# リストの3番目の数字でソート(降順)
a = sorted(n, reverse = True, key = lambda x: x[2])
print(a)
[[1, 5, 4], [3, 2, 5], [4, 1, 3]]
[[4, 1, 3], [3, 2, 5], [1, 5, 4]]
[[3, 2, 5], [1, 5, 4], [4, 1, 3]]

keyには自分で作った関数を当てはめることもできます。下の例では素数かどうか判定する関数をkeyに適用し、素数(True)を前に、素数ではないもの(False)を後ろに並び変えています。(2, 3, 5, 7, 11, 13が素数)

def sosu(number):
    for i in range(2, number//2+1):
        if number % i == 0:
            return False
    return True

n = [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
print(n)
n.sort(reverse = True, key = sosu)
print(n)
[2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
[2, 3, 5, 7, 11, 13, 4, 6, 8, 9, 10, 12, 14, 15, 16]

リスト内の値のカウント

リスト.count(値)でリスト内の値の数をカウントできます。多次元のリストの深さ方向まではカウントしてくれません。

s = ['a', 'b', 'a', 'a', 'c']
# 'a'の数
print(s.count('a'))
# 'x'の数
print(s.count('x'))
3
0

値を指定して削除

特定の値を指定して削除できます。値に一致するリストの最初のものが削除されます。その値がリストにないときはエラーを返します。

s =  ['a', 'b', 'a', 'a', 'c']
print(s)
s.remove('a')
print(s)
s.remove('a')
print(s)
['a', 'b', 'a', 'a', 'c']
['b', 'a', 'a', 'c']
['b', 'a', 'c']

リストのアンパック

リストのアンパックを使うと、リストから要素を効率的に取り出したり代入することができます。
それを使って、リストの要素を関数や変数に同時に入れたり、出力したりできます。アンパックを使うことにより、コードをシンプルに記載することができます。

リストをアンパックしてprintで出力

通常のリストのprintでの出力(リストとして出力)

a = [13, 15, 18, 21]
print(a)
[13, 15, 18, 21]

リストをアンパックしてprintで出力(複数要素としてスペース区切りで出力)

print(*a)
13 15 18 21
アンパックしたリストを関数に代入

アンパックしたリストを直接複数の引数として関数に代入できます。下の例では二つの引数の掛け算をするコードですが、アンパックしたリストを直接引数として渡しています。

def func(i, j):
    return i * j

a = [4, 8]

print(func(*a))
32
リストを単一要素とリストに切り分ける

値はコンマ区切りの変数に取り出すことができる。また、アスタリスクをつけた変数は残りの要素をリストとして回収する。口では説明しにくいですが、下の結果をご確認ください。「*c」や「*b」には残った数字がリストとして代入されています。

n = [1, 2, 3, 4, 5, 6, 7, 8]
print(n)
a, b, *c = n
print(a)
print(b)
print(c)
print('########')
a, *b, c = n
print(a)
print(b)
print(c)
[1, 2, 3, 4, 5, 6, 7, 8]
1
2
[3, 4, 5, 6, 7, 8]
########
1
[2, 3, 4, 5, 6, 7]
8

zip関数:複数のリストから値を取得

zip関数を使うと複数のリストから値を取得します。for文で回すと、順々に値を取得してくれます。zip関数で取り出したものをタプルに入れて、リスト化することもできます。また、リスト内包括でリストに代入することもできます。上のリストのアンパックで使ったアスタリスク変数を使うとリストとして直接扱えるので便利です。リストの転置をするときに使えそうです。

a = [1 ,2, 3, 4, 5]
b = ['a', 'b', 'c', 'd', 'e']
c = [50, 60, 100, 40, 30]

# for文で一つずつ取り出し
for *s, in zip(a, b, c):
    print(s)

# 取り出したタプルをリスト化
t = list(zip(a, b, c))
print(t)
    
# 内包表記でリストとして取得
u = [x for *x, in zip(a, b, c)]
print(u)
[1, 'a', 50]
[2, 'b', 60]
[3, 'c', 100]
[4, 'd', 40]
[5, 'e', 30]
[(1, 'a', 50), (2, 'b', 60), (3, 'c', 100), (4, 'd', 40), (5, 'e', 30)]
[[1, 'a', 50], [2, 'b', 60], [3, 'c', 100], [4, 'd', 40], [5, 'e', 30]]

終わりに

今回、リストの様々な使いこなしを見てきました。調べてみると便利な機能がたくさんあります。nampyやpandasなどのライブラリを使わなくても、リストの機能だけでもいろいろなことができることが分かりました。

コメント