Ir para conteúdo

POWERED BY:

Arquivado

Este tópico foi arquivado e está fechado para novas respostas.

Daniel Ribeiro

[DESAFIO] Caixa Eletrônico

Recommended Posts

Mas o código do Bruno não funciona direito (ou eu fiz algo errado).

$ATM = new ATM( 6 );

$ATM -> getDistribution( TRUE );

 

Saída:

 

Withdrawal Request: 6 in BRL

 

ATM has delivered 5 bills of 5 Reais

 

Sorry, but your withdrawal was not entirely perfect. We could not deliver 1 Real

 

Thank you for use our service!

 

Veja que é possível sacar 6 reais com as notas disponíveis ali. (2,5,10,20,50,100), retirando 3 notas de 2 reais. Pelo que a mensagem está dizendo, você retirou 5 notas de 5 reais para sacar 6 reais... é isto mesmo ou a mensagem está errada? Se for isto mesmo, eu to torcendo pra você fazer o sistema do caixa eletrônico do meu banco... :D

 

Até agora o único código que funcionou para qualquer combinação de notas foi o meu monstrinho!!!! O código do Evandro retorna bem certinho a quantidade de notas, mas depende do usuário. Se colocar um valor que não seja possível ele não sai do loop. Falta ver o do Daniel e o do João (que disse que já terminou, mas vai esperar um pouco pra postar pra não estragar a brincadeira... :P)

 

Carlos Eduardo

 

Realmente, não tinha testado para este caso.

 

O meu código está pronto, mas está em casa.

 

Saindo do trabalho colocarei o código aqui.

 

OBS: Como assim "o código do João vai estragar a brincadeira"? Não entendi... <_<

Compartilhar este post


Link para o post
Compartilhar em outros sites

OBS: Como assim "o código do João vai estragar a brincadeira"? Não entendi... <_<

 

O "estragar a brincadeira" é frase minha, na tentativa de brincar com o fato que os códigos do João normalmente são abstratos, completos, complexos e grandes... Conhecendo o João do jeito que eu conheço e pelo que ele me falou que implementou (beeeeeemmmmmm mais que o desafio), tenho a impressão que vai colocar o meu monstrinho de volta na jaula dele :D. O seu, pelo que pude entender, deve implementar o que o desafio se propõe (nada a mais), estando preparado para realizar novas tarefas, mas ainda não implementadas.

 

Não era a minha intenção desmerecer ninguém, era só uma forma de fazer uma brincadeira.

 

Carlos Eduardo

Compartilhar este post


Link para o post
Compartilhar em outros sites

Pensando justamente nessa interação da ATM com aqueles USB's teimosos, ajustei meu código para que caso a transação seja efetuada de forma imperfeita, talvez pelo valor requisitado ser menor do que o mínimo e também, caso algumas notas estejam faltando (não apenas as menores), quando a diferença na entrega pode ser maior que apenas um real, o próprio ATM instrua o usuário sobre o que fazer.

 

Sem contar um pequeno bug brutal na contagem distribuição de distribuição que ocorria com valores baixos.

 

 

<?php 

class ATM {

   /**
    * Locale Constants
    */

   // General Messages

   const NO_WITHDRAWAL = 'Please, enter the withdrawal amount!';
   const UNSUFICIENT   = "Sorry! At momment this ATM doesn't have sufficient funds to complete your request.";
   const BYE           = 'Thank you for use our service!';
   const NEXT_ATM      = 'Your withdrawal is too low for this ATM. Please use another ATM instead or contact the Bank Manager.';

   // Don't change the placeholders

   const INIT          = 'Withdrawal Request: %d in %s<br /><br />';
   const MISSING_ONE   = '<br />Sorry, but your withdrawal was not entirely perfect.<br />We could not deliver 1 %s because this bill is not available in this ATM.<br />';
   const MISSING_MORE  = 'Sorry, but we could not deliver %d %s of your Withdrawal Request<br /><br />Please check if this ATM works with all the bills you are expecting to receive or contact Bank Manager<br />';
   const DELIVERED     = 'ATM has delivered %d bills of %d %s<br />';

   // Currency

   const CURRENCY                 = 'BRL';
   const CURRENCY_TEXT_SINGULAR   = 'Real';
   const CURRENCY_TEXT_PLURAL     = 'Reais';

   /**
    * Available bills
    * 
    * @var	array	$bills
    */
   private $bills = array( 2, 5, 10, 20, 50, 100 );

   /**
    * Available money in ATM
    * 
    * @var	integer	$availability
    */
   private $availability = 10000;

   /**
    * Withdrawal Amount
    * 
    * @var	integer	$withdrawal
    */
   private $withdrawal;

   /**
    * Distribution Results
    * 
    * @var	array	$distribution
    */
   private $distribution = array();

   /**
    * Is a perfect withdrawal?
    * 
    * @var	integer	notDelivered
    */
   private $notDelivered = 0;

   /**
    * ATM Constructor
    * 
    * @param	mixed|integer	$withdrawal
    */
   public function __construct( $withdrawal ) {

       $this -> withdrawal = (integer) $withdrawal;

       $this -> distribution = array_combine( $this -> bills, array_fill( 0, count( $this -> bills ), 0 ) );

       // Proccessing...

       $this -> proccessWithdrawal();
   }

   /**
    * Get Request Withdrawal
    * 
    * @return	integer
    */
   public function getWithdrawal() {
       return $this -> withdrawal;
   }

   /**
    * Get bills distribution
    * 
    * @param	mixed|boolean	$fancy	If TRUE, displays the results more descriptively way
    * 									If FALSE, returns the Distribution Array
    * 
    * @return	array|string
    */
   public function getDistribution( $fancy = FALSE ) {

       if( (bool) $fancy ) {

           printf( self::INIT, $this -> withdrawal, self::CURRENCY );

           // Damn! I hope someday Closures work properly in objects' context :(

           $message   = self::DELIVERED;
           $singular  = self::CURRENCY_TEXT_SINGULAR;
           $plural    = self::CURRENCY_TEXT_PLURAL;

           array_walk(

               $this -> distribution,

               function( $amount, $value ) use( $message, $singular, $plural ) {

                   if( $amount != 0 ) {

                       $text = ( $value == 1 ? $singular : $plural );

                       printf( $message, $amount, $value, $text );
                   }
               }
           );

           // Additional message if the amount was not perfect

           if( $this -> notDelivered != 0 ) {

               if( $this -> notDelivered <= 1 ) {

                   printf( self::MISSING_ONE, self::CURRENCY_TEXT_SINGULAR );

               } else {

                   printf( self::MISSING_MORE, $this -> notDelivered, self::CURRENCY_TEXT_PLURAL );
               }
           }

           // Saying Goodbye :)

           printf( '<br />%s', self::BYE );

           return;
       }

       return $this -> distribution;
   }

   /**
    * Proccess the Withdrawal
    * 
    * @return	array
    */
   private function proccessWithdrawal() {

       // No withdrawal request

       if( $this -> withdrawal == 0 ) {
           throw new ATMException( self::NO_WITHDRAWAL );
       }

       // Insuficient Funds in ATM

       if( $this -> withdrawal > $this -> availability ) {
           throw new ATMException( self::UNSUFICIENT );
       }

       // Withdrawal request is too low

       if( $this -> withdrawal < min( $this -> bills ) ) {
           throw new ATMException( self::NEXT_ATM );
       }

       // Perfect Match

       if( in_array( $this -> withdrawal, $this -> bills ) ) {

           $this -> distribution[ $this -> withdrawal ] = 1;

           return;
       }

       // Duplicate Withdrawal Value

       $withdrawal = $this -> withdrawal;

       // Starting from the most valueable bill

       arsort( $this -> bills );

       $next = TRUE;

       while( $next ) {

           // Getting the current bill

           $currentBill = current( $this -> bills );

           // Computing number of bills needed

           $amount = floor( $withdrawal / $currentBill );

           if( $amount > 0 ) {

               // Subtracting it from withdrawal request

               $times = $amount * $currentBill;

               $withdrawal -= $times;

               // Recording Information

               $this -> distribution[ $currentBill ] += $amount;
           }

           if( next( $this -> bills ) === FALSE && $this -> withdrawal > 0 ) $next = FALSE;
       }

       // Is it a perfect transaction?

       if( $withdrawal != 0 ) {

           $this -> notDelivered = $withdrawal;
       }

       // Sorting results

       krsort( $this -> distribution );
   }
}

class ATMException extends RuntimeException {}

 

Compartilhar este post


Link para o post
Compartilhar em outros sites

OBS: Como assim "o código do João vai estragar a brincadeira"? Não entendi... <_<

 

O "estragar a brincadeira" é frase minha, na tentativa de brincar com o fato que os códigos do João normalmente são abstratos, completos, complexos e grandes... Conhecendo o João do jeito que eu conheço e pelo que ele me falou que implementou (beeeeeemmmmmm mais que o desafio), tenho a impressão que vai colocar o meu monstrinho de volta na jaula dele :D. O seu, pelo que pude entender, deve implementar o que o desafio se propõe (nada a mais), estando preparado para realizar novas tarefas, mas ainda não implementadas.

 

Não era a minha intenção desmerecer ninguém, era só uma forma de fazer uma brincadeira.

 

Carlos Eduardo

 

Com relação ao meu código, exatamente. Ele resolve o problema proposto e está pronto para se expandir de acordo com a demanda.

 

Acho que o tópico propõe soluções rápidas, simples e objetivas, saca?

 

Sobre o código do João, conversei com ele. Acho que se ele tem uma solução melhor, ele deveria postar logo pra compartilhar o conhecimento e dar a oportunidade àqueles que tiveram dificuldades tirarem suas dúvidas sobre o processo. Mas ele prefere postar o código depois do término do desafio, então temos que respeitar a vontade dele.

Compartilhar este post


Link para o post
Compartilhar em outros sites

O problema da contagem da nota foi corrigido, mas o problema do saque continua.

$ATM = new ATM( 6 );

$ATM -> getDistribution( TRUE );

Saída

 

Withdrawal Request: 6 in BRL

 

ATM has delivered 1 bills of 5 Reais

 

Sorry, but your withdrawal was not entirely perfect.

We could not deliver 1 Real because this bill is not available in this ATM.

 

Thank you for use our service!

 

Na verdade o caixa deveria sacar 3 notas de 2 reais, já que o objetivo do desafio é exibir o menor número de notas necessárias para efetuar o saque com as notas disponíveis.

 

Continua só o meu monstrinho atendendo o desafio... :P

 

Carlos Eduardo

Compartilhar este post


Link para o post
Compartilhar em outros sites

Cacetada, esse fórum tá doido.

 

Quando eu respondi com meu update que além de maximizar essa interação ATM-Usuário corrigia esse bug brutal que o Matias apontou não haviam as mensagens #20, #21 e #22, estando apenas a #19 em que o Daniel analisa a situação e a minha.

 

O bug está corrigido e funciona para qualquer distribuição. Mas essa do fórum foi bizarra. :lol:

 

Quanto a comparação do código do Matias com o meu, bem, só posso dizer que eu não soube mesmo ^_^

 

Na bem da verdade, nem sei se um ATM de verdade faz esse tipo de distinção, para sempre retornar o menor de notas e tal...

Compartilhar este post


Link para o post
Compartilhar em outros sites

Cacetada, esse fórum tá doido.

 

Quando eu respondi com meu update que além de maximizar essa interação ATM-Usuário corrigia esse bug brutal que o Matias apontou não haviam as mensagens #20, #21 e #22, estando apenas a #19 em que o Daniel analisa a situação e a minha.

 

O bug está corrigido e funciona para qualquer distribuição. Mas essa do fórum foi bizarra. :lol:

 

Quanto a comparação do código do Matias com o meu, bem, só posso dizer que eu não soube mesmo ^_^

 

Na bem da verdade, nem sei se um ATM de verdade faz esse tipo de distinção, para sempre retornar o menor de notas e tal...

 

Sim, o processo é adotado como padrão, até para economia de cédulas.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Bruno, qual é o código correto então? Porque o último que você postou continua aparecendo o erro na distribuição das notas. A contagem está certa (1 nota de 5 reais), mas continua dizendo que não dá pra sacar R$ 6,00, sobrando R$ 1,00.

 

Posta o código certo, como executar e a saída (para R$ 6,00), pra não dar problema.

 

Carlos Eduardo

Compartilhar este post


Link para o post
Compartilhar em outros sites

É esse o código. Essa mensagem é definida apenas na exibição mais descritiva e quando NÃO EXISTE a nota de um real.

 

Melhorando o que eu falei, eu não assim tão ninja :lol:

 

[EDIT]

 

Agora que fui testar o "seu monstrinho". Peuei alguns valores gerados aleatóriamente pelo seu para comparar com o meu e, a principio, funciona como o meu.

 

O meu faz até um pouco a mais. O meu ATM até entrega o máximo de notas possível.

 

Um exemplo foi no seu 913 reais com notas de 10, 50 e 100. O seu abortou, o meu entregou nove de 100 e uma de 10, pedindo desculpa por não ter entregado 3.

Compartilhar este post


Link para o post
Compartilhar em outros sites

É esse o código. Essa mensagem é definida apenas na exibição mais descritiva e quando NÃO EXISTE a nota de um real.

 

Melhorando o que eu falei, eu não assim tão ninja :lol:

 

Justamente Bruno.

 

O problema é que, e aí irás entender o meu comentário anterior, você não pode dizer para o usuário que é impossível conceder o valor de 6 reais.

 

A quantidade mínima de notas seria uma de 5 e uma de 1. Se com essa combinação o valor não pode ser concedido, pule para a próxima combinação possível, evidentemente com mais notas: 3 notas de 2.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Eu ainda acho que na relação performance-resultado, não entregar um valor baixo é melhor do que tentar novamente.

 

Claro, num ATM d verdade isso é ínfimo, mas com PHP já vale se considerar.

 

Eu tentei, mas acho que por não ter entendido direito a proposta, ficou simples assim. Estou na curiosidade sobre a solução final. De repente eu até sei fazer, mas não sei que sei. ^_^

Compartilhar este post


Link para o post
Compartilhar em outros sites

Eu ainda acho que na relação performance-resultado, não entregar um valor baixo é melhor do que tentar novamente.

 

Claro, num ATM d verdade isso é ínfimo, mas com PHP já vale se considerar.

 

Eu tentei, mas acho que por não ter entendido direito a proposta, ficou simples assim. Estou na curiosidade sobre a solução final. De repente eu até sei fazer, mas não sei que sei. ^_^

 

Bruno,

 

eu sugiro que você tente implementar a lógica de chain a que me referi no post anterior.

 

O caso de um valor de 6 é perfeito para demonstrar essa lógica:

 

6 = 5 + 1

6 = 2 + 2 + 2

6 = 1 + 1 + 1 + 1 + 1 + 1

A partir disso você consegue detectar e manipular quaisquer outras situações reais.

 

OBS: O pattern chain of responsibility simula exatamente o processo de um ATM.

 

chainofresponsibilityex.png

 

É possível de se implementar uma estrutura interessante para simular a operação de um ATM utilizando o pattern chain of responsibility.

 

Apesar da minha solução estar focada no problema, no desafio, e ser bastante simples e objetiva, vou postar, mais tarde, essa implementação mais real e abstrata a qual me refiro.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Infelizmente, não manjo muito de padrões de projeto.

 

Sempre que me entusiasmo com o assunto o suficiente para aprender, ou o assunto acaba ou esfria e sou obrigado a buscar informações estrangeiras que nem sempre são lá muito explicativas, não se baseiam na aplicação dos mesmos com PHP e/ou usam de casos tão malucos e inúteis que a explicação focada fica difícil para alguém que não entende aplicar na vida real, em aplicações reais.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Infelizmente, não manjo muito de padrões de projeto.

 

Sempre que me entusiasmo com o assunto o suficiente para aprender, ou o assunto acaba ou esfria e sou obrigado a buscar informações estrangeiras que nem sempre são lá muito explicativas, não se baseiam na aplicação dos mesmos com PHP e/ou usam de casos tão malucos e inúteis que a explicação focada fica difícil para alguém que não entende aplicar na vida real, em aplicações reais.

 

Tudo bem... o tópico abriu a brecha para falarmos do pattern chain, mas não somos obrigados a utilizá-lo a partir de agora.

 

Faça o seguinte: implemente a resolução do último problema que seu código apresentou e vamos em frente com o desafio.

Compartilhar este post


Link para o post
Compartilhar em outros sites

O pattern chain of responsibility simula exatamente o processo de um ATM.

 

Eu não usaria Chain of Responsibility nesse caso, usaria Command e o motivo é bem simples :seta: Uma transação pode não ser bem sucedida.

 

Caso um determinado comando (ou uma lista de comandos) não seja bem sucedido, a transação precisa retornar ao seu estado anterior, nesse caso um rollback precisa ser feito para cancelar todo o processo.

 

Outro ponto é o conhecimento das notas que podem existir em um caixa eletrônico, não existe nota de R$ 1.5 ou nota de R$ 57, a base de Chain of Responsibility é o desconhecimento do objeto que tratará uma requisição enquanto um Command é enviado para um objeto específico.

 

Um outro padrão para ser utilizado é Visitor:

 

Um caixa eletrônico possui vários cofres, um para cada nota. O Visitor visitaria cada um desses cofres e executaria um comando "contar notas", o sucesso desse comando mudaria o estado do Visitor ou, caso não haja notas suficientes naquele cofre específico, o Visitor pode ter seu estado revertido para uma situação anterior.

Compartilhar este post


Link para o post
Compartilhar em outros sites
Eu não usaria Chain of Responsibility nesse caso, usaria Command e o motivo é bem simples :seta: Uma transação pode não ser bem sucedida.

 

Na realidade, João, meu pensamento estava sob a hipótese do Chain of Responsibility utilizar o Command para representar as requisições como objetos "palpáveis".

 

A base do Chain of Responsibility, a nível conceitual, também pode ser pensada de forma que cada objeto da corrente irá contribuir da sua maneira para responder à requisição em um todo, o que é exatamente a realidade de um ATM.

 

Em um nível abstrato, existe sim um Transaction que irá manipular operações como Withdrawal e Deposit, e para este caso o Command seria bastante pertinente.

 

No nível interno da operação, porém, pode-se pensar que cada nota mantém a referência para a próxima nota na corrente, assim a separação do dinheiro em espécie será sempre bem sucedida, enquanto o Transaction, outra operação totalmente diferente, pode não ser.

 

Em CoR, a implementação conhece a "cabeça" da corrente, ou a entrada. Vejo necessária a utilização deste padrão específico para o processo porque, à medida que cada nota da corrente mantém referência para a próxima, pode-se ter uma flexibilidade para essa cabeça, podendo sempre ser alterada de acordo com a demanda. Para exemplificar, quando a nota de 100 não estivesse mais disponível, a nota mais alta depois dela se tornaria a cabeça da corrente, neste caso a nota de 50.

 

Veja que quem executa o rollback é o Transaction, repetindo todo o processo de separação de dinheiro.

 

OBS: Agora não tem jeito, posta sua solução aí! Hehehehe...^_^

Compartilhar este post


Link para o post
Compartilhar em outros sites
Posta a sua solução também Daniel. To esperando pra ver.

 

Carlos Eduardo

Com certeza, Matias.

 

Como havíamos comentado anteriormente, a minha solução é simples e objetiva, focada apenas no problema.

 

Essa implementação abstrada sobre a qual estamos discutindo ainda será feita e postada, porém mais à frente.

 

Só estou esperando para ver a resolução dos problemas nos códigos do Bruno.

 

O seu já está 100%, certo?

Compartilhar este post


Link para o post
Compartilhar em outros sites

O meu apesar de não lidar com essa questão de voltar atrás e tentar de novo, também é final, não vou mexer mais nele, afinal ele segue o conceito que eu tenho de um Caixa Eletrônico, não tenta fazer coisas mirabolantes sem ao menos poder.

Compartilhar este post


Link para o post
Compartilhar em outros sites

×

Informação importante

Ao usar o fórum, você concorda com nossos Termos e condições.