2011-01-26 23 views
18

Estaba buscando un equivalente de matriz String#split en Ruby Core, y me sorprendió descubrir que no existía. ¿Hay una manera más elegante que la siguiente para dividir una matriz en sub-arrays en función de un valor?Divide el arreglo en sub-arrays basado en el valor

class Array 
    def split(split_on=nil) 
    inject([[]]) do |a,v| 
     a.tap{ 
     if block_given? ? yield(v) : v==split_on 
      a << [] 
     else 
      a.last << v 
     end 
     } 
    end.tap{ |a| a.pop if a.last.empty? } 
    end 
end 

p (1..9).to_a.split{ |i| i%3==0 }, 
    (1..10).to_a.split{ |i| i%3==0 } 
#=> [[1, 2], [4, 5], [7, 8]] 
#=> [[1, 2], [4, 5], [7, 8], [10]] 

Editar: Para los interesados, el problema del "mundo real", que dio pie a esta petición se puede ver en this answer, donde yo he usado @ respuesta de fd a continuación para la aplicación.

+0

Bueno, en Python que podría convertirla en una cadena (valores separados por comas o algo así), que divide, y luego volver a una lista. No sé si esa es una opción en Ruby. –

+0

@Rafe Sería, pero solo si los contenidos fueran solo cadenas. Incluso entonces, eso difícilmente podría considerarse elegante. : p – Phrogz

+0

@Phrogz si fueran números, funcionaría bien también. Simplemente harías '','. Join ([str (x) para x en list_of_nums])', luego dividir en lo que sea, luego volver a unir y dividir en comas. Funcional, sí, elegante, eh no. –

Respuesta

10

He intentado jugar al golf un poco, todavía no es un método único sin embargo:

(1..9).chunk{|i|i%3==0}.reject{|sep,ans| sep}.map{|sep,ans| ans} 

o más rápido:

(1..9).chunk{|i|i%3==0 || nil}.map{|sep,ans| sep&&ans}.compact 

Además, Enumerable#chunk parece ser Ruby 1.9+, pero está muy cerca de lo que desea.

Por ejemplo, la salida cruda sería:

(1..9).chunk{ |i|i%3==0 }.to_a          
=> [[false, [1, 2]], [true, [3]], [false, [4, 5]], [true, [6]], [false, [7, 8]], [true, [9]]] 

(El to_a es hacer IRB impresión de algo agradable, ya que chunk le da un enumerador en lugar de una matriz)


Editar: Tenga en cuenta que las soluciones elegantes anteriores son 2-3 veces más lentas que la implementación más rápida:

module Enumerable 
    def split_by 
    result = [a=[]] 
    each{ |o| yield(o) ? (result << a=[]) : (a << o) } 
    result.pop if a.empty? 
    result 
    end 
end 
+0

¡Agradable! No había visto 'chunk' antes. Para el registro, es 1.9.2+, pero eso es totalmente aceptable para mí. – Phrogz

+2

Aquí hay un enlace al documento: http://ruby-doc.org/core/classes/Enumerable.html#M001523 –

+0

No es sorprendente (debido a las iteraciones adicionales necesarias para rechazar/mapa) el fragmento es un poco más lento; He agregado una 'respuesta' de evaluación comparativa de implementaciones. – Phrogz

1

Otros Enumerable métodos es posible que desee considerar es each_slice o each_cons

No sé cómo en general que quiera que sea, aquí está una manera

>> (1..9).each_slice(3) {|a| p a.size>1?a[0..-2]:a} 
[1, 2] 
[4, 5] 
[7, 8] 
=> nil 
>> (1..10).each_slice(3) {|a| p a.size>1?a[0..-2]:a} 
[1, 2] 
[4, 5] 
[7, 8] 
[10] 
+0

Solo para mi ejemplo específico de mod 3, pero no en general. – Phrogz

5

Éstos son los puntos de referencia de concentración de las respuestas (que no voy a aceptar esta respuesta):

require 'benchmark' 
a = *(1..5000); N = 1000 
Benchmark.bmbm do |x| 
    %w[ split_with_inject split_with_inject_no_tap split_with_each 
     split_with_chunk split_with_chunk2 split_with_chunk3 ].each do |method| 
    x.report(method){ N.times{ a.send(method){ |i| i%3==0 || i%5==0 } } } 
    end 
end 
#=>        user  system  total  real 
#=> split_with_inject   1.857000 0.015000 1.872000 ( 1.879188) 
#=> split_with_inject_no_tap 1.357000 0.000000 1.357000 ( 1.353135) 
#=> split_with_each   1.123000 0.000000 1.123000 ( 1.123113) 
#=> split_with_chunk   3.962000 0.000000 3.962000 ( 3.984398) 
#=> split_with_chunk2   3.682000 0.000000 3.682000 ( 3.687369) 
#=> split_with_chunk3   2.278000 0.000000 2.278000 ( 2.281228) 

Las implementaciones están probando (sobre el Ruby 1.9.2):

class Array 
    def split_with_inject 
    inject([[]]) do |a,v| 
     a.tap{ yield(v) ? (a << []) : (a.last << v) } 
    end.tap{ |a| a.pop if a.last.empty? } 
    end 

    def split_with_inject_no_tap 
    result = inject([[]]) do |a,v| 
     yield(v) ? (a << []) : (a.last << v) 
     a 
    end 
    result.pop if result.last.empty? 
    result 
    end 

    def split_with_each 
    result = [a=[]] 
    each{ |o| yield(o) ? (result << a=[]) : (a << o) } 
    result.pop if a.empty? 
    result 
    end 

    def split_with_chunk 
    chunk{ |o| !!yield(o) }.reject{ |b,a| b }.map{ |b,a| a } 
    end 

    def split_with_chunk2 
    chunk{ |o| !!yield(o) }.map{ |b,a| b ? nil : a }.compact 
    end 

    def split_with_chunk3 
    chunk{ |o| yield(o) || nil }.map{ |b,a| b && a }.compact 
    end 
end 
+0

Un poco tarde también la fiesta, pero: estos métodos no son del todo comparables, porque los resultados de estos métodos no son todos iguales. Los tres primeros devuelven algo similar a lo que devuelve 'String # split' (incluidas las matrices vacías cuando se encuentran dos separadores posteriores), mientras que' split_with_chunk' y 'split_with_chunk2' nunca devuelven matrices vacías y' split_with_chunk3' todavía contiene el valor de 'agrupación' de pedazo. – Confusion

1

aquí es otra uno (con un punto de referencia comparándolo con el más rápido split_with_each aquí https://stackoverflow.com/a/4801483/410102):

require 'benchmark' 

class Array 
    def split_with_each 
    result = [a=[]] 
    each{ |o| yield(o) ? (result << a=[]) : (a << o) } 
    result.pop if a.empty? 
    result 
    end 

    def split_with_each_2 
    u, v = [], [] 
    each{ |x| (yield x) ? (u << x) : (v << x) } 
    [u, v] 
    end 
end 

a = *(1..5000); N = 1000 
Benchmark.bmbm do |x| 
    %w[ split_with_each split_with_each_2 ].each do |method| 
    x.report(method){ N.times{ a.send(method){ |i| i%3==0 || i%5==0 } } } 
    end 
end 

         user  system  total  real 
split_with_each  2.730000 0.000000 2.730000 ( 2.742135) 
split_with_each_2 2.270000 0.040000 2.310000 ( 2.309600) 
+0

Esto es como la partición Array #, no String # split. – Lloeki

14

Somet IME partition es una buena manera de hacer cosas por el estilo:

(1..6).partition { |v| v.even? } 
#=> [[2, 4, 6], [1, 3, 5]] 
+0

Irrelevante para la pregunta: el autor desea dividir secuencias de ejecución delimitadas. – Lloeki

Cuestiones relacionadas