PythonでリストとNumPy配列に要素を挿入する方法の備忘録

# はじめに

Pythonにおいて、リストやNumPy配列に要素を挿入する方法は多岐にわたります。この記事では、これらの方法を理解しやすいように、具体的な例を通じて説明してみます。

https://colab.research.google.com/drive/1tbN97bJrly9YXpl-Cb-RzVQbF5Mkh8PO?usp=sharing

がこの記事の google colab へのリンクです。

使い方としては、

- リストは + でくっつけることができるが、numpy は配列同士の演算になるので、要素の結合にはならない。
- リストは可変長なので、同じリストに新しい要素を追加できるが、numpy の配列は固定長なので新しい numpy 配列に追加になる。
- リストはスライス `a[i:i]=b` のように、同じ場所を指定してそこに挿入ができるが、numpy だとできない。

## 初期データの設定

まず、操作に使用するリストとNumPy配列を定義します。

``` python:
import numpy as np

a_list = [(1, 1), (2, 2), (3, 3)]
b_list = [(10, 10), (20, 20), (30, 30)]
a_np = np.array(a_list)
b_np = np.array(b_list)
``` 

ここでは、`a_list` と `b_list` という2つのリスト、およびこれらに相当するNumPy配列 `a_np` と `b_np` を作成しています。

# リストへの挿入
## スライスを使用する方法

``` python:
a_slice = a_list.copy()
a_slice[1:1] = b_list
print("スライス:", a_slice)
# スライス: [(1, 1), (10, 10), (20, 20), (30, 30), (2, 2), (3, 3)]
```

スライスを使用する方法では、指定した位置に新しいリスト `b_list` の要素を挿入します。ここでは、`a_list` のインデックス1の位置に `b_list` を挿入しています。

`a_slice[1:1] = b_list` の `1:1` は、Pythonのスライス表記により、リスト `a_slice` のインデックス1の直前（インデックス1の位置）に、リスト `b_list` の要素を挿入することを指します。スライス表記 `1:1` は、開始インデックスと終了インデックスが同じであるため、元のリストからは何も選択されません（つまり、空の範囲を示しています）。

具体的には、以下のように動作します：

- `a_slice[1:1]` は、リスト `a_slice` の`インデックス1`の位置を指し、そこから何も選択しません（空のスライスを作成します）。
- `a_slice[1:1] = b_list` は、その空のスライスの位置に `b_list` の要素を挿入します。

## + 演算子を使用する方法

``` python:
a_plus = a_list[:1] + b_list + a_list[1:]
print("+ 演算子:", a_plus)
# + 演算子: [(1, 1), (10, 10), (20, 20), (30, 30), (2, 2), (3, 3)]
```

`+ 演算子`を使用すると、リストを分割し、その間に別のリストを挿入することができます。この方法も、`a_list` の指定された位置に `b_list` を挿入しています。

## insert を使う方法

``` python:
a_insert = a_list.copy()
a_insert.insert(1, b_list)
print("insert:", a_insert)
# insert: [(1, 1), [(10, 10), (20, 20), (30, 30)], (2, 2), (3, 3)]
``` 

`insert` メソッドを使用すると、リストの特定の位置に直接新しい要素を挿入できます。この例では、`a_list` のインデックス1に `b_list` を挿入しています。

# NumPy配列への挿入

## concatenate を使う

``` python:
a_concat = np.concatenate((a_np[:1], b_np, a_np[1:]))
print("concatenate:", a_concat)
# concatenate: 
#[[ 1  1]
# [10 10]
# [20 20]
# [30 30]
# [ 2  2]
# [ 3  3]]
``` 

NumPyの `concatenate` 関数を使うと、複数の配列を結合して新しい配列を作成できます。この例では、`a_np` の指定された位置に `b_np` を挿入しています。

## numpy.insert を使う

``` python:
a_insert = np.insert(a_np, 1, b_np, axis=0)
print("np.insert:", a_insert)
# np.insert: 
#[[ 1  1]
# [10 10]
# [20 20]
# [30 30]
# [ 2  2]
# [ 3  3]]
```

NumPyの `insert` 関数を使うと、配列の特定の位置に新しい行または列を挿入できます。この例では、`a_np` のインデックス1に `b_np`の行を挿入しています。

# サンプルコード全文

``` python:
import numpy as np

# 初期データの設定
a_list = [(1, 1), (2, 2), (3, 3)]
b_list = [(10, 10), (20, 20), (30, 30)]
a_np = np.array(a_list)
b_np = np.array(b_list)

# 挿入位置
insert_index = 1

# リスト操作
def insert_into_list(a, b, index):
    # スライスを使用する方法
    a_slice = a.copy()
    a_slice[index:index] = b
    print("スライス:", a_slice)

    # + 演算子を使用する方法
    a_plus = a[:index] + b + a[index:]
    print("+ 演算子:", a_plus)

    # insert を使う方法
    a_insert = a.copy()
    a_insert.insert(index, b)
    print("insert:", a_insert)

insert_into_list(a_list, b_list, insert_index)

# NumPy配列操作
def insert_into_numpy(a, b, index):
    # concatenate を使う
    a_concat = np.concatenate((a[:index], b, a[index:]))
    print("concatenate:", a_concat)

    # numpy.insert を使う
    a_insert = np.insert(a, index, b, axis=0)
    print("np.insert:", a_insert)

insert_into_numpy(a_np, b_np, insert_index)
```

これを実行すると、

```
スライス: [(1, 1), (10, 10), (20, 20), (30, 30), (2, 2), (3, 3)]
+ 演算子: [(1, 1), (10, 10), (20, 20), (30, 30), (2, 2), (3, 3)]
insert: [(1, 1), [(10, 10), (20, 20), (30, 30)], (2, 2), (3, 3)]
concatenate: [[ 1  1]
 [10 10]
 [20 20]
 [30 30]
 [ 2  2]
 [ 3  3]]
np.insert: [[ 1  1]
 [10 10]
 [20 20]
 [30 30]
 [ 2  2]
 [ 3  3]]
 ```

という出力になります。


# その他のオペレーション

## map関数を用いた掛け算

map関数を使うことで、関数にするほどのではない演算を短くかけます。`list(map(` という使い方をすることで、`map` で生成される要素を `list` で強制的に配列の全要素に作用させることになります。具体例でみてみましょう。

### 一次元のリストの場合

``` python:
my_list = [1, 2, 3, 4, 5]
factor = 2
multiplied_list = list(map(lambda x: x * factor, my_list))
# multiplied_list
# [2, 4, 6, 8, 10]
``` 

### ２次元のリストの場合

各タプルの第一要素に2を掛け、第二要素に3を掛けるとします。
以下のようにmap関数を使用できます。

``` python:
my_list = [(1, 2), (3, 4), (5, 6)]
multiplied_list = list(map(lambda x: (x[0] * 2, x[1] * 3), my_list))
# multiplied_list
# [(2, 6), (6, 12), (10, 18)]
``` 

### map関数の使い方の補足

`map`関数は、指定された関数をリスト（または他のイテラブル）の各要素に適用し、結果を`mapオブジェクト`として返します。`mapオブジェクト`はイテレータであり、すべての結果を一度にメモリに格納するのではなく、要求されたときにそれぞれの結果を生成します。

`list関数`を`map`の外側に使用する理由は、`map`オブジェクトをリストに変換して、すべての結果を一度に取得するためです。`list`を使用しない場合、`mapオブジェクト`を直接イテレートするか、next関数を使用して各要素を個別に取り出す必要があります。

例えば、以下のように書くことができます：

``` python:
# mapオブジェクトを直接使用
my_list = [(1, 2), (3, 4), (5, 6)]
multiplied_map = map(lambda x: (x[0] * 2, x[1] * 3), my_list)

# mapオブジェクトから要素を一つずつ取り出す
for item in multiplied_map:
    print(item)
```

この方法は、すべての結果を一度にリストとして格納する必要がない場合に便利ですが、結果を再利用したい場合や、結果の全体像が必要な場合はlist関数を使用してリストに変換するのが良いでしょう。