English Русский 中文 Español Deutsch Português 한국어 Français Italiano Türkçe
MQL5 プログラミング基礎:配列

MQL5 プログラミング基礎:配列

MetaTrader 5 | 19 11月 2015, 16:09
11 813 0
Dmitry Fedoseev
Dmitry Fedoseev

はじめに

配列は変数や関数と共にほとんどすべてのプログラム言語に欠くことのできないものです。初心者プログラマーの多くは配列を恐れがちです。おかしなことに聞こえますが本当のことなのです!配列はなにも恐れることはないと断言できます。実際、配列は一般的変数に類似しています。特別な表記について細かい部分に踏み込まなければ、シンプルな変数を用いて式を書くこことに大きな違いはなにもありません。

Variable0=1;
Variable1=2;

Variable2=Variable0+Variable1;

または配列を用いて

double Variable[3];

Variable[0]=1;
Variable[1]=2;

Variable[2]=Variable[0]+Variable[1];

ご覧のように、配列を使うとき変数名がカギカッコに入っている以外大きな違いはありません。もう一つ重要な違いがあります。変数を宣言するとき、それぞれの変数名を指定する必要があります。一方配列を宣言するとき、名前は一度書くだけで、カギカッコ内に変数の番号(配列エレメントの番号)を指定する必要があります。現実のプログラミングタスクを大量に処理するとき、変数より配列を使うことのメリットはもっとはっきりします。

配列がなにやら複雑なもののように見える理由は "[" と "]"を使うことに関係しているのでしょうか?これら記号は配列を扱う際のプログラム以外ではほとんど使われることはありません。そのため、キーボード上の位置を忘れがちで、不安に感じるのです。実際にはそれらがどこにあるか簡単に思い出すことができます。これら2個のキーは理にかなった順序で『エンター』の隣にあります。:開くカッコに続いて閉じるカッコです。


配列の定義と一般的プロパティ

配列は番号が付けられた同じ名前を持つ変数のセットです。配列の一般的プロパティは配列名、変数タイプ(int、doubleなど)、配列サイズです。配列エレメントはゼロからインデックスが付けられます。配列エレメントに関しては、配列エレメントはゼロから数え始めるために『番号を付ける』という語よりも『インデックスを付ける』という語を使うほうがつねに好ましいとされます(番号は通常1から始まるため)。この方法でインデックスが付けられるエレメントで、最終エレメントのインデックスは配列エレメント数マイナス1となります。

配列が以下のように宣言されている場合:

double Variable[3];

次のエレメントを持ちます。:Variable[0]、Variable[1]、Variable[2]。

一見したところ、エレメント数と最終エレメントのインデックスが一致しないことは都合が悪いように思います。実際、それは配列エレメントに 1 からインデックスが付けられるか、または配列内の実エレメント数よりも最終エレメントのインデックスによって配列サイズが決まるプログラム言語について多大なメリットをもたらします。

MQL5ではArraySize() 関数を使用して配列サイズを決めます。

double Variable[3];

int Size=ArraySize(Variable);

このコードの実行後、 Size 変数の値は 3 となります。


静的配列と動的配列

配列は静的あるいは動的でありえます。配列サイズが宣言内で指定されていれば、その配列は静的です。

double Variable[3];

静的配列のサイズはプログラム内での変更は不可です。配列を宣言するとき、そのサイズは数字(上記例のように)または定義済みの定数として直接指定することができます。

#define SIZE 3

double Variable[SIZE];

宣言にてサイズが指定されていない配列は動的です。

double Variable[];

そのような配列は使用する前にサイズを設定する必要があります。そのサイズは ArrayResize() 関数によって設定されます。

ArrayResize(Variable,3);

動的配列のサイズはプログラム実行中に必要なだけ何度でも変更可能です。それが動的配列と静的配列の根本的な違いです。

配列を完全に解放する必要があれば、ArrayFree() 関数を使用します。

ArrayFree(Variable);

この関数を実行する際、配列サイズは 0 に設定されます。この関数による結果は次の処理と似ています。

ArrayResize(Variable,0);

配列の解放は配列がもはやその後のプログラム処理で必要なくなった(これはプログラムが使用するメモリ量を削減します)場合または、関数実行の開始時(配列がデータ収集に使用される場合)に便利です。

ArrayIsDynamic() 関数により既定の配列が静的配列か動的配列か判断することができます。

bool dynamicArray=ArrayIsDynamic(Variable);

dynamicArray 変数は配列が動的であれば値 = 真を持ち、静的であれば値 = 偽となります。


配列の初期化

宣言の際、即座に値を配列に書き込む必要がある場合もあります。同タイプのボタンを複数作成し、連続して整列したいとします。各ボタンにはそれぞれテキストがあります。そういう場合に配列の大きなメリットが物を言うのです。各ボタン(ボタンは何十とあるかもしれません)にコードをコピーする必要はありません。また、同じ関数を繰り返し呼ぶ必要もありません。関数呼び出しコードを一度書くと、ループ内で配列を繰り返すことで必要なボタン数を作成することができるのです。

ただ配列を1個宣言し、すぐにエレメントに値を割り当てるだけです。

string Variable[] = {"Button 1", "Button 2", "Button 3"};

この方法で宣言されると、サイズが指定されていないにもかかわらず配列はまだ静的です。これはエレメントの数が値リスト(中括弧内)により決められているためです。

配列エレメント数を指定してもなんら間違いではありません。

string Variable[3] = {"Button 1", "Button 2", "Button 3"};

ただ、配列エレメント数は指定しない方がよいでしょう。今後プログラムをさらに改善していく中で配列値リストを変更し、より多いエレメント数、またはもっと少ないエレメント数を使用する必要があるかもしれません。使用するコードの部分部分で配列サイズを決めるには、特定の数値よりも ArraySize() 関数を採用することがおすすめです。この方法により主要コードに影響を与えることなく値リストを変更するだけですみます。配列サイズにに対する変数を宣言し、プログラム初期化の際に ArraySize() 関数により取得される値をそこに割りあてる方が適切です。

値リストによって静的配列を初期化できない場合、配列サイズを指定するには定数を用いるのが良いでしょう。通常、さらにプログラムの改善が必要なことがあれば変更を要求されるコードの量を減らすという原則に従います。配列エレメントすべてに同じ値を書き込む必要があれば、ArrayInitialize() 関数を使用します。

ArrayInitialize(Variable,1);

このコードの実行後、 Var 配列エレメントはすべて値1を取得します。いくつかの配列エレメントにのみ同じ値を割り当てる必要がある場合は、ArrayFill() 関数を使用します。

double Variable[4];

ArrayFill(Variable,0,2,1);
ArrayFill(Variable,2,2,2);

このコードの実行後、エレメント0 とエレメント 1 は値 1 を取得し、エレメント 2 とエレメント 3 は値 2 を取得します。


配列反復ループ

配列は通常 for ループを用いて処理されます。サイズが前もってわからない静的配列を使用する際、作業中のタスクによってその配列について前方または後方に向けて反復します。

//--- forwards
for(int i=0; i<SIZE; i++){ 
  // some manipulations on the Variable[i] element
}

//--- backwards
for(int i=SIZE-1; i>=0; i--){
  // some manipulations on the Variable[i] element
}

配列が動的であれば、ループの直前で配列サイズに対する変数を宣言し、配列サイズを取得し、ループを行う必要があります。

int Size=ArraySize(Var);

for(int i=0; i<Size; i++){
  // some manipulations on the Variable[i] element
}

配列サイズに対する変数を用いる代わりに for ループ内の条件を確認する際 ArraySize() 関数を呼ぶなら反復時間はかなり長引く可能性があります。というのも、ArraySize() 関数はループ反復時に毎回呼ばれるためです。関数呼び出しは変数呼び出しよりも時間がかかるのです。

for(int i=0; i<ArraySize(Variable); i++){
   // some manipulations on the Variable[i] element
}
上記コードの使用はお薦めできません。

プログラムアルゴリズムが後ろ向きのループ反復を許すなら、配列サイズに対する変数なしでそれを行うことができます。

for(int i=ArraySize(Variable)-1; i>=0; i--){
  // some manipulations on the Variable[i] element
}

この場合、ArraySize() 関数はループの最初で一度だけ呼ばれ、ループは速く実行されます。


多次元配列

これまで1次元配列のみ考察してきました。それらは次のように表すことができます。

1次元配列

配列は多次元です。1次元配列がインデックスごとに値を一つだけ持つのに対し、多次元配列はインデックスごとの値を複数持ちます。多次元配列は次のように宣言されます。

double Variable[10][3];

これは、配列の最初の次元にはエレメントが 10 あり、二番目の次元にはエレメントが 3 あることを意味します。それは以下のように図示されます。

多次元配列

理解しやすいように、二次元配列は平面として描くことができます。一番目の次元サイズは長さを決め、二番目は幅、そしてエレメント値は平面上に与えられた点のパラメータを決めます。たとえば、海抜の高さです。

一つの配列が三次元であることもあります。

double Variable[10][10][10];

この配列は立方体または平行四辺形として表すことができます。一番目の次元サイズは長さを決め、二番目は幅、三番目は高さ、そしてエレメント値は空間内で与えられた点のパラメータを決めます。

MQL5 で許可される最大配列次元数は 4です。

多次元配列は静的で、第一次元でのみ動的、それ以上の次元ではすべて静的です。そのため ArrayResize() 関数では第一次元のサイズ変更しかできません。その他の次元サイズは配列が宣言されるときに指定する必要があります。

double Variable[][3][3];

ArraySize() 関数を用いて多次元配列のサイズを決めるとき、あることに留意が必要です。それは、ArrayResize() 関数を使用して配列サイズを変更する際は、関数の二番目のパラメータは配列の第一次元のサイズとなるということです。また、ArraySize() 関数は第一次元のサイズではなくエレメントの合計数を返します。

double Variable[][3][3]; 

ArrayResize(Variable,3); 
int Size = ArraySize(Variable);

このコードの実行後、 Size 変数の値は 27 となります。第一次元のサイズを取得する必要がある場合、多次元配列の反復の際のこの特殊性を忘れないようにします。

double Variable[][3][3];
 
ArrayResize(Variable,3); 

int Size=ArraySize(Variable)/9; // Determine the size of the first dimension

for(int i=0; i<Size; i++) {
   for(int j=0; j<3; j++) {
      for(int k=0; k<3; k++) {
            //  some manipulations on the Var[i][j][k] element;
      }   
   }   
}

前に述べたとおり、さらにプログラムの改善が必要なことがあれば変更を要求されるコードの量を減らすという原則に従うことをお薦めします。上記コード例では、それでも計算が可能な数字 9 を使用しました。このため、配列の指定次元にあるエレメント数を返す ArrayRange() 関数を使用することができます。配列次元数がわからない場合にはシンプルな計算が可能です。

int Elements=ArrayRange(Variable,1)*ArrayRange(Variable,2);
int Size=ArraySize(Variable)/Elements;

これをさらに汎用化できます。

int Elements=1; // One element for a one-dimensional array
int n=1; // Start with the second dimension (dimensions are numbered from zero)

while(ArrayRange(Variable,n) > 0){ // Until there are elements in the dimension
   Elements*=ArrayRange(Variable,n); // Multiplication of the number of elements
   n++; // Increase in the dimension's number
}

この点で、そのような計算をする関数を作成するのがよいのでは、とお思いかもしれません。残念ながらこれは不可能です。というのもランダム配列は関数に渡すことができないからです。関数の引数を宣言するとき、第一次元以外のすべての配列次元で明確にエレメント数を指定する必要があります。よって、そのような関数は意味がありません。こういった計算はプログラムの初期化において簡単にうまく行われます。配列を宣言する際、次元サイズを決める定数を使用するとよいでしょう。

#define SIZE1 3
#define SIZE2 3
#define TOTAL SIZE1*SIZE2 

値リストを用いて多次元配列を初期化することは一次元配列の初期化と似通っています。ただ、多次元配列は複数のその他配列から構成されているようなものなのでこれら配列はそれぞれ中括弧で分離する必要があります。

以下のようは配列があるとします。

double Variable[3][3];

この配列はそれぞれにエレメントを3個持つ3つの配列で構成されています。

double Variable[][3]={{1, 2, 3},{ 4, 5, 6},{7, 8, 9}};

三次元配列は同じ方法で処理されます。コードは配列ストラクチャを理解しやすいように、複数行に分割することができます。

double Variable[][3][3]={
   {
      {1, 2, 3},
      {4, 5, 6},
      {7, 8, 9}
   },
   {
      {10, 20, 30},
      {40, 50, 60},
      {70, 80, 90}
   },
   {
      {100, 200, 300},
      {400, 500, 600},
      {700, 800, 900}
   }
};

ArrayInitialize() 関数を使った多次元配列の初期化は一次元配列の初期化と同じ方法で行われます。

ArrayInitialize(Variable,1);

このコードの実行後、 全配列エレメントはすべて値 1 を取得します。ArrayFill() 関数に関しても同じです。

double var[3][3][3];

ArrayFill(Variable,0,9,1);
ArrayFill(Variable,9,9,10);
ArrayFill(Variable,18,9,100);

このコードの実行後、第一次元の最初のエレメントに関連するすべてのエレメントは値 1 を取得し、二番目のエレメントに関連するエレメントは10、そして三番目エレメントに関連するエレメントは 100 を取得します。


関数への配列渡し

変数とは異なり、配列は参照のみによって関数に渡されます。これは関数はそれ自体配列のインスタンスを作成しないが、渡された配列を直接扱うことを意味します。関数が配列に対して行う変更はすべて元の配列に影響を与えます。

通常の方法(値で)で変数が関数に渡されれば、渡された変数の値は関数によって変更されることはありません。

int x=1;
Func(x);

void  Func(int arg){
   arg=2;
}

Func() 関数実行後、 値 x は 1 のままです。

変数が参照によって( &で表記されます)渡されると関数はそこに渡される変数の値を変更することがあります。

int x=1;
Func(x);

void  Func(int &arg){
   arg=2;
}

Func() 関数実行後、 値 x は 2 になります。

配列を関数に渡す際、引数が参照で渡され配列を表す(カッコ内)ことを指定する必要があります。

void Func(double &arg[]){
   // ...
}

関数に多次元配列を渡す際、次元サイズを指定します(第一次元以外)。

double var[][3][3];

void Func(double &arg[][3][3]){
   // ...
}

この場合、定数を使用するのがよりよいでしょう。

#define SIZE1 3
#define SIZE2 3

double Var[][SIZE1][SIZE2];

void Func(double &arg[][SIZE1][SIZE2]){
   // ...
}


ファイルからの配列保存とロード

ファイルから配列を保存しロードするときつねに配列の第一次元サイズと配列エレメントの合計数の値差に配慮が必要です。配列を保存するには、まず配列サイズ(ArraySize() 関数により決められたエレメントの合計数)を書き、それからファイルに配列全体を書きます。

bool SaveArrayToFile(string FileName,string &Array[])
  {
//--- Open the file
   int h=FileOpen(FileName,FILE_TXT|FILE_WRITE);
   if(h==-1) return(false); // Error opening the file
//--- Write to the file
   FileWriteInteger(h,ArraySize(Array),INT_VALUE); // Write the array size
   FileWriteArray(h,Array); // Write the array
//--- Close the file
   FileClose(h);
   return(true); // Saving complete
  }

結果として一次元配列を保存するためのひじょうに汎用的な関数を取得します。

ファイルから配列をロードするには、まず配列サイズを読み、サイズを変更し、そして配列を読む必要があります。

bool LoadArrayFromFile(string FileName,double &Array[])
  {
//--- Open the file
   int h=FileOpen(FileName,FILE_BIN|FILE_READ);
   if(h==-1) return(false); // Error opening the file
//--- Read the file
   int Size=FileReadInteger(h,INT_VALUE); // Read the number of array elements
   ArrayResize(Array,Size); // Resize the array. 
                            // In one-dimensional arrays the size of the first dimension is equal to the number of array elements.
   FileReadArray(h,Array); // Read the array from the file
//--- Close the file
   FileClose(h);
   return(true); // Reading complete
  }

ファイルから多次元配列をロードするときは第一次元のサイズを計算する必要があります。たとえば三次元配列を読む場合。

bool LoadArrayFromFile3(string FileName,double &Array[][SIZE1][SIZE2])
  {
//--- Open the file
   int h=FileOpen(FileName,FILE_BIN|FILE_READ);
   if(h==-1)return(false); // Error opening the file
//--- Read the file   
   int SizeTotal=FileReadInteger(h,INT_VALUE); // Read the number of array elements
   int Elements=SIZE1*SIZE2; // Calculate the number of elements 
   int Size=SizeTotal/Elements; // Calculate the size of the first dimension
   ArrayResize(Array,Size); // Resize the array
   FileReadArray(h,Array); // Read the array
//--- Close the file
   FileClose(h);
   return(true); // Reading complete
  }

ファイルには 2×3 の配列があり、ここでそれを 3×3 として読むとします。エレメント数により計算される第一次元サイズをかけ算することでサイズ同士の一致を確認することができます。結果の値がトータル配列数に等しければ、一致していると言うことができます。

ただし、Var[2][3] 配列は Var[3][2] 配列に対応します。またこのケースを取り上げる必要があるなら、多次元配列に関してもっと情報を保存する必要があります。たとえば、まず配列エレメント数を保存し、それから配列次元数、続いて各次元サイズと配列そのものを保存します。

上記にある最後の関数は汎用的ではなく、第二次元のサイズが SIZE1 で、第三次元のサイズが SIZE2 な三次元配列を読むためにだけに作成されています。すべての配列次元サイズを動的に変更する方法はないので、これは問題ではありません。プログラムで使用する必要のある配列用関数を作成するのです。

この場合汎用性は必要ありません。配列次元のサイズ(第一次元以外)はプログラムの外部パラメータによって制御されないのです。ただ、他の次元サイズを制御する機能を実装する必要があれば、故意に大きなサイズの多次元配列およびそれ以外の変数またはオブジェクト指向プログラム (OOP) テクニックによってこのタスクを遂行することが可能です。この二番目の方法については本稿で後に取り上げます。


動的配列の使用

前もって配列サイズがわからないときには動的配列が使用されます。配列サイズがプログラムのプロパティウィンドウに設定されるパラメータに依存する場合、動的配列を使用することは問題ありません。配列サイズはプログラム初期化時一度だけ変更されるからです。

配列は動的に特定情報を収集するのに使用することができます。たとえば指値注文に関してなど。その数は変わる可能性があります。すなわち前もって必要なサイズが判らないということです。この場合、もっとも簡単な方法はオーダーをすべて渡す前に配列サイズを 0 に変更し、各オーダーを渡すごとにエレメントの数だけ配列サイズを増やすことです。ただこの方法は役立ちますがスピードがひじょうに遅くなります。

オーダーをすべて渡す前にオーダー数に応じて一度だけ配列サイズを変更することもできます。これには配列の最終のアクティブエレメントのインデックスに対する別の変数が必要となります(またはインデックスの代わりに実際にアクティブな配列エレメント数)。この方法はすでに最大配列サイズが判っている場合に適しています。最大配列サイズがわからなければ、チャンクを使って配列のサイズを変更することでそれとの連携をスピードアップすることができます。それは以下のクラスで示されています。

class CDynamicArray
  {
private:
   int               m_ChunkSize;    // Chunk size
   int               m_ReservedSize; // Actual size of the array
   int               m_Size;         // Number of active elements in the array
public:
   double            Element[];      // The array proper. It is located in the public section, 
                                     // so that we can use it directly, if necessary
   //+------------------------------------------------------------------+
   //|   Constructor                                                    |
   //+------------------------------------------------------------------+
   void CDynamicArray(int ChunkSize=1024)
     {
      m_Size=0;                            // Number of active elements
      m_ChunkSize=ChunkSize;               // Chunk size
      m_ReservedSize=ChunkSize;            // Actual size of the array
      ArrayResize(Element,m_ReservedSize); // Prepare the array
     }
   //+------------------------------------------------------------------+
   //|   Function for adding an element at the end of array             |
   //+------------------------------------------------------------------+
   void AddValue(double Value)
     {
      m_Size++; // Increase the number of active elements
      if(m_Size>m_ReservedSize)
        { // The required number is bigger than the actual array size
         m_ReservedSize+=m_ChunkSize; // Calculate the new array size
         ArrayResize(Element,m_ReservedSize); // Increase the actual array size
        }
      Element[m_Size-1]=Value; // Add the value
     }
   //+------------------------------------------------------------------+
   //|   Function for getting the number of active elements in the array|
   //+------------------------------------------------------------------+
   int Size()
     {
      return(m_Size);
     }
  };

このクラスは添付の CDynamicArray.mqh ファイルにインクルードされています。またこのファイルはターミナルデータディレクトリの MQL5\Include フォルダにあります。

両方の状況でのコードのパフォーマンスを評価し比較します。ここで配列サイズはチャンクを使用して連続 1 ずつ増やします。

int n=50000;
   double ar[];
   CDynamicArray da;

//--- Option 1 (increasing the size by the 1st element)
   long st=GetTickCount(); // Store the start time 
   ArrayResize(ar,0); // Set the array size to zero 
   for(int i=0;i<n;i++)
     {
      ArrayResize(ar,i+1); // Resize the array sequentially
      ar[i]=i;
     }
   Alert("Option 1: "+IntegerToString(GetTickCount()-st)+" ms"); // Message regarding the amount of time required to perform the first option

//--- Option 2 (increasing the size using chunks)
   st=GetTickCount(); // Store the start time 
   for(int i=0;i<n;i++)
     {
      da.AddValue(i); // Add an element
     }
   Alert("Option 2: "+IntegerToString(GetTickCount()-st)+" ms"); // Message regarding the amount of time required to perform the second option

  }

スクリプト形式のこの検証は添付ファイル sTest_Speed.mq5 で確認できます。ファイルはターミナルデータディレクトリの MQL5\Scripts フォルダにあります。

結果としては最初のオプションのパフォーマンスには数秒かかり、二番目ではほとんど瞬時でした。


配列のインデックス順

配列のサイズ変更の際は通常その配列の最後に新しいエレメントが追加されます。

double ar[]; // Array
ArrayResize(ar,2); // Prepare the array
ar[0]=1; // Set the values
ar[1]=2; 
ArrayResize(ar,3); // Increase the array size
ar[2]=3; // Set the value for the new array element
Alert(ar[0]," ",ar[1]," ",ar[2]); // Print array values

このコードの実行後、配列内の値は 1、2、3 となります。

配列内のエレメントには逆の順序でインデックスを付けることも可能です。インデックスをつける順序は ArraySetAsSeries() 関数で設定します。

ArraySetAsSeries(ar,true); // set indexing in reverse order
ArraySetAsSeries(ar,false); // set normal indexing

逆の順序でインデックスを付けられた配列のサイズを変更するとき、通常は配列の最初に新しいエレメントが追加されます。

double ar[]; // Array
ArrayResize(ar,2); // Prepare the array
ar[0]=1; // Set the values
ar[1]=2; 
ArraySetAsSeries(ar,true); // Change the indexing order
ArrayResize(ar,3); // Increase the array size
ar[0]=3; // Set the value for the new array element
Alert(ar[0]," ",ar[1]," ",ar[2]); // Print array values

このコードの実行後、配列内の値は 3、2、1 となります。

どちらの場合も新しいエレメントは配列の同じ側に追加されます。唯一異なる点はインデックスの順序です。この関数は正順でインデックスがつけられたエレメントの配列の最初にエレメントを追加するために使用することはできません。正順でインデックスが付けられた配列の最後にエレメントを追加するには、配列サイズを増やし最終エレメントに値を一つ割り当てるだけです。

配列の最初にエレメントを追加するには、配列サイズを増やし、すべての値を移動し、新しい値をゼロエレメントに割り当てます。逆順にインデックスが付けられた配列では、簡単に新しいエレメントを配列の最初に追加することができます。ただ、配列の最後に新しいエレメントを追加する必要があれば、まず配列サイズを増やし、その後すべての値を配列の初めに移動し、最後のエレメントに新しい値を割り当てます。インデックスを付ける順序を操作することでこの問題は解決されません。

配列のインデックス順序を決めるには ArrayIsSeries() 関数を使用します。

bool series=ArrayIsSeries(ar);

配列順序が逆の場合、この関数は真を返します。

Expert Advisorsでは主として逆順でインデックスが付けられている配列が使われています。EAを開発する際、右から左にバーを数え、価格データやインディケータバッファを逆インデックス順で配列にコピーする方が便利です。


配列のコピー

もっとも簡単なコピー方法はループで配列を反復し、一つの配列から別の配列にひとつずつエレメントをコピーすることです。が、MQL5 には配列をコピーすることのできる特殊な関数があります。ArrayCopy()です。

double ar1[]={1,2,3};
double ar2[];

ArrayCopy(ar2,ar1);

上記コードの実行後、 ar2 配列はar1配列同様の値:1、2、3の3個のエレメントで構成されます。

コピーするエレメント数がコピー先の配列サイズにフィットしていなければ、配列サイズは自動的に増やされます(配列が動的である必要があります)。配列がコピーされるエレメント数よりも大きければ、サイズは元のままとなります。

ArrayCopy() 関数により配列の一部をコピーすることも可能です。関数のオプションパラメータを使うと、コピーする最初のエレメント、新しい配列内で最初にコピーするインデックス、コピーしようとしているエレメント数を指定することができます。

ある配列から別の配列のエレメントをコピーする以外にも ArrayCopy() 関数は同一配列内のエレメントをコピーするのにも使用することができます。

double ar1[]={1,2,3,4,5};
ArrayCopy(ar1,ar1,1,2);

インデックス 2 のエレメントからデータコピーを始め、それらをインデックス 1 からペーストします。このコードの実行後、配列は値:1、3、4、 5、5を持ちます。

ArrayCopy() 関数によりデータを右に移動することができます。

double ar1[]={1,2,3,4,5};
ArrayCopy(ar1,ar1,2,1);

インデックス 1 のエレメントからデータを取り始め、それらをインデックス 2から並べます。このコードの実行後、配列は値:1、2、2、3、 4 を持ちます。

ArrayCopy() 関数は多次元配列にも適用可能です。そこでは配列が一次元でそのエレメントがすべて連続して並べられているように扱います。

double ar[3][2]={{1, 2},{3, 4},{5, 6}};
ArrayCopy(ar,ar,2,4);

このコードの実行後、配列は{1, 2}, {5, 6}, {5, 6} のように表示されます。


配列の格納

ArraySort() 関数を用いて配列を格納することができます。

double ar[]={1,3,2,5,4};
ArraySort(ar);

上記コードの実行後、配列値は1、2、3、4、5の順序で並べられます。

ArraySort() 関数は多次元配列には適用できません。多次元配列およびデータストラクチャをソートする情報は、記事"Electronic Tables in MQL5"にあります。


ArrayBsearch() 関数を使ってバイナリ検索を行います。この関数はソートされた配列に対してのみ適切に動作します。バイナリ検索と呼ばれるのは、アルゴリズムが継続的に配列をに分割することによります。アルゴリズムはまず対象値を配列の中央エレメント値と比較し、それで対象エレメントを含む半分を判断します。左側の従属配列または右側の従属配列です。そして対象値を従属配列の中央値と比較していきます。

ArrayBsearch() 関数は対象値のエレメントのインデックスを返します。

double ar[]={1,2,3,4,5};

int index=ArrayBsearch(ar,3);

このコードの実行後、インデックス変数値は 2 となります。

対象値が配列内に見つからなければ、この関数は小さい方で一番近い値のエレメントのインデックスを返します。この特質のため、この関数は時刻でバーを検索するのに使用できます。指定時刻にバーがなければ、小さい時刻値のバーが計算に使われます。

対象値が配列になく、配列値の範囲を超えたところにある場合、この関数は 0 (対象値が最小値より小さければ)、または最終インデックス(対象値が最小値より大きければ)を返します。

ソートされていない配列内で検索をする方法は一つしかありません。配列の反復です。

int FindInArray(int &Array[],int Value){
   int size=ArraySize(Array);
      for(int i=0; i<size; i++){
         if(Array[i]==Value){
            return(i);
         }
      }
   return(-1);
}

上記例では、関数は対象値のエレメントのインデックスを返します。対象値が配列になければ、関数は -1を返します。


最大値および最小値の検索

配列内の最大値および最小値は、それぞれ最大値および最小値を持つエレメントのインデックスを返す関数 ArrayMaximum() および ArrayMinimum() を使用して見つけることができます。

double ar[]={3,2,1,2,3,4,5,4,3};

int MaxIndex=ArrayMaximum(ar);
int MinIndex=ArrayMinimum(ar);
double MaxValue=ar[MaxIndex];
double MinValue=ar[MinIndex];

このコードの実行後、MaxIndex 変数は 6、MinIndex 変数は 2、MaxValue はf 5、そして MinValue は 1となります。

関数 ArrayMaximum() および ArrayMinimum() により検索範囲の最初のエレメントのインデックスと検索範囲のエレメント数を指定することで検索範囲を限定することができます。

int MaxIndex=ArrayMaximum(ar,5,3);
int MinIndex=ArrayMinimum(ar,5,3);

この場合、MaxIndex は値 6、 MinIndex は値 5 となります。指定範囲には最小値 4 のポジションが 2 つ、すなわちポジション 5 とポジション 7 が含まれることに注意が必要です。このような場合、この関数は配列の最初に近いエレメントのインデックスを返します。こういった関数は逆順でインデックスが付けられた配列を同じ方法で処理します。すなわち最小インデックスを返すのです。

これで、MQL5 で配列を扱うことのできる標準的関数をすべて詳しく見ました。


OOP を用いた多次元配列の作成

多次元配列を作成するためのクラスセットにはクラスが 3 つあります。基本クラスが 1 つと派生クラスが 2 つです。オブジェクト作成段階で選択される派生クラスに依存するので、オブジェクトはダブル変数の配列またはオブジェクト配列を表します。オブジェクト配列の各エレメントはオブジェクトまたは変数の別の配列を表すことができます。

基本クラスと派生クラスには実質上関数は一つも含まれません。ただし、基本クラスのデストラクタと派生クラスのコンストラクタは例外です。基本クラスのデストラクタはプログラムまたは関数の完成時にすべてのオブジェクトを削除する役目を果たします。派生クラスのコンストラクタははコンストラクタのパラメータで指定されるサイズに従い配列を計測するためだけに使用されます。すなわち一つのクラス内オブジェクトの配列および別のクラスの変数の配列を計測するのです。以下はそのようなクラスを実装するためのコードです。

//+------------------------------------------------------------------+
//|   Base class                                                     |
//+------------------------------------------------------------------+
class CArrayBase
  {
public:
   CArrayBase       *D[];
   double            V[];

   void ~CArrayBase()
     {
      for(int i=ArraySize(D)-1; i>=0; i--)
        {
         if(CheckPointer(D[i])==POINTER_DYNAMIC)
           {
            delete D[i];
           }
        }
     }
  };
//+------------------------------------------------------------------+
//|   Child class 1                                                  |
//+------------------------------------------------------------------+
class CDim : public CArrayBase
  {
public:
   void CDim(int Size)
     {
      ArrayResize(D,Size);
     }
  };
//+------------------------------------------------------------------+
//|   Child class 1                                                  |
//+------------------------------------------------------------------+
class CArr : public CArrayBase
  {
public:
   void CArr(int Size)
     {
      ArrayResize(V,Size);
     }
  };

これらクラスは添付ファイル CMultiDimArray.mqh にあります。またこのファイルはターミナルデータディレクトリの MQL5\Include フォルダにあります。

ここからこのクラスを使用して類似した一次元配列を構築します。

CArrayBase * A; // Declare a pointer
   A=new CArr(10); // Load a child class instance that scales the array of variables. 
                   // The array will consist of 10 elements.

//--- Now the array can be used:
   for(int i=0; i<10; i++)
     {
      //--- Assign to each element of the array successive values from 1 to 10
      A.V[i]=i+1;
     }
   for(int i=0;i<10;i++)
     {
      //--- Check the values
      Alert(A.V[i]);
     }
   delete A; // Delete the object
  }

この例のスクリプト形式は添付ファイル sTest_1_Arr.mq5 にあります。ファイルはターミナルデータディレクトリの MQL5\Scripts フォルダにあります。

次に二次元配列を作成してみます。第一次元の各エレメントは第二次元のエレメントの異なる数を持ちます。第一次元のうちの1つ、二第二次元のうちの2つ、などです。

CArrayBase*A;  // Declare a pointer
   A=new CDim(3); // The first dimension represents an array of objects

//--- Each object of the first dimension represents an array of variables of different sizes 
   A.D[0]=new CArr(1);
   A.D[1]=new CArr(2);
   A.D[2]=new CArr(3);
//--- Assign values
   A.D[0].V[0]=1;

   A.D[1].V[0]=10;
   A.D[1].V[1]=20;

   A.D[2].V[0]=100;
   A.D[2].V[1]=200;
   A.D[2].V[2]=300;
//--- Check the values
   Alert(A.D[0].V[0]);

   Alert(A.D[1].V[0]);
   Alert(A.D[1].V[1]);

   Alert(A.D[2].V[0]);
   Alert(A.D[2].V[1]);
   Alert(A.D[2].V[2]);
//---
   delete A; // Delete the object

この例のスクリプト形式は添付ファイル sTest_2_Dim.mq5 にあります。ファイルはターミナルデータディレクトリの MQL5\Scripts フォルダにあります。

このクラスが配列サイズを変更するメソッドを持たないため、結果の配列はいわば静的です。 ただ、配列 D[] および V[] がクラスのパブリックなセクションに位置しているため、それらに対してあらゆる操作が可能です。そして V[] 配列を計測するのに何も難しいことはありません。D[] 配列を計測しサイズを減らすとき、まず指摘されているオブジェクトを削除するオブジェクトによって削除するか、配列サイズを増やす場合はそれらにオブジェクトをロードします。

必要に応じてOOP または データストラクチャによってだ次元配列を実装する別の方法を考えることもできます。


おわりに

本稿は MQL5で使用可能な配列に関わる標準 関数をすべて取り上げました。配列を扱うための特殊性ともっとも重要なテクニックをいくつか詳しく見ました。MQL5 言語は合計15個の関数を提供します。その中には最重要なものもあり、また特殊な問題を解決する必要がない限り全く利用しないであろうものもあります。それら関数は重要性と使用頻度によって以下のように整理することができます。

  1. ArraySize() および ArrayResize() は基礎的関数です。

  2. ArrayMaximum()ArrayMinimum()ArrayCopy()ArrayInitialize()ArrayFill()ArrayFree() は配列の作業をかなり簡単にする関数です。

  3. ArraySort() は重要で便利な関数ですが、機能性が低いためほとんど使用されません。

  4. ArrayBsearch() はほとんど使用されない関数ですが、稀な例外ケースにとってはひじょうに重要な関数です。

  5. ArraySetAsSeries()ArrayRange()ArrayGetAsSeries()ArrayIsDynamic()ArrayIsSeries() は使用されるのがひじょうに稀、またはほとんど全く使用されない関数です。

本稿で述べられているプログラムテクニックの一つである動的配列の使用には特別な注意が必要です。それは大きな影響を与えプログラムのパフォーマンスを決定すると言えます。

MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/567

添付されたファイル |
cdynamicarray.mqh (2.59 KB)
stest_speed.mq5 (1.6 KB)
cmultidimarray.mqh (1.67 KB)
stest_1_arr.mq5 (1.29 KB)
stest_2_dim.mq5 (1.43 KB)
Mac OSでのMetaTrader 4 Mac OSでのMetaTrader 4
Apple社の製品は、とても高い人気を得ています。MetaQuotes Software Corp.社は、コンピューターテクノロジーの発展に注意深く注目し、iOSデバイス版、つまり、iPhone版のMetaTrader 4とMetaTrader 5用の専用モバイルアプリケーションをすでにリリースしています。MQL4.communityのフォーラムでは、幾度となくMac OSでのMetaTrader 4の起動の可能性についての話題が上がっていました。この記事では、多くの人が愛用するAppleのオペレーションシステムで、いかに簡単にMetaTrader 4を使用できるか解説していきたいと思います。
MetaTrader 5のトレードシグナル:PAMM アカウントへのよりよい代替手段 MetaTrader 5のトレードシグナル:PAMM アカウントへのよりよい代替手段
MetaTrader 5 が今トレードシグナルを備え、そのため投資家や幹事会社に力強いツールを提供できることをうれしく思います。成功しているトレーダーのトレードをフォローする間にも、ターミナルは自動でそれらをみなさんのアカウントに再生しているのです。
LinuxにおけるMetaTrader 4 LinuxにおけるMetaTrader 4
この記事では、一般的なLinuxバージョン(UbuntuとDebian)にMetaTrader 4をインストールする簡単な方法を示します。これらのシステムは、サーバーハードウェアだけでなく、トレーダーのパーソナルコンピューターでも広く使用されています。
チャート上でトレーディングの考え方を時間をかけずに検証する方法 チャート上でトレーディングの考え方を時間をかけずに検証する方法
本稿はトレーディングの考え方を速く視覚的に検証する方法について述べます。その方法は価格チャート、シグナルインディケータ、残高計算インディケータの組合せを基にしています。そんなわけで、これからトレーディングの考え方を検索する方法やその考え方を時間をかけずに検証する方法をみなさんと共有したいと思います。