Python入門(6) リスト

Pythonの柔軟なプログラミングを可能としているデータ構造にリスト(list)とディクショナリがある。 リストは、1つの値ではなく、一連の値を格納しておくための変数で、他の言語では配列と呼ばれているものだ。 CやJavaなどのようにリスト要素の数(サイズ)を指定する必要もなく、いつでもサイズを変更できる。 しかも、リスト内には(整数や浮動小数点や文字列など一つ定めた(同じタイプの要素を並べる必要はなく、任意のオブジェクトを取ることが可能である。

リストは 角括弧 [ ] 内に、リスト要素をカンマ(,)で区切って並べる。

>>> a = [24, 'Alice', 3.14, True, 'B', 0xaff0]
>>> a
[24, 'Alice', 3.14, True, 'B', 45040]
>>> len(a)
6
>>> a[0]
24
a[4]
'B'
>>> a[5]
45040
>>> a[len(a) - 1]
45040
>>> a[6]
>>> Traceback (most recent call last):
  File "", line 1, in 
IndexError: list index out of range

上のPython shellの結果からわかるように、リスト変数 a を作成するには角括弧 [ と ] をカンマで区切った要素を挟む。 リストの長さは関数 len で取得できる(上の例では len(a) の結果は6)。

リスト要素へのアクセス(取得)には要素位置(インデックス)を a[位置] の用に指定るする。 ただし、リスト先頭の要素位置は 0 から始まる(多くのプログラム言語も同様)。 したがって、リストの末尾要素は a[len(a) - 1] である。 リスト要素位置の範囲を逸脱すると、最後の例のように IndexError: list index out of range のエラーとなる。

リストを空リストで作成してもよい。 あとからいくらでも要素を追加できる。

>>> a = []

リストのメソッド

リスト変数名を alist とする。 文字列へメソッドはPython入門(3) 文字列で見たように、元の文字列内容を変化させない。 一方以下で見るように、リストへのメソッドはリスト内容を変化させてしまう。 このようなリストの性質を変更可能 (mutability)という。

リストメソッドの一部
method名使用例意味
appendalist.append(item)リストの末尾に要素 item を追加する。リスト alist の内容は変化する。
insertalist.insert(k, item)リストのk番目位置に末尾に要素 item を挿入する. リスト alist の内容は変化する。
extendalist.extend(blist)リスト alist に別に用意したリスト blist を末尾に追加する。リスト alist の内容は変化する。
popalist.pop()リスト末尾の要素を削除し、その要素を返す。リスト alist の内容は変化する。
popalist.pop(k)リストのk位置の要素を削除し、その要素を返す。リスト alist の内容は変化する。
removealist.remove(item)リスト内で値 item を持つ最初の要素を削除する。リスト alist の内容は変化する。該当する要素が無ければエラー。
reversealist.reverse()リスト要素の並びを逆にする. リスト alist の内容は変化する。
indexalist.index(item)リスト内で item が最初に登場する位置を返す。リストに itemがなければエラー。
countalist.count(item)リスト内にある item の数を返す。リストに itemがなければ 0を返す。
sortalist.sort()リスト内の要素を並べ替える。リスト alist の内容は変化する。 sort(key=None, reverse=None)とオプション key と reverse をもつ。 オプションを省略したときはデフォルト値 None が指定されたとする。 reverse=Trueとすると降順並べ替えする。 key を使って、比較を行う前にリストの各要素に対して呼び出される関数を指定できる。

文字列操作とことなり、 リスト内の要素を別の要素に置き換えるメソッド replace は用意されていない。 リスト要素の置き換えは

リストのメソッドではないが、しばはリスト要素の削除に使われる del文 がある。

文名使用例意味
deldel alist[k] 関数として del(alist[k]) と書くことも多い。リスト位置のk番目の要素を削除し、リスト内容を変更する。

リスとの並べ替え(sort)では、メソッド .sort() 以外に、次の関数 sorted がよく使われる。

関数名使用例意味
sortedsorted(slist)ソートされたリストを返す。元のリストは変更されない。 sorted(alist, reverse=True) パラメータとすると降順となる。 key パラメータも指定でき、比較を行う前にリストの各要素に対して呼び出される関数を指定できる。

リストに要素を追加する

Pythonでは、リストに新たに要素を追加する3つのメソッド appendinsertextend がある。

append(要素) はリストの末尾に1つ要素を追加する。 次は、リスト a の末尾に文字列要素 "new" を追加した様子である。 メソッド append によってリスト内容も更新されていることに注意する。

>>> a = [24, 'Alice']
>>> a.append('new')
>>> a
>>> [24, 'Alice', 'new']

insert(場所, 要素) は指定した要素場所に要素を追加する。 次は、リスト a の要素位置 1 に文字列要素 "new" を追加した様子である。 メソッド insert によってリスト内容も更新されていることに注意する。 insert 前のリスト a の a[1] は文字列 "Alice" であるが、要素位置 1 にinsert 後には a[1] は "new" となり、追加前の要素位置が1つづつ後ろにずれている。

>>> a = [24, 'Alice']
>>> a[1]
'Alice'
>>> a.insert(1, 'new')
>>> a[1]
'new'
>>> a
[24, 'new', 'Alice']

extend は、現在のリスト末尾に別のリストのすべての要素を追加する(append, insert 共に追加する要素は1つずつだ)。 次は、リスト a の末尾に別のリスト b の全ての要素を追加した様子である。 メソッド extend によってリスト内容も更新されていることに注意する。

>>> a = [24, 'Alice']
>>> b = ['Bob', 32, 1.414]
>>> a.extend(b)
>>> a
[24, 'Alice', 'Bob', 32, 1.414]
>>> b
['Bob', 32, 1.414]

次のスクリプト list_add.py を見てみよう。 キーボードから文字列 "e" または "E" が入力されないかぎり、入力文字列を appenし続ける。 break は、ある条件が発生した場合に while文またはfor文のループから脱出する。

a を空リストとして初期化する
a = []

while True:
    st = input('input string(e/E for termination) :')
    if not(st == 'e' or st == 'E'):
        a.append(st)
    else:
        break
print('Here is out of for block')
print('list a ', a)

実行結果は次のようになる。

input string(e/E for termination) :apple
input string(e/E for termination) :orange
input string(e/E for termination) :lemon
input string(e/E for termination) :e
This is out of for block
You make a list  ['apple', 'orange', 'lemon']
演習 [重要]: スクリプト list_add.py を実行して、スクリプトの動作に沿って説明しなさい。
演習 次のスクリプト list_add1.py ではappendしたリストに「ゴミ」が混じってしまい好ましくない。 その理由を検討し、先の list_add.py の方がベターであることを説明しなさい。
# a を空リストとして初期化する
a = []

st = ''
while  not(st == 'e' or st == 'E'):
    a.append(st)
    print('list a ', a)
    st = input('input string(e/E for termination) :')
print('Here is out of for block')
print('You make a list ', a)
演習: スクリプト list_reverse_add.py として、先の list_add.py とは逆に、キー入力した "e" または "E" 以外の文字列をリスとの先頭に挿入する(最初に入力した文字列がリストの末尾に位置する)スクリプトを書きなさい。

iterable要素をカウンタ値とするfor文

iterable要素をカウンタ値とするで説明したように、 Pythonの繰り返し for A in B: であらわれる B は繰り返し可能オブジェクト(iterable)である。 iterableなオブジェクトとは要素が並んでいるデータで、順番に次の要素が取り出されるというようになっている。

たとえば、次のようにして、for文のカウンタ変数 item が取る値をリスト a 内の要素にすることができる。

for item in a:
    文1
    ...
    文n
演習 [重要]: 先のスクリプト list_add.py を修正して、文字列 "e" または "E" が入力されるまで入力文字列を appenし続けて作成したリストを、そのすべてのリスト要素を先頭から1つずつfor文を使って表示してみなさい。

リスト内容を書き出す

まず、次の演習を行ってみよう。

演習:: リスト a を [[1,2], 3, [4,[5,6]]] としたとき(リストの要素が任意のリストでもよい)
a = [[1,2], 3, 'Apple', [4,[5,6]], False]
for i in a:
    print(i)
を実行してみなさい。

リスト a が既にあってその要素にアクセス(表示)するには、上の素朴な演習以外にも、次のようにリスト位置からなるリストを range(len(a)) で作成して、リスト内の要素位置(index)を指定しながら書き出すことができる。

# リスト a は既に定義されている
for index in range(len(a)):
    print(index, a[index])
演習: 先のスクリプト list_add.py を修正して、文字列 'e' または 'E' が入力されるまで入力文字列を appenし続けて作成したリストを、リスト要素位置とリスト要素とを書き出すようにしてみなさい。

関数 enumerate によって、リスト a を引数として enumerate(a) は インデクス付けされたリスト要素の列(リスト) [(0, a[0]), (1, a[1]), ...] を返す。 したがって、上と同じ結果を得るには次のように書けばよい。

# リスト a は既に定義されている
for (index, item) in enumerate(a):
    print(index, item)
演習: 上の演習で書いたスクリプトを変更して、関数 enumerate を使ってリスト要素位置とリスト要素とを書き出すようにしてみなさい。

文字列の文字をカウンタ値とするfor文

For文のカウンタ値としてリスト要素を取り得るのと同様に、For文で文字列内の文字をカウンタ値とすることができる。


st = 'Hello, World'
for ch in st:
    print(ch)
このスクリプトの実行結果は次のようになる。
H
e
l
l
o
,
 
W
o
r
l
d
演習: 上のスクリプト for_string.py実行してみなさい。
演習: 次のスクリプトfor_word_char.py の動作を説明し、実行してみなさい。
word_list =['Apple', 'Orange', 'Apricot']
char_list = []
for word in word_list:
    for char in word:
        char_list.append(char)
print(char_list) 

リスとの並べ替え

次の例は、数値文字列を要素とするリストにメソッド .sort を適用した例である。


>>> a = ['1','5', '11','44','8']
>>> a
['1', '11', '44', '5', '8']
>>> a = ['1','5', '11','44','8']
>>> a.sort(key = int)
>>> a
['1', '5', '8', '11', '44']
>>> a = ['1','5', '11','44','8']
>>> a.sort(key = int, reverse = True)
>>> a
['44', '11', '8', '5', '1']
>>> sorted(a, key = int)
['1', '5', '8', '11', '44']
演習: 上の例の並べ替えを検討して、その結果理由を説明しなさい。

リスト内包表記

Pythonでは、新たなリストを生成するリスト内包表記(list comprehension)という強力な手段を備えている。 以下の例のように、リストの内包表記は、式に続いて for文、その後ろに続くゼロ個以上の for文または if文からなる。


x を range(10) が与えるリスト [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] の各要素として、それらを2乗して得られるリストを生成するのは、次のようにして式を x * x とする内包表記で実現できる。

>>> [x * x for x in range(10)]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

range(10) が与えるリスト [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] の各要素が 2で割り切れない(余りが0でない) 要素について2乗したリストはリスト内包表記で次にように書ける。

>>> [x * x for x in range(10) if x % 2 != 0]
[1, 9, 25, 49, 81]

文字列がiterableであることを使うと、次のような選択的操作によって「複雑な」処理を一気に書くことができる。

>>> [ch.upper() for ch in 'World Wide Web' if ch not in 'internet']
['W', 'O', 'L', 'D', ' ', 'W', 'D', ' ', 'W', 'B']

文字列(文字)リストを空白なく連接(concatenate)するには、'連結文字'.join(リスト) として次のように書ける。

"".join([ch.upper() for ch in 'World Wide Web' if ch not in 'internet'])
演習: 上記のリスト内包表記で得られる結果を、リスト内包表記を「使わない」で書いてみなさい。

リスト内包表記でforを入れ子にする

上のスクリプト for_string_char.py はリスト内包表記を使うと次のように書ける。

>>> [ch for word in ['Apple', 'Orange', 'Apricot'] for ch in word ]
演習: このリスト内包表記で得られる結果リストでは、幾つかの文字が重複して出現する。 要素が重複しないリストを得るスクリプトを書きなさい。

リスト要素の置き換え

リスト要素の置き換えを考えてみよう。 Pythonでは、リスト mylist の要素 item を別の要素 other に置き換えるメソッド mylist.replace(item, other) は用意されていない

しかしながら、リスト要素が文字(列)の場合には、文字列操作においては、文字列 mystring の文字列 before を検索し、文字列 after にすべて置き換えるメソッド mystring.replace(before, after) を使うことができる。

ここでは、以下、リストは文字列からなると仮定する。

リスト mylist の要素がすべて文字(列)の場合、文字列の置き換えメソッド replace を使って

mylist = [word.replace('before', 'other') for word in mylist]
として、要素の(にすくまれる)文字(列) 'before' をすべて文字(列) 'other' に置き換えることができる。

演習: リスト ['a', 'b', 'a', 'b', 'c', 'c', 'a'] において、要素 'a' をすべて 'd' に置き換えてみなさい。

では、リスト要素に、文字(列)item1 と item2 が含まれているとき、item1 と item2 とを入れ替え(swap)する、つまり、item1 のある場所を item2 に入れ替え、『同時に』 item2 がある場所を item1 にに入れ替えるにはどうすればよいだろうか。 リスト ['a', 'b', 'a', 'b', 'c', 'c', 'a'] において、要素 'a' と 'b' とをswap a $\rightleftarrows$ b して ['b', 'a', 'b', 'a', 'c', 'c', 'b'] とするのである。

演習: リスト mylist において、要素 item1 と item2 とを入れ替える(swap)する関数 swap_item(mylist, item1, item2) を定義し、mylist = ['a', 'b', 'a', 'b', 'c', 'c', 'a'] として swap_item(mylist, 'a', 'b') を実行してみなさい。
(ヒント):item1 をリスト要素には決して含まれないような文字列 never(を考える) に置き換えたリストを作成し、次いで、item2 を item1 に置き換えたリストを作成し、 最後に、そのリストの never を item2 に置き換えたリストを返す、というように置き換えを2段階経る必要がある。

リストのリスト

リストの要素はリストでもよい。 次のような例を考える。

>>> a = [[2, 3, 4], [5, 6, 7], [8, 9, 10], [11, 12, 13]]
>>> a[0]
[2, 3, 4]
>>> a[3]
>>> [11, 12, 13]
>>> a[3][2]
13

この例では、 aは4行3列の行列(2次元配列)である。 最後の例に注意してほしい。 リスト a の3番目(のリスト)要素の2番目にアクセスするためには a[3][2] と表記すのであって、a[3,2] とは書けないということである

Pythonには、規模な多次元配列や行列のサポート、これらを操作するための高水準の拡張数学関数ライブラリ NumPyがある。 行列計算を行う場合には大変便利で、Pythonで科学計算をおこなうためにはSciPyと並んで必須モジュールである。

自分のPython環境でNumPyが使えるかは次を実行してみれば分かる。

import numpy
演習: 4行3列の行列 a = [[2, 3, 4], [5, 6, 7], [8, 9, 10], [11, 12, 13]] と 3行2列の行列 b = [[1,2], [3, 4], [5, 6]] の行列の掛け算を行うスクリプトを書きなさい。

次の3行3列の行列を入力してみる。

>>>  mat = [
     [1,2,3],
     [4,5,6],
     [7,8,9],
     ]

行と列を入れ換えて転置行列を出力したいときには、次のようなリスト内包表記が使える。

>>> print [[row[i] for row in mat] for i in [0, 1, 2]]
[[1, 4, 7], [2, 5, 8], [3, 6, 9]]

この操作は、次の手続きに相当する。

for i in [0, 1, 2]:
    for row in mat:
        print(row[i], end = '')
    print

List構造のフラット化

リストの要素がリストであるようなリスト [[2, 3, 4], [5, 6, 7], [8, 9, 10], [11, 12, 13]] で、それを構成しているアトムを要素とするリストとして並べることをフラット化(flatten)という。 [['a', 'b'], ['a']] ならどうだだろうか。

この例では、つぎのようにフラット化されるようにしたい。

[2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13] 
['a', 'b', 'a']
演習: 上のような深さ2のリストをフラット化するスクリプトを書きなさい。

深さが異なるリストのフラット化

リスト [[1, 3], [[5]], [[7], 9]] はその要素が3つのリストからなり、リストは各リスト要素は再びリストからなっている。 これ完全にをフラット化できれば [1, 3, 5, 7, 9]] となる。

また、リスト [1, [2,3], [[4, [5,6]], 7]] は3つの要素をもち、2つ目と3つ目の要素はリストで、3つ目のリストにおいて、その最初の要素はリスト [4,[5,6]]で、さらのその2つめの要素もリスト [5,6] である。 結局、与えられたリストは最大の深さ(depth)3を持つ。 これ完全にをフラット化できれば [1, 2, 3, 4, 5, 6, 7] となる。

演習: 一般に深さが異なるリスト要素からなるリストを完全フラット化するスクリプトを書きなさい。

2つの検索文字列を「同時に置き換える」で考えた文字列の置き換え代入操作をリストで考えてみよう。 要素文字 'a'と 'b' だけからなるリストを考え、'a をリスト ['a', 'b'] に、'b' をリスト ['a'] に置き換える操作 $r$ を考える。

\[ r: \begin{cases} \text{'a'}\rightarrow \text{['a', 'b']},\\ \text{'b'}\rightarrow \text{['a']} \end{cases} \]

この操作によって、リスト ['a'] に操作 $r$ を繰り返し適用することによって次のようなリストを得ることができる。

\begin{align*} r\text{['a']} &\rightarrow\text{['a', 'b']}\\ r^2\text{['a']} &\rightarrow \text{['a', 'b', 'a']},\\ r^3\text{['a']} &\rightarrow \text{['a', 'b', 'a', 'a', 'b']}\\ r^4\text{['a']} &\rightarrow \text{['a', 'b', 'a', 'a', 'b', 'a', 'b', 'a']},\\ \dots & \end{align*}

結果を文字列として得たいときは。文字列操作メソッド join を使って次のようにすればよい。

>>> "".join(['a', 'b', 'a', 'a', 'b', 'a', 'b', 'a'])
'abaababa'
演習: 与えた文字 'a' だけからなるリスト ['a'] に、上のような置き換え操作を指定回数($n=0,\dots10$ までくらい)繰り返して、その都度得られたリストを返すスクリプト string_subst.py を書きなさい。