quarta-feira, 22 de junho de 2011

Fugindo dos álbuns

De tempos em tempos nos reunimos no trabalho para incentivar práticas de programação e design através de Dojos, e nos próximos dias teremos um Coding Dojo para exercitar desenvolvimento em Java, e o problema que apresentaremos será decompor um valor em cédulas de uma determinada moeda. A prática do Dojo será evolutiva, partindo do problema inicial como sendo a decomposição de valores inteiros em cédulas inteiras, evoluindo para o tratamento de centavos e finalizando com um controle de cédulas disponíveis. Resolvi praticar este problema individualmente, com Clojure (claro) e de início cheguei na seguinte implementação do problema inicial:

(ns troco)

(def cedulas-reais { :cem 100, :cinquenta 50, :vinte 20, :dez 10, :cinco 5, :dois 2, :um 1 })

(defn decompor [montante cedulas]
(loop [ total-em-cedulas {}
valor-restante montante
notas (seq cedulas)]
(if (not-empty notas)
(let [ face ((first notas) 0),
quanto-vale ((first notas) 1) ]
(recur (assoc total-em-cedulas face (quot valor-restante quanto-vale))
(mod valor-restante quanto-vale)
(rest notas)))
total-em-cedulas)))

(defn decompor-em-cedulas [valor moeda]
(filter #(> (% 1) 0) (seq (decompor valor moeda))))

;(decompor-em-cedulas 37 cedulas-reais)



Desta minha solução, tenho alguns pontos a melhorar
1 - O uso de seq para transformar o map em seqüência de vetores "cédula x valor" é necessário?
2 - Ao invés de declarar variáveis auxiliares (let) para obter a cédula e o valor, não seria melhor criar uma função ou então algum tipo de dado?
3 - Para ignorar as cédulas não utilizadas a melhor forma é utilizar um filter sobre - novamente - um map convertido em seqüência?


(questão 1 resolvida)


(ns troco)

(def cedulas-reais { :cem 100, :cinquenta 50, :vinte 20, :dez 10, :cinco 5, :dois 2, :um 1 })

(defn decompor [montante cedulas]
(loop [ total-em-cedulas {}
valor-restante montante
notas cedulas]
(if (not-empty notas)
(let [ face ((first notas) 0),
quanto-vale ((first notas) 1) ]
(recur (assoc total-em-cedulas face (quot valor-restante quanto-vale))
(mod valor-restante quanto-vale)
(rest notas)))
total-em-cedulas)))

(defn decompor-em-cedulas [valor moeda]
(filter #(> (% 1) 0) (decompor valor moeda)))


Hoje li que a função (first) já opera sobre maps como sendo seqs, retornando um vetor com o primeiro key/value deste.

(questão 2 respondida)


(ns troco)

(def cedulas-reais { :cem 100, :cinquenta 50, :vinte 20, :dez 10, :cinco 5, :dois 2, :um 1 })

(defn face [cedula]
(cedula 0))

(defn valor [cedula]
(cedula 1))

(defn decompor [montante cedulas]
(loop [ total-em-cedulas {}
valor-restante montante
notas cedulas]
(if (not-empty notas)
(let [ cedula (first notas)]
(recur (assoc total-em-cedulas (face cedula) (quot valor-restante (valor cedula)))
(rem valor-restante (valor cedula))
(rest notas)))
total-em-cedulas)))

(defn decompor-em-cedulas [valor moeda]
(filter #(> (% 1) 0) (decompor valor moeda)))


Não sei dizer se fez diferença em termos de clareza: acabou por ficar um (let) para referenciar a cédula topo do map, e as duas funções para retornar o primeiro e segundo elementos do vetor key/value, respectivamente.

Sobre utilizar algum tipo de dado, até pensei em tornar o map em um vetor de maps, composto por duas chaves. Algo assim:


(ns troco)

(def cedulas-reais [{:face :cem, :valor 100}
{:face :cinquenta, :valor 50}
{:face :vinte, :valor 20}
{:face :dez, :valor 10}
{:face :cinco, :valor 5}
{:face :dois, :valor 2}
{:face :um, :valor 1}])

(defn decompor [montante cedulas]
(loop [ total-em-cedulas {}
valor-restante montante
notas cedulas]
(if (not-empty notas)
(let [ cedula (first notas)]
(recur (assoc total-em-cedulas (cedula :face) (quot valor-restante (cedula :valor)))
(rem valor-restante (cedula :valor))
(rest notas)))
total-em-cedulas)))

(defn decompor-em-cedulas [valor moeda]
(filter #(> (% 1) 0) (decompor valor moeda)))


Mas não sei ainda afirmar o que parece "mais correto" em clojure =/

Ah, sutilmente =) troquei (mod) por (rem). Puramente por questões sintáticas.

(questão 3 respondida)

(ns troco)

(def cedulas-reais [{:face :cem, :valor 100}
{:face :cinquenta, :valor 50}
{:face :vinte, :valor 20}
{:face :dez, :valor 10}
{:face :cinco, :valor 5}
{:face :dois, :valor 2}
{:face :um, :valor 1}])

(defn decompor-em-cedulas [valor cedulas]
(loop [ total-em-cedulas {}
valor-restante valor
notas cedulas]
(if (not-empty notas)
(let [ cedula (first notas)]
(if (>= valor-restante (cedula :valor))
(recur (assoc total-em-cedulas (cedula :face) (quot valor-restante (cedula :valor)))
(rem valor-restante (cedula :valor))
(rest notas))
(recur total-em-cedulas
valor-restante
(rest notas))))
total-em-cedulas)))


Não me passou pela cabeça que pudesse colocar 2 (recur) mas faz sentido, já que o (recur) é uma substituição à chamada recursiva da 'função' definida como (loop). Só não estou gostando de ter estes dois (if), e ainda me pergunto se não dá pra resolver com somente um (recur), mas continuarei pesquisando.

Por hoje era isto ;-)

Nenhum comentário:

Postar um comentário