sábado, julio 21, 2012

Renombrando canciones 2.0

Hoy necesitaba mi viejo script para renombrar canciones y el cabroncete me ha dejado un poco tirado, no borraba completamente el autor que estaba repetido en todos los nombres de los ficheros (era "Hans Zimmer & James New Howard" y sólo se cargaba el "Hans").

Como no me apetecía una puta mierda ponerme a depurarlo (el código tiene ya 7 años y los bits se van pudriendo), me he dicho, ¿a ver cómo lo reescribiría siendo un poco más viejo?

El resultado es este:


#!/usr/bin/env python
import sys

allchars = (chr(i) for i in range(256))
table = ''.join(x if x.isalnum() else ' ' for x in allchars)

def remove_common_tokens(tokenized_lines):
        common = reduce(set.__and__, map(set, tokenized_lines))
        is_unique = lambda x: x not in common
        return [filter(is_unique, line) for line in tokenized_lines]

originals = map(str.strip, sys.stdin)
names, extensions = zip(*[x.rsplit('.',1) for x in originals])
tokenized_lines = [x.translate(table).title().split() for x in names]
unique_tokens = remove_common_tokens(tokenized_lines)
numbers = [str(int(x[0])).zfill(2) for x in unique_tokens]
new_names = [' '.join(x[1:]) for x in unique_tokens]

for original, number, new_name, extension in zip(originals, numbers, new_names, extensions):
        print 'mv "' + original + '" "' + number + ' - ' + new_name + '.' + extension + '"'

A tope. Lo que antes me llevaba 88 líneas ahora me lleva 21.  Soy un 25% más vago. Evidentemente tiene un bug (es el precio de ahorrar tanto), pero me la suda.

La mejora más aparente es que he eliminado toda la morralla de eliminar las partes iguales con un método más compacto y agresivo: cogemos todos los tokens comunes (independientemente de dónde estén) y nos los cargamos. El bug está ahí, si da la casualidad de que hay una palabra legítima que se repite en todos los títulos (como podría ser "of" por ejemplo), pues va a ir a tomar por culo también.

El resto sigue siendo más o menos igual, vamos aplicando transformaciones (tokenizar, sacar las extensiones y los números, eliminar partes repetidas, etc.) y al final componemos el resultado.

Algo gracioso que ha cambiado con la edad es que antes sólo usaba comillas dobles para los literales de strings (manías de Java, supongo), ahora suelo usar las simples.

Mi relación con map y filter es un poco de amor odio. Recuerdo que mi primera versión tiraba bastante de map, pero luego lo reescribí para usar list comprehensions. Ahora uso lo que sea más corto, si ya tengo la función definida, pues suelo tirar de map, si tengo que hacer un map y luego un filter seguramente use una comprehension que queda más clara, etc.

Sobre reduce la verdad es que no le suelo encontrar mucho uso, porque las reducciones típicas son sum y str.join y ya están los built-in, pero aquí por ejemplo tenía que encontrar la intersección de unos cuantos sets y me parecía mucho más elegante usar reduce que un bucle con acumulador.

En el próximo post (espero que en una semana o así) seguramente cuelgue algo de código en... ¡Javascript! quién me lo iba a decir a mí después de tantos años.