Entradas en "encapsulación"

IDs únicas

Volvemos con los problemas de la semana. Este es uno de mis favoritos, muy útil para usar en una entrevista de trabajo si queréis poner a prueba al candidato 😉 (debería hacer una colección con este tipo de problemas, porque son simples, pero demuestran mucho de quien los resuelve; me gustan mucho!).

Pregunta: En cualquier lenguaje de programación, escribe una función UID que devuelva un ID (número) único cada vez que se ejecute.

Consideraciones: La función no puede hacer uso de datos o variables globales de ninguna forma ni aceptar parámetros. Toda su funcionalidad debe quedar encapsulada. Tampoco puede ser el método de un objeto.

Ejemplo de ejecución:

x=UID(); //x vale 1
x=UID(); //x vale 2
x=UID(); //x vale 3
/* Nota: Los números pueden o no ser secuenciales,
 el único requisito es que nunca se repitan. */

Expansión (para los que quieran sumar puntos extra): Escribe en cualquier lenguaje de programación, una función GUID que devuelva una función (o puntero a función en lenguajes como C++) según las especificaciones del problema anterior pero que funcionen de forma independiente.

Ejemplo de ejecución:

UID1=GUID();
UID2=GUID();
UID3=GUID();
x=UID1(); //x vale 1
x=UID1(); //x vale 2
x=UID2(); //x vale 1
x=UID1(); //x vale 3
x=UID2(); //x vale 2
x=UID1(); //x vale 4
x=UID3(); //x vale 1

*** SOLUCIÓN ***

El enunciado era sencillo pero la solución puede complicarse si no tomamos una primera decisión correcta. Erful consiguió resolverlo en Javascript (aunque no tenía Internet y no pudo responder con su código) y StormByte iba muy bien encaminado con la solución.

La primera decisión correcta e importante es la elección lenguaje de programación; en algunos lenguajes la solución es trivial… por eso es importante, cuando leemos “en cualquier lenguaje de programación”, no interpretarlo como “nuestro favorito” o “el que más dominamos”; si no “el más adecuado” para el problema.

Necesitamos un lenguaje que soporte clausuras o cálculo lambda.

Con esto, vamos con las soluciones:

Solución Simple en Javascript:

var UID = (function() {
   var id=0; return function() {
      return ++id;
   }
})();
//===========================
var x;
x=UID(); //x vale 1
x=UID(); //x vale 2
x=UID(); //x vale 3

Lo que tenemos es la creación de una función anónima que se ejecuta conforme es definida. Esta función forma la clausura que contiene a una variable id inicializada a 0. Además la función devuelve otra función que retorna e incrementa el valor de id, esta función es la que se asigna a UID, y que cada vez que se ejecuta, tiene acceso a la variable id de ámbito superior que pertenece a la clausura de la función anónima.

Con esta idea, hacer un generador de funciones UID es trivial:

Solución Expandida en Javascript:

function GUID() {
   var id=0;
   return function() {
      return ++id;
   }
};
//===========================
var UID1=GUID();
var UID2=GUID();
x=UID1(); //x vale 1
x=UID1(); //x vale 2
x=UID2(); //x vale 1
x=UID1(); //x vale 3
x=UID2(); //x vale 2

La diferencia es que la función anónima de clausura original, ya no es anónima, si no que se llama GUID y se ejecuta creando una nueva clausura cada vez, devolviendo la función que incrementa y devuelve el valor de id.

En otros lenguajes de programación el planteamiento es el mismo:

Solución Expandida PHP

function GUID() {
   $id=0;
   return function() use (&$id) {
      return ++$id;
   };
}
//===========================
$UID1=GUID();
$UID2=GUID();
echo $UID1(); //1
echo $UID1(); //2
echo $UID2(); //1
echo $UID1(); //3
echo $UID2(); //2

Fijaos en la sintaxis, para ejecutar UID1 y UID2 no se utiliza la sintaxis de llamada a función normal; si no que es necesario indicar que son variables: así a() != $a(). Requiere PHP >= 5.3 para funcionar.

Solución Expandida C++

#include <iostream>
#include <functional>

//===========================
std::function<int()> GUID()
{
    return []()->std::function<int()>
    {
        int id=0;
        return [=]() mutable -> int { return ++id; };
    }();
}
//===========================

int main(int argc, char * argv[])
{
    auto UID1= GUID();
    auto UID2= GUID();

    std::cout << UID1() << std::endl; //1
    std::cout << UID1() << std::endl; //2
    std::cout << UID2() << std::endl; //1
    std::cout << UID1() << std::endl; //3
    std::cout << UID2() << std::endl; //2
    return 0;
}

Para simplificar y evitar el uso de punteros a funciones usamos el tipo function de c++11 (la última versión del estándar C++, requiere G++>=4.7). Usamos el cálculo lambda para crear la clausura necesaria y devolver la función. Si queréis aprender sobre la sintaxis del cálculo lambda en C++ está bien explicada en la documentación de referencia de MSDN.

¿Os animáis a hacer la prueba en otros lenguajes? 😉 ¿Quién nos enseña una solución en LISP o SCHEME?

Leer más