KataWordWrap

Como parte de mi pomodoro y recomendación de mi estimado amigo Alfred (@alfredochv) me di un poco de tiempo para hacer la kata WordWrap. Les comparto mi solución, se me ocurrió otra manera de hacerlo, pero por falta de tiempo no la he concretado, tal vez la haga otro día.

/*
Ejemplo con 4 columnas

You write a class called Wrapper
....|
You
writ
e a
clas
s
call
ed
Wrap
per
*/

class Wrapper{
    static wrap = { inputStr, colNum ->
        if(colNum <= 0 || inputStr.length() <= colNum){
            return inputStr
        }

        def outputString = ""
        def tempStr = inputStr[0..colNum-1]
        def inputStrLength = inputStr.length()-1
        def finPosTmp = 0
        def iniPosInputStr = 0
        def lastWhiteSpace = tempStr.lastIndexOf(' ')
       
        if(tempStr.endsWith(' ')){
            finPosTmp = tempStr.length() -1
            iniPosInputStr = colNum-1
        }else if(lastWhiteSpace > 0){
            finPosTmp = lastWhiteSpace
            iniPosInputStr = lastWhiteSpace + 1
        }else{
            finPosTmp = colNum-1
            iniPosInputStr = colNum
        }
       
        outputString = tempStr[0..finPosTmp].trim()+'\n'
        inputStr = inputStr[iniPosInputStr..inputStrLength].replaceAll(/^\s+/, '')
        outputString + this.wrap(inputStr, colNum)
    }
}

assert Wrapper.wrap('', 0) == ''
assert Wrapper.wrap('a', 0) == 'a'
assert Wrapper.wrap('a', 1) == 'a'
assert Wrapper.wrap('ab', 2) == 'ab'
assert Wrapper.wrap('ab', 1) == 'a\nb'
assert Wrapper.wrap('abc', 1) == 'a\nb\nc'
assert Wrapper.wrap('a bc def ghij klmnñ', 3) == 'a\nbc\ndef\nghi\nj\nklm\nnñ'
assert Wrapper.wrap('You write a class called Wrapper', 4) == 'You\nwrit\ne a\nclas\ns\ncall\ned\nWrap\nper'
assert Wrapper.wrap('You write a class called Wrapper', 5) == 'You\nwrite\na\nclass\ncalle\nd\nWrapp\ner'
assert Wrapper.wrap('You write a class called Wrapper', 9) == 'You\nwrite a\nclass\ncalled\nWrapper'
assert Wrapper.wrap('You write a class called Wrapper', 14) == 'You write a\nclass called\nWrapper'
assert Wrapper.wrap("wordword", 3) == "wor\ndwo\nrd"
assert Wrapper.wrap("word word", 3) == "wor\nd\nwor\nd"
assert Wrapper.wrap("word word", 4) == "word\nword"
assert Wrapper.wrap("word word", 5) == "word\nword"
assert Wrapper.wrap("word word", 6) == "word\nword"
assert Wrapper.wrap("word word word", 6) == "word\nword\nword"
assert Wrapper.wrap("word word word", 11) == "word word\nword"

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 yngwie74

Como que hay muchas variables

Como que hay muchas variables en la rutina y el nombre, aunque es largo, no me dice mucho respecto a porqué son necesarias, por ejemplo:
* colNum se refiere al ancho máximo de línea permitido en la cadena de salida, right?
* inputStrLength en realidad no es la longitud, sino el índice del último caracter de la cadena, ¿correcto?

Si el primer segmento de inputStr[0..colNum-1] termina en un espacio, entonces necesariamente lastWhitespace > 0, por lo que ambas condiciones se pueden unificar. Si tu preocupación es *no* dejar espacios en blanco finales en la cadena resultante, creo que el trim que usas cuando armas la cadena del resultado se debería hacer cargo de ellos, ¿no lo crees?

Recuerda que después de lograr que pase cada prueba, el paso siguiente es refactorizar :)

Es una buena solución, pero creo que es posible simplificarla aún más.

Saludos

Imagen de rodrigo salado anaya

Gracias por los comentarios...

Hola gracias por los comentarios.

Estoy de acuerdo, hay muchas variables, en esta etapa de refactoring trate de enfocarme en la legibilidad, sin buscar eliminar variables, aunque al final si quedaron cosas que digo “aí nanita eso no esta bien!!”, a ver que opinan.

Y gracias, fue una observación muy acertada la de simplificar las condiciones que mencionas (inputStr[0..colNum-1] Y astWhitespace > 0 )

Lo que no entendí fue la parte de  inputStrLength en realidad no es la longitud, sino el índice del último caracter de la cadena, lo dices por el '-1' supongo, por que el método length() es el que trae la longitud, si es así estoy de acuerdo, solo que ese tipo de detalles se me van mucho.

Pues dejo el resultado de la 1ra fase de [re]factorización.

class Wrapper{
    static inputStr
    static lineWidth
   
    static wrap = { inputStr, lineWidth ->
        this.inputStr = inputStr
        this.lineWidth = lineWidth

        if(lineWidth <= 0 || inputStr.length() - 1 < lineWidth){
            return inputStr
        }

        def indexOflastSpace = inputStr[0..lineWidth - 1].lastIndexOf(' ')
       
        indexOflastSpace > 0 ?
            this.getOutPutString(this.getTheCurrentLine(indexOflastSpace), this.getTheRestOfText(indexOflastSpace + 1))
            :
            this.getOutPutString(this.getTheCurrentLine(lineWidth - 1), this.getTheRestOfText(lineWidth))
    }

    static getOutPutString = {currentLine, theRestOfText->
        currentLine + theRestOfText
    }

    static getTheCurrentLine = { endOfLine ->
        inputStr[0..lineWidth - 1][0..endOfLine].trim()+ '\n'
    }

    static getTheRestOfText = { startOfNewLine ->
        this.wrap(inputStr[startOfNewLine..inputStr.length() - 1].replaceAll(/^\s+/, ''), lineWidth)
    }
}

Agradezco mucho tus comentarios y los de los demás

Imagen de yngwie74

De hecho la prueba "You write

De hecho la prueba "You write a class called Wrapper" con ancho 9 es incorrecta; la tienes como "You\nwrite a\nclass\ncalled\nWrapper", lo cual es incorrecto:



         1         2         3
12345678901234567890123456789012
You write a class called Wrapper

Como puedes ver, el bloque "You write" mide exactamente 9 caracteres, seguidos por un espacio. Por lo tanto, la cadena debería dividirse inicialmente en "You write" y "a class..."

Saludos

Imagen de rodrigo salado anaya

Re: De hecho la prueba "You write

:O, es verdad, corrigiendo ese bug.

Gracias de nuevo!!!

Imagen de yngwie74

Bueno, pues mi versión en

Bueno, pues mi versión en Java es más o menos así:

  1. package org.javamexico.katas.textwrap;
  2.  
  3. import static org.junit.Assert.*;
  4. import org.junit.Test;
  5.  
  6. public class WrapperTests {
  7.   public static String wrap(String line, int width) {
  8.     if (line.length() <= width || width <= 0)
  9.       return line;
  10.  
  11.     int space = line.lastIndexOf(' ', width);
  12.     return space >= 0
  13.         ? divide(line, space, 1, width)
  14.         : divide(line, width, 0, width);
  15.   }
  16.  
  17.   private static String divide(String line, int at, int gap, int width) {
  18.     return line.substring(0, at) + '\n' + wrap(line.substring(at + gap), width);
  19.   }
  20.  
  21.   @Test
  22.   public void test() {
  23.     assertEquals("", wrap("", 1));
  24.     assertEquals("1", wrap("1", 0));
  25.     assertEquals("1", wrap("1", 1));
  26.  
  27.     assertEquals("123", wrap("123", 3));
  28.     assertEquals("1 2", wrap("1 2", 3));
  29.  
  30.     assertEquals("123\n4", wrap("123 4", 3));
  31.     assertEquals("1 2\n3", wrap("1 2 3", 3));
  32.     assertEquals("1\n23", wrap("1 23", 3));
  33.     assertEquals("123\n456\n7", wrap("123 456 7", 3));
  34.  
  35.     assertEquals("123\n4", wrap("1234", 3));
  36.     assertEquals("123\n456\n7", wrap("1234567", 3));
  37.  
  38.     assertEquals("1\n234\n5\n67", wrap("1 2345 67", 3));
  39.   }
  40. }

Ahora me vas a hacer instalar y aprender groovy nada más para contestarte en el mismo idioma... te pasas :P

Un muy buen aporte que hace tu versión es la comprobación del ancho pedido: yo tenía una versión anterior que entraba en un ciclo infinito cuando width = 0!

Imagen de rodrigo salado anaya

Re: Bueno, pues mi versión en

Vaya, que bien y muy elegante solución. Jejeje creo que exagere un poco con mi solución :P, tu te la hubieras echado en JRuby, vale, muchas gracias por compartir tus conocimientos conmigo y la comunidad en gral.

Nos vemos pronto Alfred, un abrazo mi estimado.

Imagen de yngwie74

Nos la echamos! :)

Nos la echamos! :)

Despues de mucho postergar y

Despues de mucho postergar y con mucho esfuerzo hice mi version solo para darme cuenta de que me faltaba una parte.

Ahora estoy viendo sus versiones y me surge la siguiente duda.

Para

wrap("1 55555 666666", 4);

El resultado deberia de ser:

1 55
555
6666
66

En vez de:

1
5555
5
6666
66

No? Porque si 55555 nunca va a caber en un ancho de columna de 4 en vez de dejar al 1 solito se le puede poner parte de la siguiente palabra.

Imagen de ezamudio

no

el wordwrap estándar funciona como tu segundo ejemplo Oscar. El 1 se queda solo, y el 55555 se parte en dos porque no cabe en una línea.

Y porque no como en el

Y porque no como en el primero? Digo, de todas formas esta partido en 2 solo que se ocupan los 2 espacios vacios de la primera linea.

De cualquier forma, usando word (seeeee... le puse tamanio de letra 270px para probarlo) hace precisamente lo que dices; deja al 1 solito y parte al "55555" en ("5555" , "5") y no en ("55", "555")

No encuentro otra razon para que sea asi m'as que "por que si"

Je!
meh, de cualquier forma mi implementacion fallaba con algo mas basico aun

ps. si, me faltan los acentos (tildes) :(

Imagen de rodrigo salado anaya

Re: Y porque no como en el

Que onda, pues como dice ezamudio es la forma 2, hechale un ojo a las pruebas en ruby http://hg.code-cop.org/ruby-katas/src/tip/kata/word_wrap/20110713/test_w..., para mayor información jejeje

Saludos Oscar

Imagen de yngwie74

Diferentes algoritmos = diferentes soluciones

En realidad Oscar tiene razón. En la definición del problema del kata no especifica cual de varias posibles formas de wordwrap usar, aunque también --como mencionó Rodrigo-- en las pruebas está implícito que lo que se trata de implementar es la variante "greedy", la cual trata de minimizar el número de líneas usadas colocando tantas palabras como quepan por linea. Es la variante más conocida y también la más usada por los editores de texto y procesadores de palabras.

Existen, sin embargo otras variantes, notablemente la usada por Tex, la cual se comporta de forma más parecida a lo descrito por Oscar, tratando de obtener una apariencia más pareja del margen derecho del texto, aunque esto tiene por consecuencia una mayor división de palabras. De hecho, si alguien aún lo recuerda, para quienes aprendimos a escribir a máquina con una máquina de escribir de verdad, este es el algoritmo que aprendimos de forma sub-conciente: *TING* tak. tak. tak. -guion- -vuelta de carro-...

Creo que es una variación interesante del Kata, pero habría que definir correctamente los requisitos de la misma para poderla considerar un Kata hecho y derecho, no?

Saludos

Si, la spec no es taaaaan

Si, la spec no es taaaaan clara en ese sentido.

Yo de plano ya tomé Word como spec :P