Clasificación de una serie de cadenas en Ruby

He aprendido dos métodos de clasificación de matrices en Ruby:

array = ["one", "two", "three"] array.sort.reverse! 

o:

 array = ["one", "two", "three"] array.sort { |x,y| yx } 

Y no soy capaz de diferenciar entre los dos. ¿Qué método es mejor y en qué se diferencian exactamente en la ejecución?

Ambas líneas hacen lo mismo (crear una nueva matriz, que se ordena en orden inverso). El argumento principal es sobre la legibilidad y el rendimiento. array.sort.reverse! es más legible que array.sort{|x,y| y<=>x} array.sort{|x,y| y<=>x} – Creo que podemos estar de acuerdo aquí.

Para la parte de rendimiento, creé una secuencia de comandos de referencia rápida, que ruby 1.9.3p392 [x86_64-linux] siguiente en mi sistema ( ruby 1.9.3p392 [x86_64-linux] ):

  user system total real array.sort.reverse 1.330000 0.000000 1.330000 ( 1.334667) array.sort.reverse! 1.200000 0.000000 1.200000 ( 1.198232) array.sort!.reverse! 1.200000 0.000000 1.200000 ( 1.199296) array.sort{|x,y| y<=>x} 5.220000 0.000000 5.220000 ( 5.239487) 

Los tiempos de ejecución son bastante constantes para las ejecuciones múltiples del script de referencia.

array.sort.reverse (con o sin ! ) es mucho más rápido que array.sort{|x,y| y<=>x} array.sort{|x,y| y<=>x} . Por lo tanto, lo recomiendo.


Aquí está el guión como referencia:

 #!/usr/bin/env ruby require 'benchmark' Benchmark.bm do|b| master = (1..1_000_000).map(&:to_s).shuffle a = master.dup b.report("array.sort.reverse ") do a.sort.reverse end a = master.dup b.report("array.sort.reverse! ") do a.sort.reverse! end a = master.dup b.report("array.sort!.reverse! ") do a.sort!.reverse! end a = master.dup b.report("array.sort{|x,y| y<=>x} ") do a.sort{|x,y| y<=>x} end end 

Realmente no hay diferencia aquí. Ambos métodos devuelven una nueva matriz.

Para los propósitos de este ejemplo, más simple es mejor. Recomendaría array.sort.reverse porque es mucho más legible que la alternativa. Pasar bloques a métodos como la sort debe guardarse para matrices de estructuras de datos más complejas y clases definidas por el usuario.

Edición: si bien los métodos destructive (¡cualquier cosa que termine en a!) Son buenos para los juegos de rendimiento, se señaló que no se requiere que devuelvan una matriz actualizada, o cualquier otra cosa, en realidad. Es importante tener esto en cuenta porque array.sort.reverse! muy probablemente podría volver a nil . Si desea utilizar un método destructivo en una matriz recién generada, ¡debería preferir llamar a .reverse! en una línea separada en lugar de tener una sola línea.

Ejemplo:

 array = array.sort array.reverse! 

debería preferirse a

 array = array.sort.reverse! 

¡Marcha atrás! es más rápido

A menudo no hay sustituto para la evaluación comparativa. Mientras que probablemente no haga ninguna diferencia en scripts más cortos, el #reverso! El método es significativamente más rápido que la clasificación utilizando el operador “nave espacial”. Por ejemplo, en MRI Ruby 2.0, y dado el siguiente código de referencia:

 require 'benchmark' array = ["one", "two", "three"] loops = 1_000_000 Benchmark.bmbm do |bm| bm.report('reverse!') { loops.times {array.sort.reverse!} } bm.report('spaceship') { loops.times {array.sort {|x,y| y<=>x} }} end 

El sistema reporta que #reverso! es casi el doble de rápido que usar el operador de comparación combinada.

  user system total real reverse! 0.340000 0.000000 0.340000 ( 0.344198) spaceship 0.590000 0.010000 0.600000 ( 0.595747) 

Mi consejo: use el que sea más significativo semánticamente en un contexto dado, a menos que se esté ejecutando en un circuito cerrado.

Con la comparación tan simple como su ejemplo, no hay mucha diferencia, pero como la fórmula de comparación se complica, es mejor evitar usar <=> con un bloque porque el bloque que pase se evaluará para cada elemento de la matriz. causando redundancia Considera esto:

 array.sort{|x, y| some_expensive_method(x) <=> some_expensive_method(y)} 

En este caso, se evaluará some_expensive_method para cada par de elementos posibles de la array .

En su caso particular, el uso de un bloque con <=> se puede evitar con reverse .

 array.sort_by{|x| some_expensive_method(x)}.reverse 

Esto se llama la transformación de Schwartzian.

Al jugar con los puntos de referencia de Tessi en mi máquina, he obtenido algunos resultados interesantes. Estoy ejecutando ruby 2.0.0p195 [x86_64-darwin12.3.0] , es decir, la última versión de Ruby 2 en un sistema OS X. Usé bmbm en lugar de bm del módulo Benchmark. Mis horarios son:

 Rehearsal ------------------------------------------------------------- array.sort.reverse: 1.010000 0.000000 1.010000 ( 1.020397) array.sort.reverse!: 0.810000 0.000000 0.810000 ( 0.808368) array.sort!.reverse!: 0.800000 0.010000 0.810000 ( 0.809666) array.sort{|x,y| y<=>x}: 0.300000 0.000000 0.300000 ( 0.291002) array.sort!{|x,y| y<=>x}: 0.100000 0.000000 0.100000 ( 0.105345) ---------------------------------------------------- total: 3.030000sec user system total real array.sort.reverse: 0.210000 0.000000 0.210000 ( 0.208378) array.sort.reverse!: 0.030000 0.000000 0.030000 ( 0.027746) array.sort!.reverse!: 0.020000 0.000000 0.020000 ( 0.020082) array.sort{|x,y| y<=>x}: 0.110000 0.000000 0.110000 ( 0.107065) array.sort!{|x,y| y<=>x}: 0.110000 0.000000 0.110000 ( 0.105359) 

En primer lugar, tenga en cuenta que en la fase de ensayo ese sort! usando un bloque de comparación entra como claro ganador. ¡Matz debió de sintonizarlo en Ruby 2!

La otra cosa que encontré extremadamente extraña fue cuánta mejora array.sort.reverse! y array.sort!.reverse! Expone en la pasada de producción. Fue tan extremo que me hizo preguntarme si había jodido y pasé estos datos ya ordenados, así que agregué verificaciones explícitas de datos ordenados o ordenados inversamente antes de realizar cada punto de referencia.


Mi variante del guión de tessi sigue:

 #!/usr/bin/env ruby require 'benchmark' class Array def sorted? (1...length).each {|i| return false if self[i] < self[i-1] } true end def reversed? (1...length).each {|i| return false if self[i] > self[i-1] } true end end master = (1..1_000_000).map(&:to_s).shuffle Benchmark.bmbm(25) do|b| a = master.dup puts "uh-oh!" if a.sorted? puts "oh-uh!" if a.reversed? b.report("array.sort.reverse:") { a.sort.reverse } a = master.dup puts "uh-oh!" if a.sorted? puts "oh-uh!" if a.reversed? b.report("array.sort.reverse!:") { a.sort.reverse! } a = master.dup puts "uh-oh!" if a.sorted? puts "oh-uh!" if a.reversed? b.report("array.sort!.reverse!:") { a.sort!.reverse! } a = master.dup puts "uh-oh!" if a.sorted? puts "oh-uh!" if a.reversed? b.report("array.sort{|x,y| y<=>x}:") { a.sort{|x,y| y<=>x} } a = master.dup puts "uh-oh!" if a.sorted? puts "oh-uh!" if a.reversed? b.report("array.sort!{|x,y| y<=>x}:") { a.sort!{|x,y| y<=>x} } end