Mostrando entradas con la etiqueta Internet y tecnología. Mostrar todas las entradas
Mostrando entradas con la etiqueta Internet y tecnología. Mostrar todas las entradas

lunes, 10 de octubre de 2016

Euler #4

https://projecteuler.net/problem=4

Python:
   1 #!/usr/bin/env python3
2
3 def isPalindromic(arg):
4 arg = str(arg)
5 cut = int(len(arg)/2)
6 (first, last) = (arg[:cut], arg[cut:][::-1])
7 if (len(last) == len(first) + 1):
8 last = last[:-1]
9 return first == last
10
11 biggest = 0
12 for x in range(100, 1000):
13 for y in range(100, 1000):
14 num = x * y
15 if isPalindromic(num) and num > biggest:
16 biggest = num
17
18 print (biggest)

Euler #3

Seguimos resolviendo problemas del proyecto Euler, vamos con el tercero.

https://projecteuler.net/problem=3

En Java.

Implementaremos el método más sencillo, división por tentativa.
   1 
2 public class EulerSqrtRev_3 {
3
4 public static boolean isPrime(long number) {
5 if (number == 2) return true;
6 if (number < 2 || number % 2 == 0) return false;
7 double max = Math.sqrt(number);
8 for (long i = 3; i <= max; i +=2) {
9 if (number % i == 0) {
10 return false;
11 }
12 }
13 return true;
14 }
15
16 public static void main(String args[]) {
17 final long number = 600851475143L;
18 long max = number/2;
19 for (long i = 2; i < max; i++) {
20 if (number % i == 0 && isPrime(i)) {
21 System.out.println(i);
22 }
23 }
24 }
25
26 }


 

domingo, 9 de octubre de 2016

Euler #1 y #2

Hay que mantenerse en forma...

https://projecteuler.net/problem=1

Python:
   1 #!/usr/bin/env python3
2
3 sum = 0
4 for n in range(1, 1000):
5 if n % 3 == 0 or n % 5 == 0:
6 sum += n
7
8 print (sum)
9




JavaScript:
   1 var sum = 0;
2 for (var i=1; i<1000; i++) {
3 if (i % 3 == 0 || i % 5 == 0) {
4 sum += i;
5 }
6 }
7 console.log(sum)
8





https://projecteuler.net/problem=2

PHP (iterativo):
   1 <?php
2
3 function fib($max) {
4 $ret = array(1, 2);
5 $x = 3;
6 while (true) {
7 $new = $ret[$x-2] + $ret[$x-3];
8 if ($new <= $max) {
9 $ret []= $new;
10 $x++;
11 } else {
12 break;
13 }
14 }
15 return $ret;
16 }
17
18
19 $sum = 0;
20 foreach (fib(4000000) as $n) {
21 if ($n % 2 == 0) {
22 $sum += $n;
23 }
24 }
25
26 echo "$sum\n";
27


 

viernes, 30 de septiembre de 2016

El (caótico) orden de los parámetros en PHP

¿En qué estaban pensando los desarrolladores del lenguaje PHP cuando lo empezaron a desarrollar?

Llevo más de 15 años programando en PHP y todavía tengo que hacer un esfuerzo consciente en recordar el orden de los parámetros en todas las funciones de búsqueda diversas y variadas, porque en unas lo que buscas va delante, y en otras el primer argumento es dónde lo buscas.
bool in_array ( mixed $needle , array $haystack [, bool $strict = FALSE ] )

bool array_key_exists ( mixed $key , array $array )

mixed strpos ( string $haystack , mixed $needle [, int $offset = 0 ] )

string strstr ( string $haystack , mixed $needle [, bool $before_needle = false ] )

int preg_match ( string $pattern , string $subject [, array &$matches [, int $flags = 0 [,int $offset = 0 ]]] )

De locos. Obsérvese preg_match y compárese con strpos, por ejemplo. La cadena sobre la que se busca en cada función es un argumento diferente. Hay que memorizar cada caso por separado, porque no hay una regla aparente.

miércoles, 27 de julio de 2016

Cómo subir un archivo de correo MBOX a Gmail

Tengo correos guardados en local desde hace varios años. Siempre que cambio de equipo tengo cuidado en copiarlos, están en su copia de seguridad, etc.

Pero al cliente de correo (suelo utilizar Thunderbird) ya no le gusta tener que manejar ficheros tan voluminosos (tengo algunos mbox de varios gigas). He optado por subirlos a Gmail.

La primera opción ha sido mapear la cuenta de Gmail en Thunderbird y copiar los mensajes desde el archivo local a las "carpetas" de Gmail, todo desde la interfaz de Thunderbird. Imposible, a veces da "timeout", otras veces el mismo proceso Thunderbird se cuelga.

He encontado la siguiente utilidad, imap_upload.py, un script en Python que hace lo que su nombre indica, subir a un servidor IMAP un fichero local de correo en formato MBOX.

El uso es trivial:
imap_upload.py --gmail --box="CarpetaRemota" FicheroMboxLocal

 

 

miércoles, 30 de marzo de 2016

Cacharreando con Arduino y MIDI

Sea una placa Arduinio, un "shield" MIDI, unos cuantos sensores piezoeléctricos y...


El código que mueve este prototipo de batería es trivial, sólo se limita a leer de las entradas analógicas, normaliza a los valores típicos del MIDI y dispara el sonido correspondiente.

Lo más complicado es equilibrar la sensibilidad de cada sensor, por ejemplo, el que dispara el "closed hi-hat" debe dar más volumen que el que dispara el "snare". También hay que intentar calibrar el rango dinámico de cada uno de los componentes de la batería.

/* Inputs mapping */
const int bass_drum_input = A0;   
const int snare_input = A1; 
const int closed_hihat_input = A2;
const int ride_input = A3;
const int crash_input = A4;

/* MIDI channel, instrument and notes */
const int channel=10;  
const int instrument=1; // Standard set
const int bass_drum_note = 36;   
const int snare_note = 38; 
const int closed_hihat_note = 42;
const int ride_note = 51;
const int crash_note = 49;
const int velocity_threshold = 10;
const int delay_between_commands = 30;
const int higher_value_from_piezo = 900;
int bass_drum_read, snare_read, closed_hihat_read, ride_read, crash_read = 0;


void setup() {
  Serial.begin(31250); //standard midi serial baud rate
  //Serial.begin(9600);          //  debug
  delay (200);
}

void loop() {

  bass_drum_read = analogRead(bass_drum_input);
  snare_read  = analogRead(snare_input);
  closed_hihat_read = analogRead(closed_hihat_input);
  ride_read = analogRead(ride_input);
  crash_read  = analogRead(crash_input);


  bass_drum_read = map(bass_drum_read, 0, higher_value_from_piezo, 10, 60);
  snare_read = map(snare_read, 0, higher_value_from_piezo, 10, 50);
  closed_hihat_read = map(closed_hihat_read, 0, higher_value_from_piezo, 10, 127);
  ride_read = map(ride_read, 0, higher_value_from_piezo, 10, 80);
  crash_read = map(crash_read, 0, higher_value_from_piezo, 10, 60);
  
  play_note(bass_drum_note, bass_drum_read);
  play_note(snare_note, snare_read);
  play_note(closed_hihat_note, closed_hihat_read);  
  play_note(ride_note, ride_read);
  play_note(crash_note, crash_read);
  
}

void play_note(int note, int velocity) {
   if (velocity > velocity_threshold) {
      midi_com(instrument);
      delay(delay_between_commands);  
      midi_note_on(note, 127);
      delay(delay_between_commands);
      midi_note_off(note, 0);   
    } 
}

void midi_note_on(int pitch, int velocity) {
  Serial.write(0x90+channel);
  Serial.write(pitch);
  Serial.write(velocity);
}

void midi_note_off(int pitch, int velocity) {
  Serial.write(0x80+channel);
  Serial.write(pitch);
  Serial.write(velocity);
}

void midi_com(int instrument) {
  Serial.write(0xC0+channel);
  Serial.write(instrument);
}


El código para inicializar la comunicación MIDI y enviar notas está basado en el que proponen en la página del shield.

2016-03-30 22.03.53

Placa Arduino One

2016-03-30 22.04.01

Shield MIDI, esquema de conexionado: https://www.olimex.com/Products/Duino/Shields/SHIELD-MIDI/resources/SHIELD-MIDI-sch.pdf

2016-03-30 22.04.32

El shield encajado en Arduino

2016-03-30 22.56.00

Sensores conectados al shield

lunes, 12 de octubre de 2015

Desempolvando Django (4)

Hasta ahora hemos desarrollado algo muy básico: un listado de marcadores. Casi todo lo que hemos hecho ha sido definir y configurar cosas.

Veamos los siguientes pasos:

  • Cada marcador podía llevar asociada una imagen, mostrémosla en el listado

  • Debemos asociar una hoja de estilo a nuestras páginas

  • Debemos añadir alguna forma de crear nuevos marcadores y editar los existentes

  • Hay que preparar la aplicación para ser servida en un entorno "de producción", no desde el servidor de desarrollo

  • Y muchas más características y mejoras, por supuesto, pero vamos poco a poco


Mostrando la imagen en el listado


Recordemos cómo era nuestro modelo de datos (models.py):
from django.db import models

class Marcador(models.Model):
    titulo = models.CharField(max_length=200)
    url = models.CharField(max_length=500)
    descripcion = models.CharField(max_length=500)
    image = models.ImageField(upload_to='savebookmarks')

    def __str__(self):
        return self.titulo + ' (' + self.url + ')'

Al definir el atributo imagen especificamos la ruta donde se subirán las imágenes, es relativa al parámetro MEDIA_ROOT de settings.py

Si hemos creado unos cuantos objetos desde el "/admin", podremos mostrar su imagen en el listado. Para ello, en desarrollo, definiremos en settings.py una ruta más para ser servida como recurso estático. Esto "en producción" habrá que cambiarlo, pero por ahora seguimos con ello así:

En settings.py
STATIC_URL = '/static/'
MEDIA_ROOT = BASE_DIR + '/media'
STATICFILES_DIRS = (BASE_DIR + '/savebookmarks', MEDIA_ROOT)

STATIC se refiere a cómo se sirven recursos estáticos (hojas de estilo, JS, iconos, etc). MEDIA_ se refiere a cómo se sirven ficheros subidos por los usuarios. Por seguridad, en producción, separaremos totalmente ambos tipos de recursos. En desarrollo, los servimos igualmente con la app 'django.contrib.staticfiles' mediante la definición STATICFILES_DIRS.

En la template, incorporamos la imagen (marcador_list.html):
{% load staticfiles %}
<link rel="stylesheet" type="text/css" href="{% static 'savebookmarks/estilo.css' %}" />

<h2>Marcadores</h2>
<ul>
    {% for m in object_list %}
        <li>
            <img src="{% static m.image %}" width="40" />
            <a target="_blank" href="{{m.url}}">{{ m.titulo }}</a>
            | (<a href="{% url "editMarcador" m.id %}">Editar</a>)
        </li>

    {% endfor %}
</ul>

<p><a href="{% url "addMarcador" %}">Añadir otro</a></p>

El tag {% static %} es atendido por la app 'django.contrib.staticfiles' y nos proporciona la URL relativa para ser servida por el servidor de desarrollo. En producción habrá que revisar esto. El resto de código lo revisaremos a continuación.

Enlazando una hoja de estilos


directorios

Como definimos en settings.py un
STATIC_URL = '/static/'

ahora podemos poner bajo esta URL nuestras hojas de estilo. El árbol de directorios del proyecto se va pareciendo a la figura.

La app 'django.contrib.staticfiles' encontrará los ficheros bajo static/ y los servirá adecuadamente.

Para enlazar estos ficheros, usamos el tag {% static %}, como se ha mostrado en la template.

En el paso a producción lo que se hace es utilizar un servidor web independiente para servir los recursos, en la documentación de Django se explica cómo hacerlo, por ahora mantenemos esta configuración en desarrollo.

Crear y editar marcadores


En este punto, utilizaremos el sistema de formularios de Django, que está muy bien pensado.

 

 

 

lunes, 5 de octubre de 2015

Desempolvando Django (3)

Lo siguiente que vamos a hacer es una vista para mostrar los marcadores ya guardados. Una de las tareas más típicas es sacar un listado de objetos guardados en la base de datos, para ello, Django tiene ya una vista preparada y lista para usar. Editemos nuestro savebookmarks/views.py
from django.views.generic import ListView
from savebookmarks.models import Marcador

class MarcadoresList(ListView):
    model = Marcador

Ya está, tenemos una vista que hereda de ListView en la que lo único que tenemos que hacer es decirle cuál es el modelo que se corresponde con los objetos que queremos listar.

En savebookmarks/urls.py, la invocamos con el método as_view() (que devuelve una respuesta adecuada como objeto HttpResponse)
from django.conf.urls import url
from . import views

urlpatterns = [
    url(r'^$', views.MarcadoresList.as_view()),
]

Finalmente, necesitaremos una plantilla o template que muestre los objetos que está recuperando la ListView para nosotros.

Para que Django encuentre las plantillas, vamos a editar el settings.py dejando así la clave DIRS (en TEMPLATES):
'DIRS': os.path.join(SETTINGS_PATH, 'templates'),

(Se supone que con el ajuste  'APP_DIRS': True, Django ya es capaz de encontrar nuestras templates, pero parece que no funciona)

A continuación, escribimos la template en /savebookmarks/templates/savebookmarks/marcador_list.html:
<h2>Marcadores</h2>
<ul>
    {% for m in object_list %}
        <li><a target="_blank" href="{{m.url}}">{{ m.titulo }}</a></li>
    {% endfor %}
</ul>

La ListView nos proporciona una lista de objetos, object_list, sobre la que iteremos para mostrarlos.

marcadores

viernes, 2 de octubre de 2015

Desempolvando Django (2)

En la anterior entrada vimos cómo montar el entorno de desarrollo, ahora empezamos a esbozar el proyecto.

Se trata de una web básica que permite guardar marcadores, como el antiguo delicious.

Empezaremos con una sola pantalla que liste los marcadores guardados y ofrezca un campo (input) para guardar uno nuevo.

Recién creado el proyecto, la estructura de directorios es la siguiente:

  • <Raíz del proyecto>/

    • manage.py

    • marcadores/ (en este directorio editaremos los siguientes archivos):

      • urls.py

      • settings.py






Recordamos que el proyecto se creó con el comando
django-admin startproject marcadores

Ahora tenemos que crear una "app" dentro del proyecto
  python manage.py startapp savebookmarks

En esta app es donde escribiremos nuestro código. Por ahora seguiremos más o menos el tutorial de Django "Writing your first Django app".

Finalmente, en /marcadores/urls.py, mapearemos las dos URLs principales que utilizaremos, /admin y /savebookmarks
from django.conf.urls import include, url
from django.contrib import admin

urlpatterns = [
    url(r'^savebookmarks/', include('savebookmarks.urls')),
    url(r'^admin/', include(admin.site.urls)),
]

Con esto, tras ajustar los parámetros necesarios en el settings.py y hacer un python manage.py migrate, podemos echar a andar el servidor de desarrollo.

(En versiones anteriores de Django no se disponía de migrations, si no recuerdo mal, había que hacerlo con una herramienta externa, South).

Empecemos a hacer algo útil en la app "savebookmarks"

Definamos el modelo (/savebookmarks/models.py)
from django.db import models

class Marcador(models.Model):
    titulo = models.CharField(max_length=200)
    url = models.CharField(max_length=500)
    descripcion = models.CharField(max_length=500)
    image = models.ImageField(upload_to='savebookmarks')

    def __str__(self):
        return self.titulo + ' (' + self.url + ')'

También crearemos un /savebookmarks/admin.py para poder actualizar desde la interfaz administrativa:
from django.contrib import admin
from .models import Marcador
admin.site.register(Marcador)

En este momento, como ha habido cambios en los modelos de datos, habrá que hacer el flujo de makemigrations/migrate como se describe en el tutorial.

En este punto, ya podemos empezar a crear desde la interfaz administrativa (http://127.0.0.1:8000/admin) algunos objetos del tipo marcador para no empezar con una base de datos vacía.

Lo siguiente, es crear la pantalla inicial. Lo más sencillo es recuperar el listado de marcadores ya creados, necesitamos definir una URL y una vista que la sirva. Definamos que la URL "/" sea atendida por la vista index, en /savebookmarks/urls.py
from django.conf.urls import url
from . import views
urlpatterns = [
    url(r'^$', views.index, name='index'),
]

Recordemos que las URL "maestras" se definen a nivel proyecto (marcadores/urls.py) y aportan el prefijo, de tal forma que una URL con el patrón /savebookmarks será definida por savebookmarks/urls.py (fichero de definición de URLs de la app).

Sólo para comprobar que funciona, definimos una función index() en savebookmarks/views.py
from django.http import HttpResponse

def index(request):
    return HttpResponse("Aquí iría el listado de marcadores")

Y eso es todo por ahora. Lo siguiente es pensar cómo vamos a montar estas vistas, porque hay varias alternativas a la hora de escribir vistas:

  • Vistas como funciones (ejemplo anterior)

  • Class-based views (https://docs.djangoproject.com/en/1.8/topics/class-based-views/), que a su vez pueden ser

    • Genéricas

    • Escritas por nosotros



sábado, 5 de septiembre de 2015

Desempolvando Django (1)

Hace como 3 años que no toco una línea de código Python, y, teniendo en cuenta que estuve una buena temporada trabajando como desarrollador Django, se me cae un poco la cara de vergüenza de estar tan oxidado.

Como estoy un poco cansado del ecosistema PHP, voy a ponerme de deberes para este curso que empieza volver a ser competente en Django.

Empezamos con lo más básico, la instalación. La forma fácil (y poco práctica en un entorno profesional) es bajarse el zip o tgz y darle al setup.py, o, peor todavía, usar la versión que nuestro gestor de paquetes nos proporciona (por ejemplo, apt-get install django).

En el momento de escribir esto, la versión en curso de Django es 1.8.4, y la forma recomendada de instalación es con pip.

Personalmente, recuerdo que es muy cómodo usar virtualenv para desarrollar Python, nos libramos de problemas con diferentes versiones, podemos probar actualizaciones en cada virtualenv, etc.

Suponiendo que tenemos ya instaladas las utilidades virtualenv y pip, allá vamos:

  1. Creamos un virtualenv con Python 3:
    yo@home:~/Desarrollo$ virtualenv --python=python3 django1.8.4

    Esto crea un virtualenv con Python3 en el directorio "django1.8.4"

  2. A continuación, nos metemos en el directorio recién creado y activamos el virtualenv:
    yo@home:~/Desarrollo$ cd django1.8.4/
    yo@home:~/Desarrollo/django1.8.4$ source bin/activate


  3. Instalamos Django ya con el virtualenv activado:
    (django1.8.4)yo@home:~/Desarrollo/django1.8.4$ pip install Django==1.8.4

    Vemos que nos queda Django sólo instalado para este entorno:
    (django1.8.4)yo@home:~/Desarrollo/django1.8.4$ ls lib/python3.4/site-packages/
    django Django-1.8.4.dist-info ...

    Con esto ya tenemos montado el virtualenv con una instalación propia de Python 3 y Django 1.8.4, sin afectar a la instalación que haya en el sistema.

  4. Con este entorno activado ya podemos salir del directorio donde hemos instalado el virtualenv y crear el proyecto Django en cualquier otro directorio. Procedemos de la forma habitual, con un startproject y comprobando que se crea el manage.py y el directorio para el código del proyecto:
    (django1.8.4)yo@home:~/Desarrollo/django1.8.4$ cd ..
    (django1.8.4)yo@home:~/Desarrollo$ django-admin startproject marcadores
    (django1.8.4)yo@home:~/Desarrollo$ cd marcadores/
    (django1.8.4)yo@home:~/Desarrollo/marcadores$ ls
    manage.py  marcadores


  5. Nos puede surgir la duda... pero si yo ahora invoco Python... ¿cuál estaré usando? Respuesta: si estamos con el virtualenv activado, será el que tenemos en nuestro directorio virtualenv:
    (django1.8.4)yo@home:~/Desarrollo/marcadores$ which python 
    /home/yo/Desarrollo/django1.8.4/bin/python

    Si desactivamos el virtualenv, volvemos al intérprete por defecto instalado en el sistema:
    (django1.8.4)yo@home:~/Desarrollo/marcadores$ deactivate
    yo@home:~/Desarrollo/marcadores$ which python
    /usr/bin/python



Por ahora ya hemos preparado el entorno. En la siguiente sesión, empezaremos a programar.

 

lunes, 15 de junio de 2015

Adiós, Sourceforge, que te zurzan

Durante muchos años, Sourceforge fue uno de los principales repositorios de software "open source". GitHub le quitó el puesto, pero me temo que son ellos mismos (SF) los que se acaban de poner el último clavo al ataúd.

Desde hace un tiempo, empezaron a poner adware en los instaladores de los desarrolladores, llegando al límite de quedarse con la cuenta de algunos proyectos emblemáticos, como GIMP, VLC, Nmap o Notepad++, al más puro estilo Softonic (parece que estos últimos ya se han reformado, menos mal).

Todos estamos de acuerdo en que hay que buscar vías de financiación, pero apoderarse del código e instalar crapware a los usuarios finales... no puede ser.

Les deseo una pronta desaparición y que se busquen la vida de una forma más honrada y respetuosa con los usuarios y desarrolladores.

Actualización: parece que también han llenado de crapware un instalador de Firefox. Morir matando, lo llaman.

viernes, 8 de mayo de 2015

El venerable cowsay

Lo bien que me lo pasaba "tuneando" la shell con cowsay... Lo divertido es que se lo enseñas a gente joven y se queda alucinada ;-)

pantallazo-2015-05-08_091358

martes, 1 de octubre de 2013

Probabilidades y palabrotas

Situación típica: escribimos un fragmento de código para generar contraseñas más o menos aleatorias:

$src = 'abcdefghijkmlnopqrstuvwxyz0123456789';
$new = "";
$x = 0;
while($x < 8){
  $new .= substr($src, rand(0, strlen($src)-1), 1);
  $x++;
}

Estas líneas generan una cadena aleatoria de 8 caracteres alfanumérico. ¿Cuál es la probabilidad de que salga alguna palabrota? Tengo que recordar mis rudimentos de combinatoria, pero podemos asegurar que es una probabilidad bastante baja.

Pues bien, por baja que sea, no es cero. Caso verídico: he tenido una queja de una persona que se quejaba de que en la contraseña que le enviamos ponía "puta".

Vamos a comprobarlo mediante fuerza bruta:

<?php

function generarCadena($src, $len) {
    $x = 0;
    $new = "";
    while($x < $len) {
      $new .= substr($src, rand(0, strlen($src)-1), 1);
      $x++;
    }
    return $new;
}

ini_set('display_errors', FALSE);
$opts = getopt('s:l:w:');
if(!$opts['w']) die("-w");
$word = $opts['w'];
$src = isset($opts['s']) ? $opts['s'] : 'abcdefghijkmlnopqrstuvwxyz0123456789';
$len = $opts['l'] && is_numeric($opts['l']) ? (int)$opts['l'] : 8;
$count = 0;

while(true) {
    $new = generarCadena($src, $len);
    if(strpos($new, $word) !== FALSE) {
        die("Encontramos '$word' en la cadena '$new' tras ".number_format($count, 0, ",", ".")." iteraciones.n");
    }
    $count++;
}

?>

En este fragmento de código pedimos una palabra y empezamos a generar cadenas con una longitud de $len a partir de la semilla $src hasta que la encontramos.  Veamos cuánto tarda en aparecer la palabra "puta":

david@localhost:~/dev$ while(true); do php testRandomString.php -w puta  -l 8; done
Encontramos 'puta' en la cadena 'putarzd7' tras 113.588 iteraciones.
Encontramos 'puta' en la cadena 'tputag94' tras 745.111 iteraciones.
Encontramos 'puta' en la cadena 'oputa6in' tras 540.359 iteraciones.
Encontramos 'puta' en la cadena 'vputazze' tras 321.592 iteraciones.
Encontramos 'puta' en la cadena 'putarx1t' tras 331.196 iteraciones.
Encontramos 'puta' en la cadena 'puta75cs' tras 782.809 iteraciones.
Encontramos 'puta' en la cadena 'crputazr' tras 180.502 iteraciones.
Encontramos 'puta' en la cadena '13qqputa' tras 56.523 iteraciones.
Encontramos 'puta' en la cadena 'g1puta3n' tras 12.595 iteraciones.
Encontramos 'puta' en la cadena '6fg6puta' tras 190.036 iteraciones.
Encontramos 'puta' en la cadena 'oputayon' tras 644.111 iteraciones.
Encontramos 'puta' en la cadena 'ckputako' tras 170.121 iteraciones.
Encontramos 'puta' en la cadena 'jyputaob' tras 285.211 iteraciones.
Encontramos 'puta' en la cadena 'avputao5' tras 864.466 iteraciones.
Encontramos 'puta' en la cadena '88putaqa' tras 605.127 iteraciones.
Encontramos 'puta' en la cadena '9putawoi' tras 101.390 iteraciones.
Encontramos 'puta' en la cadena 'oputaik1' tras 15.688 iteraciones.
Encontramos 'puta' en la cadena '7puta940' tras 19.865 iteraciones.
Encontramos 'puta' en la cadena 'fjputajw' tras 355.393 iteraciones.
Encontramos 'puta' en la cadena 'putafpkn' tras 273.997 iteraciones.
Encontramos 'puta' en la cadena '7asputa5' tras 4.631 iteraciones.
Encontramos 'puta' en la cadena 'eputartt' tras 484.259 iteraciones.
Encontramos 'puta' en la cadena 'gz5putax' tras 266.215 iteraciones.
Encontramos 'puta' en la cadena '4ftpputa' tras 834.145 iteraciones.
Encontramos 'puta' en la cadena 'ryoputas' tras 318.049 iteraciones.
^C

No es tan improbable como pensábamos. En la línea marcada se ve cómo ha habido un ciclo de ejecuciones en las que sólo han sido necesarias 4.631 iteraciones para que aparezca la palabra que buscamos.

Conclusión: ojo con los generadores de palabras "aleatorios". Pueden generar patrones reconocibles que pueden ofender a algún usuario.

Por curiosidad, también lo he probado con palabras más largas:

david@localhost:~/dev$ while(true); do php testRandomString.php -w zorro -l 8; done
Encontramos 'zorro' en la cadena 'kozorro7' tras 14.560.786 iteraciones.
Encontramos 'zorro' en la cadena 'zorro9v7' tras 1.676.779 iteraciones.
Encontramos 'zorro' en la cadena 'zorro6i3' tras 6.871.325 iteraciones.
^C

david@localhost:~/dev$ while(true); do php testRandomString.php -w cabron -l 8; done
Encontramos 'cabron' en la cadena '0hcabron' tras 935.995.307 interaciones.
^C

viernes, 13 de septiembre de 2013

Xscreensaver - BSOD

Me encanta el BSOD que viene como salvapantallas. Resulta que ahora lo empaquetan por separado. El WARNING no tiene desperdicio.

 

yo@localhost:~$ apt-cache show xscreensaver-screensaver-bsod
Package: xscreensaver-screensaver-bsod
Priority: optional
Section: universe/x11
Installed-Size: 524
Maintainer: Ubuntu Developers
Original-Maintainer: Jose Luis Rivas
Architecture: amd64
Source: xscreensaver
Version: 5.15-2ubuntu1
Replaces: xscreensaver-data-extra (<< 5.10-4)
Depends: libc6 (>= 2.7), libgdk-pixbuf2.0-0 (>= 2.22.0), libglib2.0-0 (>= 2.12.0), libx11-6, libxext6, libxmu6, libxt6, xscreensaver-data-extra
Conflicts: xscreensaver-data-extra (<< 5.10-4)
Filename: pool/universe/x/xscreensaver/xscreensaver-screensaver-bsod_5.15-2ubuntu1_amd64.deb
Size: 151922
MD5sum: c9b4848ea9a0a6f651af766bc83e984d
SHA1: 14debe28946f9ccbf010f54588b8852bedf43c26
SHA256: a800c369299009e2dd36f833cd5c092e5893e0e78a4026b5e7c00469e6a0523b
Description-en: BSOD hack from XScreenSaver
 This package ships the hack BSOD that shows the popular Blue Screens of Death
 from several OSes including BSD, Windows, Linux, Solaris, Apple and much more.
 .
 WARNING: This screensaver could be confused with a real BSOD and could lead an
 user to reboot the machine with consequences like data loss.

Homepage: http://www.jwz.org/xscreensaver/
Description-md5: fa1f8a3ec4ff356bc203ff76a929e702
Bugs: https://bugs.launchpad.net/ubuntu/+filebug
Origin: Ubuntu

miércoles, 24 de julio de 2013

La caída de "The Old Reader"

Antes de cerrar Google Reader busqué algún servicio alternativo y encontré The Old Reader. Importó mis suscripciones bien y desde hace varios meses soy un feliz usuario.
Hasta este domingo, que estaba caído. Parecía que estaban haciendo un mantenimiento. En el momento en que escribo esto llevan más de 72 horas sin servicio. Han tenido graves problemas con los servidores y tienen que restaurar la base de datos desde el último backup.


Esta entrada no es una queja, faltaría más. Sé lo que supone restaurar desde un backup del orden de decenas de Gigas. Su base de datos parece que rondaba un Tera, así que el proceso debe ser una pesadilla.



Sólo me pregunto si The Old Reader sobrevivirá a esta caída. Los usuarios no solemos ser muy pacientes. Me encantaría saber las cifras de nuevos usuarios de otros servicios similares en los últimos días.
Les deseo un pronto restablecimiento. Desconozco la fuente de financiación de The Old Reader, pero me temo que peligre.

Actualización (29/07/2013): 
Parezco un cuervo de mal agüero, pero es que lo de la semana pasada pintaba muy mal. The Old Reader cierra y se mantendrá como un servicio privado para unas pocas cuentas escogidas:
http://blog.theoldreader.com/post/56798895350/desperate-times-call-for-desperate-measures

Actualización final:
Parece que todo vuelve a la normalidad, vuelven a funcionar en abierto. Bienvenidos sean.

miércoles, 19 de junio de 2013

UTF8 sí o sí

Función susceptible de mejora, pero extremadamente útil.

/**
* Sirve en UTF-8 sí o sí.
*/
function supericonv($txt) {
    $txt1 = iconv('ISO-8859-15', 'UTF-8', $txt);
    $retVal = strpos($txt1, 'Ã') === FALSE ? $txt1 : $txt;
    $retVal = str_replace(array("¿", "»", "«"), array("¿", "»", "«"), $retVal);
    return $retVal;
}

viernes, 13 de abril de 2012

(n+1)*n/2

La suma de los N primeros números naturales es
S = (N+1)*N/2

Utilidad aplicada:

Supongamos un modelo de datos (una tabla) en el que hay un atributo para ordenar, un entero correlativo que empieza en 0. Para garantizar la coherencia de los datos, tenemos que asegurarnos que cada operación de modificación deja la tabla bien:

Aquí tenemos un ejemplo de datos coherentes:
+--------+-------+
| _order | id    |
+--------+-------+
|      0 | 18685 |
|      1 | 21971 |
|      2 | 21979 |
|      3 | 21977 |
|      4 | 21975 |
|      5 | 21973 |
|      6 | 22047 |
+--------+-------+


Y aquí uno de datos no coherentes: se ha borrado un dato pero no se ha actualizado bien el _order en la tabla:

+--------+-------+
| _order | id    |
+--------+-------+
|      0 | 18685 |
|      1 | 21971 |
|      2 | 21979 |
|      3 | 21977 |
|      4 | 21975 |
|      6 | 21973 |
|      7 | 22047 |
+--------+-------+


¿Cómo comprobar en una tabla estas incoherencias? Aquí entra en juego nuestra fórmula.

Si todo está bien, la suma de los _order + 1 debe ser igual a la suma de los n enteros hasta los N registros que tengamos en la tabla.



SELECT COUNT(id), 
cliente,
SUM(_order+1) AS suma_order,
ROUND((COUNT(id)+1)*COUNT(id)/2, 0) AS suma_buena
FROM tabla
GROUP BY cliente
HAVING ROUND((COUNT(id)+1)*COUNT(id)/2, 0) != SUM(_order+1)
ORDER BY COUNT(id);

jueves, 14 de julio de 2011

Tira nº 76: usuaria avanzada


A ver cuánto tardan en sacar algo así. Sacar algo relativo a la teoría de conjuntos debe ser una tentación muy fuerte :-)