String Calculator en Groovy

Hola, como parte de la tarea de un curso que estoy tomando, nos dejaron resolver String Calculator y aquí comparto mi solución, la termine en poco tiempo, pero no en menos de 30 minutos la parte de probar las exceptions se me dificulto, se que con shouldFail de GroovyTestCase eso se pudo resolver, pero quise experimentar un poquito. Ya me dio flojera hacer que los delimitadores fueran mas complejos como por ejemplo [****].

String.metaClass.sum = { ->
        if(delegate.trim()){
                def delimiters = '[,'

                if(delegate.startsWith("//")){
                        def limitPosition = delegate.indexOf("\n");
                        delimiters += delegate[2..limitPosition]
                }

                def negatives = []
               
                def result = delegate.split(delimiters + ']').collect{ num ->
                        num = num.isNumber() ? num as int : 0
                        num >= 0 ?: (negatives << num)
                        num < 1001? num : 0
                }.sum()
                if(negatives){
                        throw new RuntimeException(
                                "Negatives not allowed $negatives"
                        )
                }
                result
        } else {
                0
        }
}

assert 0 == "".sum()
assert 0 == " ".sum()
assert 1 == "1".sum()
assert 1 == " 1".sum()
assert 1 == " 1 ".sum()
assert 1 == "  1  ".sum()
assert 0 == "a".sum()
assert 3 == "1,2".sum()
assert 1 == "1, a".sum()
assert 2 == "a, 2".sum()
assert 6 == "1, 2, 3".sum()

assert 6 == "//;\n1, 2, 3".sum()
assert 15 == "//;'\n1, 2, 3' 4' 5".sum()
assert 15 == "//;'\n1, 2, 3' 4' 5, \n'".sum()
assert 15 == "//;'\n1, 2, 3' 4' 5, \n', a, b; c' d".sum()
assert 0 == ",;\n".sum()

try{ "-1, 2, 3".sum()
        assert false
}catch(e){println e}
try{ "-1, 2, 3, -4, -5".sum() }catch(e){ println e}
try{ "//;'\n1, 2, -3' -4' 5".sum() }catch(e){ println e}

assert 1005 == "1000, 2, 3".sum()
assert 11 == "1001, 2, 3, 4000, 5000, 6".sum()

Fue divertido para estas horas de insomnio. Que opinan le mejorarían algo?

Saludos y pasenla muy bien : )

Comentarios

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.
Imagen de rodrigo salado anaya

No vi...

Orale esta padre que pongan videos de como lo resuelven, conste que no los vi antes de escribir el mio. :)

Imagen de alcides

String Calculator en Groovy y en Kawa

Estimado Rodrigo:

¿Como has estado? Espero que muy bien.
Gracias por publicar tu código y compartirlo con la comunidad.
A continuación te doy mi opinión al respecto.

Según el requerimiento en la página de esta Kata, había que crear una clase
con un método int Add(string numbers). No se si están considerando esto en
el ejercicio para el curso. Lo menciono porque me llama la atención que en
este caso has decidido implementar la solución agregando un método a la
clase String.

Ahora bien, al ejecutar el script y me manda el siguiente error:

java.lang.RuntimeException: Negatives not allowed [-1]
java.lang.RuntimeException: Negatives not allowed [-1, -4, -5]
java.lang.RuntimeException: Negatives not allowed [-3, -4]

Y es todo lo que sale. No dice si eso es bueno o malo (o todo lo contrario).
Lo cual según el punto 5 del ejercicio es correcto, pero la prueba
unitaria no indica nada al respecto.

Por lo anterior yo también agregaría algunos comentarios, pues el script
no tiene ninguno.

Dado que String Calculator es una Kata para practicar el TDD; yo en lo
particular organizaría el código y las pruebas unitarias en algo así como:

// -*- coding: utf-8; mode: Groovy -*-

import groovy.util.GroovyTestCase

class StringCalcTest extends GroovyTestCase{

  void setUp(){
    String.metaClass.sum = { ->
      if(delegate.trim()){
        def delimiters = '[,'
        if(delegate.startsWith("//")){
          def limitPosition = delegate.indexOf("\n");
          delimiters += delegate[2..limitPosition]
        }
        def negatives = []

        def result = delegate.split(delimiters + ']').collect{ num ->
          num = num.isNumber() ? num as int : 0
          num >= 0 ?: (negatives << num)
          num < 1000? num : 0
        }.sum()
        if(negatives){
          throw new RuntimeException(
            "Negatives not allowed $negatives"
          )
        }
        result
      } else {
        0
      }
    }
  }

  void testStringCalc() {
    assert 0 == "".sum()
    assert 0 == " ".sum()
    assert 1 == "1".sum()
    assert 1 == " 1".sum()
    assert 1 == " 1 ".sum()
    assert 1 == "  1  ".sum()
    assert 0 == "a".sum()
    assert 3 == "1,2".sum()
    assert 1 == "1, a".sum()
    assert 2 == "a, 2".sum()
    assert 6 == "1, 2, 3".sum()
    assert 6 == "//;\n1, 2, 3".sum()
    assert 15 == "//;'\n1, 2, 3' 4' 5".sum()
    assert 15 == "//;'\n1, 2, 3' 4' 5, \n'".sum()
    assert 15 == "//;'\n1, 2, 3' 4' 5, \n', a, b; c' d".sum()
    assert 0 == ",;\n".sum()
    try{ "-1, 2, 3".sum()
      assert false
    }catch(e){println e}
    try{ "-1, 2, 3, -4, -5".sum() }catch(e){ println e}
    try{ "//;'\n1, 2, -3' -4' 5".sum() }catch(e){ println e}
    assert 5 == "1000, 2, 3".sum()
    assert 11 == "1000, 2, 3, 4000, 5000, 6".sum()
  }
}

Tratandose de Groovy esta forma es ligeramente mas engorrosa que usar el
script original directamente pero ejecuta las pruebas con el Runner de
JUnit y permite saber si los mensajes corresponden a una prueba fallida
o no (y cuantas pasaron, cuantas no, etc).

Y luego tal vez dividiría las pruebas en varios métodos testSomething()
para cada uno de los puntos del requerimiento. Esto no solo por cuestión
de modularidad sino porque otro de los objetivos de las pruebas unitarias
es documentar la funcionalidad del código de producción y también servir
como una especificación ejecutable del requerimiento funcional del código.
De tal forma que al leer las pruebas podamos saber qué hace y que no y
cuál es el requerimiento del código de producción.

Aquí por lo que comentas me dá la impresión de que te quedaste en el punto
6 (de 9) del ejercicio ,
esto es todo lo que puedo suponer solo con leer el código (y el
requerimiento).

Por lo demás, creo que para haberlo hecho en media hora en la madrugada del
domingo está muy bien.

Y bueno pues aquí te comparto mi solución en Scheme Kawa, estoy viendo como
traducir la idea a Groovy.

; -*- coding: utf-8; mode: Scheme -*-
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Coding Kata String Calculator <http://osherove.com/tdd-kata-1/>

(require 'srfi-1 ) ;; List Library
(require '
srfi-13) ;; String Library
(require 'srfi-14) ;; Character-set Library

;; (define-simple-class StringCalculator ()
;;   ;; methods
;;   ((Add numbers ::String) ::int allocation: 'static

(define (Add numbers ::String) ::int
  (let* ((lstnum (filter (lambda (n)
                           (if (<= n 1000) #t #f))
                         (map string->number
                              (string-tokenize
                               numbers
                               (char-set-adjoin char-set:digit #\-)))))
         (negativos (filter negative? lstnum)))
    (if (null? negativos)
        (apply + lstnum)
        (throw (java.lang.RuntimeException
                (format #f
                        "No se permiten negativos: ~s~%"
                        negativos))) )))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Pruebas Unitarias
(require 'srfi-64) ;; Testing Library

;;(define-alias Add StringCalculator:Add)

(test-begin "string-calculator")
;;(test-assert "Prueba no implementada" #f)
;;Req 1.1 y 1.2
(test-equal 0 (Add "Hola"))
(test-equal 0 (Add ""))
(test-equal "Suma '1'" 1 (Add "1"))
(test-equal "Suma '
1,2'" 3 (Add "1,2"))
;;Req 2 y 3
(test-equal "Suma '
1,2,3'" 6 (Add "1,2,3"))
(test-equal "Suma '
1\n2,3'" 6 (Add "1\n2,3"))
;;Req 4
(test-equal 3 (Add "//;\n1;2"))
;;Req 6, 8 y 9
(test-equal 2 (Add "2,1001"))
(test-equal 6 (Add "//[*][%]\n1*2%3"))

;; Pruebas del script de Groovy
(test-equal 0 (Add "")) ;;assert 0 == "".sum()
(test-equal 0 (Add " ")) ;;assert 0 == " ".sum()
(test-equal 1 (Add "1")) ;;assert 1 == "1".sum()
(test-equal 1 (Add " 1")) ;;assert 1 == " 1".sum()
(test-equal 1 (Add " 1 ")) ;;assert 1 == " 1 ".sum()
(test-equal 1 (Add "  1  ")) ;;assert 1 == "  1  ".sum()
(test-equal 0 (Add "a")) ;;assert 0 == "a".sum()
(test-equal 3 (Add "1,2")) ;;assert 3 == "1,2".sum()
(test-equal 1 (Add "1, a")) ;;assert 1 == "1, a".sum()
(test-equal 2 (Add "a, 2")) ;;assert 2 == "a, 2".sum()
(test-equal 6 (Add "1,2,3")) ;;assert 6 == "1, 2, 3".sum()
(test-equal 6 (Add "//;\n1, 2, 3")) ;;assert 6 == "//;\n1, 2, 3".sum()
(test-equal 15 (Add "//;'\n1, 2, 3' 4' 5")) ;;assert 15 == "//;'\n1, 2, 3' 4' 5".sum()
(test-equal 15 (Add "//;'\n1, 2, 3' 4' 5, \n'")) ;;assert 15 == "//;'\n1, 2, 3' 4' 5, \n'".sum()
(test-equal 15 (Add "//;'\n1, 2, 3' 4' 5, \n', a, b; c' d")) ;;assert 15 == "//;'\n1, 2, 3' 4' 5, \n', a, b; c' d".sum()
(test-equal 0 (Add ",;\n")) ;;assert 0 == ",;\n".sum()
(test-error <java.lang.RuntimeException> (Add "-1, 2, 3")) ;;try{ "-1, 2, 3".sum()
(test-error <java.lang.RuntimeException> (Add "-1, 2, 3, -4, -5")) ;;try{ "-1, 2, 3, -4, -5".sum() }catch(e){ println e}
(test-error <java.lang.RuntimeException> (Add "//;'\n1, 2, -3' -4' 5")) ;;try{ "//;'\n1, 2, -3' -4' 5".sum() }catch(e){ println e}

(test-end "string-calculator")

NOTAS:
Escribir una clase con un solo método estático es una mala práctica.
Aunque en este caso el requerimiento así lo estipule, he preferido
dejar el método Add como una función ordinaria de Scheme y dejar el
código de definición de clase y método comentados solo como ejemplo.

Agregué al final de las pruebas unitarias, la traducción a Scheme de las
pruebas del script de Groovy

Saludos y un abrazo.

Imagen de alcides

Re: No vi...

Saludos mi estimado. ¿Te refieres a videos como el de Michael Feathers resolvidendo esta Kata en Haskell en 15 min? http://vimeo.com/18423904

Imagen de rodrigo salado anaya

Juato a ese..

Si juato a ese, y este fue otro que vi http://vimeo.com/8506325

Un saludo enorme de retache mi buen : )

Imagen de alcides

Re: String Calculator en Groovy

La entrada original completa ya está arriba (Gracias a OscarRyz).

Imagen de alcides

De String Calculator en Groovy

http://www.javamexico.org/blo

Encontré el link y lo desmarqué como spam
http://www.javamexico.org/blogs/alcides/string_calculator_en_kawa_0

Imagen de alcides

Muchas gracias

Muchas gracias mi estimado Oscar.

Por cierto, vi tu solución en Go que compartiste en GitHub.
Pregunta indiscreta: ¿Tardaste los 30 min reglamentarios?¿Mas o menos tiempo?
Digamos que me interesa saber el tiempo promedio en que un programador competente (como Rodrigo y como tú) es capaz de terminar ésta kata en específico. Esto porque estamos pensando usarla en Coding-Dojo y quisiera corroborar ese dato en específico.

Saludos cordiales.

Imagen de rodrigo salado anaya

String calculator by OscarRyz

Saludos amigos, les dejo la liga de la Kata de Oscar.

https://gist.github.com/OscarRyz/4747501

No tienes intalado Go? Aquí la solución:

http://play.golang.org/p/qRDSpjoM4F

Imagen de ezamudio

bug

La spec dice que los números mayores a 1000 deben ser ignorados. Esto significa que 1000 sí debe ser considerado.

Imagen de rodrigo salado anaya

fix

Es verdad Enrique, gracias por la observación y esta corregido.