Pythonでの行列の取り扱いの注意

##リストから行列を作成する
行列を作成しようとして、面倒くさがり屋の私は次のようなコードを実行しました。

~~~python
>>> raw=[0,0,0,0]
>>> matrix=[]
>>> for i in range(4):
	matrix.append(raw)
	
>>> matrix
[[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
~~~
見事に４×４の行列ができました。
これをミンコフスキー計量(意味がわからなくても問題ありません)にしようと、さらに次のような操作をしました。

~~~python
matrix[0][0]=-1
>>> for i in range(3):
	matrix[i+1][i+1]=1
	
>>> matrix
[[-1, 1, 1, 1], [-1, 1, 1, 1], [-1, 1, 1, 1], [-1, 1, 1, 1]]
~~~
行列の１行１列に‐1を代入し、その他の対角成分には1を代入したのだから、結果は、
[[-1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]
となるべきですが、この結果は全く意味不明です。
原因を解明すべく、行列のIDを確認してみると、

~~~python
>>> id(matrix[0])
643765320
>>> id(matrix[1])
643765320
>>> id(matrix[2])
643765320
>>> id(matrix[3])
643765320
>>> 
~~~
あれ？全く同じになっています。
最初にこの現象を目撃した時は、何が起こっているのか数日混乱しました。
しかし、こういう場合はリストに値(｢オブジェクト｣)が代入されているのではなく、値が入っている何らかのリストのIDの参照がリンクしているだけです。つまり、この変数matrixは見かけは４×４の行列ですが、実際には要素が４個のリスト(ID:643765320)が１つだけ存在していて、そのリスト(ID:643765320)４個を縦に並べて表示しているだけです。
だから、matrix[0][0]=-1とするとすべての行の１列目(matrix[i][0])に-1が代入され、matrix[1][1]=1とするとすべての行の２列目(matrix[i][1])に1が代入され、・・・となるのです。
そして、ID:643765320であるリストの正体は(もうおわかりかと思いますが)、次のように確認できます。

~~~python
>>> id(raw)
643765320
~~~
つまり、最初に作ったリストrawが実際に作成されたリストで、その後のfor文でmatrixにrawを追加し行列を作成しているように見えますが、実際にはどこを参照すればrawがあるのかというIDを与えて４×４の行列のように表示しているだけです。

このような現象を避けて多次元配列(行列を含む)を作成するための正攻法は後述しますが、私はここでcopyというモジュールにあるdeepcopy()を用いた**深いコピー**を知り、次にようにしてうまく作成しました。
｢深いコピー｣についてはこちらを参照してください→[pythonでのリストの要素の変更とリストのコピー](https://qiita.com/masarin1126/items/a0afa4a5fecacdd02be9)

~~~python
>>> import copy
>>> raw=[0,0,0,0]
>>> matrix=[]
>>> for i in range(4):
	matrix.append(copy.deepcopy(raw))

	
>>> matrix[0][0]=-1
>>> for i in range(3):
	matrix[i+1][i+1]=1

	
>>> matrix
[[-1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]
~~~
見事にミンコフスキー計量(この言葉の意味は知らなくても問題ありません)となりました！！

なお、よく知られていると思いますが、次のようにして行列を作成しても同様な現象が起こります。

~~~python
>>> matrix=[[0]*4]*4
>>> matrix
[[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
>>> matrix[0][0]=-1
>>> matrix
[[-1, 0, 0, 0], [-1, 0, 0, 0], [-1, 0, 0, 0], [-1, 0, 0, 0]]
>>> id(matrix[0])
643757512
>>> id(matrix[1])
643757512
~~~
##多次元配列作成の正攻法
上述のようにdeepcopy()を利用しても良いのですが、多次元配列の作成は、次のように内包表記を用いて行うのが一般的なようです。

３行４列の行列

~~~python
>>> matrix = [[0 for j in range(4)] for i in range(3)]
>>> matrix
[[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
>>> matrix[0][0]=1
>>> matrix
[[1, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
~~~
 入れ子構造を表しているので、列数が前で行数が後になることに注意してください。
matrix = [[0 for j in range(*列数*)] for i in range(*行数*)]


３×４×５の配列

~~~python
>>> matrix = [[[0 for k in range(5)] for j in range(4)] for i in range(3)]
>>> matrix
[[[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]], [[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]], [[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]]]
~~~
少々複雑ですが、３×４×５の配列となっています。

このようにどんどん入れ子構造にしていけば、次元の高い配列が作成できます。

##配列の多重ソート
少々、テーマがそれますが非常に便利な機能なので記載しておきます。
リストの要素を、数字を大きいや小さい順番にソートするコードの作成はアリゴリズムの学習テーマとしてはオーソドックですが、関数が用意されています。また、１行目について大きな順番にソートし、さらに１行目が同じである場合は２行目の要素が大きい順番にソートするなどのいわゆる「多重ソート」も関数を用いて求めることができます。

単純なリストのソート
(数字が小さな順番にソートする）
sorted()という関数を単純に用います。引数をソートしたいリストにします。

~~~python
>>> list1=[1,5,8,9,10,23,4]
>>> list2=sorted(list1)
>>> list2
[1, 4, 5, 8, 9, 10, 23]
~~~~
（数字が大きな順番にソートする）
引数として「reverse=True」を追記することで、降順のソートが求められます。（要素に-1を乗じた上でsorted()でソートし、さらに要素に-1を乗じて元に戻すといったことをしなくてもよい）

~~~python
list1=[3,4,5,8,0,2,9,10,11]
>>> list2=sorted(list1,reverse=True)
>>> list2
[11, 10, 9, 8, 5, 4, 3, 2, 0]
~~~
なお、要素の型が文字になっている場合は数字の大きさという概念では処理されないので、注意が必要です。

~~~python
list1=["1","8","6","0","34","19"]
>>> list2=sorted(list1)
>>> list2
['0', '1', '19', '34', '6', '8']
~~~

行列のソート
（行列の列を指定してソートする）
あるMatrixの３列目の数字をキーとして大きな順番にする(ベストランキング）

~~~python
>>> matrix=[["kana",90,65,54],["nami",85,90,85],["kumi",75,95,60],["ayu",100,100,100],["hikaru",65,65,70]]
>>> matrix2=sorted(matrix, key=lambda x:x[2], reverse=True)
>>> matrix2
[['ayu', 100, 100, 100], ['kumi', 75, 95, 60], ['nami', 85, 90, 85], ['kana', 90, 65, 54], ['hikaru', 65, 65, 70]]
~~~
このような形式になっています。
matrix2=sorted(matrix, key=lambda x:x[*キーとなる列のインデックス*], reverse=True)

３列目の数字をキーとして小さな順番にする(ワーストランキング）

~~~python
>>> matrix2=sorted(matrix, key=lambda x:x[2])
>>> matrix2
[['kana', 90, 65, 54], ['hikaru', 65, 65, 70], ['nami', 85, 90, 85], ['kumi', 75, 95, 60], ['ayu', 100, 100, 100]]
~~~

３列目の数字をキーとして大きな順番にし、３列目が同一の場合は２列目をキーとして大きな順番にする

~~~python
>>> matrix2=sorted(matrix, key=lambda x:(x[2],x[1]), reverse=True)
>>> matrix2
[['ayu', 100, 100, 100], ['kumi', 75, 95, 60], ['nami', 85, 90, 85], ['kana', 90, 65, 54], ['hikaru', 65, 65, 70]]
~~~
matrix2=sorted(matrix, key=lambda x:(x[*最初のキーとなる列のインデックス*],x[*次のキーとなる列のインデックス]), reverse=True)

もう少し大きな配列でk個のキーがある場合なら、
matrix2=sorted(matrix, key=lambda x:(x[*１番目のキーとなる列のインデックス*],x[*２番目のキーとなる列のインデックス],x[*３番目のキーとなる列のインデックス],....,x[*k-1番目のキーとなる列のインデックス],x[*k番目のキーとなる列のインデックス]), reverse=True)

なお、１番目のキーについて大きな順番、２番目のキーについては小さな順番といったように降順と昇順を複合することはできないようなので、この場合 reverse=True の引数を用いるなら昇順とする列の要素に‐1を乗じて処理するか、もしくはreverseを用いないなら降順とする列の要素に-1を乗じて処理すれば良い。