Usamos cookies para medir audiência e melhorar sua experiência. Você pode aceitar ou recusar a qualquer momento. Veja sobre o iMasters.
Olá pessoal, como estão?
Indo diretamente ao assunto, proponho o seguinte desafio:
A implementação de um script que simule um Caixa Eletrônico (ATM). O objetivo é que o código receba um valor X requisitado e retorne a quantidade mínima de notas que o usuário requisitante deverá receber, considerando um vetor de notas disponíveis (não vamos nos preocupar com quantas notas de cada o caixa terá disponível).
Considere:
class ATM
{
/**
* Notas disponíveis no caixa eletrônico.
* @var int
*/
private $bills = array(2, 5, 10, 20, 50, 100);
/**
* Retorna a quantidade mínima de notas necessárias para a determinada
* quantia de dinheiro.
*
* @param int $amountOfCash
* @return int $minimalNumberOfBills
*/
static public function getMinimalNumberOfBills($amountOfCash) $minimalNumberOfBills = 0;
...
return $minimalNumberOfBills;
}
}@Matias Rezende
Show de bola Carlos! Parabéns, o código está funcionando perfeitamente!
OBS: A verificação que você faz para dizer ao usuário que o valor solicitado não pode ser alcançado, na verdade, deve ser feita antes da realização da lógica pesada. Assim, pode-se otimizar o algorítmo. A princípio, está funcionando. Depois vou dar uma olhada com mais calma.
Mais à frente vamos adicionar um problema a este desafio! Fique atento ao tópico!
Só por farra, aí vai uma outra solução:
<?php
$time = microtime( true );
class ATM
{
/**
* Notas disponíveis no caixa eletrônico.
* @var int
*/
// OBS: Adicionado a "nota" de 1 real para quantias ímpares
private static $bills = array( 1, 2, 5, 10, 20, 50, 100 );
/**
* Retorna a quantidade mínima de notas necessárias para a determinada
* quantia de dinheiro.
*
* @param int $amountOfCash
* @return int $minimalNumberOfBills
*/
static public function getMinimalNumberOfBills( $amountOfCash )
{
$minimalNumberOfBills = 0;
$arrIndex = count( static::$bills ) - 1; // nota de maior valor
while ( $amountOfCash > 0 )
{
if ( $amountOfCash >= static::$bills[ $arrIndex ] )
{
$minimalNumberOfBills += (int)( $amountOfCash / static::$bills[ $arrIndex ] );
$amountOfCash %= static::$bills[ $arrIndex ];
}
else
{
$arrIndex--;
}
}
return $minimalNumberOfBills;
}
}
echo '7: ' . ATM::getMinimalNumberOfBills( 7 ) . " notas\n";
echo '100: ' . ATM::getMinimalNumberOfBills( 100 ) . " notas\n";
echo '101: ' . ATM::getMinimalNumberOfBills( 101 ) . " notas\n";
echo '102: ' . ATM::getMinimalNumberOfBills( 102 ) . " notas\n";
echo '105: ' . ATM::getMinimalNumberOfBills( 105 ) . " notas\n";
echo '110: ' . ATM::getMinimalNumberOfBills( 110 ) . " notas\n";
echo '200: ' . ATM::getMinimalNumberOfBills( 200 ) . " notas\n";
echo '230: ' . ATM::getMinimalNumberOfBills( 230 ) . " notas\n";
var_dump( microtime( true ) - $time );
?>
Tornei $bills estático, já que o método é estático e precisa ter acesso a ele
Também adicionei a "nota" de 1 real. Mas outra solução seria só testar se o número é par
saída:
$ php atm.php
7: 2 notas
100: 1 notas
101: 2 notas
102: 2 notas
105: 2 notas
110: 2 notas
200: 2 notas
230: 4 notas
float(0.00010895729064941)
:thumbsup:
@Beraldo
Muito bom!
Gostei bastante do seu algorítmo, bem conciso e otimizado.
OBS: Sobre testar se o número é par, não entendi. O valor de 105 é ímpar e pode ser retirado do caixa eletrônico com duas (2) notas.
>
OBS: Sobre testar se o número é par, não entendi. O valor de 105 é ímpar e pode ser retirado do caixa eletrônico com duas (2) notas.
No meu código, sim, pois adicionei nota de um real
Seu exemplo no post inicial não tinha nota de um, ou seja, só retiraria valores pares.
;)
@Beraldo
É justamente disso que estou falando. O número 105, por exemplo, é ímpar e pode ser retirado tranquilamente pelo caixa eletrônico com uma nota de 100 e uma nota de 5.
E sem precisar da nota de 1.
Também são ímpares e podem ser retirados os valores 107, 109, 111...
Entendeu?
Galera, achei um problema no meu código. Tentando sacar R$ 6,00 ele dispara o erro. Outros valores também deve acontecer o mesmo.
A quantidade mínima de notas para sacar R$ 6,00 é de Não é possível realizar o saque, pois com as notas disponíveis neste caixa eletrônico não é possível chegar ao valor solicitado, restando o valor de R$ 1,00
Mais tarde eu reviso a lógica e posto aqui a correção.
Carlos Eduardo
>
Galera, achei um problema no meu código. Tentando sacar R$ 6,00 ele dispara o erro. Outros valores também deve acontecer o mesmo.
A quantidade mínima de notas para sacar R$ 6,00 é de Não é possível realizar o saque, pois com as notas disponíveis neste caixa eletrônico não é possível chegar ao valor solicitado, restando o valor de R$ 1,00
Mais tarde eu reviso a lógica e posto aqui a correção.
Carlos Eduardo
Interessante... ainda não analisei seu código, mas que bom que encontrou o problema.
Mais tarde, quando sair do trabalho, irei testar os dois códigos com mais calma, inclusive com essa situação.
>
@Beraldo
É justamente disso que estou falando. O número 105, por exemplo, é ímpar e pode ser retirado tranquilamente pelo caixa eletrônico com uma nota de 100 e uma nota de 5.
E sem precisar da nota de 1.
Também são ímpares e podem ser retirados os valores 107, 109, 111...
Entendeu?
é verdade. acho que eu estava com muito sono quando postei isso e não percebi :P
>
>
@Beraldo
É justamente disso que estou falando. O número 105, por exemplo, é ímpar e pode ser retirado tranquilamente pelo caixa eletrônico com uma nota de 100 e uma nota de 5.
E sem precisar da nota de 1.
Também são ímpares e podem ser retirados os valores 107, 109, 111...
Entendeu?
é verdade. acho que eu estava com muito sono quando postei isso e não percebi :P
Não se preocupe... eu imaginei algo do tipo. Hehehe...
E aí galera, alguém mais se habilita?
Lembrando que em breve vou dar uma apimentada no desafio!
>
No meu código, sim, pois adicionei nota de um real
Mas essa era a graça real do desafio.
>
Galera, achei um problema no meu código. Tentando sacar R$ 6,00 ele dispara o erro. Outros valores também deve acontecer o mesmo.
A quantidade mínima de notas para sacar R$ 6,00 é de Não é possível realizar o saque, pois com as notas disponíveis neste caixa eletrônico não é possível chegar ao valor solicitado, restando o valor de R$ 1,00
Mais tarde eu reviso a lógica e posto aqui a correção.
Carlos Eduardo
Na verdade não é bem um problema, pois foi previsto por você. A questão é que o código não cobre valores que poderiam ser pagos.
Vou facilitar, não é possível sacar apenas 1 e 3 R$. Qualquer outro valor é possível.
Segue a minha versão. A classe já está otimizada para uma futura implementação de verificação de cédulas disponíveis.
class ATM {
private $ballots = array(
2 => 0,
5 => 0,
10 => 0,
20 => 0,
50 => 0,
100 => 0
);
public function withdraw($amount){
$left = (integer)$amount;
if($left == 1 || $left == 3) return trigger_error('Impossível conceder a quantia solicitada!');
$values = array_keys($this->ballots);
$send = array();
for($value = end($values); $left; $value = prev($values)){
$send[$value] = 0;
while(($left - $value) >= 0){
$partial = $left - $value;
if($partial == 1 || $partial == 3) break;
$left -= $value;
$send[$value]++;
}
if(!$send[$value]) unset($send[$value]);
}
return $send;
}
}Então Evandro... O seu código só funciona se existirem todas estas notas (2,5,10,20,50 e 100). Se for retirada a nota de 2, por exemplo, o código entra em loop infinito.
Eu fiz um código aqui. Vou postar, mas antes já aviso. Ficou um monstrinho (ou monstrão)!!!!! Achei horrível, mas pelo menos ele atende a qualquer variação de notas com qualquer valor de saque.
Só funciona PHP 5.3+, porque eu usei um closure (só pra testar mesmo). Não to mais com saco de tirar ele dali, então vai ficar assim mesmo. Se alguém se habilitar, fique à vontade. :P
AVISO - AO CLICAR NO BOTÃO SPOILER VOCÊ ACEITA TODOS OS RISCOS DE LIBERAR ESTE MONSTRO GAMBIARRENTO. NÃO ME RESPONSABILIZO PELA SUA CURIOSIDADE. :lol:
<?php
class ATM {
/**
* @var array Notas disponíveis no caixa eletrônico.
*/
private $notasDisponiveis = array ();
/**
* @var array qtde de cada nota para realizar o saque
*/
private $qtde---ota = array ();
/**
*
* @var float valor solicitado para saque, definido no construtor da classe
*/
private $valorSolicitado = 0;
/**
* Retorna a quantidade mínima de notas necessárias para a determinada quantia de dinheiro.
*
* @param int $valorSolicitado
* @return int $qtdeNotas
*/
public function getQtdeMinimaDeNotas() {
if($this->valorSolicitado == 0) {
throw new Exception('Não foi definido o valor a ser sacado.');
}
if(empty($this->notasDisponiveis)) {
throw new Exception('Não existem notas disponíveis neste caixa eletrônico.');
}
// ordenando as notas disponíveis
rsort($this->notasDisponiveis);
$valorSolicitado = $this->valorSolicitado;
// removendo as notas maiores do que o valor solicitado, evitando iterações desnecessárias
$notasDisponiveis = array_filter($this->notasDisponiveis, function ($elemento) use ($valorSolicitado) {
return (bool)($elemento <= $valorSolicitado);
} );
// caso não existam notas menores ou iguais ao valor solicitado, o saque não poderá ser efetuado.
if (count ( $notasDisponiveis ) > 0) {
$qtdeNotas = 0;
$iteracao = 0;
$maximoIteracao = floor ( $valorSolicitado / end($notasDisponiveis) );
rsort ( $notasDisponiveis );
while ( $valorSolicitado != 0 && $iteracao < $maximoIteracao ) {
foreach ( $notasDisponiveis as $chave => $nota ) {
if ($valorSolicitado == 0) {
break;
}
$qtdeDestaNota = floor ( $valorSolicitado / $nota );
if ($qtdeDestaNota > 0 && $chave <= ($iteracao - 1)) {
$qtdeDestaNota --;
}
$valorSolicitado -= $nota * $qtdeDestaNota;
$qtdeNotas += $qtdeDestaNota;
$this->qtde---ota [$nota] = $qtdeDestaNota;
}
if ($valorSolicitado != 0) {
$qtdeNotas = 0;
$iteracao ++;
$valorSolicitado = $this->valorSolicitado;
continue;
}
}
}
if ($valorSolicitado != 0) {
return 0;
} else {
return $qtdeNotas;
}
}
/**
* Retorna a quantidade de cada nota, onde o índice é a nota e o valor é a quantidade daquela nota.
* @return array $qtde---ota
*/ return $this->qtde---ota;
}
/**
* Retorna as notas disponíveis
* @return array
*/
public function getNotasDisponiveis() {
return $this->notasDisponiveis;
}
/**
* Método que define quais notas estão disponiveis
* @param array|float $nota
*/
public function addNotasDisponiveis ($nota) {
if(is_array($nota)) {
foreach ( $nota as $item ) {
$this->addNotasDisponiveis ( $item );
}
} else {
$nota = ( float ) $nota;
if (! in_array ( $nota, $this->notasDisponiveis )) {
array_push ( $this->notasDisponiveis, $nota );
}
}
}
/**
* Método que define o valor solicitado
* @param float $valor
*/
public function setValorSaque($valor) {
if (! is_numeric ( $valor )) {
throw new InvalidArgumentException ( 'O valor solicitado é inválido.' );
}
$this->valorSolicitado = $valor;
}
}
try {
// só para testar diversos valores
$valores = range ( 1, 1000 );
// só para testar com diversas sequências de notas
$sequenciaNotas = array (array (2, 5, 10, 20, 50, 100 ), array (20, 50, 100 ), array (10, 50, 100 ) );
foreach ( $valores as $valorSolicitado ) {
shuffle ( $sequenciaNotas );
$atm = new ATM ();
$atm->setValorSaque ( $valorSolicitado );
$atm->addNotasDisponiveis ( $sequenciaNotas [0] );
$qtdeMinimadeNotas = $atm->getQtdeMinimaDeNotas ();
if ($qtdeMinimadeNotas != 0) {
echo 'A quantidade mínima de notas para sacar R$ ', number_format ( $valorSolicitado, 2, ',', '.' ), ' é de ', $qtdeMinimadeNotas, ', sendo:<ul>';
foreach ( $atm->getQtde---ota () as $nota => $qtde ) {
echo '<li>R$ ', number_format ( $nota, 2, ',', '.' ), ' -> ', $qtde, '</li>';
}
echo '</ul>';
} else {
printf ( 'Não é possível realizar o saque. Notas disponíveis : %s. Valor solicitado : R$ %s<br/>', implode ( ' - ', $atm->getNotasDisponiveis () ), number_format ( $valorSolicitado, 2, ',', '.' ) );
}
echo '<hr>';
}
} catch ( Exception $e ) {
echo $e->getMessage ();
}
Carlos Eduardo
PS : Cansei de refatorar o código, mas ainda achei muito confuso e já estou com sono. Se alguém quiser melhorar, fiquem à vontade.
@Evandro
>
Na verdade não é bem um problema, pois foi previsto por você. A questão é que o código não cobre valores que poderiam ser pagos.
Vou facilitar, não é possível sacar apenas 1 e 3 R$. Qualquer outro valor é possível.
Eu já tinha percebido isto, mas eu queria montar algo que funcionasse com qualquer combinação de notas.
-------------------------------------------------
EDIT: Modifiquei a classe, porque fiz um negócio errado. Nem executava. Nome do método errado. Editei o código lá em cima. Valeu @João Batista Neto
Bom eu achei que seria mais difícil, mas acabou que foi fácil.
Tão fácil que acho que me excedi um pouquinho:
<?php
class ATM {
/**
* Locale Constants
*/
// General Messages
const NO_WITHDRAWAL = 'Please, enter the withdrawal amount!';
const INSUFFICIENT = "Sorry! At momment this ATM doesn't have sufficient funds to complete your request.";
const BYE = 'Thank you for use our service!';
// Don't change the placeholders
const INIT = 'Withdrawal Request: %d in %s<br /><br />';
const NOT_PERFECT = '<br />Sorry, but your withdrawal was not entirely perfect. We could not deliver 1 %s<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 boolean isPerfect
*/
private $isPerfect = TRUE;
/**
* ATM Constructor
*
* @param mixed|integer $withdrawal
*/
public function __construct( $withdrawal ) {
$this -> withdrawal = (integer) $withdrawal;
// 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 -> isPerfect ) {
printf( self::NOT_PERFECT, self::CURRENCY_TEXT_SINGULAR );
}
// Saying Goodbye :)
printf( '<br />%s', self::BYE );
return;
}
return $this -> distribution;
}
/**
* Proccess the Withdrawal
*
* @return array
*/
private function proccessWithdrawal() {
if( $this -> withdrawal == 0 ) {
throw new ATMException( self::NO_WITHDRAWAL );
}
if( $this -> withdrawal > $this -> availability ) {
throw new ATMException( self::INSUFFICIENT );
}
// Duplicate Withdrawal Value
$withdrawal = $this -> withdrawal;
arsort( $this -> bills );
$next = TRUE;
while( $next ) {
// Getting the current bill
$currentBill = current( $this -> bills );
// Computing number of bills needed
$amount = floor( $withdrawal / $currentBill );
// Subtracting it from withdrawal request
$times = $amount * $currentBill;
$withdrawal -= $times;
// Recording Information
$this -> distribution[ $currentBill ] = $times;
if( next( $this -> bills ) === FALSE ) $next = FALSE;
}
if( $withdrawal != 0 ) {
$this -> isPerfect = FALSE;
}
}
}
class ATMException extends RuntimeException {}
Para testar:
// Testing...
$start = microtime( TRUE );
$ATM = new ATM( mt_rand( 1, 10000 ) );
$ATM -> getDistribution( TRUE );
// Benchmarking End...
$end = microtime( TRUE );
echo '<br /><br />Runtime: ' . ( $end - $start );
Aqui executou, em média, em 0.0006 segundos. Na maior parte das vezes em 0.0005, mas as vezes rodava em 0.0007, quando gerava números que requeriam distribuição de mais notas.
Assim como o código do Matias tem um requerimento mínimo do PHP 5.3, mas isso é apenas quando getDistribution() receber o valor TRUE, já que também usei uma Closure para deixar mais elegante.
E não há o problema de algumas notas não estarem disponíveis, porém a nota de UM REAL é obrigatória.
Se ela não existir, ao gerar o valor e faltar dinheiro você verá, se habilitado o retorno mais descritivo, uma mensagem de desculpas quanto à esse valor não entregue.
>
>
No meu código, sim, pois adicionei nota de um real
Mas essa era a graça real do desafio.
>
Galera, achei um problema no meu código. Tentando sacar R$ 6,00 ele dispara o erro. Outros valores também deve acontecer o mesmo.
A quantidade mínima de notas para sacar R$ 6,00 é de Não é possível realizar o saque, pois com as notas disponíveis neste caixa eletrônico não é possível chegar ao valor solicitado, restando o valor de R$ 1,00
Mais tarde eu reviso a lógica e posto aqui a correção.
Carlos Eduardo
Na verdade não é bem um problema, pois foi previsto por você. A questão é que o código não cobre valores que poderiam ser pagos.
Vou facilitar, não é possível sacar apenas 1 e 3 R$. Qualquer outro valor é possível.
Segue a minha versão. A classe já está otimizada para uma futura implementação de verificação de cédulas disponíveis.
class ATM {
private $ballots = array(
2 => 0,
5 => 0,
10 => 0,
20 => 0,
50 => 0,
100 => 0
);
public function withdraw($amount){
$left = (integer)$amount;
if($left == 1 || $left == 3) return trigger_error('Impossível conceder a quantia solicitada!');
$values = array_keys($this->ballots);
$send = array();$partial = $left - $value;
if($partial == 1 || $partial == 3) break;
$left -= $value;
$send[$value]++;
}
if(!$send[$value]) unset($send[$value]);
}
return $send;
}
}
Evandro, notei o mesmo problema que o Matias no seu código. Ele acabou ficando amarrado à implementação.
E um outro problema que percebi, apesar de não ter feito a análise concreta, foi a performance. Me parece bem lento assimptoticamente. Veremos com calma isso mais tarde, com a análise.
>
Então Evandro... O seu código só funciona se existirem todas estas notas (2,5,10,20,50 e 100). Se for retirada a nota de 2, por exemplo, o código entra em loop infinito.
Eu fiz um código aqui. Vou postar, mas antes já aviso. Ficou um monstrinho (ou monstrão)!!!!! Achei horrível, mas pelo menos ele atende a qualquer variação de notas com qualquer valor de saque.
Só funciona PHP 5.3+, porque eu usei um closure (só pra testar mesmo). Não to mais com saco de tirar ele dali, então vai ficar assim mesmo. Se alguém se habilitar, fique à vontade. :P
AVISO - AO CLICAR NO BOTÃO SPOILER VOCÊ ACEITA TODOS OS RISCOS DE LIBERAR ESTE MONSTRO GAMBIARRENTO. NÃO ME RESPONSABILIZO PELA SUA CURIOSIDADE. :lol:
<?php
class ATM {
/**
* @var array Notas disponíveis no caixa eletrônico.
*/
private $notasDisponiveis = array ();
/**
* @var array qtde de cada nota para realizar o saque
*/
private $qtde---ota = array ();
/**
*
* @var float valor solicitado para saque, definido no construtor da classe
*/
private $valorSolicitado = 0;
/**
* Retorna a quantidade mínima de notas necessárias para a determinada quantia de dinheiro.
*
* @param int $valorSolicitado
* @return int $qtdeNotas
*/
public function getQtdeMinimaDeNotas() {
if($this->valorSolicitado == 0) {
throw new Exception('Não foi definido o valor a ser sacado.');
}
if(empty($this->notasDisponiveis)) {
throw new Exception('Não existem notas disponíveis neste caixa eletrônico.');
}
// ordenando as notas disponíveis
rsort($this->notasDisponiveis);
$valorSolicitado = $this->valorSolicitado;
// removendo as notas maiores do que o valor solicitado, evitando iterações desnecessárias
$notasDisponiveis = array_filter($this->notasDisponiveis, function ($elemento) use ($valorSolicitado) {
return (bool)($elemento <= $valorSolicitado);
} );
// caso não existam notas menores ou iguais ao valor solicitado, o saque não poderá ser efetuado.
if (count ( $notasDisponiveis ) > 0) {
$qtdeNotas = 0;
$iteracao = 0;
$maximoIteracao = floor ( $valorSolicitado / end($notasDisponiveis) );
rsort ( $notasDisponiveis );
while ( $valorSolicitado != 0 && $iteracao < $maximoIteracao ) {
foreach ( $notasDisponiveis as $chave => $nota ) {
if ($valorSolicitado == 0) {
break;
}
$qtdeDestaNota = floor ( $valorSolicitado / $nota );
if ($qtdeDestaNota > 0 && $chave <= ($iteracao - 1)) {
$qtdeDestaNota --;
}
$valorSolicitado -= $nota * $qtdeDestaNota;
$qtdeNotas += $qtdeDestaNota;
$this->qtde---ota [$nota] = $qtdeDestaNota;
}
if ($valorSolicitado != 0) {
$qtdeNotas = 0;
$iteracao ++;
$valorSolicitado = $this->valorSolicitado;
continue;
}
}
}
if ($valorSolicitado != 0) {
return 0;
} else {
return $qtdeNotas;
}
}
/**
* Retorna a quantidade de cada nota, onde o índice é a nota e o valor é a quantidade daquela nota.
* @return array $qtde---ota
*/
public function getQtde---ota() {
return $this->qtde---ota;
}
/**
* Retorna as notas disponíveis
* @return array
*/
public function getNotasDisponiveis() {
return $this->notasDisponiveis;
}
/**
* Método que define quais notas estão disponiveis
* @param array|float $nota
*/
public function addNotasDisponiveis ($nota) {
if(is_array($nota)) {
foreach ( $nota as $item ) {
$this->addNotasDisponiveis ( $item );
}
} else {
$nota = ( float ) $nota;
if (! in_array ( $nota, $this->notasDisponiveis )) {
array_push ( $this->notasDisponiveis, $nota );
}
}
}
/**
* Método que define o valor solicitado
* @param float $valor
*/
public function setValorSaque($valor) {
if (! is_numeric ( $valor )) {
throw new InvalidArgumentException ( 'O valor solicitado é inválido.' );
}
$this->valorSolicitado = $valor;
}
}
try {
// só para testar diversos valores
$valores = range ( 1, 1000 );
// só para testar com diversas sequências de notas
$sequenciaNotas = array (array (2, 5, 10, 20, 50, 100 ), array (20, 50, 100 ), array (10, 50, 100 ) );
foreach ( $valores as $valorSolicitado ) {
shuffle ( $sequenciaNotas );
$atm = new ATM ();
$atm->setValorSaque ( $valorSolicitado );
$atm->addNotasDisponiveis ( $sequenciaNotas [0] );
$qtdeMinimadeNotas = $atm->getQtdeMinimaDeNotas ();
if ($qtdeMinimadeNotas != 0) {
echo 'A quantidade mínima de notas para sacar R$ ', number_format ( $valorSolicitado, 2, ',', '.' ), ' é de ', $qtdeMinimadeNotas, ', sendo:<ul>';
foreach ( $atm->getQtde---ota () as $nota => $qtde ) {
echo '<li>R$ ', number_format ( $nota, 2, ',', '.' ), ' -> ', $qtde, '</li>';
}
echo '</ul>';
} else {
printf ( 'Não é possível realizar o saque. Notas disponíveis : %s. Valor solicitado : R$ %s<br/>', implode ( ' - ', $atm->getNotasDisponiveis () ), number_format ( $valorSolicitado, 2, ',', '.' ) );
}
echo '<hr>';
}
} catch ( Exception $e ) {
echo $e->getMessage ();
}
Carlos Eduardo
PS : Cansei de refatorar o código, mas ainda achei muito confuso e já estou com sono. Se alguém quiser melhorar, fiquem à vontade.
@Evandro
>
Na verdade não é bem um problema, pois foi previsto por você. A questão é que o código não cobre valores que poderiam ser pagos.
Vou facilitar, não é possível sacar apenas 1 e 3 R$. Qualquer outro valor é possível.
Eu já tinha percebido isto, mas eu queria montar algo que funcionasse com qualquer combinação de notas.
-------------------------------------------------
EDIT: Modifiquei a classe, porque fiz um negócio errado. Nem executava. Nome do método errado. Editei o código lá em cima. Valeu @João Batista Neto
Matias, seu código realmente ficou um monstrinho gambiarrado, mas resolveu o problema. Pelo que percebi, também está bem lento.
>
Bom eu achei que seria mais difícil, mas acabou que foi fácil.
Tão fácil que acho que me excedi um pouquinho:
<?php class ATM { /** * Locale Constants */ // General Messages const NO_WITHDRAWAL = 'Please, enter the withdrawal amount!'; const INSUFFICIENT = "Sorry! At momment this ATM doesn't have sufficient funds to complete your request."; const BYE = 'Thank you for use our service!'; // Don't change the placeholders const INIT = 'Withdrawal Request: %d in %s<br /><br />'; const NOT_PERFECT = '<br />Sorry, but your withdrawal was not entirely perfect. We could not deliver 1 %s<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 boolean isPerfect */ private $isPerfect = TRUE; /** * ATM Constructor * * @param mixed|integer $withdrawal */ public function __construct( $withdrawal ) { $this -> withdrawal = (integer) $withdrawal; // 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 -> isPerfect ) { printf( self::NOT_PERFECT, self::CURRENCY_TEXT_SINGULAR ); } // Saying Goodbye :) printf( '<br />%s', self::BYE ); return; } return $this -> distribution; } /** * Proccess the Withdrawal * * @return array */ private function proccessWithdrawal() { if( $this -> withdrawal == 0 ) { throw new ATMException( self::NO_WITHDRAWAL ); } if( $this -> withdrawal > $this -> availability ) { throw new ATMException( self::INSUFFICIENT ); } // Duplicate Withdrawal Value $withdrawal = $this -> withdrawal; arsort( $this -> bills ); $next = TRUE; while( $next ) { // Getting the current bill $currentBill = current( $this -> bills ); // Computing number of bills needed $amount = floor( $withdrawal / $currentBill ); // Subtracting it from withdrawal request $times = $amount * $currentBill; $withdrawal -= $times; // Recording Information $this -> distribution[ $currentBill ] = $times; if( next( $this -> bills ) === FALSE ) $next = FALSE; } if( $withdrawal != 0 ) { $this -> isPerfect = FALSE; } } } class ATMException extends RuntimeException {}
Para testar:
// Testing... $start = microtime( TRUE ); $ATM = new ATM( mt_rand( 1, 10000 ) ); $ATM -> getDistribution( TRUE ); // Benchmarking End... $end = microtime( TRUE ); echo '<br /><br />Runtime: ' . ( $end - $start );
Aqui executou, em média, em 0.0006 segundos. Na maior parte das vezes em 0.0005, mas as vezes rodava em 0.0007, quando gerava números que requeriam distribuição de mais notas.
Assim como o código do Matias tem um requerimento mínimo do PHP 5.3, mas isso é apenas quando getDistribution() receber o valor TRUE, já que também usei uma Closure para deixar mais elegante.
E não há o problema de algumas notas não estarem disponíveis, porém a nota de UM REAL é obrigatória.
Se ela não existir, ao gerar o valor e faltar dinheiro você verá, se habilitado o retorno mais descritivo, uma mensagem de desculpas quanto à esse valor não entregue.
Bruno, o seu código ficou muito bom a nível de performance. Foi muito bem otimizado.
O problema é que foge totalmente da realidade. Você não pode basear um algorítmo em algo hipotético ou não-concreto. No seu caso, depender da nota de 1 real é totalmente contrário ao princípio do desafio.
Tente implementar algo sob a ideia inicial: trabalhar sem depedência de notas, sem considerar a quantidade de cada notas, considerando as notas de 2, 5, 10, 20, 50 e 100.
@todos
Logo vou postar a minha solução, e quem sabe faremos uma análise conjunta da performance de todas as soluções.
O tópico está bem bacana!
>
Matias, seu código realmente ficou um monstrinho gambiarrado, mas resolveu o problema.
Hehehehe... :P
>
Pelo que percebi, também está bem lento.
Olha... lento não tá não. Tá gambiarrento, mas lento... Vejamos:
<?php
$inicio = microtime ( true );
class ATM {
/**
* @var array Notas disponíveis no caixa eletrônico.
*/
private $notasDisponiveis = array ();
/**
* @var array qtde de cada nota para realizar o saque
*/
private $qtde---ota = array ();
/**
*
* @var float valor solicitado para saque, definido no construtor da classe
*/
private $valorSolicitado = 0;
/**
* Retorna a quantidade mínima de notas necessárias para a determinada quantia de dinheiro.
*
* @param int $valorSolicitado
* @return int $qtdeNotas
*/
public function getQtdeMinimaDeNotas() {
if ($this->valorSolicitado == 0) {
throw new Exception ( 'Não foi definido o valor a ser sacado.' );
}
if (empty ( $this->notasDisponiveis )) {
throw new Exception ( 'Não existem notas disponíveis neste caixa eletrônico.' );
}
// ordenando as notas disponíveis
rsort ( $this->notasDisponiveis );
$valorSolicitado = $this->valorSolicitado;
// removendo as notas maiores do que o valor solicitado, evitando iterações desnecessárias
$notasDisponiveis = array_filter ( $this->notasDisponiveis, function ($elemento) use($valorSolicitado) {
return ( bool ) ($elemento <= $valorSolicitado);
} );
// caso não existam notas menores ou iguais ao valor solicitado, o saque não poderá ser efetuado.
if (count ( $notasDisponiveis ) > 0) {
$qtdeNotas = 0;
$iteracao = 0;
$maximoIteracao = floor ( $valorSolicitado / end ( $notasDisponiveis ) );
rsort ( $notasDisponiveis );
while ( $valorSolicitado != 0 && $iteracao < $maximoIteracao ) {
foreach ( $notasDisponiveis as $chave => $nota ) {
if ($valorSolicitado == 0) {
break;
}
$qtdeDestaNota = floor ( $valorSolicitado / $nota );
if ($qtdeDestaNota > 0 && $chave <= ($iteracao - 1)) {
$qtdeDestaNota --;
}
$valorSolicitado -= $nota * $qtdeDestaNota;
$qtdeNotas += $qtdeDestaNota;
$this->qtde---ota [$nota] = $qtdeDestaNota;
}
if ($valorSolicitado != 0) {
$qtdeNotas = 0;
$iteracao ++;
$valorSolicitado = $this->valorSolicitado;
continue;
}
}
}
if ($valorSolicitado != 0) {
return 0;
} else {
return $qtdeNotas;
}
}
/**
* Retorna a quantidade de cada nota, onde o índice é a nota e o valor é a quantidade daquela nota.
* @return array $qtde---ota
*/ return $this->qtde---ota;
}
/**
* Retorna as notas disponíveis
* @return array
*/
public function getNotasDisponiveis() {
return $this->notasDisponiveis;
}
/**
* Método que define quais notas estão disponiveis
* @param array|float $nota
*/
public function addNotasDisponiveis($nota) {
if (is_array ( $nota )) {
foreach ( $nota as $item ) {
$this->addNotasDisponiveis ( $item );
}
} else {
$nota = ( float ) $nota;
if (! in_array ( $nota, $this->notasDisponiveis )) {
array_push ( $this->notasDisponiveis, $nota );
}
}
}
/**
* Método que define o valor solicitado
* @param float $valor
*/
public function setValorSaque($valor) {
if (! is_numeric ( $valor )) {
throw new InvalidArgumentException ( 'O valor solicitado é inválido.' );
}
$this->valorSolicitado = $valor;
}
}
try {
// só para testar diversos valores
$valores = range ( 1, 1000 );
// só para testar com diversas sequências de notas
$sequenciaNotas = array (array (2, 5, 10, 20, 50, 100 ), array (20, 50, 100 ), array (10, 50, 100 ) );
foreach ( $valores as $valorSolicitado ) {
shuffle ( $sequenciaNotas );
$atm = new ATM ();
$atm->setValorSaque ( $valorSolicitado );
$atm->addNotasDisponiveis ( $sequenciaNotas [0] );
$qtdeMinimadeNotas = $atm->getQtdeMinimaDeNotas ();
if ($qtdeMinimadeNotas != 0) {
echo 'A quantidade mínima de notas para sacar R$ ', number_format ( $valorSolicitado, 2, ',', '.' ), ' é de ', $qtdeMinimadeNotas, ', sendo:<ul>';
foreach ( $atm->getQtde---ota () as $nota => $qtde ) {
echo '<li>R$ ', number_format ( $nota, 2, ',', '.' ), ' -> ', $qtde, '</li>';
}
echo '</ul>';
} else {
printf ( 'Não é possível realizar o saque. Notas disponíveis : %s. Valor solicitado : R$ %s<br/>', implode ( ' - ', $atm->getNotasDisponiveis () ), number_format ( $valorSolicitado, 2, ',', '.' ) );
}
echo '<hr>';
}
$tempo = microtime ( true ) - $inicio;
echo 'O tempo total de execução de ', count ( $valores ), ' saques foi de ', $tempo , ' milisegundos. Na média de cada saque tivemos ',$tempo / count($valores) ;echo $e->getMessage ();
}
Ao executar assim, realizando um shuffle desnecessário, só para dificultar mais ainda o processo, temos como resultado (5 vezes o mesmo código - vou copiar só a mensagem do benchmark).
Vejam, está sendo realizado o saque de valores entre 1 e 1000. Além disto, estou adicionando as notas de forma aleatória, de acordo com opções pré definidas. Então, considero que o código está bem rápido. Está gambiarrento, isto não há dúvida. Mas lento ele não está não.
Carlos Eduardo
Vejam, está sendo realizado o saque de valores entre 1 e 1000. Além disto, estou adicionando as notas de forma aleatória, de acordo com opções pré definidas. Então, considero que o código está bem rápido. Está gambiarrento, isto não há dúvida. Mas lento ele não está não.
Veja bem, Matias, quando me refiro à lentidão no processo do algorítmo, falo sob a perspectiva da análise assimptótica, e não sob a perspectiva de tempo real (em milisegundos). Seu código, é claro, vai rodar rapidamente em qualquer máquina moderna trabalhando com entradas normais, regulares.
O que eu vejo bastante e costumo lutar contra são essas análises de performance feitas em cima de microtimes, que, na realidade, não dizem nada a não ser uma simulação de tempo de execução baseada em hardware e estabilidade da linguagem.
Quero que vocês entendam que o conceito de análise de performance vai além da linguagem e do hardware sob onde irá trabalhar.
Seu algorítmo depende diretamente de uma entrada N, e cresce constantemente sob essa base N. Assim, temos um algorítmo de ordem constante O(N), ou seja, o algorítmo cresce em função de N. Isso é aceitável e relativamente rápido para entradas pequenas, normais. Porém, à medida que temos um crescimento dessa ordem constante, temos, respectivamente, uma perda enorme de performance do algorítmo.
OBS: Essa análise é bastante simples e precária, e está em um nível hipotético, visto que não considerei absolutamente nenhum processo interno da função, mas somente o valor de entrada dessa função.
É claro que isso tudo é muito teórico e não representa nada para nós na realidade em que trabalhamos ou estudamos. Só estou explicando para que entendam o que quero dizer quando falo em lentidão e otimização.
Daniel, perceba que no meu código postado NÃO HÁ a nota de um real sendo considerada, logo ele não é inteiramente dependente dela. Porém ele trabalha com a indisponibilidade da nota, apenas alertando o retirante que a transação não foi perfeita.
Essa consideração poderia ser feita pelo próprio usuário, se ele lê-se (ou lesse?) a descrição de disponibilidade das notas da máquina em questão. Ele não vai nem tentar tirar 10 reais de uma máquina que só trabalha com 50 e 100. ;)
Obviamente, a chave da solução é a nota de cinco reais já que ela é a única responsável por gerar valores ímpares:
13 reais => Uma de cinco e 4 de dois.
21 reais => Uma de 10, uma de cinco e três de dois.
47 reais => Duas de vinte, uma de cinco e uma de dois.
99 reais => Uma de cinquenta, duas de vinte, uma de cindo e duas de dois.
Todos os exemplos são dependentes da nota de cinco reais. Não vejo como, porém, executar a mesma rotina de distribuição sem ter a dependência de notas.
Tanto é verdade que se você retirar da matriz a nota de cinco, e não considerar a de um, jamais poderá entregar uma quantia ímpar.
Então, eu optei por uma solução mais simples e menos "dolorosa" na relação fictícia entre o ATM e o usuário retirante: Deixar de entregar UM REAL é melhor do que deixar de entregar CINCO, não?
>
Daniel, perceba que no meu código postado NÃO HÁ a nota de um real sendo considerada, logo ele não é inteiramente dependente dela. Porém ele trabalha com a indisponibilidade da nota, apenas alertando o retirante que a transação não foi perfeita.
Essa consideração poderia ser feita pelo próprio usuário, se ele lê-se (ou lesse?) a descrição de disponibilidade das notas da máquina em questão. Ele não vai nem tentar tirar 10 reais de uma máquina que só trabalha com 50 e 100. ;)
Obviamente, a chave da solução é a nota de cinco reais já que ela é a única responsável por gerar valores ímpares:
13 reais => Uma de cinco e 4 de dois.
21 reais => Uma de 10, uma de cinco e três de dois.
47 reais => Duas de vinte, uma de cinco e uma de dois.
99 reais => Uma de cinquenta, duas de vinte, uma de cindo e duas de dois.
Todos os exemplos são dependentes da nota de cinco reais. Não vejo como, porém, executar a mesma rotina de distribuição sem ter a dependência de notas.
Tanto é verdade que se você retirar da matriz a nota de cinco, e não considerar a de um, jamais poderá entregar uma quantia ímpar.
Então, eu optei por uma solução mais simples e menos "dolorosa" na relação fictícia entre o ATM e o usuário retirante: Deixar de entregar UM REAL é melhor do que deixar de entregar CINCO, não?
Bruno, sua reflexão é bastante pertinente do ponto de vista funcional, visto que, realmente, a operação de saque depende diretamente da disponibilidade de notas de um caixa eletrônico.
Concordo com você quando fala que o usuário irá "censurar" o funcionamento da máquina, no sentido de obter a informação sobre a disponibilidade e optar por um valor diferente do requerido inicialmente.
Do ponto de vista estrutural, todavia, se pensarmos em disponibilidade, caímos no maior problema que um caixa eletrônico pode enfrentar. Em linguagem de análise, chamaríamos isso de cenário de pior caso:
Imagine que um usuário tenha 47 reais na sua conta. Ele faz uma requisição inicial de 40 reais e o caixa eletrônico retorna uma mensagem de erro, alertando-o de que somente estão disponíveis notas de 50 e 100. E agora? Como proceder?
Esse caso representa exatamente o que falaste no teu comentário sobre a questão da rotina de distribuição. Aliás, foi um comentário bastante pertinente ao desafio em geral.
E que bom que chegamos nesse nível conceitual de discussão: esse caso cria obrigatoriamente um elo de dependência entre disponibilidade de notas e implementação do programa de saque.
OBS: Realmente não tinha entendido o real sentido da sua lógica, nem visto que não utilizava a nota de 1 real. Depois de analisar com calma seu código e sua análise, pude entender melhor o processo.
Aliás, parabéns. Seu código ficou muito bom. :D
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
>
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... <_<
>
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
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 {}
>
>
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.
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
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...
Então vamos lá (não deu pra revisar porque tenho que colocar as crianças pra dormir, então... :P):
<?php
class ATM {
/**
* @var array Notas disponíveis no caixa eletrônico.
*/
private $notasDisponiveis = array (2, 5, 10, 20, 50, 100 );
/**
* @var array qtde de cada nota para realizar o saque
*/
private $qtde---ota = array ();
/**
* Retorna a quantidade mínima de notas necessárias para a determinada quantia de dinheiro.
*
* @param int amountOfCash
/**
* Retorna a quantidade de cada nota, onde o índice é a nota e o valor é a quantidade daquela nota.
public function getQtde---ota() {
try {
foreach ( $atm->getQtde---ota () as $nota => $qtde ) {
} catch ( Exception $e ) {
Saída:
>
A quantidade mínima de notas para sacar R$ 1.725,00 é de 19, sendo:
Carlos Eduardo