Mostrando entradas con la etiqueta ruby. Mostrar todas las entradas
Mostrando entradas con la etiqueta ruby. Mostrar todas las entradas

sábado, 31 de mayo de 2014

Mejor no dar algo por sentado

Introducción


Sin darnos cuenta, nos acostumbramos a utilizar herramientas de cierto modo, y cuando utilizamos una similiar damos por hecho que funciona exactamente igual.

Por ejemplo, solemos dar por sentado que ciertas operaciones funcionan igual en un lenguaje de programación que en otro. Normalmente no hay problema porque suele ser así, pero es importante ser concientes de que no tiene por qué serlo:

a veces el funcionamiento de alguna característica de un lenguaje depende de la convención que se siguiera al diseñarlo y puede ser diferente de la convención seguida al diseñar otro lenguaje.

Un ejemplo real


Una vez escribí un programa en Ruby y al hacer pruebas obtenía resultados que no coincidían con lo esperaba.

Depurándolo acoté la zona del programa donde se realizaba mal un cálculo, pero por mucho que miraba el código no entendía lo que ocurría.

Hasta que descubrí que la aritmética en Ruby no funciona como yo suponía...

Estaba tan acostumbrado a utilizar la aritmética de C/C++ o Java, que no me esperaba que algo tan básico como dividir un entero entre otro no funcionase igual en Ruby. Pero vaya, así es.

Cuando se divide un número entero entre otro y el resultado no es un número entero, sino uno real (con decimales), hay que realizar un redondeo (o más bien un truncamiento). Pues resulta que no todos los lenguajes siguen la misma convención.

Al dividir un número negativo entre uno positivo o viceversa, en C/C++ y Java se trunca el resultado hacia cero (arriba), de modo que el valor absoluto del truncamiento de una división entera no depende del signo de los operandos. Por ejemplo:

OperaciónResultadoComentario
+5/3+1Resultado de truncar +1.667 hacia cero o -inf (abajo)
-5/3-1Resultado de truncar -1.667 hacia cero o +inf (arriba)

Sin embargo, en Ruby el truncamiento siempre es hacia abajo, variando el valor absoluto en función del signo de los operandos:

OperaciónResultadoComentario
+5/3+1Resultado de truncar +1.667 hacia cero o -inf (abajo)
-5/3-2Resultado de truncar -1.667 hacia -inf (abajo)

Ruby sigue una convenión distinta a la que siguen C++ y Java, por lo que se obtienen resultados diferentes.

Compruébalo tu mismo


Aquí hay unos pequeños programas que demuestran lo comentado en el apartado anterior: en Java, en C++ y en Ruby. Cada programa se puede copiar, pegar y probar online en el enlace indicado debajo.

public class RoundTest {

    public static void main(String[] args) {
        int a= -5;
        int b= 3;
        System.out.format("(%s/%s)=%s%n", a, b, (a/b));
        a= 5;
        b= 3;
        System.out.format("(%s/%s)=%s%n", a, b, (a/b));
    }
}

#include <cstdio>

using namespace std;

int main() {
    int a= -5;
    int b= 3;
    printf("(%d/%d)=%d\n", a, b, (a/b));
    a= 5;
    b= 3;
    printf("(%d/%d)=%d\n", a, b, (a/b));

    return 0;
}

#!/usr/local/bin/ruby -w

Integer a= -5;
Integer b= 3;
puts "(#{a}/#{b})=#{(a/b)}";
a= 5;
b= 3;
puts "(#{a}/#{b})=#{(a/b)}";

Con estos pequeños programas es fácil comprobar que los resultados en Ruby difieren de los obtenidos en C++ y Java.

Recuerda que hay más casos


Hay más lenguajes que implementan la división de forma similar a Ruby: Python, Tcl... También varía la implementación de la operacion Módulo (Modulus).

Un ejemplo mucho más típico y conocido son las sentencias condicionales multivía (case, when, switch...). Unos lenguajes, por ejemplo Pascal, tratan de forma exclusiva cada caso, de modo que se ejecuta exclusivamente el caso por el que se entra, mientras que otros, como C/C++ proporcionan un punto de entrada, pero debe ser el programador quien indique explícitamente donde interrumpir el flujo. de ejecución.

No sólo entre hay diferencia entre lenguajes de programación. Hay otras características que varían de unos sistemas a otros, como por ejemplo, el tratamiento que hace Oracle de las Strings vacías: las asimila a NULL.

Conclusión


Conviene tener el citado ejemplo en cuenta para recordar que al cambiar de un lenguaje de programación a otro, no se debe asumir que todo funciona exactamente igual.

Nunca está de más hacer alguna pequeña prueba para cerciorarse de que el programa se comporta como es de esperar.

Enlaces


Enlaces de interés relacionados con este artículo:

(Actualizado 31/05/2014)

domingo, 27 de abril de 2014

¿Cómo obtener la ocurrencia más corta usando expresiones regulares?

Introducción


Alguna vez habrás usado expresiones regulares (regular expression), ya sea programando, en la línea de comandos o en algún editor de texto.

¿No te ha pasado que al usar algún comodín de repetición como el * en vez de obtener la ocurrencia más corta obtienes la más larga (que contiene ocurrencias más cortas)?

Por ejemplo, al analizar la cadena XML:

<a>1111</a><a>2222</a><a>3333</a><a>4444</a>

con la expresión regular siguiente para locazizar los elementos (tags) a:

<a>.*</a>

obtienes toda la cadena en vez de únicamente el primer elemento <a>1111</a>.

Ups, hay ambigüedad: encajan varios trozos de la cadena y además encaja toda la cadena. ¿Qué resultado se elige? 

Greediness vs Laziness


El problema es que al diseñar un motor de expresiones regulares, hay que resolver la ambigüedad. Hay dos comportamientos:
  1. Greediness (avaricia, codicia): el comodín se expande consumiento el máximo número de caracteres posible. En la documentación se suele denominar modo greedy.
  2. Laziness (pereza): el comodín se expande consumiendo el mínimo número de caracteres posible. En la documentación se suele denominar modo lazy, non-greedy o reluctant.
Algunos motores de búsqueda permiten configurar el comportamiento, otros no.

Si te interesa mucho el tema, en estos enlaces se explica (en inglés) muy bien como funcionan los motores de expresiones regulares y sus comportamientos:

Cómo capturar la ocurrencia más corta (non-greedy)


Algunos motores de expresiones regulares han resuelto la ambigüedad utilizando operadores diferentes para los modos greedy y non-greedy.

El operador para 0 o N ocurrencias más conocido es el greedy * mientras que el non-greedy suele ser una variación suya *? en la mayoría de los casos.

A continuación hay ejemplos del operador non-greedy en varios entornos. Para ello, se utiliza la cadena y la expresión regular de la introducción.

En lenguajes interpretados se muestra la lista de ocurrencias, por ser fácil y muy ilustrativo.

En lenguajes compilados, por simplicidad, se reemplazan todas las ocurencias por la palabra Replaced, de modo que mirando el resultado se sabe si el comportamiento fue greedy (Replaced aparecerá sólo una vez) o non-greedy (Replaced aparecerá varias veces).

grep (hay que activar el modo Perl)
echo "<a>1111</a><a>2222</a><a>3333</a><a>4444</a>" | grep -P -o "<a>.*?</a>"

Vim (este caso difiere mucho del resto)
<a>.\{-}<\/a>
Para detectar tags cuyo contenido incluya saltos de línea:
<a>\_.\{-}<\/a>

Perl (ejecutable en línea de comandos)
perl -e 'my @matches= "<a>1111</a><a>2222</a><a>3333</a><a>4444</a>" =~ /<a>.*?<\/a>/g; print (join("\n", @matches), "\n");'

Ruby (ejecutable en línea de comandos)
ruby -e 'puts "<a>1111</a><a>2222</a><a>3333</a><a>4444</a>".scan( /<a>.*?<\/a>/ ).to_a'

PostgreSQL
SELECT regexp_matches('<a>1111</a><a>2222</a><a>3333</a><a>4444</a>', '<a>.*?<\/a>', 'g');

PHP (ejecutable en línea de comandos)
php -r '$matches= array(); $search= preg_match_all("/<a>.*?<\/a>/", "<a>1111</a><a>2222</a><a>3333</a><a>4444</a>", $matches); print_r($matches);'

Java
"<a>1111</a><a>2222</a><a>3333</a><a>4444</a>".replaceAll("<a>.*?</a>", "Replaced")

Javascript
function doReplace() {
  var str= "<a>1111</a><a>2222</a><a>3333</a><a>4444</a>"
  str= str.replace(new RegExp("<a>.*?</a>", "g"), "Replaced");
  alert(str);
}

C++
Para menejar expresiones regulares en C++ haría falta un compilador que soporte por completo C++11 (GCC 4.9) o bibliotecas alternativa como boost o clang.

sed (este comando siempre es greedy)
http://stackoverflow.com/questions/1103149/non-greedy-regex-matching-in-sed

bash (sus funciones/operadores de expresiones regulares como =~ siempre son greedy)
Al parecer, las expresiones regulares POSIX sólo soportan modo greedy.
http://stackoverflow.com/questions/18738576/regex-in-bash-expression

Prúebalo tu mismo


Aquí hay enlaces a varias paǵinas web para probar algunas de las expresiones anteriores directamente on-line:

Enlaces


Enlaces de interés relacionados con este artículo:


(Actualizado 10/07/2014)