Библиотека Generic классов - ошибки, описание, вопросы, особенности использования и предложения - страница 19

 
Vasiliy Sokolov:
можно ведь написать свою специализацию функции для класса.
 
Комбинатор:
можно ведь написать свою специализацию функции для класса.

Без интерфейса нельзя.

 
Vasiliy Sokolov:

Без интерфейса нельзя.

Что без интерфейса нельзя? )
 
Комбинатор:
Что без интерфейса нельзя? )

Ну а ты подумай. Короче, не захломляй ветку плиз.

 
Vasiliy Sokolov:

Проблема

Очевидно, что GetHashCode невозможно реализовать в пользовательском окружении MQL5 должным образом. Это так, потому что не ко всем объектам есть доступ. И если на примитивных членах вроде double int и т.п. реализация работает нормально, то для сложных объектов (классы, структуры и даже перечисления) хеш расчитывается по имени. Очевидно, что если у нас тысяча объектов типа CObject или даже ENUM_что-то_там, то и CHashMap и CHashSet вырождаются в обычный LinkedList:

избежать этого не получиться, потому что все что у нас есть на пользовательском уровне это имя объекта:

Следовательно, хеши у всех объектов сложных типов равны между собой и вот этот код для поиска задействует полный перебор массива:

На самом деле это настолько критично, что перечеркивает смысл использования всего Generic на корню. Дело в том, что в большинстве случаев, требуется ассоциация или хранения сложных объектов: перечислений, структур или классов. Если бы требовалось оперирование только простыми типами, можно было бы обойтись чем-нибудь по проще.

Для того что бы коллекции Generic работали корректно с объектами классов, эти классы должны реализовывать интерфейс IEqualityComparable, в котором определены методы Equals и HashCode. Т.е. пользователю самому необходимо задавать методы вычисления хэш кодов, и это пока единственный вариант, так как реализовать эти методы автоматически, как это сделано например в .Net, средствами MQL5 не получиться.

 
Roman Konopelko:

Для того что бы коллекции Generic работали корректно с объектами классов, эти классы должны реализовывать интерфейс IEqualityComparable, в котором определены методы Equals и HashCode. Т.е. пользователю самому необходимо задавать методы вычисления хэш кодов, и это пока единственный вариант, так как реализовать эти методы автоматически, как это сделано например в .Net, средствами MQL5 не получиться.

Роман, Вы забыли упомянуть, что в MQL5 нет интерфейсов. Любые разговоры о интерфейсах в сегодняшнем MQL5 это злостная инсинуация и демагогия.

p.s. Но даже если бы интерфейсы в MQL5 появились, вопрос с структурами и перечислениями так и остался бы нерешенным.

 
Vasiliy Sokolov:

Роман, Вы забыли упомянуть, что в MQL5 нет интерфейсов. Любые разговоры о интерфейсах в сегодняшнем MQL5 это злостная инсинуация и демагогия.

p.s. Но даже если бы интерфейсы в MQL5 появились, вопрос с структурами и перечислениями так и остался бы нерешенным.

В MQL5 на данный момент невозможно в принципе написать шаблонные методы, которые будут одновременно работать для классов, структур и перечислений из-за особенностей передачи данных.
 
Roman Konopelko:
В MQL5 на данный момент невозможно в принципе написать шаблонные методы, которые будут одновременно работать для классов, структур и перечислений из-за особенностей передачи данных.

Вот о том и речь. Но среда MQL5 знает о своих объектах все! Она владеет и указателями на объекты и знает все идентификаторы перечислений (EnumToString). Вот поэтому GetHashCode нужна как системная и всеядная функция.

А еще разрешите наконец-то множественное наследование интерфейсов. Перепишите Generic на нормальные интерфейсы и будет конфетка. 

 

Ситуация очевидна: разработчики MQ так часто обжигались от множественного наследования в C++ что теперь боятся любого его проявления. В итоге предлагается избежать одного треша (множественное наследование) путем использования другого треша: нелепых цепочек наследования.

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

 

Форум по трейдингу, автоматическим торговым системам и тестированию торговых стратегий

Библиотека Generic классов - ошибки, описание, вопросы, особенности использования и предложения

Roman Konopelko, 2017.12.18 16:29

1) Коэффициент прироста объема(capacity) не равен 1.2, для расчета нового объема в CHashMap используется метод CPrimeGenerator::ExpandPrime:

int CPrimeGenerator::ExpandPrime(const int old_size)
  {
   int new_size=2*old_size;
   if((uint)new_size>INT_MAX && INT_MAX>old_size)
      return INT_MAX;
   else
      return GetPrime(new_size);
  }

В данном методе старый размер коллекции умножается на два, далее для полученного значения находим ближайшее с верху простое число и возвращаем его, как новый объем.

Про начальное значение capacity - согласен, он очень маленький.

Но с другой стороны всегда есть конструкторы, в которых явно можно указать начальный объем:

class CHashMap: public IMap<TKey,TValue>
  {
public:
                     CHashMap(const int capacity);
                     CHashMap(const int capacity,IEqualityComparer<TKey>*comparer);
  }

Поэтому особого смысла здесь что-то менять не вижу.


Да, был не прав, каюсь. 
Действительно, коэффициент прироста объема(capacity) для CHashMap больше 2-х.
Спасибо за указание на ошибку и извините за потраченное время.



С другой стороны удалось уделить время на изучению реализации CPrimeGenerator.

//+------------------------------------------------------------------+
//| Fast generator of parime value.                                  |
//+------------------------------------------------------------------+
int CPrimeGenerator::GetPrime(const int min)
  {
//--- a typical resize algorithm would pick the smallest prime number in this array
//--- that is larger than twice the previous capacity. 
//--- get next prime value from table
   for(int i=0; i<ArraySize(s_primes); i++)
     {
      int prime=s_primes[i];
      if(prime>=min)
         return(prime);
     }
//--- outside of our predefined table
   for(int i=(min|1); i<INT_MAX;i+=2)
     {
      if(IsPrime(i) && ((i-1)%s_hash_prime!=0))
         return(i);
     }
   return(min);
  }


И имеются несколько предложений, в основном на улучшение быстродействия.


1. Устранить неоднозначность поведения:
Если передать "INT_MAX - 10" в качества параметра в CPrimeGenerator::ExpandPrime, то вернется результат "INT_MAX".
Если передать "INT_MAX - 10" в качества параметра в CPrimeGenerator::GetPrime, то вернется тот же результат: "INT_MAX - 10".

Так же в обоих случаях возвращаемое значение не является простым числом, что вводит пользователя в заблуждение.



2. При вызове GetPrime для чисел больше 7199369 в приоритете становится экономия памяти, однако это не оправдывает относительную низкую производительность и бесполезные расчеты.

Предлагается:
- добавить сравнение числа с последним значением массива CPrimeGenerator::s_primes[] и не выполнять ненужный перебор всех 72-х элементов массива.
- заменить динамический поиск простого числа (идет перебор все чисел подряд) на массив предопределенных значений на подобии CPrimeGenerator::s_primes[], но не с квадратическим приростом, а линейным.
Прирост значений будет составлять порядка 1 миллиона (цифра аналогична приросту s_primes на последних элементах массива).
Количество элементов до 3000, значения в диапазоне от 8М до INT_MAX.
Поиск по массиву будет осуществляться через upper bound binary search, количество необходимых итераций - 12.

 


3. При вызове GetPrime для чисел меньше 7199369 в худшем случае выполняется линейный перебор всех 72-х значений массива CPrimeGenerator::s_primes[].

Предлагается: 
- уменьшить количество эллементов в массиве до 70 шт. (удалив первых два, или первый и последний):

const static int  CPrimeGenerator::s_primes[]=
  {
   3,7,11,17,23,29,37,47,59,71,89,107,131,163,197,239,293,353,431,521,631,761,919,
   1103,1327,1597,1931,2333,2801,3371,4049,4861,5839,7013,8419,10103,12143,14591,
   17519,21023,25229,30293,36353,43627,52361,62851,75431,90523,108631,130363,156437,
   187751,225307,270371,324449,389357,467237,560689,672827,807403,968897,1162687,1395263,
   1674319,2009191,2411033,2893249,3471899,4166287,4999559,5999471,7199369
  };

- если входное значение меньше или равно 6-ому значению в новом массиве CPrimeGenerator::s_primes - то перебирать числа линейно (до 6-ти сравнений).
- иначе использовать upper bound binary search между 7-м и 70-м значениями массива (порядка 6-ти итераций).

Идея заключается в использовании линейного перебора лишь до той поры, пока не существует проигрыш в производительности по сравнению с бинарным поиском.
Предложенное количество элементов - 6-ть используется для примера, в реальности все зависит от конкретной реализации upper bound binary search.
Общий прирост производительности в виду невысокой интенсивности вызова конкретной функции может быть не на столько выгоден, что бы производить какие-либо работы по улучшению данной функциональности.

Причина обращения: