Biblioteca de classes genéricas - bugs, descrição, perguntas, recursos de uso e sugestões - página 19

 
Vasiliy Sokolov:
você pode escrever sua própria função de especialização para a classe, afinal de contas.
 
Combinador:
Afinal, você pode escrever a sua própria especialização de uma função para uma classe.

Você não pode fazer isso sem uma interface.

 
Vasiliy Sokolov:

Não o podes fazer sem uma interface.

Não se pode fazer o quê sem uma interface? )
 
Combinador:
O que você não pode fazer sem uma interface? )

Bem, pensem nisso. De qualquer forma, não desarrume o fio, por favor.

 
Vasiliy Sokolov:

Problema

Obviamente, o GetHashCode não pode ser implementado corretamente no ambiente do usuário MQL5. Isto é porque nem todos os objectos podem ser acedidos. E se a implementação funciona bem para membros primitivos como int duplo, etc., para objetos complexos (classes, estruturas e até enumerações) o hash é calculado pelo nome. Se tivermos milhares de CObjects ou mesmo ENUM_something_that, então CHashMap e CHashSet irão degenerar em LinkedList:

Não podemos evitar isto porque tudo o que temos ao nível do utilizador é o nome do objecto:

Assim, os hashes de todos os objetos de tipos complexos são iguais entre si e este código de busca aqui envolve uma enumeração completa da matriz:

Na verdade, isto é tão crítico que se sobrepõe a todo o ponto de usar o genérico na raiz. A questão é que na maioria dos casos você precisa associar ou armazenar objetos complexos: enumerações, estruturas ou classes. Se você precisasse operar com tipos simples, você poderia fazer com algo mais simples.

Para que coleções genéricas funcionem corretamente com objetos de classe, estas classes devem implementar a interface IEqualityComparable, onde os métodos Equals e HashCode são definidos. Isso significa que o usuário deve definir métodos de cálculo de códigos hash por ele mesmo, e essa é a única opção até agora, pois é impossível implementar esses métodos automaticamente, como é feito em .Net, por exemplo, por meio de MQL5.

 
Roman Konopelko:

Para que coleções genéricas funcionem corretamente com objetos de classe, estas classes devem implementar a interface IEqualityComparable, na qual são definidos métodos Equals e HashCode. Isso significa que o usuário tem que definir métodos de cálculo de códigos hash e essa é a única opção até agora, pois é impossível implementar esses métodos automaticamente, como foi feito em .Net, por exemplo, por meio de MQL5.

Roman, esqueceste-te de mencionar que em A MQL5 não tem interfaces. Este artigo irá introduzir o conceito de interfaces na MQL5.

p.s. Mas mesmo que aparecessem interfaces na MQL5, a questão das estruturas e enumerações permaneceria por resolver.

 
Vasiliy Sokolov:

Roman, esqueceste-te de mencionar que em A MQL5 não tem interfaces. Qualquer discussão sobre interfaces na MQL5 de hoje é uma insinuação maliciosa e demagogia.

p.s. Mas mesmo que tivessem surgido interfaces na MQL5, a questão das estruturas e enumerações teria ficado por resolver.

Na MQL5 no momento não é possível escrever métodos de modelos que funcionarão para classes, estruturas e enumerações ao mesmo tempo, devido às peculiaridades da transferência de dados.
 
Roman Konopelko:
Por enquanto na MQL5 não é possível, em princípio, escrever métodos de modelos que funcionariam para classes, estruturas e enumerações ao mesmo tempo, devido às peculiaridades da transferência de dados.

É disso que estou a falar. Mas o ambiente MQL5 sabe tudo sobre seus objetos! Ele tem apontadores para objetos e conhece todos os identificadores de enums (EnumToString). É por isso que precisamos do GetHashCode como um sistema e função omnívora.

E, finalmente, permitir a herança de múltiplas interfaces. Rewrite Generic para interfaces normais e você terá um ponto doce.

 

A situação é óbvia: os desenvolvedores de MQ eram tão frequentemente queimados por herança múltipla em C++ que agora temem qualquer manifestação dela. Como resultado, é proposto evitar uma porcaria (herança múltipla) usando outra porcaria: cadeias de herança ridículas.

Você tem que entender que as interfaces não têm nada a ver com herança. Uma interface é uma declaração de que uma classe se compromete a fornecer uma determinada funcionalidade. Se duas classes implementam a mesma funcionalidade, elas não devem herdar uma da outra. Herança = maldade.

 

Fórum sobre negociação, sistemas de negociação automatizados e testes de estratégia de negociação

Biblioteca de classes genéricas - bugs, descrição, perguntas, peculiaridades de uso e sugestões

Roman Konopelko, 2017.12.18 16:29

1) O factor de crescimento do volume (capacidade) não é igual a 1.2, CPrimeGenerator::O método ExpandPrime é utilizado para calcular o novo volume no 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);
  }

Neste método, o tamanho da colecção antiga é multiplicado por dois, depois para o valor resultante encontramos o mais próximo do número primo superior e devolvemo-lo como o novo volume.

Sobre o valor inicial da capacidade - concordo, é muito pequena.

Mas, por outro lado, há sempre construtores, onde se pode especificar explicitamente a capacidade inicial:

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

Por isso não vejo muito sentido mudar algo aqui.


Sim, eu estava errado, eu arrependo-me.
Realmente, o fator de incremento de volume (capacidade) para CHashMap é mais de 2.
Obrigado por apontar o erro e pedir desculpas por desperdiçar tempo.



Por outro lado, eu consegui dedicar tempo para estudar a implementação do 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);
  }


E há algumas sugestões, principalmente para melhorar o desempenho.


1. Eliminar comportamentos ambíguos:
Se passarmos "INT_MAX - 10" como parâmetro ao CPrimeGenerator::ExpandPrime, o resultado "INT_MAX" será devolvido.
Se passarmos "INT_MAX - 10" como parâmetro ao CPrimeGenerator::GetPrime, o mesmo resultado será devolvido: "INT_MAX - 10".

Além disso, em ambos os casos, o valor retornado não é um número primo que induza o usuário em erro.



2. Ao ligar para oGetPrime para números superiores a7199369, a poupança de memória torna-se uma prioridade, mas isso não justifica o relativo mau desempenho e cálculos inúteis.

Sugestão:
- adicionar uma comparação de número com o último valor do arrayCPrimeGenerator::s_primes[] e não realizar enumeração desnecessária de todos os 72 elementos do array.
- Substitua a busca dinâmica por números simples (passa por todos os números em uma linha) por um array de valores pré-definidos comoCPrimeGenerator::s_primes[], mas com incremento linear, não quadrático.
O incremento de valores será de cerca de 1 milhão (um valor semelhante ao incremento de s_primes nos últimos elementos do array).
O número de elementos é de até 3000, os valores variam de 8M a INT_MAX.
A matriz será pesquisada através de uma pesquisa binária de limite superior, o número de iterações necessárias é 12.


3. Ao chamarGetPrime para números inferiores a7199369, o pior caso é uma busca linear de todos os 72 valores do arrayCPrimeGenerator::s_primes[].

A sugestão é:
- reduzir o número de elementos da matriz para 70. (apagando os dois primeiros, ou o primeiro e o último):

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

- se o valor de entrada for menor ou igual ao 6º valor no novoCPrimeGenerator::s_primes array - então itere os números linearmente (até 6 comparações).
- Caso contrário, use a pesquisa binária de limite superior entre o valor do 7° e 70° array (cerca de 6 iterações).

A idéia é usar a busca linear apenas enquanto não houver perda de desempenho em comparação com a busca binária.
O número sugerido de elementos - 6 é usado como exemplo, na realidade tudo depende da implementação concreta da pesquisa binária de limite superior.
O ganho de desempenho geral devido à baixa intensidade de chamada de uma determinada função pode não ser tão benéfico a ponto de fazer qualquer trabalho para melhorar esta funcionalidade.