2018年10月12日金曜日

[MQL4] CArrayObj はソート済みじゃないと Search() の結果が -1 になってしまう

[MQL4 で可変長配列を使用する方法](https://strategyofc.blogspot.com/2018/10/mql4.html) で既に書いたのですが、以下のように `CArrayObj::Search()` を呼んでも正しくインデックスが返ってこない時があります。

`Item` 等の定義は [MQL4 で可変長配列を使用する方法](https://strategyofc.blogspot.com/2018/10/mql4.html) を参照してください。

```mq4
    for (int i = 0; i < 10; i++) {
        int value = (i % 2 == 0) ? i : i + 10;
        m_item_array.Add(new Item(value));
    }    

    // 途中に挿入
    Item* item_100 = new Item(100);
    m_item_array.Insert(item_100, 5);
    
    // 今挿入したばかりのオブジェクトを検索する
    int index = m_item_array.Search(item_100);

    printf("index: %d", index);
    
    // 結果
    // index: -1
```



### 原因
`Include/Arrays/ArrayObj.mqh` 中の `Search()` の定義を見てみると、`m_sort_mode==-1` とあります。

```mq4
`title: "Include/Arrays/ArrayObj.mqh"; first-line: 571; highlight: 575;
int CArrayObj::Search(const CObject *element) const
  {
   int pos;
//--- check
   if(m_data_total==0 || !CheckPointer(element) || m_sort_mode==-1)
      return(-1);
```

この `m_sort_mode` とは何かというと、基底クラスの中に以下のような実装があります。
ソートが行われると、`m_sort_mode` がセットされるというわけです。

```mq4
`title: "Include/Arrays/ArrayObj.mqh"; first-line: 79; highlight: 84;
void CArray::Sort(const int mode)
  {
//--- check
   if(IsSorted(mode))
      return;
   m_sort_mode=mode;
```

つまり、`Sort()` を実行していない配列に対しては `Search()` は `-1` を返すということです。
ちょっと不親切ですね。


### 対策
上記の通り、`Search()` を呼ぶ前に `Sort()` を呼べば OK です。

```mq4
`first-line: 10; highlight: 10;
    m_item_array.Sort();
    int index = m_item_array.Search(item_100);

    printf("index: %d", index);
    
    // 結果
    // index: 10
```

なお、`Compare()` の実装によっては `Sort()` したとしても正しい index が返ってこない場合があります。
注意が必要です。

> 参考
>
> [[MQL4] CArrayObj::Search() が想定外のインデックスを返してくることがある](https://strategyofc.blogspot.com/2018/10/mql4-carrayobjsearch.html)


### 補足: sort_mode とは
`Sort()` の引数として渡される `mode`。
以下のようにデフォルト `0` で定義されています。

```mq4
`title: "Include/Arrays/ArrayObj.mqh"; first-line: 37;
   void              Sort(const int mode=0);
```

これは何に使われるのでしょうか?

実は、[CObject](https://www.mql5.com/en/docs/standardlibrary/cobject) の `Compare()` の引数としてそのまま渡されます。

```mq4
    virtual int Compare(const CObject *node, const int mode=0) const;
```

この `mode` のについて明確に定義しているドキュメントは見つけることが出来ませんでした。

`Compare()` を override した人が自分で決めれば良いようです。
一般的に、`0` ならば降順、`1` ならば昇順といった感じでしょうか。