2011-05-04 15 views
7

Tengo el siguiente método que escribí para Project Euler - Problem 36. Todo lo que hace es sumar todos los números menos de 1.000.000 que son palíndromos, tanto en la base 10 y la base 2.¿La asignación variable es realmente cara en Ruby, o optimiza los métodos de cadena encadenada?

def problem_36 
    (1...1_000_000).select do |n| 
    n.to_s == n.to_s.reverse && n.to_s(2) == n.to_s(2).reverse 
    end 
end 

Ahora, esto funciona y da el resultado correcto en poco más de 1 segundo. Quería que fuera menos de 1 segundo, así que decidí reducir la cantidad de veces que convertía números a cadenas. Así que hice los siguientes cambios:

def problem_36 
    (1...1_000_000).select do |n| 
    base10 = n.to_s 
    base2 = n.to_s(2) 
    base10 == base10.reverse && base2 == base2.reverse 
    end 
end 

El problema es que esta versión realmente ejecuta alrededor del 50% más lenta que el original. Entonces, la pregunta es: ¿es realmente tan lento asignar esas dos variables? ¿O está Ruby optimizando las llamadas al método encadenado?

+0

Y solo para aclarar, no había una necesidad real de hacer la primera versión más rápido, solo quería ver si podía. Eso solo me hizo tropezar con esta aparente rareza. –

+0

Echa un vistazo a los [resultados en ideone.com] (http://ideone.com/4Seut) –

+1

Mi sospecha: La diferencia está en cómo el recolector de basura maneja objetos temporales sin nombre frente a objetos almacenados en variables. – Chuck

Respuesta

7

En esta línea

n.to_s == n.to_s.reverse && n.to_s(2) == n.to_s(2).reverse 

la segunda parte no se ejecuta si la primera parte es false (&& operador cortocircuitos de Ruby, unlike its & counterpart). Eso ahorra muchas llamadas al to_s(2).

+1

basándose en esto, la primera versión no hace la conversión de base2 a menos que n sea un palindrome base10, que supongo que es menos del 10% de los números. La segunda versión hace la conversión de base2 independientemente. Así que intente con 'base10 == base10.reverse && (base2 = n.to_s (2)) == base2.reverse' – AShelly

+0

Eso tiene mucho sentido. No pensé en el hecho de que un montón de tiempo no estoy llamando 'n.to_s (2)' porque la primera parte del '&&' falla. Voy a intentar la sugerencia de @ AShelly y ver qué pasa. –

0

Interesante.

La regla de rendimiento de Ruby habitual es que si un programa utiliza las operaciones integradas, será rápido. Si no lo hace, será lento.

Aunque su segunda versión puede o no hacer menos conversiones, ejecuta tres líneas de Ruby contra una.

Una vez leí un archivo de registro de gran tamaño. En mi primera versión, mantuve una lista enlazada eficiente de las líneas usando el código de Ruby.

1. Complejidad del tiempo: O (1).

Luego lo cambié para simplemente usar << y virar cada nuevo nodo al final de una matriz.

2. complejidad Tiempo: O (N), o al menos O (N 1 + ε)

La segunda versión (1) fue más rápido.


1. Es evidente que la aplicación fue la expansión de la matriz en trozos, por lo que no era totalmente cuadrática, pero todavía mucho más trabajo que una lista enlazada.

+3

Las listas vinculadas han tenido un rendimiento peor que las matrices de autoexpansión para edades en muchos idiomas, no solo en Ruby. * Especialmente * no en Ruby, donde tener un puntero es más caro que en, por ejemplo, C. –

+0

Sobre el tema de la lista vinculada frente al rendimiento de la matriz echa un vistazo a esta publicación del blog: http://drmcawesome.com/rubyArrays - proporciona una buena descripción de cómo se implementan las matrices, y por lo tanto, por lo general, superan a las listas vinculadas. – Elad

Cuestiones relacionadas