こんにちは、ナナです。
「2次元配列」とは、プログラミングの世界でよく利用される、縦と横に広がる表のようなデータ構造のことです。
10 | 20 | 30 |
40 | 50 | 60 |
70 | 80 | 90 |
このように、Excelのような表計算ソフトでよくある形式で、「行」と「列」の関係性があるデータ構造です。
Pythonには『配列』というものはないですが、『リスト』を使った2次元のデータ構造を作り出すことが可能です。
本記事では次の疑問点を解消する内容となっています。
では、『リスト』の2次元配列表現について学んでいきましょう。
2次元配列を『リスト』で定義する方法
2次元配列って『配列』が何個も重なった構造でしたよね。C言語で学びましたよ!
『リスト』を使えば2次元配列と同じものが作れるってことですか?
2次元配列はC言語で出てきたね。データを階層的に管理したいニーズは言語を問わずあるってことだね。
Pythonもそのニーズに応えられるように、『リスト』を使った2次元のデータ構造をサポートしています。
2次元配列をリストで表現するプログラミング
リストを2次元として表現する方法は、次のようにプログラミングします。
# 2次元リストの定義
list2D = [[10, 20, 30], [40, 50, 60], [70, 80, 90]]
print(list2D)
[[10, 20, 30], [40, 50, 60], [70, 80, 90]]
このように[]の中に、[]が含まれる構造が2次元のリスト構造です。
リストの定義は次のように改行してわかりやすく書くこともできます。
# 2次元リストの定義
list2D = [
[10, 20, 30], # 0行目
[40, 50, 60], # 1行目
[70, 80, 90], # 2行目
]
print(list2D)
こちらの方が実際の表のような形となっているため、可読性がよくなりますね。
2次元のリストのデータ構造のイメージ図
2次元のリストとは次のようなデータ構造となっています。
プログラムとリストオブジェクトの関係性は、次の図のものとなります。「list2D」変数には4つのリストオブジェクトが繋がっていることになります。
『リスト』というコンテナが、別の『リスト』を内包する、これがリストの2次元構造です。
インスペクタ機能を使った2次元リストの確認方法
PyCharmにおいて2次元のリストもしっかりと確認することが可能です。
>のマークをクリックすることでリストが展開されます。目的の要素を自由に参照することができます。
2次元のリスト構造の複製方法
師匠!『2次元リスト』とは、『リスト』の中に『リスト』があるってことなんですねー。
リストの複製は「copyメソッド」を使えば簡単にできますよね。2次元リストもcopyメソッドを使えば、ばんばんコピーできちゃいますねー。
いや、2次元リストの場合は「copyメソッド」では複製できないんだよ。これには、注意が必要だね。
複製する方法が別で用意されているから、その方法を教えようね。
2次元のリストの「copyメソッド」をすると起きる問題
それでは、まずは問題が発生するcopyメソッドを使ってリストを複製してみましょう。
# 2次元リストの定義
list1 = [[10, 20, 30], [40, 50, 60], [70, 80, 90]]
# listを複製する
list2 = list1.copy()
# 複製後のリスト要素を変更
list2[0][0] = -1
print("list1:", list1)
print("list2:", list2)
list1: [[-1, 20, 30], [40, 50, 60], [70, 80, 90]]
list2: [[-1, 20, 30], [40, 50, 60], [70, 80, 90]]
このように、list2[0][0]の要素を書き換えると、「list1」「list2」の両方が更新されてしまいました。これが2次元リストの複製に関わる問題です。
このようにcopyメソッドで複製したにも関わらず、要素の変更が元のリストに影響してしまいました。これには理由があるんです。
リストが更新される原因
この現象はリストオブジェクトのIDを明確化するとはっきりします。id関数を使って表示してみましょう。
# 2次元リストの定義と複製
list1 = [[10, 20, 30], [40, 50, 60], [70, 80, 90]]
list2 = list1.copy()
print("list1 :", id(list1))
print("list1[0]:", id(list1[0]))
print("list1[1]:", id(list1[1]))
print("list1[2]:", id(list1[2]))
print("list2 :", id(list2))
print("list2[0]:", id(list2[0]))
print("list2[1]:", id(list2[1]))
print("list2[2]:", id(list2[2]))
実行すると次のように「list1」と「list2」変数が参照するオブジェクトIDは違いますが、[0]~[2]の要素のIDは変わっていません。
list1 : 2403257547208
list1[0]: 2403248136712
list1[1]: 2403248136776
list1[2]: 2403257547912
list2 : 2403260953224
list2[0]: 2403248136712
list2[1]: 2403248136776
list2[2]: 2403257547912
リストのオブジェクトIDを精査すると、次のように構成されていることになります。
このような表面的なコピーを「浅いコピー」と呼びます。Pythonに限らず、これと同じ現象は他の言語においても発生します。
これが「list2」の要素を更新すると、「list1」も更新されてしまう理由ですね。copyしたから大丈夫!と思ったら足元をすくわれますよ!
copyモジュールの「deepcopyメソッド」を利用して複製する
2次元のリストを完全に複製するためには、「深いコピー」と呼ばれる階層を潜りながら複製する処理が必要となります。
C言語といった言語では自前で実装するのですが、そこは便利なPythonです。
Pythonで使えるcopyモジュールには、「深いコピー」を行うためのメソッドが用意されています。その名も「deepcopy」です。
import copy
# 2次元リストの定義と複製
list1 = [[10, 20, 30], [40, 50, 60], [70, 80, 90]]
# copyモジュールによる「深いコピー(deepcopy)」
list2 = copy.deepcopy(list1)
list2[0][0] = -1
print("list1:", list1)
print("list2:", list2)
list1: [[10, 20, 30], [40, 50, 60], [70, 80, 90]]
list2: [[-1, 20, 30], [40, 50, 60], [70, 80, 90]]
これで「list1」へ影響することなく、要素の変更が可能になりました。
念のためリストのオブジェクトIDが本当に変わったことを確認しておきましょう!
import copy
# 2次元リストの定義と複製
list1 = [[10, 20, 30], [40, 50, 60], [70, 80, 90]]
# copyモジュールによる深いコピー
list2 = copy.deepcopy(list1)
print("list1 :", id(list1))
print("list1[0]:", id(list1[0]))
print("list1[1]:", id(list1[1]))
print("list1[2]:", id(list1[2]))
print("list2 :", id(list2))
print("list2[0]:", id(list2[0]))
print("list2[1]:", id(list2[1]))
print("list2[2]:", id(list2[2]))
list1 : 2200075310280
list1[0]: 2200075225480
list1[1]: 2200075224456
list1[2]: 2200075310216
list2 : 2200075309960
list2[0]: 2200075309896
list2[1]: 2200075310024
list2[2]: 2200075310088
4つ全てのリストオブジェクトが複製され、合計8つのリストオブジェクトになっています。
Q&A:Pythonの2次元リストに関するよくある質問
2次元のリストについて質問ある人はどうぞ!
Q:リストの最後の要素の後に「,」があるけどいいの?
師匠!2次元リストの最後の要素の後に「,」が付いてますよ。もう~しっかりしてくださいよ~。
間違ってますから、わたしが消してあげますね。
「あちゃー間違えちゃったー・・・」って間違ってないんだよ!
このカンマは書いても書かれてなくても大丈夫なんだよ。C言語でもね、このような最後のカンマは許されていたりしますね。
2次元のリストに関わらず、リストの最後の要素の後にカンマ「,」を付けることは認められています。
# リスト
list1 = [
10,
20,
30, # カンマが付いてる
]
# 2次元リスト
list2D = [
[10, 20, 30],
[40, 50, 60],
[70, 80, 90], # カンマが付いてる
]
これって実は便利なものなんですよ。リストの要素というのは、将来的に追加されることが予想されます。
追加する場合は、最後の要素の後に追加したい時もあるわけです。次のように[15, 25, 35]のリストを追加したいとしましょう。
カンマを付けておくことで、修正が1行のみに集約されましたね。
「それだけかよ~」と思うかもしれませんが、こういった小さな積み重ねがプログラミングの作業効率なんです。
開発経験が長い開発者とは、こういった小さな効率を積み上げるんです。少しでも将来のメンテナンスコストを下げる工夫をしているんですよ。
C言語技術者が知るべきPython言語との違い:「2次元リスト」
すでにC言語を習得している人は、次のポイントに気を付けよう!
Pythonの「変数」は、C言語でいうと「ポインタ変数」に相当します。
そのため、Pythonの「リスト」とはC言語における「ポインタ変数の配列」に相当することになります。
C言語においても「ポインタの配列」を扱うような場面では、「深いコピー」が必要になるケースがあります。