Convertir números a letras.

Este ejercicio estuvo de moda esta semana, y solo con el fin de que no se quede este humilde código en el baúl de los olvidos, lo dejo para la comunidad.

Soy nuevo en Groovy, y como siempre me gustan las recomendaciones y críticas sobre mi código.

Resumen del código: Convierte números enteros del 0 al 1000 en su forma escrita, letras en minúsculas y español.
Conforme se analicen números en la función ‘convierteNumeroAPalabras’ se irán agregando en el mapa ‘numerosEscritos’. Así que si se deseara llenar el mapa con la secuencia completa se usaría algo como:  (0..1000).each{convierteNumeroAPalabras(it) } , después solo se podrían recuperar la conversión con un método propio del mapa, por ejemplo: numerosEscritos.get(numero)

/*Mapa con las conversiones de los números*/
numerosEscritos = [
                0:"cero",  1:"uno",  2:"dos",   3:"tres", 4:"cuatro",
                5:"cinco", 6:"seis", 7:"siete", 8:"ocho", 9:"nueve",
               
                10:"diez",   11:"once",      12:"doce",       13:"trece",     14:"catorce",
                15:"quince", 16:"dieciseis", 17:"diecisiete", 18:"dieciocho", 19:"diecinueve",
               
                20:"veinte",  30:"treinta", 40:"cuarenta", 50:"cincuenta",
                60:"sesenta", 70:"setenta", 80:"ochenta",  90:"noventa",
               
                100:"cien",       200:"doscientos",   300:"trescientos",  400:"cuatrocientos", 500:"quinientos",
                600:"seicientos", 700:"setecientos", 800:"ochocientos", 900:"novecientos",   1000:"mil",

                //La sección del veinte es un caso especial.
                21:"ventiuno",  22:"ventidos",   23:"ventitres", 24:"venticuatro", 25:"venticinco",
                26:"ventiseis", 27:"ventisiete", 28:"ventiocho", 29:"ventinueve"
    ]

/**
 * Convierte un número entero a su versión en palabras.
 * @param numero Número entero a convertir.
 * @return Número en forma de palabras.
 */

def convierteNumeroAPalabras(numero){
    assert numero >= 0 && numero <= 1000, "Número [$numero] aun no soportado!!"
    if (numerosEscritos.get(numero) == null){
        numerosIndividuales = numero.toString().getChars()
        primerDigito = (numerosIndividuales[0] as String).toInteger()
        switch(numero.toString().length()){
            case 2:
                segundoDigito = (numerosIndividuales[1] as String).toInteger()
                numeroEscrito = numerosEscritos.get(primerDigito * 10) +
                                " y " + numerosEscritos.get(segundoDigito)
                break
            case 3:
                segundoDigito = ((numerosIndividuales[1] as String) +  
                                (numerosIndividuales[2] as String)).toInteger()
                numeroEscrito = numerosEscritos.get(primerDigito * 100) +
                                // La sección de cien no sigue el mismo patron.                                
                               ((primerDigito * 100 )==100? "to ":" ") +
                               convierteNumeroAPalabras(segundoDigito)
                break
        }
        numerosEscritos.put(numero, numeroEscrito)
    }
    numerosEscritos.get(numero)
}

/*test-cases*/
assert convierteNumeroAPalabras(0) == "cero"
assert convierteNumeroAPalabras(11) == "once"
assert convierteNumeroAPalabras(103) == "ciento tres"
assert convierteNumeroAPalabras(239) == "doscientos treinta y nueve"
assert convierteNumeroAPalabras(576) == "quinientos setenta y seis"
assert convierteNumeroAPalabras(763) == "setecientos sesenta y tres"
assert convierteNumeroAPalabras(896) == "ochocientos noventa y seis"
assert convierteNumeroAPalabras(1000) == "mil"

Saludos a todos. : )

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 ezamudio

pruebas

Yo tengo una batería de pruebas un poco más completa, para un convertidor que yo hice:

assert letra(0) == 'cero'
assert letra(1) == 'uno'
assert letra(9) == 'nueve'
assert letra(10) == 'diez'
assert letra(15) == 'quince'
assert letra(20) == 'veinte'
assert letra(25) == 'veinticinco'
assert letra(30) == 'treinta'
assert letra(42) == 'cuarenta y dos'
assert letra(99) == 'noventa y nueve'
assert letra(100) == 'cien'
assert letra(101) == 'ciento uno'
assert letra(110) == 'ciento diez'
assert letra(117) == 'ciento diecisiete'
assert letra(120) == 'ciento veinte'
assert letra(128) == 'ciento veintiocho'
assert letra(130) == 'ciento treinta'
assert letra(192) == 'ciento noventa y dos'
assert letra(200) == 'doscientos'
assert letra(300) == 'trescientos'
assert letra(512) == 'quinientos doce'
assert letra(768) == 'setecientos sesenta y ocho'
assert letra(1000) == 'un mil'
assert letra(1001) == 'un mil uno'
assert letra(1024) == 'un mil veinticuatro'
assert letra(1234) == 'un mil doscientos treinta y cuatro'
assert letra(5678) == 'cinco mil seiscientos setenta y ocho'
assert letra(9000) == 'nueve mil'
assert letra(10000) == 'diez mil'
assert letra(10001) == 'diez mil uno'
assert letra(10019) == 'diez mil diecinueve'
assert letra(10047) == 'diez mil cuarenta y siete'
assert letra(10240) == 'diez mil doscientos cuarenta'
assert letra(11111) == 'once mil ciento once'
assert letra(12345) == 'doce mil trescientos cuarenta y cinco'
assert letra(100000) == 'cien mil'
assert letra(200001) == 'doscientos mil uno'
assert letra(200015) == 'doscientos mil quince'
assert letra(200027) == 'doscientos mil veintisiete'
assert letra(200099) == 'doscientos mil noventa y nueve'
assert letra(200500) == 'doscientos mil quinientos'
assert letra(234567) == 'doscientos treinta y cuatro mil quinientos sesenta y siete'
assert letra(1000000) == 'un millón'
assert letra(2000000) == 'dos millones'
assert letra(50000000) == 'cincuenta millones'
assert letra(1000001) == 'un millón uno'
assert letra(1000102) == 'un millón ciento dos'
assert letra(1001000) == 'un millón un mil'
assert letra(1002345) == 'un millón dos mil trescientos cuarenta y cinco'
assert letra(1025678) == 'un millón veinticinco mil seiscientos setenta y ocho'
assert letra(1234567) == 'un millón doscientos treinta y cuatro mil quinientos sesenta y siete'
Imagen de ezamudio

duh

Ah y mi función, que realmente la hice como un closure, cuando estaba aprendiendo Groovy precisamente... en vez de un mapa, tengo varias listas y obtengo las cadenas por su posición. Creo que es más eficiente usar un mapa.

def letra = { int num ->
  List units = ['cero','uno','dos','tres','cuatro','cinco','seis','siete','ocho','nueve']
  List decs = ['X', 'y', 'veinte', 'treinta', 'cuarenta', 'cincuenta', 'sesenta', 'setenta', 'ochenta', 'noventa']
  List dieces=['diez','once','doce','trece','catorce','quince','dieciseis','diecisiete','dieciocho','diecinueve']
  List cientos=['x', 'cien', 'doscientos', 'trescientos', 'cuatrocientos', 'quinientos', 'seiscientos', 'setecientos', 'ochocientos', 'novecientos']
  //Millones
  int millones = num.intdiv(1000000)
  //Millares
  int millares = num.intdiv(1000) % 1000
  //Centenas
  int centenas = num.intdiv(100) % 10
  //Decenas
  int decenas = num.intdiv(10) % 10
  //Unidades
  int unidades = num % 10
  def letras = new StringBuilder()
  if (millones == 1) {
    letras << 'un millón'
  } else if (millones > 1) {
    letras.append(call(millones)).append ' millones'
  }
  if (millares == 1) {
    letras << ' un mil' // o 'mil' nada mas, si no se quiere tipo "moneda"
  } else if (millares > 0) {
    letras.append(' ').append(call(millares)).append ' mil'
  }
  if (centenas == 1) {
        letras << (num % 100 == 0 ? ' cien' : ' ciento')
  } else if (centenas > 0) {
    letras.append(' ').append cientos[centenas]
  }
  if (decenas == 1) {
    letras.append(' ').append dieces[num % 10]
    unidades = 0
  } else if (decenas == 2 && unidades > 0) {
    letras.append(' veinti').append units[unidades]
    unidades = 0
  } else if (decenas > 1) {
    letras.append(' ').append decs[decenas]
    if (unidades > 0) {
      letras << ' y'
    }
  }
  if (unidades > 0) {
    letras.append(' ').append units[unidades]
  } else if (num == 0) {
    letras << units[0]
  }
  letras.toString().trim()
}
Imagen de rodrigo salado anaya

Forma tan creativa.

Órale, que forma tan creativa de resolverlo, y acabe aprendiendo la existencia de intdiv. En un principio pensé en ocupar algo muy similar a ti con las listas.
¿Existe en Groovy alguna manera de no usar tanas sentencias if’s, algo como Pattern Matching?
No creo verdad. Pero bueno tenía que preguntar.
Saludos.

Imagen de ezamudio

case

Puedes usar case, el case en Groovy es mucho más avanzado que el que conoces de Java, que casi fue tomado de C. Pero si ves los if's que tengo, son muy heterogéneos, no estoy evaluando distintos valores de la misma variable, sino que evalúo distintas variables, a veces con condiciones adicionales; no usé case porque sólo quedaría más enredado (menos legible).

Imagen de yngwie74

Python

Sr. Rodrigo

Me llama la atención que con Groovy su solución se comienza a parecer (con sus obvias diferencias, claro) un *poquito* a la versión en Python que discutimos hace unos días. Probablemente es el resultado de usar un lenguaje más dinámico.

El detalle que veo y que para mi es como una fila de focos parpadeando, es el switch a media función. Seguramente en caso de tener una suite más extensa, como la de ezamudio habría marcado una diferencia, ya que como --creo que a estas alturas-- todos sabemos: mientras más específicas las pruebas, más genérico el código.

Y gracias por compartir el código con la comunidad!!

--Alfred

@RSa... algo como un...

@RSa... algo como un... switch?

Imagen de ezamudio

switch y pattern matching

En este caso yo no usé switch ni pattern matching, ni siquiera en la versión de Scala, por lo que ya mencioné: las condiciones que evalúo son sobre distintas variables, a lo mucho tengo if-else sobre la misma variable, y además las comparaciones son numéricas (que si un número es mayor o menor a otro, etc). Ni el switch de Groovy ni el pattern matching en Scala permiten hacer comparaciones numéricas así (que si X es mayor a 0 por ejemplo), al menos de manera directa, hay que hacer algo de código adicional (un case class en el caso de Scala, o Matcher en el caso de Groovy).

algoritmo convertir numero a letras