0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

2個のデータ列を交互に合わせるシンプルな方法・速度比較 - ぼんやり生成AIに質問していたらベンチマークするテストコードが出来上がっていた

Last updated at Posted at 2025-04-13

「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 + スライス代入」の方法と分かりました。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?