2011-04-02 9 views
8

estoy trabajando a través de Allen Downey Cómo Pensar como un Informático, y he escrito lo que yo creo que es una solución funcionalmente correcta al ejercicio 10.10 . Pero me llevó algo más de 10 horas (!) Ejecutar, así que me pregunto si me falta algo de optimización realmente obvia y útil.Cómo optimizar el código Python (de ThinkPython, el ejercicio 10.10)

Aquí está el ejercicio:..

" 'enclavamiento' Dos palabras si tomar cartas alternando de cada uno forma una nueva palabra Por ejemplo, 'zapato' y el enclavamiento de 'frío' para formar 'educado' Escribir un programa que encuentra todos los pares de palabras que se entrelazan. Sugerencia: ¡No enumere todos los pares! "

(.. Para estos problemas lista de palabras, Downey ha suministrado un archivo con 113809 palabras Podemos suponer que estas palabras están en una lista, una palabra por elemento de la lista)

aquí está mi solución:

from bisect import bisect_left 

def index(lst, target): 
    """If target is in list, returns the index of target; otherwise returns None""" 
    i = bisect_left(lst, target) 
    if i != len(lst) and lst[i] == target: 
     return i 
    else: 
     return None 

def interlock(str1, str2): 
    "Takes two strings of equal length and 'interlocks' them." 
    if len(str1) == len(str2): 
     lst1 = list(str1) 
     lst2 = list(str2) 
     result = [] 
     for i in range(len(lst1)): 
      result.append(lst1[i]) 
      result.append(lst2[i]) 
     return ''.join(result) 
    else: 
     return None 

def interlockings(word_lst): 
    """Checks each pair of equal-length words to see if their interlocking is a word; prints each successful pair and the total number of successful pairs.""" 
    total = 0 
    for i in range(1, 12): # 12 because max word length is 22 
     # to shorten the loops, get a sublist of words of equal length 
     sub_lst = filter(lambda(x): len(x) == i, word_lst) 
     for word1 in sub_lst[:-1]: 
      for word2 in sub_lst[sub_lst.index(word1)+1:]: # pair word1 only with words that come after word1 
       word1word2 = interlock(word1, word2) # interlock word1 with word2 
       word2word1 = interlock(word2, word1) # interlock word2 with word1 
       if index(lst, word1word2): # check to see if word1word2 is actually a word 
        total += 1 
        print "Word 1: %s, Word 2: %s, Interlock: %s" % (word1, word2, word1word2) 
       if index(lst, word2word1): # check to see if word2word1 is actually a word 
        total += 1 
        print "Word 2, %s, Word 1: %s, Interlock: %s" % (word2, word1, word2word1) 
    print "Total interlockings: ", total 

Las declaraciones de impresión no son el problema; mi programa encontró solo 652 de esos pares. El problema son los bucles anidados, ¿verdad? Quiero decir, aunque estoy revisando listas que contienen solo palabras de la misma longitud, hay (por ejemplo) 21727 palabras de longitud 7, lo que significa que mi programa debe verificar más de 400 millones de "enclavamientos" para ver si ' re palabras reales --- y eso es solo para la longitud-7 palabras.

De nuevo, este código tardó 10 horas en ejecutarse (y no encontró pares con palabras de longitud 5 o superior, en caso de que tuviera curiosidad). ¿Hay una mejor manera de resolver este problema?

Gracias de antemano por cualquier idea. Soy consciente de que "la optimización prematura es la raíz de todo mal" --- y quizás ya he caído en esa trampa --- pero en general, aunque normalmente puedo escribir código que se ejecuta correctamente, a menudo me cuesta escribir código que funciona bien

+0

La respuesta a continuación tiene mucho sentido, pero estoy interesado si trataste de perfilar este código para identificar qué es lo que realmente lo hace lento. Otras cosas útiles para tratar de acelerar son simplemente ejecutarlo a través de Psyco o PyPy. – Glenjamin

+0

@Glenjamin: No he perfilado el código porque no sé cómo. ¿Puede ofrecer un enlace a alguna documentación que explique cómo hacer esto? ¡Gracias! –

+0

http://docs.python.org/library/profile.html lo explica mucho mejor que yo :) Versión corta: 'python -m cProfile myscript.py' – Glenjamin

Respuesta

14

Hazlo al revés: itera por todas las palabras y divídelas en dos palabras tomando las letras pares e impares. Luego busca esas dos palabras en el diccionario.

Como un nodo lado, las dos palabras que enclavamiento no debe necesariamente tener la misma longitud - las longitudes también podría diferir en 1.

Algunos (no probado) código:

words = set(line.strip() for line in open("words")) 
for w in words: 
    even, odd = w[::2], w[1::2] 
    if even in words and odd in words: 
     print even, odd 
+0

¡Gracias! Intentaré implementarlo hoy y ver si eso ayuda. En cuanto a su nota al margen: pensé en eso y decidí que era demasiado complicado para un primer pase, y si conseguía que el caso de igual duración funcionara primero, volvería y consideraría también el caso de diferir-por-1. . Una vez que implemente su sugerencia con éxito, incorporaré ese giro en ella. –

+0

Holy Moley !! El tiempo transcurrido pasó de 10 horas a 15.6 segundos. Y eso es * incluso * el caso de diferir-por-1 en la nueva implementación (que fue trivial de implementar). Guau. ¡Gracias una tonelada! –

+0

Creo que este es el enfoque correcto, pero el resultado es incorrecto. es decir, estás tomando una sola palabra y dividiéndola en partes pares e impares de esa sola palabra. El problema establecido es tomar DOS palabras y combinarlas en una sola palabra nueva. – dawg

1

definición Alternativa de enclavamiento:

import itertools 

def interlock(str1, str2): 
    "Takes two strings of equal length and 'interlocks' them." 
    return ''.join(itertools.chain(*zip(str1, str2))) 
+0

Solo funciona si las dos cadenas son de la misma longitud. – dawg

0

Una cosa importante es su función index: es la función que se ejecuta más de una función. Cuando no necesita el índice de la palabra encontrada, ¿por qué definir una función para encontrar ese índice?

if word1word2 in lst: es suficiente en lugar de if index(lst, word1word2):.

Lo mismo para if index(lst, word2word1):.

OK. la bisección funciona realmente más rápido que la sintaxis in. Para mejorar un poco más la velocidad, sugiero usar la función bisect_left directamente en su función interlockings.

Por ejemplo en lugar de:

 if index(lst, word1word2): # check to see if word1word2 is actually a word 
      total += 1 
      print "Word 1: %s, Word 2: %s, Interlock: %s" % (word1, word2, word1word2) 

Uso:

 q = bisect_left(lst, word1word2) 
     if q != len(lst) and lst[q] == word1word2: 
      total += 1 
      print "Word 1: %s, Word 2: %s, Interlock: %s" % (word1, word2, word1word2) 

una muy ligera mejora en la velocidad.

+0

Downey sugiere anteriormente en el libro que la sintaxis de "elemento en la lista" se ejecuta más lentamente que un algoritmo de bisección, que usa mi función de "índice". Confieso que no he probado su afirmación para ver si mi función de índice funciona realmente más rápido que la sintaxis "in" incorporada, así que tal vez lo pruebe más tarde. –

+0

@Alex: Tiene razón acerca de que su función 'index()' es más rápida que la sugerencia de Hossein 'word in lst'. Incluso más rápido que ambos es usar 's = set (words)' y probar 'word in s'. –

1

Una versión alternativa:

with open('words.txt') as inf: 
    words = set(wd.strip() for wd in inf) 

word_gen = ((word, word[::2], word[1::2]) for word in words) 
interlocked = [word for word,a,b in word_gen if a in words and b in words] 

En mi máquina esta se ejecuta en 0.16 segundos y devuelve 1254 palabras.


Editar: como señaló @ John Machin en Why is this program faster in Python than Objective-C? Esto se puede mejorar aún más por la ejecución perezoso (sólo realizan la segunda rebanada si los primeros resultados en una palabra válida):

with open('words.txt') as inf: 
    words = set(wd.strip() for wd in inf) 
interlocked = [word for word in words if word[::2] in words and word[1::2] in words] 

Esto reduce el tiempo de ejecución en un tercio, a 0.104 segundos.

+0

Esto no funciona. Si 'words' es' set (['cold', 'shoe']) '- el ejemplo OP,' word_gen' es entonces [('cold', 'cl', 'od'), ('shoe', 'so', 'él')] – dawg

+0

@drewk: Si 'words' fuera' set (['cold', 'shoe']) ', no habría un par de palabras entrelazadas, y el código anterior no encontraría alguna. Si 'words' fuera' set (['cold', 'shoe', 'schooled']) ', habría un par de palabras entrelazadas, y el código anterior lo encontraría. –

+0

@Sven Marnach: ¡Duh! Lo siento, ambos son correctos ... – dawg

Cuestiones relacionadas