「2個のデータ列を交互に合わせる」シンプルな方法は何なのか…とぼんやり思って、生成AIに質問していたら、どんどん話が進んで、「2個のデータ列を交互に合わせる」方法を比較するテストコードが出来上がったので、記載します。
処理速度を実際に計測してどの方法がよいかが分かる、正しく動作するテストコードが苦労することもなく得られました。生成AI(ChatGPT)、素晴らしい。
生成AIに質問(ChatGPT)
何もない状態から、生成AI(ChatGPT)に下記の質問をして、「2個のデータ列を交互に合わせる」方法を比較するテストコードが出来上がりました。
- Pythonで、1Dのデータ列を交互に分けて2つのデータ列にしたい
- 分けた
data1
,data2
のデータ列を、元の1Dデータ列に戻したい - もっと効率的でもっとシンプルな方法は?
-
zip
を使わないで実現する方法は? -
zip
を使う方法(例:restored = list(chain.from_iterable(zip(data1, data2)))
)と、
zip
を使わない方法(例:restored[::2] = data1, restored[1::2] = data2
)で、
どちらが高速で処理できるのか? - NumPyを使った高速ベクトル演算版の例が見たい
-
timeit.timeit
に関数で渡す方法で、前述の3ケースの比較を行うコードがほしい -
# --------- 処理の確認 ---------
のセクションを追加して、処理結果が想定通りか確認するコードを追加したい - 結果のデータ列の末尾辺りを表示して、実際の値を確認したい
-
# --------- 1. zip + chain.from_iterable ---------
の方法で、入力データ列のサイズが異なる場合の対処が出来ていない -
assert result_zip == result_slice == result_numpy, "出力結果が一致しません"
の部分について、
assertで中断しないようにしたい(処理は続行)
質問・回答の全履歴
テストコード
「2個のデータ列を交互に合わせる」方法を比較するテストコード:
(生成AIが作成したコードに、出力例等のメモを追加記載したもの)
test__2個のデータ列を交互に合わせる速度比較.py
# 2個のデータ列を交互に合わせる方法3つ、速度比較
# 生成AIに聞く: https://chatgpt.com/share/67fa869e-c4e4-8004-8255-350bd7181062
# 結果: 動作OK
# 出力例:
"""
▼ 結果データ列の末尾10要素:
expected : [99991, 99992, 99993, 99994, 99995, 99996, 99997, 99998, 99999, -1]
zip + chain : [99991, 99992, 99993, 99994, 99995, 99996, 99997, 99998, 99999, -1]
スライス代入 : [99991, 99992, 99993, 99994, 99995, 99996, 99997, 99998, 99999, -1]
NumPy : [99991, 99992, 99993, 99994, 99995, 99996, 99997, 99998, 99999, -1]
✔ すべての手法で結果が一致しました。
▼ ベンチマーク(100回実行)
データ列のサイズ: data1: 50001, data2: 50000
zip + chain: 0.407367 秒(0.004074 秒/回)
スライス代入: 0.175750 秒(0.001758 秒/回)
NumPy: 0.044295 秒(0.000443 秒/回)
"""
import timeit
from itertools import chain
import numpy as np
# --------- 共通のデータセットを準備 ---------
N = 100_000
data1_list = list(range(0, N, 2)) + [-1] # +[-1]:長さが異なる場合の模擬
data2_list = list(range(1, N, 2))
data1_np = np.array(data1_list)
data2_np = np.array(data2_list)
# --------- 1. zip + chain.from_iterable ---------
def method_zip_chain():
# zip は短い方で止まる
restored = list(chain.from_iterable(zip(data1_list, data2_list)))
# 長さの差を考慮して、余りを追加
if len(data1_list) > len(data2_list):
restored.extend(data1_list[len(data2_list):])
elif len(data2_list) > len(data1_list):
restored.extend(data2_list[len(data1_list):])
return restored
# --------- 2. スライス代入(リスト) ---------
def method_slice_assign():
restored = [0] * (len(data1_list) + len(data2_list))
restored[::2] = data1_list
restored[1::2] = data2_list
return restored
# --------- 3. NumPy(empty + スライス代入) ---------
def method_numpy():
restored = np.empty(len(data1_np) + len(data2_np), dtype=data1_np.dtype)
restored[::2] = data1_np
restored[1::2] = data2_np
return restored
# --------- 処理の確認 ---------
expected = list(range(N)) + [-1] # +[-1]:長さが異なる場合の模擬
result_zip = method_zip_chain()
result_slice = method_slice_assign()
result_numpy = method_numpy().tolist() # NumPy → list に変換して比較
# 末尾10要素の表示
print("▼ 結果データ列の末尾10要素:")
print("expected :", expected[-10:])
print("zip + chain :", result_zip[-10:])
print("スライス代入 :", result_slice[-10:])
print("NumPy :", result_numpy[-10:])
if expected == result_zip == result_slice == result_numpy:
print("✔ すべての手法で結果が一致しました。")
else:
print("✖ 結果が一致しません!")
# --------- ベンチマーク ---------
number = 100
zip_time = timeit.timeit(method_zip_chain, number=number)
slice_time = timeit.timeit(method_slice_assign, number=number)
numpy_time = timeit.timeit(method_numpy, number=number)
print(f"\n▼ ベンチマーク({number}回実行)")
print(f"データ列のサイズ: data1: {len(data1_list)}, data2: {len(data2_list)}")
print(f"zip + chain: {zip_time:.6f} 秒({zip_time/number:.6f} 秒/回)")
print(f"スライス代入: {slice_time:.6f} 秒({slice_time/number:.6f} 秒/回)")
print(f"NumPy: {numpy_time:.6f} 秒({numpy_time/number:.6f} 秒/回)")
実行結果
上記コードの出力例:
▼ 結果データ列の末尾10要素:
expected : [99991, 99992, 99993, 99994, 99995, 99996, 99997, 99998, 99999, -1]
zip + chain : [99991, 99992, 99993, 99994, 99995, 99996, 99997, 99998, 99999, -1]
スライス代入 : [99991, 99992, 99993, 99994, 99995, 99996, 99997, 99998, 99999, -1]
NumPy : [99991, 99992, 99993, 99994, 99995, 99996, 99997, 99998, 99999, -1]
✔ すべての手法で結果が一致しました。
▼ ベンチマーク(100回実行)
データ列のサイズ: data1: 50001, data2: 50000
zip + chain: 0.407367 秒(0.004074 秒/回)
スライス代入: 0.175750 秒(0.001758 秒/回)
NumPy: 0.044295 秒(0.000443 秒/回)
3番目「Numpy」の方法が、非常に高速。
最も「シンプル」かつ「高速処理」となる方法
前述のベンチマーク結果から、最も「シンプル」かつ「高速処理」となる方法は、「NumPy + スライス代入」の方法と分かりました。