| 4. Comentário Técnico |
. SportAgent e classes derivadas
A classe SportAgent serve de suporte às quatro classes que irão conter os dados sobre
jogadores (nacionais e internacionais), treinadores, dirigentes e árbitros.
Esta classe inclui duas funções virtuais puras: Clone() para devolver um apontador
para um SportAgent (onde existirá uma cópia do objecto que executou a função - logo do
mesmo tipo); uma função Type() que retorna uma StringBasic correspondente ao tipo de objecto
(jogador, árbitro, etc).
A classe Player define um jogador. Optamos por incluir nesta classe tanto jogadores
nacionais como internacionais visto ser mais lógico e apenas se desperdiçarem 4+1 bytes
(de um apontador para char* na StringBasic correspondente ao país) ao lidar com jogadores
nacionais. Uma outra opção seria separar ambas as classes, criando por exemplo um
Player sem país e depois derivar desta classe um PlayerInternational, mas esta organização
seria menos lógica e de maior complexidade.
Sendo assim para distinguir jogadores nacionais de internacionais utiliza-se a função
IsNational(). A posição do jogador é definida por um enumerado e criaram-se também
duas funções static para converter de uma StringBasic para a posição e vice-versa.
As classes Manager, Director e Refree não trazem nada de novo em relacção à classe Player
visto serem muito semelhantes, distinguindo-se apenas pelo facto de não terem tantos
membros.
. SportAgentsVectorPolymorphic
Esta classe será talvez a mais complexa de todo o programa.
Tratando-se de um array (através de um apontador incial - data member) de
apontadores para SportAgents permite armazenar qualquer tipo de objecto derivado
da classe SportAgent. Contudo apesar da complexidade aderente a qualquer vector polimórfico
exitem apenas dois pontos a destacar nesta classe:
O operador [] utiliza o overloading para vectores const e não const
devolvendo assim um objecto constante ou não, o que nos irá permitir alterar informações
directamente nesse objecto.
Para além disto utilizamos também um método de leitura e escrita para objectos deste tipo
ao qual se chama Prototypical Read/Write, o que não é mais que um método que está preparado
para ler e escrever objectos de determinadas classes derivadas de SportAgent. Contudo
temos que conhecer estas classes e alterar estas funções se pretendermos potencia-las
com o suporte de um novo tipo de classe derivada de SportAgent.
De resto será de mencionar que utilizando um método semelhante a este (apontadores para
apontadores do tipo a armazenar) seria possivel obter uma maior efeciência
em qualquer vector através de um
processo de Growing que não eleminasse objectos mas sim os mantivesse e para
acrescentar um novo (quando a capacidade tivesse sido atingida) se limitasse a copiar
os valores dos apontadores utilizados, refazendo o array onde se encontravam
(através do operador new). Desta forma poupar-se-ia o tempo de copiar objectos
inteiros, contudo este não foi o método utilizado visto não ter sido adoptado pelo
Professor Pedro Guerreiro em classes do mesmo caracter (mesmo assim penso que não traria
problemas).
. StringSearchable
Esta classe é como que o núcleo da performance do programa. Qualquer alteração irá afectar
a preformance durante o processamento dos ficheiros...
Na secção 5. Limites explicamos porque utilizamos este
sistema de busca (incluindo o processo de leitura dos ficheiros a partir da função
Process() da classe OurSelection) e não um sistema utilizando classes fornecidas pelo
Professor (como por exemplo o Tokenizer).
Para já será importante descrever as 3 fases de desenvolvimento desta classe
(em especial da função Search() visto a função SearchAndReplace() se basear grandemente
na primeira).
Numa primeira fase o processo de busca era extremamente lento, por exemplo para um ficheiro
de 400 Kb que era lido em 5 ou 6 segundos (novamente uma explicação na secção seguinte sobre
este facto e como poderiamos optimiza-lo) a busca de uma simples String era quase interminável:
|
From StringSearchable VERSÃO 1.0 |
|
int StringSearchable::Search(const
StringBasic& other, bool ignoreCase) const // Please note that function IndexOf(const StringBasic& other, int start) // was note used due to it's unability to deal with the ignore case option { int count = 0; int j = 0; // Travels other's chars, used in while latter // Using Count() all over is what makes this function soo slow // (optimizing StringBasic could solve this) for(int i = 0; i < this->Count(); i++, j = 0) // I had programmed this without reseting j here and it worked fine // and probably faster, but the code was rather hard to read so... :-) while((*this)[i] == other[j] && j < other.Count()) { j++, i++; // get next chars, only if while acts if (j == other.Count()) count ++; // *this has all of other } return count; } |
Ora como o Count() de ambas as Strings se mantem constante durante todo o processo
é desnecessário estar sempre a calcula-lo. Portanto pode-se assim optimizar esta função
tal como a SearchAndReplace() que calcula (aritmeticamente, se usar a função Count())
quando o comprimento de alguma das Strings é alterado. Assim no mesmo ficheiro de 400Kb
a busca de uma só palavra tornou-se imediata (mantendo-se os 5/6 segundos de leitura do
ficheiro).
|
From StringSearchable VERSÃO 2.0 |
|
int StringSearchable::Search(const
StringBasic& other, bool ignoreCase) const // Please note that function IndexOf(const StringBasic& other, int start) // was note used due to it's unability to deal with the ignore case option { int count = 0; int j = 0; // Travels other's chars, used in while latter // Optimizing for large strings! (huge amount of time gained) int thisLen = this->Count(); int otherLen = other.Count(); for(int i = 0; i < thisLen; i++, j = 0) // I had programmed this without reseting j here and it worked fine // and probably faster, but the code was rather hard to read so... :-) while((*this)[i] == other[j] && j < otherLen) { j++, i++; // get next chars, only if while acts if (j == otherLen) count ++; // *this has all of other } return count; } |
Ora, como em sempre ainda foi possível proceder a mais uma optimização a quando
da introdução do suporte do ignoreCase, que, como se pode constatar, ainda
não tinha sido implementado nesta fase.
Dentro da expressão avaliada pelo while estamos constantemente a ler o
caracter j da other String mas grande parte das ocasiões j será 0 portanto
estamos a perder muito mais tempo do que é necessário. Podemos ler o caracter j
da String para um caracter declarado incialmente, e isto só quando j é alterado.
Assim obtivemos a classe StringSearchable actual que utiliza a função SmallLetter()
da classe Character.
|
From StringSearchable VERSÃO 3.2 |
|
int StringSearchable::Search(const
StringBasic& other, bool ignoreCase, int thisEND) const { int count = 0; int j = 0; char otherChar = ignoreCase ? Character::SmallLetter(other[j]) : other[j]; int thisLen = thisEND < 0 ? this->Count() : thisEND; int otherLen = other.Count(); for(int i = 0; i < thisLen; i++, j != 0 ? (j = 0, (ignoreCase ? Character::SmallLetter(other[j]) : other[j])) : 0) while(j < otherLen && (ignoreCase ? Character::SmallLetter((*this)[i]) : (*this)[i]) == otherChar) { j++, i++; if (j == otherLen) count ++; otherChar = ignoreCase ? Character::SmallLetter(other[j]) : other[j]; } return count; } |
Pois, mas apesar do esforço a perfomance, mesmo da 3ª versão, não se demonstrou
próximo sequer do pretendido... Essa foi a razão porque mesmo durante o desenvolvimento
deste relatório aproveitamos para voltar a alterar esta função. Utilizando a função
IndexOf (que usa a função strstr() da Standart Library, e aí está a razão pela qual ganhamos tanta
performance) e uma programação extremamente simples. Com o objectivo de optimizar a velocidade,
a opção de ignore case está separada
em duas funções diferentes (uma default sem ignoreCase, que já não é usado
visto usarmos um método mais rápido a partir da função Process() da classe OurSelection).
. OurSelection
A classe OurSelection utiliza (e controla) as funções da classe MenuDisplay para a
UserInterface. A classe MenuDisplay não tem nada a destacar excepto o facto de
tornar o suporte de outras linguagens bastante mais simples e implementável através
de um ficheiro de configuração.
As funções Run() e MainMenu() da classe OurSelection são bastante simples, sendo a primeira
como que uma lista sequêncial do que o programa tem a fazer e a segunda um controlo
do User Interface (após a primeira fase de leitura dos dados e processamento)
implementado pela classe MenuDisplay.
De destacar na função Run() é o suporte de erros como por exemplo a inexistência de
ficheiros, informando o utilizador do erro e abortando o programa quando algo corre mal.
Outro suporte de erros existe no operador >> do SportAgentsVectorPolymorphic
que ignora linhas com um formato diferente do esperado e quando não reconhece a posição
de um jogador é exactamente isso que indica quando o escreve!