Programa para resolver ecuaciones diofánticas

En el artículo anterior expliqué cómo se resuelven las ecuaciones diofánticas y su relación con las ecuaciones de congruencia. En la presente entrada vamos a ver un programa en Python que las resuelve.

#! /usr/bin/env python
# -*- coding: utf-8 -*-
# Resuelve ecuaciones diofánticas tipo ax + by = c

import sys
from sys import argv

def extendedEuclideanAlgorithm(old_r, r):
    negative = False
    s, old_t = 0, 0
    old_s, t = 1, 1

    if (r < 0):
        r = abs(r)
        negative = True
        
    while r > 0:
        q = old_r / r
        #MCD:
        r, old_r = old_r - q * r, r
        #Coeficiente s:
        s, old_s = old_s - q * s, s
        #Coeficiente t:
        t, old_t = old_t - q * t, t
        
    if negative:
        old_t = old_t * -1
        
    return old_r, old_s, old_t

a = long(argv[1])
b = long(argv[2])
c = long(argv[3])

mcd, s, t = extendedEuclideanAlgorithm(a, b)
if c % mcd == 0:
    a1, b1, c1 = -a / mcd, b / mcd, c / mcd
    x1, y1 = s * c1, t * c1
    print "x = {0}{1:+d}k" . format(x1, b1)
    print "y = {0}{1:+d}k" . format(y1, a1)
else:
    print "No tiene solución"

Para calcuar 23x -4y = 11 hacemos:

vic@LESBIAN:~/mates$ ./diofanticas.py 23 -4 11
x = -11-4k
y = -66-23k

Aplicando el concepto de clase de equivalencia, tal y como se explica en el anterior artículo, podemos computar la forma paramétrica más simplificada como muestra el siguiente programa:

#! /usr/bin/env python
# -*- coding: utf-8 -*-
# Resuelve ecuaciones diofánticas tipo ax + by = c
# El primero par x e y es la forma parmétrica más simplificada
 
import sys
from sys import argv

def extendedEuclideanAlgorithm(old_r, r):
    negative = False
    s, old_t = 0, 0
    old_s, t = 1, 1

if (r < 0):
    r = abs(r)
    negative = True

while r > 0:
    q = old_r / r
    #MCD:
    r, old_r = old_r - q * r, r
    #Coeficiente s:
    s, old_s = old_s - q * s, s
    #Coeficiente t:
    t, old_t = old_t - q * t, t

if negative:
    old_t = old_t * -1

return old_r, old_s, old_t

a = long(argv[1])
b = long(argv[2])
c = long(argv[3])

mcd, s, t = extendedEuclideanAlgorithm(a, b)
if c % mcd == 0:
    a1, b1, c1 = a / mcd, b / mcd, c / mcd
    x1, y1 = s * c1, t * c1
    # Uso abs() pues Python no hace la división Euclídea con cociente negativo
    equivClass = x1 % abs(b1)
    print "x = {0}{1:+d}k" . format(equivClass, b1)
    print "y = {0}{1:+d}k" . format((c1 - (a1 * equivClass)) / b1, -a1)
    print "x = {0}{1:+d}k" . format(x1, b1)
    print "y = {0}{1:+d}k" . format(y1, -a1)
else:
    print "No tiene solución"

Este es el resultado de diferentes ejecuciones:

vic@LESBIAN:~/mates$ ./diofanticas.py 4 7 29
x = 2+7k
y = 3-4k
x = 58+7k
y = -29-4k
vic@LESBIAN:~/mates$ ./diofanticas.py 23 -4 11
x = 1-4k
y = 3-23k
x = -11-4k
y = -66-23k

El uso de la función abs(), que nos devuelve el valor absoluto, es debido a que Python no hace la división Euclídea cuando el cociente es negativo. Los dos pasos:

  • q = old_r / r
  • old_r = old_r – q * r

Se podrían unificar con la función divmod(), pero me parece menos claro para quien no conoce el lenguaje, siendo los dos pasos más parecidos al pseudocódigo.

Programa para resolver ecuaciones de congruencia lineal

Editado el 11/03/2018:

Casi un año después de su publicación, observo en Google Analytics que este artículo tiene un porcentaje de rebote muy elevado. Tal vez este sea debido a que la mayoría de visitantes estén buscando simplemente un programa que resuelva congruencias lineales. Si es tu caso, puedes instalar fácilmente el intérprete de Python 2 en tu ordenador, es muy ligero y existe para todas las plataformas. Como alternativa, dispones de intérpretes en línea (1, 2) donde puedes copiar y pegar el código; después deberás modificarlo ligeramente porque los parámetros no serán argumentos pasados a través de la línea de comandos.


Una ecuación de congruencia es una congruencia entre expresiones literales, los valores que la satisfacen son sus soluciones. Por ejemplo 13x ≡ 5 (mod 77) es una ecuación de congruencia.

Sus soluciones nos las da x ≡ 30 (mod 77) Es decir, todos los infinitos x que al dividirlos por 77 dan el mismo resto que 30/77 Dicho resto es 30 y todos los x serían múltiplos de 77· + 30: 30, 107, 184, 261…

Si reemplazamos estos x en la ecuación de congruencia lineal inicial veremos que son soluciones:

13*30 ≡ 30 (mod 77) => Resto 5. Así como dan el mismo resto:

13*107 ≡ 30 (mod 77)

13*184 ≡ 30 (mod 77)

Para poder encontrar la solución de una congruencia lineal en primer lugar hay que saber hallar el inverso de a (mod m) Este inverso, a’, es un número tal que a’*a ≡ 1 (mod m)

Por ejemplo, el inverso de 6 (mod 11) es 2: 2*6 ≡ 1 (mod 11)

Teorema: La condición necesaria y suficiente para que dos números a y b sean congruentes respecto al módulo m es que su diferencia sea un múltiplo de m, es decir, m | (a -b)

Por lo tanto en el ejemplo sería 11 | (a’ * 6 – 1) => 6a’ – 1 = 11· , 6a’ = 11· + 1 Como ha de ser un múltiple de 11 + 1 vemos fácilmente que a’ = 2 cumple la condición.

Si para hallar el inverso de 2 (mod 8) hacemos: 8 | (a’ * 2 – 1) Vemos que no hay solución pues 2a’ -1 siempre será un número impar mientras que 8 es par. Por lo tanto no siempre existe un inverso. El siguiente teorema nos permite saber cuando existe el inverso:

La condición necesaria y suficiente para que un entero a posea un inverso módulo m, m > 1, es que mcd(a, m) = 1 Además, si existe tal inverso, ese inverso es único módulo m.

Esto nos resulta familiar con el teorema de Bezout explicado en el artículo anterior. Por ejemplo, para calcular el inverso de 111 (mod 250) primero calculamos mcd(111, 250) = 1 Por lo tanto sabemos que existe un inverso a’ y que habrá un s y un t tal que:

111 *s + 250 * t = 1

Aplicando el algoritmo del artículo obtenemos s = -9 y t = 11 y por lo tanto a’ = -9. Así es:

-9 * 111 ≡ 1 (mod 250)

  • -999 dividido entre 250 da el mismo resto que 1 entre 250, r = 1
  • 250 | (-9 * 111 -1) => 250· = -9 * 111 – 1 => Efectivamente -1000 es múltiplo de 250

Ahora bien, existe un teorema que matiza el anterior:

Existe un entero positivo único a’ menor que m que es un inverso de a (mod m) y cualquier otro inverso de a (mod m) será congruente con a’ (mod m)

Como dijimos en el anterior artículo, el par s y t no es único. La expresión general que nos da todos los coeficientes es:

1 =111(-9 + 250h) + 250(4 – 111h) ∀ h ∈ Z

Para h = 1 tendríamos:

1 = 111*241 + 250*(-107)

Como s = 241 y t = -107 tendremos 111*241 ≡ 1 (mod 250) Por lo tanto es 241 el inverso positivo menor que 250.

Si por un lado tenemos la ecuación de congruencia lineal ax ≡ b (mod m) y esta tiene un a’ ∈ Z tal que a’*a = 1 (mod m) Por otro lado, como cualquier número es congruente consigo mismo módulo m podemos crear la congruencia a’ ≡ a’ (mod m).

Un importante teorema dice:

Sean a, b, c y d enteros y m un entero positivo. Si es ab (mod m) y cd (mod m), entonces también es ac ≡ bd (mod m).

Con este teorema, de las dos congruencias anteriores resulta:

ax * a’ ≡ a’ * b (mod m)

Como a’ * a (mod m) = 1 Queda:

1 * x ≡ a’ * b (mod m)

Si existe el inverso modular, es decir, si m y a son primos relativos, o lo que es lo mismo, si mcd(m, a) = 1, todo x congruente con a’ * b (mod m) será solución a la ecuación de congruencia.

Vamos a calcular la solución de 3x ≡ 5 (mod 13):

El programa del artículo anterior, para a y m, es decir, 3 y 13 nos devuelve:

m.c.d. = 1

Bezout s = -4

Bezout t = 1

Como a y m son primos entre si, sin más cálculos podemos ver que el inverso de 3 (mod 13) es 9, pero procedamos como hemos visto. Si el inverso es -4 necesitamos encontrar el positivo, busquemos para ello otro par s y t con h = 1:

1 = 3*(-4 + 13*1) + 13*(1 – 3*1) = 3*9 + 13*(-2)

Por esta vía también obtenemos a’ = 9. Todo x congruente con 9*5 (mod 13) será solución. Como 45 (mod 13) = 6, todos los múltiples de 13· + 6 serán las soluciones a 3x ≡ 5 (mod 13). Por lo tanto, x ≡ 6 (mod 13) es una congruencia equivalente más simple.

Vamos a calcular la solución de 5x ≡ 2 (mod 6):

El programa nos devuelve:

m.c.d. =  1
Bezout s =  -1
Bezout t =  1

Como a y m son primos entre si, tendremos que el inverso a’ es -1. Busquemos el positivo:

1 = 5 * (-1 + 6*1) + 6*(1 – 5*1) = 5 * 5 + 6 * -4

El inverso positivo menor que m es 5.

5*2 (mod 6), 10 (mod 6), x ≡ 10 (mod 6) Los infinitos x congruentes con 10 módulo 6 serán solución de 5x ≡ 2 (mod 6). Es decir, todos los múltiples 6· + 4 Por lo tanto, es equivalente x ≡ 4 (mod 6)

Cuando a y m no son primos entre si, es decir mcd(a, m) > 1 y b es un múltiplo del mcd, es decir mcd | b, la congruencia tiene solución. Si queremos entender el proceso para hallar las soluciones y no limitarnos a aplicar fórmulas será necesario introducir los conjuntos de restos y las clases de equivalencia, pero para ello será necesario un nuevo artículo.

El siguiente programa en Python calcula las ecuaciones de congruencia que se han tratado en este artículo.

#! /usr/bin/env python
# -*- coding: utf-8 -*-
# Resuelve ecuaciones de congruencia tipo ax ≡ b (mod m)

import sys
from sys import argv

def extendedEuclideanAlgorithm(old_r, r):
    negative = False
    s, old_t = 0, 0
    old_s, t = 1, 1

    if (r < 0):
        r = abs(r)
        negative = True

    while r > 0:
            q = old_r / r
            #MCD:
            r, old_r = old_r - q * r, r
            #Coeficiente s:
            s, old_s = old_s - q * s, s
            #Coeficiente t:
            t, old_t = old_t - q * t, t

    if negative:
        old_t = old_t * -1
        
    return old_r, old_s, old_t

a = long(argv[1])
b = long(argv[2])
m = long(argv[3])
#Para las soluciones no necesitaremos t pero lo recogemos para evitar el error.
mcd, s, t = extendedEuclideanAlgorithm(a, m)
if mcd == 1:
    inverse = (s * b) % m
    print "x ≡ {0} (mod {1})" . format(inverse, m)
elif b%mcd == 0:
    print "Ver siguiente artículo"
else:
    print "No tiene solución"

Para resolver la ecuación 3x ≡ 5 (mod 13) haríamos:

vic@LESBIAN:~/mates$ ./inversoModulo.py 3 5 13
x ≡ 6 (mod 13)

Cálculo del mcd y la identidad de Bezout

Editado el 11/03/2018:

Casi un año después de su publicación, observo en Google Analytics que este artículo tiene un porcentaje de rebote muy elevado. Tal vez este sea debido a que la mayoría de visitantes estén buscando simplemente un programa que calcule el mcd de dos números y sus coeficientes de Bezout. Si es tu caso, puedes instalar fácilmente el intérprete de Python 2 en tu ordenador, es muy ligero y existe para todas las plataformas. Como alternativa, dispones de intérpretes en línea (1, 2) donde puedes copiar y pegar el código; después deberás modificarlo ligeramente porque los parámetros no serán argumentos pasados a través de la línea de comandos.


He hecho un breve programa en Python que calcula el máximo común divisor (mcd) de dos números pasados como parámetros y sus coeficientes de Bezout. El teorema de Bezout nos dice que dados dos números a y b, cuyo mcd(a, b) = d existen dos números enteros s y t tales que:

a*s + b*t = d

Estos dos números s y t son los llamados coeficientes de Bezout. Este par s y t no tiene por qué ser único, pueden haber otros s y t que cumplan la igualdad. La identidad de Bezout no es una mera curiosidad matemática sino que tiene muchas aplicaciones: desde calcular el inverso de a módulo m, lo que permite resolver ecuaciones de congruencia lineal, hasta resolver ecuaciones diofánticas.

El máximo común divisor se puede calcular a mano como nos enseñaron en la escuela mediante el algoritmo de Euclides. Los coeficientes de Bezout los podemos calcular a mano mediante el algoritmo extendido de Euclides, pero a la hora de programar es más fácil implementar el siguiente algoritmo explicado en la Wikipedia.

Todos estos valores se calculan mediante la división euclídea y esta impone la condición 0 >= r < |d| Es decir, el resto debe ser mayor o igual a 0 y menor que el valor absoluto del divisor. Como expliqué en un artículo anterior, Python no hace la división euclídea cuando el divisor es negativo, por lo tanto en el código hago una pequeña manipulación al signo del segundo número para obtener los coeficientes de Bezout con los signos correctos.

#! /usr/bin/env python
# -*- coding: utf-8 -*-

import sys
from sys import argv

old_r = long(argv[1])
r = long(argv[2])
negative = False
s, old_t = 0, 0
old_s, t = 1, 1

if r < 0:
    r = abs(r)
    negative = True

while r > 0:
    q = old_r / r
    #MCD:
    r, old_r = old_r - q * r, r
    #Coeficiente s:
    s, old_s = old_s - q * s, s
    #Coeficiente t:
    t, old_t = old_t - q * t, t

if negative:
    old_t = old_t * -1
    
print "m.c.d. = ", old_r
print "Bezout s = ", old_s
print "Bezout t = ", old_t

Ejemplo de su ejecución desde la terminal:

vic@LESBIAN:~/mates$ ./bezoutYmcd.py 23 -4
m.c.d. =  1
Bezout s =  -1
Bezout t =  -6

Si convertimos este programa en una función tendremos una herramienta útil para calcular el inverso de a módulo m, resolver ecuaciones de congruencia lineal y ecuaciones diofánticas.

Un algoritmo de generación de números pseudoaleatorios

En la nota a pie del artículo La privacidad no existe citaba un artículo de la Wikipedia acerca de cómo generar números aleatorios mediante software. En esta entrada voy a poner un ejemplo sencillo desarrollado en Python. Obviamente Python tiene sus propias funciones para ello, pero nada nos impide desarrollar nuestra propia función.

En primer lugar veamos la teoría que hay detrás. Estos algoritmos lo que realmente generan son números pseudoaleatorios porque usan procedimientos sistemáticos. El método más empleado es el de congruencia lineal. Se eligen cuatro números enteros: un módulo m, un multiplicador a, el incremento c y la semilla x0 Estos números deben ser números enteros positivos mayores de 0. El artículo de la Wikipedia también impone que  a, c y x0 deben ser menores que m pero no encuentro ninguna razón para ello. De hecho, al final el artículo entra en contradicción al afirmar que el estándar POSIX para C define a = 1103515245 y m = 32768 Con lo que a > m

Una semilla válida la podemos obtener del reloj interno de la CPU: el RTC. En el ejemplo usaré el método time() de la librería time. Éste nos da el tiempo que ha pasado desde el tiempo Unix: el 1 de Enero de 1970. Como se puede ver, devuelve un número en coma flotante:

>>> time.time()
1487416084.126488

Como necesitamos un entero lo convertimos, el número de segundos será suficiente para el ejemplo.

Siguiendo con la teoría, la semilla nos dará el primer número aleatorio y este mismo número será la semilla para el siguiente. Partiendo de una semilla inicial xn sería:

xn+1 = (axn + c) mod m

Como a, c y m usaré los valores del estándar Posix para C. Este programa genera 10 números aleatorios:

import time

xn = int(time.time()) #Semilla

for i in range(10):
    xn1 = (1103515245 * xn + 12345) % 32768
    print(xn1)
    xn = xn1

Si lo ejecuto este es el resultado:

13566
9311
7852
10101
8970
10107
31128
17905
26070
16471

Lo ejecuto otra vez lo más rápidamente posible:

875
16072
23393
9862
27463
32628
19613
9490
29923
7904

Como vemos hay bastante aleatoriedad. Suficiente para videojuegos y modelos de simulación. Aunque entre ambas ejecuciones sólo pase un segundo los resultados no tienen nada que ver. Si necesitamos diferentes ejecuciones en menos de un segundo podríamos crear un entero fruto de la concatenación de la parte entera y la parte decimal del número que devuelve time()

Llegará un momento en que el resultado obtenido coincida con alguno de los resultados ya obtenidos. Cuando eso pase sabremos que la serie ha acabado y a continuación se repetirán los mismos números. Veámoslo usando unos valores más pequeños: m = 7, a = 5, c = 3 y x0 = 2:

x1 = 5x0 + 3 = 5*2 + 3 = 13 mod 7 = 6
x2 = 5x1 + 3 = 5*6 + 3 = 33 mod 7 = 5
x3 = 5x2 + 3 = 5*5 + 3 = 28 mod 7 = 0
x4 = 5x3 + 3 = 5*0 + 3 = 3 mod 7 = 3
x5 = 5x4 + 3 = 5*3 + 3 = 18 mod 7 = 4
x6 = 5x5 + 3 = 5*4 + 3 = 23 mod 7 = 2
x7 = 5x6 + 3 = 5*2 + 3 = 13 mod 7 = 6

Por lo tanto la serie que se repetirá ad infinitum es: 6, 5, 0, 3, 4, 2, …

De hecho, podríamos decir que la serie empieza por el 2, pues ya x6 = x0. Ahí tenemos la primera coincidencia, antes de llegar a x7 En el algoritmo en Python no se muestra x0 para no tener el primer número pseudoaleatorio de la serie demasiado parecido en diferentes ejecuciones.

En el mejor de los casos posibles el número de elementos de la serie será igual a m. Una mala elección de los valores iniciales m, c y x0 conduciría a una serie muy corta. Según el teorema de Hull-Dobell un generador congruencial tiene una secuencia completa (el número de elementos es igual a m) si y solo si:

  1. m y c son primos entre si.
  2. a -1 es divisible por todos los factores primos de m.
  3. Si 4 divide a m, entonces 4 divide a a – 1.

Además, los algoritmos más eficientes escogerán como m una potencia de 2, pues la operación módulo podrá ser calculada simplemente truncando los 32 o 64 bits más a la derecha.

El estándar POSIX para C ha escogido cuidadosamente los valores cumpliendo con las condiciones del teorema de Hull-Dobell. Veamos:

  1. 1103515245 y 32768 son primos entre si.
  2. El único factor primo de 32768 es 2: 32768 = 215 y 1103515244 | 2 Además es eficiente al ser potencia de 2.
  3. Se cumple tanto 32768 | 4 como 1103515244 | 4

Por lo tanto el algoritmo anterior debería dar una serie 32768 números únicos. Veamos si es cierto con una sencilla modificación del programa anterior que irá añadiendo números a la tupla hasta que se produzca la primera repetición:

import time

xn = int(time.time())
total = []
repeated = False

while (not repeated):
    xn1 = (1103515245 * xn + 12345) % 32768
    xn = xn1
    repeated = xn1 in total
    if (not repeated):
        total.append(xn1)
    
print(total)
print(len(total))

No voy a mostrar la tupla resultante por ser demasiado larga, será bastante con ver el número de elementos que la componen (segundo print):

32768

El número de elementos de la tupla resultante siempre será el mismo por más veces que lo ejecutemos.

Si necesitamos números aleatorios entre 0 y 1 haríamos xn / m :

xn = int(time.time())

for i in range(10):
    xn1 = ((1103515245 * xn + 12345) % 32768) / 32768.00
    print(xn1)
    xn = xn1

Los dos decimales .00 añadidos a c son un “truco” para que Python imprima el resultado xn con decimales: le estamos diciendo al intérprete que queremos un float como resultado de la división. Este es uno de los resultados que da su ejecución:

0.654296875
0.87919062376
0.538866591363
0.578234577039
0.358640995601
0.190647192963
0.728419539036
0.0794397516329
0.638488339469
0.501262168

Mis primeros 4 scripts en Python.

Os presento mis primeros 4 scripts en Python. Me parece un lenguaje muy interesante con unas librerías muy potentes, creo que en parte es debido a que es fácil portar librerías de C a Python y en este lenguaje se ha programado de todo. Desde luego que no son para un manual de programación en Python, os los presento solo para que veáis la potencia del lenguaje.

Lo que hace éste script está comentado en el mismo código:

#! /usr/bin/env python
# -*- coding: utf-8 -*-

# El script espera dos argumentos:
# 1 - Una fecha en formato '%d-%m-%Y %H:%M', por ejemplo '21-01-2011 11:40'
# Mostrará sólo los ficheros que se hayan modificado después de esta fecha.
# 2 - Un path en formato *nix, por ejemplo '/home/vic/python'. No acepta '~' para referirse a 'home'.
# Buscará recursivamente, subdirectorios incluidos, sin profundizar en enlaces simbólicos, todos los ficheros a partir de ese path.
#
# os.path.getmtime -> Da el número de segundos desde epoch (medianoche UTC desde el 1 de enero de 1970).
# time.strptime devuelve una estructura time (struct_time).
# time.mktime(struct_time) devuelve el nº de segundos desde epoch. time.localtime(segundos_desde_epoch) hace la función inversa.
# El nº de segundos desde epoch se expresa en coma flotante.
#
#Para la fecha de creación de un fichero o dir: stat -c %w nombre
#Para saber la fecha de la última molificación: stat -c %y nombre
#Cuando se crea un fichero dentro de un directorio se modifica solo la fecha de modificacion del dir y no la de creación.
#Cuando se modifica un fichero dentro de un directorio no se altera la fecha de modificación ni de creación del dir.

import os, time, sys
from sys import argv

stModDesde = time.strptime(argv[1], '%d-%m-%Y %H:%M')
fModDesde = time.mktime(stModDesde)

top = argv[2]
for root, dirs, files in os.walk(top, topdown=False):
    for name in files:
        elemPath = os.path.join(root, name)
        fModificado = os.path.getmtime(elemPath)
        if (fModificado > fModDesde):
            print 'Fichero ' + elemPath + ' Modificado:' + time.ctime(fModificado)
#     for name in dirs:
#         elemPath = os.path.join(root, name)
#         Si queremos trabajar también con los directorios descomentar este for / in

¡Daros cuenta de que tan solo son 11 líneas! Aquí una función en Object Pascal que hice hará unos 7 años que aun teniendo que hacer algo más sencillo (buscar un fichero en el directorio actual y subdirectorios) era más compleja: recursiva y con más líneas de código.

unit Llibreria;

interface
uses
SysUtils, Classes, Forms;

procedure busca(path: string; carpet: string; coincidencias: TStrings);

implementation

procedure busca(path: string; carpet: string; coincidencias: TStrings);
    var
    sr: TSearchRec;
    res: byte;
    lg: integer;
    begin
        lg:=strLen(PChar(path)); //lg:=length(path);
        path:=path+carpet;
        res:=FindFirst(path, faAnyFile, sr);
        if (res=0) then coincidencias.Add(copy(path,1,lg) + sr.Name);
        FindClose(sr);
        path:=copy(path,1,lg) + '*.*';
        res:=FindFirst(path, faAnyFile, sr);
        while (res=0) do begin
            if (((sr.Attr and faDirectory)<>0) and (sr.Name[1]<>'.')) then begin
                path:=copy(path,1,lg);
                path:=path + sr.Name + '\';
                application.ProcessMessages;
                busca(path,carpet,coincidencias);
            end;
            res:=FindNext(sr);
        end;
    FindClose(sr);
    path:=copy(path,1,lg);
    end;
end.

Aquí un script para criptografía simétrica, usa el algoritmo Blowfish. El modo de cifrado es CTR, pero no le hagáis mucho caso a la función del contador. Una vez más creo que los comentarios ya explican bastante. (Python es el primer lenguaje en que me veo obligado a usar más líneas de comentarios que de código)

#! /usr/bin/env python
# -*- coding: latin-1 -*-

# El script espera 3 argumentos:
# 1º: Encriptar / Desencriptar: e / d
# 2º y 3º: paths a ficheros.
#     Con 'e' el 2º parámetro es el fichero a encriptar y el 3º el del encriptado (el resultado).
#     Con 'd' el 2º parámetro es el fichero encriptado y el 3º el desencriptado (el resultado).
#     ATENCIÓN: ¡en ambos casos el fichero resultante se sobreescribirá si ya existe!
#
# A continuación pedirá la clave.

from Crypto.Cipher import Blowfish
from os import path
from sys import argv, exit
import getpass

if not path.isfile(argv[2]):
    exit(argv[2] + " no existe o no es un fichero.")

# Añadir 'b' (binary) lo hace portable a Windows que sí distingue entre ficheros de texto y binarios,
# permitiendo encriptar tanto ficheros de texto como binarios.
f1 = open(argv[2], 'rb')

passw = getpass.getpass("Contraseña:")

class contador(object):
    def __init__(self, inicio):
        self.cont = inicio
    def next(self):
        self.cont = self.cont + 1
        sCont = str(self.cont)
        while len(sCont) < 8:
            sCont = sCont + '0'
        return sCont

c = contador(0)

oBlow = Blowfish.new(passw, Blowfish.MODE_CTR, counter=c.next)
# El primer param. es la clave. El segundo es el modo.
# Parece que el modo ECB es el más inseguro y CTR el más seguro. Ver http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher_feedback_.28CFB.29
# El tercer parámetro no es opcional en el caso de CTR: requiere el contador. Debe ser una cadena de 8 carácteres.
# Se instancia el objeto antes del if pues el contador debe ser el mismo para la encriptación y la desencriptación.
# Usar algo como "sAleatoria = os.urandom(8)" tiene el inconveniente de tener que almacenar en el fichero ese contador para luego poder desencriptar.

if argv[1] == 'e':
    texto = f1.read() # Texto a encriptar.

    # La cadena a encriptar debe tener una longitud que sea múltiple de 8. Independientemente de la extensión de la clave.
    while (len(texto) % 8 != 0):
        texto = texto + chr(3) # El ASCII 3 es End of TeXt (ETX).

        textoEncriptado = oBlow.encrypt(texto)
        #print "Texto encriptado: " + textoEncriptado
        #textoDesencriptado = oBlow.decrypt(textoEncriptado)
        #print "Texto desencriptado: " + textoDesencriptado
        f2 = open(argv[3], 'wb') # Sobreescribe el fichero si ya existe.
        f2.write(textoEncriptado)
        f1.close()
        f2.close()
elif argv[1] == 'd':
    textoEncriptado = f1.read()
    texto = oBlow.decrypt(textoEncriptado)
    f2 = open(argv[3], 'wb')
    f2.write(texto)
    f1.close()
    f2.close()
else:
    print "Opción desconocida. 'e' para encriptar y 'd' para desencriptar"

¿Y Python para la web? He aquí un pequeño script que lee todas las referencias de productos de un fichero csv para después buscar en la web de la empresa las imágenes que les corresponden y guardarlas en el disco duro.

Creo que con las librerías “mechanize” y “BeautifulSoup” podemos construir fantásticas aplicaciones de testeo web, de una manera más rápida, eficiente y consumiendo muchos menos recursos que con herramientas como Selenium. Aquí el código:

#! /usr/bin/env python
# coding=utf-8

import mechanize
import cookielib
from BeautifulSoup import BeautifulSoup
import urllib

br = mechanize.Browser()

cj = cookielib.LWPCookieJar()
br.set_cookiejar(cj)
#Inicio configuración browser
br.set_handle_equiv(True)
br.set_handle_gzip(False)
br.set_handle_redirect(True)
br.set_handle_referer(True)
br.set_handle_robots(False)

#Para debug:
#br.set_debug_http(True)
#br.set_debug_redirects(True)
#br.set_debug_responses(True)

# User-Agent (no es Linux! pero lo engañamos O:-)
br.addheaders = [('User-agent', 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.1) Gecko/2008071615 Fedora/3.0.1-1.fc9 Firefox/3.0.1')]

br.set_proxies({'http' : '192.168.4.4:3128'})

# Fin configuración browser
# Abre fichero de referencias:
f = open('/cygdrive/c/users/vic/documents/Empresa/Empresa.csv', 'r')
fContent = f.readlines()
for ref in fContent:
    ref = ref.strip()
    print "'" + ref + "'"
    #Busca por cada referencia su imagen:
    response1 = br.open("http://www.empresa.com/en/search/node/" + urllib.quote(ref))
    #print br.title() Título de la página
    #print response1.info() # Cabeceras
    #print response1.read() # HTML
    # Si no obtenemos HTML da un error:
    if not br.viewing_html():
        continue
    sHtml = response1.read()
    if sHtml.find('http://www.empresa.com/en/products/') == -1:
        continue
    #Sigue el link del resultado de la búsqueda:
    response1 = br.follow_link(url_regex = "http://www.empresa.com/en/products/")
    if not response1:
        continue;
    sHtml = response1.read()
    #Pone el HTML en la sopa para procesarlo:
    soup = BeautifulSoup(sHtml)
    #Busca la imagen y obtiene su valor src
    bsTagImg = soup.find(id='productimage')
    src = bsTagImg['src']
    #Descarga la imagen en /tmp
    fTemp = br.retrieve(src)[0]
    #La copiamos al directorio que queramos:
    fh = open(fTemp, 'rb')
    fContentImg = fh.read()
    f2 = open('/home/vic/python/ref_' + ref + '.jpg', 'wb')
    f2.write(fContentImg)
    fh.close()
    f2.close()
f.close()

Realmente un lenguaje potente, ¿verdad?