恥は/dev/nullへ by 初心者

~ PC初心者による右往左往の記録 ~

[MQL5]ArrayCopyは同一配列にも使用可能

同一の配列の中でデータの位置をずらす際にもMQL5のArrayCopy関数が使えることを最近知りました(私が知らなかっただけで、別に大した話ではないのですが、汗)。

以前は、別個の配列(たとえば、配列Aと配列B)の間でデータをコピーする時に使うものだと思っていました。そのため、1つの配列の中で要素を移動する時はfor文を使っていました。

たとえば、次のような感じです。要素数が5つのarrayという配列において、array[1]からarray[4]の値をarray[0]からarray[3]に移動する場合

for(int i = 0; i <= 3; i++) {
    array[i] = array[i + 1];
}


ずっと気になっていたのですが、このやり方は力技な感じがするんですよね。要素数がもっと多かったら、その数だけループさせることになりますから。

しかし、ネット上で見かけたコードによると、同一の配列の中でデータを移動するのにArrayCopyが使えると気づきました。

目次


ArrayCopyの説明

ArrayCopyに関するヘルプページを見てみます。

ArrayCopy
  配列を別の配列に複製します。

int  ArrayCopy( 
   void&        dst_array[],        // 複製先の配列 
   const void&  src_array[],        // ソース配列 
   int          dst_start=0,        // 複製先の配列の開始インデックス 
   int          src_start=0,        // ソース配列の開始インデックス 
   int          count=WHOLE_ARRAY   // 要素の数 
   );


冒頭に「別の配列に」と書かれているんですよね。私はこの言葉を今まで信じていました・・・(遠い目)。

たとえば、次のように使います。

ArrayCopy(array_B, array_A, 0, 1, 4);

array_Aからarray_Bにデータをコピーします(第1引数と第2引数)。
データのコピー先の先頭位置はarray_Bの要素[0]です(第3引数)。
array_Aの要素[1]から4つの要素(array_A[1]~array_A[4])をコピーします(第4, 第5引数)

ある配列から別の配列にArrayCopy

上述したことをコードで行ってみます。
このコードでは、ある配列から別の配列にデータをコピーしています。

void OnStart()
{
    // 配列Aと配列Bを用意
    int array_A[5];
    int array_B[5];

    // 配列Aを初期化
    array_A[0] = 10;
    array_A[1] = 20;
    array_A[2] = 30;
    array_A[3] = 40;
    array_A[4] = 50;

    // 配列Bを初期化
    ArrayInitialize(array_B, 0);

    // 配列の要素を表示
    for(int i = 0; i < 5; i++)
        printf("array_A[%d] = %d", i, array_A[i]);

    Print("");

    for(int i = 0; i < ArraySize(array_A); i++)
        printf("array_B[%d] = %d", i, array_B[i]);

    // 配列Aの要素のうち、先頭要素以外の4つを配列Bにコピー
    ArrayCopy(array_B, array_A, 0, 1, 4);


    Print("\nArrayCopyを実行しました\n");

    for(int i = 0; i < 5; i++)
        printf("array_B[%d] = %d", i, array_B[i]);
}


実行結果

array_A[0] = 10
array_A[1] = 20
array_A[2] = 30
array_A[3] = 40
array_A[4] = 50

array_B[0] = 0
array_B[1] = 0
array_B[2] = 0
array_B[3] = 0
array_B[4] = 0

ArrayCopyを実行しました

array_B[0] = 20
array_B[1] = 30
array_B[2] = 40
array_B[3] = 50
array_B[4] = 0


同一の配列の中でArrayCopy

このコードでは、1つの配列の中でデータをコピーしています。

void OnStart()
{
    int array_A[5];

    array_A[0] = 10;
    array_A[1] = 20;
    array_A[2] = 30;
    array_A[3] = 40;
    array_A[4] = 50;

    // 配列の各要素を表示
    for(int i = 0; i < 5; i++)
        printf("array_A[%d] = %d", i, array_A[i]);

    // array_A[1]からarray_A[4]の値を、array_A[0]からarray_A[3]に移動
    ArrayCopy(array_A, array_A, 0, 1, 4);

    Print("\nArrayCopyを実行しました\n");

    for(int i = 0; i < 5; i++)
        printf("array_A[%d] = %d", i, array_A[i]);

    // 最終要素に値を代入
    array_A[4] = 60;

    Print("\n最終要素に60を代入しました\n");

    for(int i = 0; i < 5; i++)
        printf("array_A[%d] = %d", i, array_A[i]);
}


実行結果

array_A[0] = 10
array_A[1] = 20
array_A[2] = 30
array_A[3] = 40
array_A[4] = 50

ArrayCopyを実行しました

array_A[0] = 20
array_A[1] = 30
array_A[2] = 40
array_A[3] = 50
array_A[4] = 50

最終要素に60を代入しました

array_A[0] = 20
array_A[1] = 30
array_A[2] = 40
array_A[3] = 50
array_A[4] = 60


定数の一部を関数に置き換えたコード

void OnStart()
{
    int array[5];

    array[0] = 10;
    array[1] = 20;
    array[2] = 30;
    array[3] = 40;
    array[4] = 50;

    // 配列の各要素を表示
    for(int i = 0; i < ArraySize(array); i++)
        printf("array[%d] = %d", i, array[i]);

    // array[1]からarray[4]の値を、array[0]からarray[3]に移動
    ArrayCopy(array, array, 0, 1, ArraySize(array) - 1);

    Print("\nArrayCopyを実行しました\n");

    for(int i = 0; i < ArraySize(array); i++)
        printf("array[%d] = %d", i, array[i]);

    // 最終要素に値を代入
    array[ArraySize(array) - 1] = 60;

    Print("\n最終要素に60を代入しました\n");

    for(int i = 0; i < ArraySize(array); i++)
        printf("array[%d] = %d", i, array[i]);
}


実行結果

array[0] = 10
array[1] = 20
array[2] = 30
array[3] = 40
array[4] = 50

ArrayCopyを実行しました

array[0] = 20
array[1] = 30
array[2] = 40
array[3] = 50
array[4] = 50

最終要素に60を代入しました

array[0] = 20
array[1] = 30
array[2] = 40
array[3] = 50
array[4] = 60


実際の使用例

私は、平均値を求めたり、急激な価格変化(上昇、下降)を調べるために、過去 N 本のローソク足情報を配列に保存しています。
たとえば、「実体の大きさ」「実体とヒゲの比率」「ヒゲも含めた全長」など。

ためしに、1本前のローソク足から5本前のローソク足の全長を配列の中に格納することを例にしてコードを書いてみます。

int    sizeOfArray = 5;                    // 配列の要素数
double array[];                            // 配列
double multiplier = MathPow(10, _Digits);  // 価格をポイント数に変換するための乗数


//=================================
// OnInit()の中で実行
//=================================
// 配列のサイズをセット
ArrayResize(array, sizeOfArray);

// 5本前から1本前のローソク足の全長を配列に格納
for(int i = 0; i < ArraySize(array); i++) {
    int shift = sizeOfArray - i;
    array[i] = iHigh(NULL, 0, shift) * multiplier - iLow(NULL, 0, shift) * multiplier;
}

// 配列の中身は次のとおり
//     array[0] = 5本前の全長
//     array[1] = 4本前の全長
//     array[2] = 3本前の全長
//     array[3] = 2本前の全長
//     array[4] = 1本前の全長


//=================================
// OnCalculate()の中で実行
//=================================
// 新しいローソク足が発生した時に1回だけ処理
static datetime barTime = D'2000.01.01 00:00';   // ←初期値はテキトー

if(barTime != iTime(NULL, 0, 0)) {

    // 2番目の要素から最終要素を残して、先頭要素を破棄
    // (この例では、array[1]からarray[4]の値を、array[0]からarray[3]に移動)
    ArrayCopy(array, array, 0, 1, ArraySize(array) - 1);

    // 最終要素に新たな値を代入
    array[ArraySize(array) - 1] = iHigh(NULL, 0, 1) * multiplier - iLow(NULL, 0, 1) * multiplier;

    barTime = iTime(NULL, 0, 0);   // タイムスタンプを更新
}