Ещё раз про сортировку массива структур

 

Прежде чем решиться на создание этой темы, я провёл поиск по форуму и кое-что нашёл. Но у меня почему-то это не работает ...

Есть необходимость хранить информацию об открытых позициях, а именно цену открытия и тикет. Система учёта позиция хэджинговая, поэтому позиций по одному и тому же инструменту может быть несколько. Для хранения нужной информации я создал структуру с двумя полями, и массив для хранения этих структур. В какой-то момент возникла необходимость отсортировать массив структур по ценам открытия позиций.

Я нашёл вот здесь код для сортировки массива структур от @fxsaber:

template <typename T>                                       
void ArrayReindex( T &Array[], const double &TmpSort[][2] )
{                         
  T TmpArray[];
  
  for (int x = ::ArrayResize(TmpArray, ::ArrayRange(TmpSort, 0)) - 1; x >= 0; x--)
    TmpArray[x] = Array[(int)(TmpSort[x][1] + 0.1)];
    
  ::ArraySwap(Array, TmpArray);
              
  return;     
}             

// Сортировка массива структур и указателей на объекты по (под-) полю/методу.
#define ArraySortStruct(ARRAY, FIELD)                                      \
{                                                                          \
  double TmpSort[][2];                                                     \
                                                                           \
  for (int x =::ArrayResize(TmpSort, ::ArraySize(ARRAY)) - 1; x >= 0; x--) \
  {                                                                        \
    TmpSort[x][0] = (double)ARRAY[x].FIELD;                                \
    TmpSort[x][1] = x;                                                     \
  }                                                                        \
                                                                           \
  ::ArraySort(TmpSort);                                                    \
  ::ArrayReindex(ARRAY, TmpSort);                                          \
}                                  

Сохранил этот код в файле SortStruct.mqh. Затем отсюда взял вот такой пример для проверки:

void OnStart()
{
  MqlRates Rates[];
  
  CopyRates(_Symbol, PERIOD_CURRENT, 0, 5, Rates); // Взяли бары
  
  Print("\nБары без сортировки - как получили.");
  ArrayPrint(Rates);
  
  Print("\nСортируем по open-цене.");
  ArraySortStruct(Rates, open);
  ArrayPrint(Rates);

  Print("\nСортируем по high-цене.");
  ArraySortStruct(Rates, high);
  ArrayPrint(Rates);

  Print("\nСортируем по времени.");
  ArraySortStruct(Rates, time);
  ArrayPrint(Rates);
}

Этот пример прекрасно работает, его вывод полностью совпадает с тем, что приведён самим @fxsaber по вышеупомянутой ссылке.

Вдохновлённый этим примером, я набросал свой скрипт, где объявил структуру, переменную и массив. Массив заполнил тестовыми данными, вывел на печать, отсортировал по полю price_open и снова вывел (всё почти также, как в предыдущем примере):

#include <SortStruct.mqh>

struct open_position {
   ulong ticket;
   double price_open;
};
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart() {
   // объявляем переменную и массив
   open_position pos;
   open_position array[6];
   
   // заполняем массив тестовыми данными
   pos.ticket = 1; pos.price_open = 0.05; array[0] = pos;
   pos.ticket = 2; pos.price_open = 0.03; array[1] = pos;
   pos.ticket = 3; pos.price_open = 0.08; array[2] = pos;
   pos.ticket = 4; pos.price_open = 0.01; array[3] = pos;
   pos.ticket = 5; pos.price_open = 0.06; array[4] = pos;
   pos.ticket = 6; pos.price_open = 0.02; array[5] = pos;
   
   // выводим на печать
   Print ("Массив как есть:");
   ArrayPrint (array);
   
   // сортируем по полю price_open и снова выводим
   ArraySortStruct (array, price_open);
   Print("\nСортировка по цене:");
   ArrayPrint (array);
}

При компиляции этот скрипт не выдаёт ни одной ошибки. Но сортировка массива не производится. Вот результат выполнения этого примера:

Массив как есть:
    [ticket] [price_open]
[0]        1     0.050000
[1]        2     0.030000
[2]        3     0.080000
[3]        4     0.010000
[4]        5     0.060000
[5]        6     0.020000
        
Сортировка по цене:
    [ticket] [price_open]
[0]        1     0.050000
[1]        2     0.030000
[2]        3     0.080000
[3]        4     0.010000
[4]        5     0.060000
[5]        6     0.020000

Пожалуйста, помогите понять, что я делаю не так?

Почему в моём случае сортировка не производится и как сделать так, чтобы она производилась?

Как вообще сортируют массивы структур по произвольному полю в 2025 году?

 
Janis Ozols:

Прежде чем решиться на создание этой темы, я провёл поиск по форуму и кое-что нашёл. Но у меня почему-то это не работает ...

Есть необходимость хранить информацию об открытых позициях, а именно цену открытия и тикет. Система учёта позиция хэджинговая, поэтому позиций по одному и тому же инструменту может быть несколько. Для хранения нужной информации я создал структуру с двумя полями, и массив для хранения этих структур. В какой-то момент возникла необходимость отсортировать массив структур по ценам открытия позиций.

Я нашёл вот здесь код для сортировки массива структур от @fxsaber:

Сохранил этот код в файле SortStruct.mqh. Затем отсюда взял вот такой пример для проверки:

Этот пример прекрасно работает, его вывод полностью совпадает с тем, что приведён самим @fxsaber по вышеупомянутой ссылке.

Вдохновлённый этим примером, я набросал свой скрипт, где объявил структуру, переменную и массив. Массив заполнил тестовыми данными, вывел на печать, отсортировал по полю price_open и снова вывел (всё почти также, как в предыдущем примере):

При компиляции этот скрипт не выдаёт ни одной ошибки. Но сортировка массива не производится. Вот результат выполнения этого примера:

Пожалуйста, помогите понять, что я делаю не так?

Почему в моём случае сортировка не производится и как сделать так, чтобы она производилась?

Как вообще сортируют массивы структур по произвольному полю в 2025 году?

Когда-то я это пробовал. Из нескольких вариантов получил желаемый результат. но потом отказался.

Используйте лучше массив объектов

#include <Arrays\ArrayObj.mqh>
CArrayObj         listObj;       //--- Список объектов


      if(!listObj.Add(obj))
       {
        delete obj;//--- Если добавление завершилось ошибкой — удалим этот объект
        printf("Что-то пошло не так %s %d",__FUNCTION__,__LINE__);
        continue;
       }

Примерно так добавили.

Сортируются так

    listObj.Sort(0);

Но придётся писать свою виртуальную функцию сортировки

Можно сортировать по одному полю, по двум или как пожелаете. Главное всё описать в функции 

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

Примерно так

/********************************************************************/
template <typename T1>
int class::compare(T1 data1, T1 data2) const
 {
  if(data1 == data2)
    return 0;
  if(data1 > data2)
    return 1;
  return -1;
 };/*****************************************************************/

/********************************************************************/
int class::Compare(const CObject *node, const int mode = 0) const
 {
  const class *obj = node;
//--- при mode==0 сравниваем символ
  if(mode == 0)
    return compare(this.getSymbol(), obj.getSymbol());
//--- при mode==1 сравниваем тикет
  if(mode == 1)
    return compare(this.m_ticket, obj.getTicket());
  return -1;
 };/*****************************************************************/
 
Alexey Viktorov #:
Используйте лучше массив объектов

Я смотрел в сторону этого класса, но меня смутил вот этот момент (цитата из документации):

Класс CArrayObj является классом динамического массива указателей на экземпляры класса CObject и его наследников. 

Разве моя простенькая структура является наследником класса CObject? Я только начинаю вникать в ООП и пока не понимаю этого.

 
Alexey Viktorov #:
Примерно так добавили.

Попробовал, не получается:

#include <Arrays\ArrayObj.mqh>

struct open_position {
   ulong ticket;
   double price_open;
};

void OnStart() {
   CArrayObj array;
   open_position pos;
   
   pos.ticket = 1; pos.price_open = 0.05; array.Add(pos);
}

Попытка компиляции:

'pos' - parameter conversion not allowed

Цветом выделено место, на которое ругается компилятор.

@Alexey Viktorov, по вашему коду мне не очень понятно, какого типа переменная obj. Судя по имени это объект? Мне надо разобраться, как заменить структуру классом, унаследованным от CObject ?
 
Janis Ozols #:

Попробовал, не получается:

Попытка компиляции:

Цветом выделено место, на которое ругается компилятор.

@Alexey Viktorov, по вашему коду мне не очень понятно, какого типа переменная obj. Судя по имени это объект? Мне надо разобраться, как заменить структуру объектом?

Я тоже далёк от полного понимания ООП, но с помощью Тришкина освоил вот это и доволен собой как бегемот на болоте.

Да. Это должен быть класс, а не структура.

В общем последовательность такая:

Объявлена переменная класса tmpObj 

Потом в этот объект добавляется, в моём случае символ

    tmpObj.setSymbol(symbol); // это здесь.
    listObj.Sort(0);                    // Сортируем список не важно пустой или нет
    if(listObj.Search(&tmpObj) == -1) // Если такой объект не найден
     {
      CSpring *obj = new CSpring(symbol); // создали указатель и добавили объект в список

      if(!listObj.Add(obj))
        delete obj;//--- Если добавление завершилось ошибкой — удалим этот объект
     }

Затем в этом списке можно искать любой объект как по одному параметру, например по символу, так и по двум, символу и ТФ например. Всё зависит от функции Compare() как вы напишете для себя.

Если я где-то неправильно называю указатель объектом, то меня кто-то поправит.

 

В итоге стоящую передо мной задачу удалось решить так:

#include <Arrays\ArrayObj.mqh>

//+------------------------------------------------------------------+
//| Класс для хранения информации об открытой позиции                |
//+------------------------------------------------------------------+
class COpenPosition : public CObject
{
private:
    ulong  m_ticket;    // Тикет позиции
    double m_price;     // Цена открытия позиции

public:
    // Конструктор по умолчанию
    COpenPosition(void): m_ticket(0), m_price(0.0) {}
    
    // Конструктор с параметрами
    COpenPosition(const ulong ticket, const double price): m_ticket(ticket), m_price(price) {}
    
    // Деструктор
    ~COpenPosition(void) {}
    
    // Методы доступа к полям класса (геттеры)
    ulong  Ticket(void) const { return m_ticket; }
    double Price(void) const { return m_price; }
    
    // Методы установки значений полей (сеттеры)
    void   Ticket(const ulong ticket) { m_ticket = ticket; }
    void   Price(const double price) { m_price = price; }
    
    // Переопределение метода Compare для сортировки по цене
    virtual int Compare(const CObject *node, const int mode=0) const
    {
        const COpenPosition *other = node;
        if(other == NULL) return 0;
        
        // Сравнение по цене
        if(m_price < other.Price()) return -1;
        if(m_price > other.Price()) return 1;
        return 0;
    }
};

//+------------------------------------------------------------------+
//| Пример использования CArrayObj с сортировкой позиций по цене     |
//+------------------------------------------------------------------+

void OnStart()
{
   // Создание массива объектов
   CArrayObj positions;
   
   // Включение флага освобождения элементов при удалении массива
   positions.FreeMode(true);
   
   // Создание позиций с разными ценами
   positions.Add(new COpenPosition(123456, 1.12345));
   positions.Add(new COpenPosition(789012, 1.67890));
   positions.Add(new COpenPosition(345678, 1.03456));
   positions.Add(new COpenPosition(901234, 1.23456));
   
   // Вывод информации до сортировки
   Print("--- Позиции до сортировки ---");
   for(int i = 0; i < positions.Total(); i++)
   {
      COpenPosition *pos = positions.At(i);
      if(pos != NULL)
         Print("Позиция ", i, " - Тикет: ", pos.Ticket(), ", Цена: ", pos.Price());
   }
   
   // Сортировка массива по возрастанию цены
   positions.Sort(0);
   
   // Вывод информации после сортировки
   Print("--- Позиции после сортировки по возрастанию цены ---");
   for(int i = 0; i < positions.Total(); i++)
   {
      COpenPosition *pos = positions.At(i);
      if(pos != NULL)
         Print("Позиция ", i, " - Тикет: ", pos.Ticket(), ", Цена: ", pos.Price());
   }
}

@Alexey Viktorov, благодарю за помощь!

 
Janis Ozols #:

В итоге стоящую передо мной задачу удалось решить так:

@Alexey Viktorov, благодарю за помощь!

Огромная благодарность Артёму Тришкину за помощь. Это он меня научил этому. Я мучал его вопросами целую неделю. А может и больше…😊

 
Janis Ozols:
Но у меня почему-то это не работает ...

1. ArraySwap возвращает false, код ошибки: ERR_INVALID_ARRAY    4006     Массив неподходящего типа, неподходящего размера или испорченный объект динамического массива

2. Изменяем код:

   // объявляем переменную и массив
   open_position pos;
   //open_position array[6];
   open_position array[];
   ArrayResize(array, 6);

3. Результат:

Сортировка по цене:
    [ticket] [price_open]
[0]        4      0.01000
[1]        6      0.02000
[2]        2      0.03000
[3]        1      0.05000
[4]        5      0.06000
[5]        3      0.08000
 
trader6_1 #:
2. Изменяем код:

Бинго! Теперь и мой первоначальный пример работает!!!

Я на самом деле взял статический массив просто для примера. Мне казалось, так будет проще. А оказалось, что именно здесь собака порылась!

@trader6_1 и вам огромное спасибо за помощь!

 

Можно и статический и динамический.

#include <QuickSort.mqh>

struct open_position
{
   ulong ticket;
   double price_open;
};

typedef bool (*FuncLess)( const open_position&, const open_position& );

bool Less( const open_position& struct1, const open_position& struct2 )
{
   return struct1.price_open < struct2.price_open;
}

void OnStart()
{
   // объявляем переменную и массив
   open_position pos;
   open_position array[6];
   
   // заполняем массив тестовыми данными
   pos.ticket = 1; pos.price_open = 0.05; array[0] = pos;
   pos.ticket = 2; pos.price_open = 0.03; array[1] = pos;
   pos.ticket = 3; pos.price_open = 0.08; array[2] = pos;
   pos.ticket = 4; pos.price_open = 0.01; array[3] = pos;
   pos.ticket = 5; pos.price_open = 0.06; array[4] = pos;
   pos.ticket = 6; pos.price_open = 0.02; array[5] = pos;
   
   // выводим на печать
   Print( "Массив как есть:" );
   ArrayPrint( array );
   
   QuickSort< open_position, FuncLess >( array, Less );
   
   Print( "\nСортировка по цене:" );
   ArrayPrint( array );
}

Библиотека QuickSort

Быстрая сортировка.
Быстрая сортировка.
  • www.mql5.com
Функции для сортировки массивов. Позволяют сортировать строки и структуры по любому условию.