miércoles, 12 de agosto de 2015

Intervalos de horas que se superponen

Problema

Supongamos que tenemos una lista de horarios con hora de comienzo y de final. Y queremos saber si algún intervalo horario se superpone con algún otro. Los horarios vienen en formato 'HH:mm:ss' pero solo nos interesan las horas y los minutos. Supongamos también que está en formato de 24hs.
Deberíamos hacer una serie de consultas con cada intervalo, pero como puede haber intervalos que cruzan las 00hs, esas consultas se vuelven muy complicadas. Para simplificar, supongamos que sólo queremos saber si la hora de comienzo de un intervalo está superpuesta en algún otro intervalo. Por lo tanto si esa hora es mayor o igual al inicio de un intervalo pero menor estricto que el final de ese intervalo se considera que el intervalo examinado se encuentra superpuesto con el otro.

Por ejemplo el intervalo '21:00:00,23:00:00' se superpone con los intervalos '20:00:00,22:00:00', '22:00:00,01:30:00' y '09:00:00,21:00:00', pero no se superpone con el intervalo '23:00:00,04:00:00' ni con el intervalo '13:00:00,18:00:00'.



Solución

A continuación presento un script en Perl para solucionar este problema. Dado un array de intervalos y un índice que es el índice del array donde está el intervalo que quiero comparar con todos los demás, ejecuto una función que me retorna si se superpone con alguno de los otros intervalos.

#!/usr/bin/perl
use strict;
use Data::Dumper;

my @intervalos;
push(@intervalos,{'desde'=>'09:00:00','hasta'=>'12:00:00'});
push(@intervalos,{'desde'=>'12:00:00','hasta'=>'15:00:00'});
push(@intervalos,{'desde'=>'14:00:00','hasta'=>'17:00:00'});
push(@intervalos,{'desde'=>'17:30:00','hasta'=>'22:30:00'});
push(@intervalos,{'desde'=>'22:00:00','hasta'=>'00:00:00'});
push(@intervalos,{'desde'=>'23:59:00','hasta'=>'08:59:00'});

print Dumper(\@intervalos);

my $indice= 5;
my $es_superpuesto= ver_solapacion($indice,@intervalos);
print "Superpuesto: $es_superpuesto\n";

sub ver_solapacion{
  my ($indice,@intervalos)= @_;
  my $s= 0;
  my $pivote_indice= $intervalos[$indice]->{'desde'};
  print "Hora 'desde' del intervalo interesante: $pivote_indice \n";
  my $pivote= time_trans($pivote_indice);
  my $i= 0;
  foreach my $row (@intervalos){
    if ($i==$indice){ $i++; next; }
    my $a= time_trans($row->{'desde'});
    my $b= time_trans($row->{'hasta'});
    my $c= time_suma($a,$b);
    my $d= time_suma($a,$pivote);
    if ($d < $c){ $s=1; last; }
    $i++;
  }
  return $s;
}
sub time_trans{
  my ($hora)= @_;
  my @h= split(':',$hora);
  return  $h[0] + ($h[1]/100);
}
sub time_suma{
  my ($a,$b)= @_;
  if ($a>$b){ return 24 + $b; }
  return $b - $a;
}

Resultado: el intervalo está superpuesto

$intervalos = [
          {
            'hasta' => '12:00:00',
            'desde' => '09:00:00'
          },
          {
            'hasta' => '15:00:00',
            'desde' => '12:00:00'
          },
          {
            'hasta' => '17:00:00',
            'desde' => '14:00:00'
          },
          {
            'hasta' => '22:30:00',
            'desde' => '17:30:00'
          },
          {
            'hasta' => '00:00:00',
            'desde' => '22:00:00'
          },
          {
            'hasta' => '08:59:00',
            'desde' => '23:59:00'
          }
        ];
Hora 'desde' del intervalo interesante: 23:59:00 
Superpuesto: 1

sábado, 8 de agosto de 2015

Clase Cuenta Bancaria en JS usando Joose


Problema

Modelizaremos una cuenta bancaria normal. En esta cuenta se puede depositar dinero, retirar dinero y comprobar su saldo actual. No se puede retirar más dinero que el que hay en la cuenta. Además modelizaremos una cuenta corriente con una protección contra sobregiros opcional. La protección contra sobregiros protegerá al propietario de la cuenta corriente retirando automáticamente los fondos necesarios de la cuenta de sobregiro para asegurar que un cheque no rebotará.

Solución

Module("Banco", function (m) {
  Class("Cuenta", {
    has: {
      balance: {
        is: "rw",
        init: 0
      }
    },
    methods: {
      depositar: function (monto) {
        this.setBalance(this.getBalance() + monto)
      },
      retirar: function (monto) {
        if(this.getBalance() < monto) {
          throw "Cuenta sobregirada"
        }
        this.setBalance(this.getBalance() - monto);
        return this.getBalance();
      }
    }
  });
  
  Class("CuentaCorriente", {
    isa: m.Cuenta,
    has: {
      cuentaSobregiro: {
        isa: m.Cuenta,
        is: "rw"
      }
    },
    before: {
      retirar: function (monto) {
        var cantidadEnRojo = monto - this.getBalance()
        
        if(this.cuentaSobregiro && cantidadEnRojo > 0) {
           this.cuentaSobregiro.retirar(cantidadEnRojo);
           this.depositar(cantidadEnRojo);
        }
      }    
    }
  })
}) 

Explicacion

La clase Cuenta tiene un "balance" inicializado en 0 y dos métodos: "depositar" y "retirar". La CuentaCorriente es una subclase de Cuenta que tiene una "cuentaSobregiro" opcional que es de tipo Cuenta. Antes de retirar un monto de la CuentaCorriente verifica si puede sobregirar la cuenta.

Módulos

Module("Banco", function (m) {
...
} 

La función Module() crea el namespace "Banco". Toma como segundo argumento una función que crea las clases Cuenta y CuentaCorriente. La función obtiene un objeto módulo como parámetro, que más tarde será utilizado para facilitar el acceso a otras clases ubicadas dentro del namespace.

Restricciones de Tipo

has: {
  cuentaSobregiro: {
    isa: m.Cuenta,
    is: "rw"
  }
}, 

Aquí introducimos un nuevo atributo para la clase CuentaCorriente llamado "cuentaSobregiro". Usando la palabra clave isa ponemos una restricción de tipo sobre el atributo. Esta restricción es chequeada cada vez que el método setCuentaSobregiro() es usado. Ese método fue creado automáticamente cuando se definió el atributo como rw.

Modificador de método "before"

before: {
  retirar: function (monto) {
    var cantidadEnRojo = monto - this.getBalance()
    
    if(this.cuentaSobregiro && cantidadEnRojo > 0) {
       this.cuentaSobregiro.retirar(cantidadEnRojo);
       this.depositar(cantidadEnRojo);
    }
  }    
} 

Introducimos un método que será ejecutado antes del método retirar(). De esta manera el método original se ve aumentado en su comportamiento. Si el dinero actual en el balance de la cuenta no es suficiente para el retiro, se extrae lo que falta de la cuentaSobregiro y se deposita en la cuenta.

Modificador de método "override"

Otra posible implementación podría haber sido sobreescribir el método retirar() usando el modificador override, el cual permite llamar al método de la superclase haciendo this.SUPER()
override: {
  retirar: function (monto) {
    var cantidadEnRojo = monto - this.getBalance()
      
    if(this.cuentaSobregiro && cuentaSobregiro > 0) {                
      this.cuentaSobregiro.retirar(cantidadEnRojo);
      this.depositar(cantidadEnRojo);
    }

    this.SUPER(amount)
  }
} 

Instanciación de los objetos

Todos los objetos Joose reciben un inicializador estándar que permite inicializar los objetos usando argumentos por nombre en el constructor:
var cajaAhorros= new Banco.Cuenta({ balance: 100 });
var cuentaCorriente= new Banco.CuentaCorriente({ 
    balance:         200, 
    cuentaSobregiro: cajaAhorros
});
console.log("Una cuenta: ");
console.log(cajaAhorros);
console.log("Una cuenta corriente: ");
console.log(cuentaCorriente);
console.log("Operamos en la cuenta con un retiro parcial de 100");
cuentaCorriente.retirar(100);
console.log(cuentaCorriente);
console.log(cajaAhorros);
console.log("Sobregiramos la cuenta con un retiro mayor: 150");
cuentaCorriente.retirar(150);
console.log(cuentaCorriente);
console.log(cajaAhorros);
console.log("Sobregiramos demasiado la cuenta: 300");
cuentaCorriente.retirar(300);

Una cuenta: 
 a Banco.Cuenta { balance=100, meta=a Joose.Class, initialize=initialize(), más...}
Una cuenta corriente: 
 a Banco.CuentaCorriente { balance=200, cuentaSobregiro=a Banco.Cuenta, meta=a Joose.Class, más...}
Operamos en la cuenta con un retiro parcial de 100
 a Banco.CuentaCorriente { balance=100, cuentaSobregiro=a Banco.Cuenta, meta=a Joose.Class, más...}
 a Banco.Cuenta { balance=100, meta=a Joose.Class, initialize=initialize(), más...}
Sobregiramos la cuenta con un retiro mayor: 150
 a Banco.CuentaCorriente { balance=0, cuentaSobregiro=a Banco.Cuenta, meta=a Joose.Class, más...}
 a Banco.Cuenta { balance=50, meta=a Joose.Class, initialize=initialize(), más...}
Sobregiramos demasiado la cuenta: 300
uncaught exception: Cuenta sobregirada

lunes, 3 de agosto de 2015

Web API Console

Comandos


console.log(object[, object, …])
Escribe un mensaje en la consola. Como primer argumento puede aceptar un objeto o un string con especificadores de formato. Por ejemplo %s para string, %d para enteros, %f para flotantes o %o para objetos.
console.log("Usuario %s posee %d créditos", userName, creditCount);

console.debug(object[, object, ...])
Idéntico a console.log.

console.info(object[, object, ...])
Idéntico a console.log pero agrega un ícono "info".

console.warn(object[, object, ...])
Idéntico a console.log pero agrega un ícono "warning".

console.error(object[, object, ...])
Similar a console.log pero agrega un ícono "error" y una traza de la pila.

console.assert(expression[, object, ...])
Testea que una expresión sea verdadera. Si no lo es escribirá un mensaje de error en la consola junto a una traza de la pila. Por ejemplo:
console.assert(list.childNodes.length < 10, "List item count is >= 10");

console.clear()
Limpia la consola.

console.dir(object)
Muestra las propiedades del objeto.

console.trace()
Muestra una traza de la pila en el punto donde es llamado.

console.time(label)
Comienza un timer con una etiqueta label asociada. Cuando console.timeEnd(label) es llamado, el timer es detenido y se muestra en la consola el tiempo transcurrido.

console.profile([title])
Inicia el CPU profile con un título opcional. Para completar el profile llame a console.profileEnd() para que imprima el reporte.

console.count([label])
Escribe el número de veces count() fue ejecutado con el mismo label o las veces que se ejecutó la misma línea de código donde count() fue llamado y ejecutado.

console.table(data[, columns])
Similar a console.log pero muestra los datos en formato de tabla. Por ejemplo:
var languages = [
    { name: "JavaScript", fileExtension: ".js" },
    { name: "Perl", fileExtension: ".pl" },
    { name: "Python", fileExtension: ".py" },
    { name: "Erlang", fileExtension: ".erl" },
    { name: "PHP", fileExtension: ".php" }
];

console.table(languages);
Muestra por consola:

name fileExtension
"JavaScript" ".js"
"Perl" ".pl"
"Python" ".py"
"Erlang" ".erl"
"PHP" ".php"

domingo, 2 de agosto de 2015

HTML DOM querySelector()

Ejemplo

document.querySelector(".ejemplo");
Obtiene el primer elemento en el documento con la clase "ejemplo".
El método querySelector() retorna el primer elemento del documento que matchea con un específico selector CSS. Si se quiere obtener todos los elementos se puede usar querySelectorAll().

Sintaxis

document.querySelector(CSS selectors)
El parámetro especifica uno o más selectores CSS separados por comas, para buscar un elemento en el documento. Retorna un objeto NodeList o null si no es encontrado.

Más ejemplos

document.querySelector("p");
Obtiene el primer elemento párrafo en el documento.
document.querySelector("p.ejemplo");
Obtiene el primer elemento párrafo en el documento con la clase "ejemplo".
document.querySelector("#demo").innerHTML = "Hola Mundo!";
Obtiene el primer elemento con id="demo" y modifica su texto.
document.querySelector("div > p");
Obtiene el primer elemento párrafo cuyo parent es un elemento div.
document.querySelector("a[target]");
Obtiene el primer elemento <a> que tenga un atributo "target".

Ejemplo con múltiples selectores

<h2>Titulo de nivel 2</h2>
<h3>Titulo de nivel 3</h3>

document.querySelector("h2, h3").style.backgroundColor = "red";
El primer elemento que encuentra es un título de nivel 2, por eso lo colorea de rojo y no sigue buscando elementos de título de nivel 3.
En el ejemplo siguiente colorea el elemento de título de nivel 3 porque es el primer elemento que matchea con alguno de los selectores, luego no sigue buscando.
<h3>Titulo de nivel 3</h3>
<h2>Titulo de nivel 2</h2>

document.querySelector("h2, h3").style.backgroundColor = "red";