Generic Class Library - bugs, description, questions, usage and suggestions - page 19

 
Vasiliy Sokolov:
You can write your own specialization function for a class, after all.
 
Combinator:
You can write your own specialization of a function for a class, after all.

You can't do it without an interface.

 
Vasiliy Sokolov:

You can't do it without an interface.

You can't do what without an interface? )
 
Combinator:
What can't you do without an interface? )

Well, think about it. In short, do not clutter up the branch please.

 
Vasiliy Sokolov:

The Problem

Obviously, GetHashCode is impossible to implement properly in custom MQL5 environment. This is because not all objects can be accessed. And while for the primitive members like double int, etc. the implementation works fine, for complex objects (classes, structures and even enumerations) the hash is calculated by name. Obviously, if we have a thousand objects like CObject or even ENUM_something_that, then both CHashMap and CHashSet degenerate into LinkedList:

You can't avoid this because all we have at the user level is the name of the object:

Consequently, the hashes of all objects of complex types are equal to each other and this code here involves a full array enumeration to search:

In fact, this is so critical that it negates the point of using all Generic at the root. The point is that in most cases, you need to associate or store complex objects: enumerations, structures or classes. If you needed to operate with simple types, you could do with something simpler.

For Generic collections to work correctly with class objects, these classes should implement IEqualityComparable interface, which has Equals and HashCode methods. That is, the user must set methods of calculating hash codes by himself, and this is the only option so far, because it's impossible to implement these methods automatically, as it is done in .Net, for example, by means of MQL5.

 
This isthe only way to make the terminal calculate the hash codes automatically, for example by means of .Net in MQL5:

For Generic collections to work correctly with class objects, these classes must implement IEqualityComparable interface, in which the Equals and HashCode methods are defined. That is, the user must set methods of calculating hash codes by himself, and this is the only option so far, because it's impossible to implement these methods automatically, as it was done in .Net, for example, by means of MQL5.

Roman, you forgot to mention that in MQL5 has no interfaces. Any discussion of interfaces in MQL5 today is a malicious insinuation and demagogy.

p.s. But even if interfaces in MQL5 had appeared, the question with structures and enumerations would have remained unsolved.

 
Vasiliy Sokolov:

Roman, you forgot to mention that in MQL5 has no interfaces. Any discussion of interfaces in MQL5 today is a malicious insinuation and demagogy.

p.s. But even if interfaces in MQL5 had appeared, the question with structures and enumerations would have remained unsolved.

In MQL5 at the moment it's not possible to write template methods that will work for classes, structures and enumerations at the same time, due to peculiarities of data transfer.
 
Roman Konopelko:
For now in MQL5 you can't in principle write template methods that will work for classes, structures and enumerations at the same time, due to the nature of data transfer.

That's what I'm talking about. But the MQL5 environment knows everything about its objects! It has pointers to objects and knows all of the enum identifiers (EnumToString). That's why we need GetHashCode as a system and omnivorous function.

Also, finally allow multiple interface inheritance. Rewrite Generic for normal interfaces and you will be fine.

 

The situation is obvious: MQ developers have been so often burned by multiple inheritance in C++ that they are now afraid of any manifestation of it. As a result, they suggest avoiding one crap (multiple inheritance) by using another crap: ridiculous inheritance chains.

You should realize that interfaces have nothing to do with inheritance. An interface is a declaration that a class commits to provide a given functionality. If two classes implement the same functionality, they should not inherit from each other. Inheritance = evil.

 

Forum on trading, automated trading systems and trading strategies testing

Generic classes library - errors, description, questions, peculiarities of use and suggestions

Roman Konopelko, 2017.12.18 16:29

1) Volume increment(capacity) coefficient is not equal to 1.2, CPrimeGenerator::ExpandPrime method is used to calculate new volume in CHashMap:

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);
  }

In this method, the old collection size is multiplied by two, then for the resulting value we find the nearest simple number from the top and return it as the new volume.

About the initial value of capacity - I agree, it's very small.

But on the other hand there are always constructors, where you can explicitly specify the initial volume:

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

So I don't see much sense to change anything here.


Yes, I was wrong, I repent.
Indeed, coefficient of increase of volume (capacity) for CHashMap is more than 2.
Thanks for pointing out the error and apologize for wasting time.



On the other hand, I managed to take time to study the CPrimeGenerator implementation.

//+------------------------------------------------------------------+
//| 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);
  }


And there are a few suggestions, mostly to improve performance.


1. Eliminate ambiguous behavior:
If you pass "INT_MAX - 10" as a parameter into CPrimeGenerator::ExpandPrime, the result "INT_MAX" will be returned.
If "INT_MAX - 10" is passed as a parameter in CPrimeGenerator::GetPrime, the same result will be returned: "INT_MAX - 10".

Also, in both cases, the returned value is not a prime number which misleads the user.



2. When callingGetPrime for numbers greater than7199369, saving memory becomes a priority, but this does not justify the relative poor performance and useless calculations.

Suggestion:
- add a comparison of a number with the last value of the arrayCPrimeGenerator::s_primes[] and not perform an unnecessary enumeration of all 72 elements of the array.
- Replace dynamic search for simple number (all numbers in a row are searched) by array of predefined values likeCPrimeGenerator::s_primes[], but with linear, not quadratic, increment.
The increment of values will be about 1 million (the figure is similar to the increment of s_primes on the last elements of the array).
Number of elements up to 3000, values in the range from 8M to INT_MAX.
The array will be searched through an upper bound binary search, the number of iterations required is 12.


3. When you callGetPrime for numbers less than7199369, the worst case is a linear search of all the 72 values of the arrayCPrimeGenerator::s_primes[].

Suggestion:
- reduce the number of elements in the array to 70. (by deleting the first two, or the first and the last one):

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
  };

- if input value is less than or equal to 6th value in newCPrimeGenerator::s_primes array - then iterate numbers linearly (up to 6 comparisons).
- Otherwise use upper bound binary search between 7th and 70th values of array (about 6 iterations).

The idea is to use linear search only as long as there is no performance loss compared to binary search.
The suggested number of elements - 6 is used for example, in reality it all depends on specific implementation of upper bound binary search.
The overall performance gain due to low call intensity of a particular function may not be so profitable as to make any work to improve this functionality.

Reason: