2010-09-16 15 views
10

Los rangos en ruby ​​son bastante geniales. termino con matrices como este:Matriz de índices para una matriz de rangos

geneRanges = [(234..25), (500..510), (1640..1653)] 

y posteriormente tenga que quitar trozos de ellos. Para eso I:

genePositions = geneRanges.collect {|range| range.entries }.flatten 
=> [500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 1640, 1641, 1642, 1643, 1644, 1645, 1646, 1647, 1648, 1649, 1650, 1651, 1652, 1653] 

Se manipulan, por lo que algunos números se excluyen, y se pueden agregar otros. Puedo terminar con esto:

[505, 506, 507, 600, 601, 602, 603, 1643, 1644, 1645, 1646, 1647, 1648, 1649, 1650, 1651, 1652, 1653, 1654] 

¿Cómo puedo convertir esto nuevamente en una matriz compacta de rangos? Parece que la función inversa debería existir? Esperaría que devuelva algo como esto:

[(505..507), (600..603), (1643..1654)] 

Gracias!

+1

más soluciones en: http://stackoverflow.com/questions/12360108 – tokland

+1

'(234..25)' es una rango no válido. '(234..25) .to_a => []'. –

Respuesta

14

(nuevo y mejorado se mantiene fresco en el refrigerador para un máximo de dos semanas):

a = [1, 2, 3, 10, 11, 20, 20, 4] 

ranges = a.sort.uniq.inject([]) do |spans, n| 
    if spans.empty? || spans.last.last != n - 1 
    spans + [n..n] 
    else 
    spans[0..-2] + [spans.last.first..n] 
    end 
end 

p ranges # [1..4, 10..11, 20..20] 
+1

+1 Buena solución, sin embargo, el valor dentro de los demás debe ser 'span [0 ..- 2] + [spans.last.first ..n] '(Editado para corregir) –

+0

@Steve Weet, gracias. Buena atrapada. –

+0

Excelente - ¡gracias! ¡Verifique también la generalización de Steve Weet y la respuesta de Mladen Jablanović más abajo! –

1
ar=[505, 506, 507, 600, 1647, 1648, 1649, 1650, 1651, 1654] 
def to_range(a) 
    s=a[0] 
    a.each_cons(2) do |a| 
    if a[1]-a[0]!=1 
     p s .. a[0] 
     s=a[1] 
    end 
    end 
    left=a.index(s) 
    p a[left..-1][0]..a[left..-1][-1] 
end 
to_range(ar) 
8

solución funcional, no muy legible:

(a[0,1]+a.each_cons(2).reject{|i,j| j-i==1}.flatten+a[-1,1]). 
    each_slice(2).map{|i,j| i..j} 

Y una agradable:

class Array 
    # splits array to sub-arrays wherever two adjacent elements satisfy a condition 
    def split_by 
    each_cons(2).inject([[first]]){|a, (i, j)| 
     a.push([]) if yield(i, j) 
     a.last.push j 
     a 
    } 
    end 

    # uses split_by to split array to subarrays with consecutive elements, then convert to range 
    def to_range 
    split_by{|i,j| j-i!=1}.map{|a| a.first..a.last} 
    end 
end 

[505, 506, 507, 600, 1647, 1648, 1649, 1650, 1651, 1654].split_by{|i,j| j-i!=1} 
#=> [[505, 506, 507], [600], [1647, 1648, 1649, 1650, 1651], [1654]] 
[505, 506, 507, 600, 1647, 1648, 1649, 1650, 1651, 1654].to_range 
#=> [505..507, 600..600, 1647..1651, 1654..1654] 
+1

que es muy elegante :) –

+0

SpaceBeforeModifierKeyword de RuboCop detectó que no había un espacio antes de 'if' en' a.push ([]) if yield (i, j) '. No sabía que era válido Ruby. O_o –

+0

Rubocop también sugiere usar 'each_with_object' en lugar de' inject'. –

5

Ésta es una cuna recta del algoritmo de Wayne Conrads con un pequeño truco para hacer que funcione para otros tipos de rangos, por ejemplo, alfabético

def array_to_ranges(a) 
ranges = a.sort.uniq.inject([]) do |spans, n| 
    if spans.empty? || spans.last.last.succ != n 
    spans + [n..n] 
    else 
    spans[0..-2] + [spans.last.first..n] 
    end 
end 
ranges 
end 

[ 
    [1..3, 10..11, 20..20, 4..4], 
    [ "a".."c", "f".."h", "x".."z"], 
    ["aa".."af"] 
].each do |arange| 
    p arange 
    p array = arange.collect {|range| range.to_a}.flatten 
    p array_to_ranges(array) 
end 

Y los resultados de la ejecución de este son

 
[1..3, 10..11, 20..20, 4..4] 
[1, 2, 3, 10, 11, 20, 4] 
[1..4, 10..11, 20..20] 
["a".."c", "f".."h", "x".."z"] 
["a", "b", "c", "f", "g", "h", "x", "y", "z"] 
["a".."c", "f".."h", "x".."z"] 
["aa".."af"] 
["aa", "ab", "ac", "ad", "ae", "af"] 
["aa".."af"] 

+1

Esto es genial. Imagine tratar de hacerlo en un lenguaje que no sea de pato. –

5

Aquí es una respuesta (adaptado de this code) que es más del doble de rápido que el otro código publicado aquí. Además, solo esta respuesta y the one by @Steve manejan matrices de números enteros.

class Array 
    def to_ranges 
    return [] if empty? 
    [].tap do |ranges| 
     init,last = first 
     each do |o| 
     if last && o != last.succ 
      ranges << (init..last) 
      init = o 
     end 
     last = o 
     end 
     ranges << (init..last) 
    end 
    end 
end 

Éstos son los resultados de referencia:

    user  system  total  real 
steve  1.107000 0.000000 1.107000 ( 1.106221) 
wayne  1.092000 0.000000 1.092000 ( 1.099220) 
user229426 0.531000 0.000000 0.531000 ( 0.523104) 
mladen1  0.780000 0.000000 0.780000 ( 0.774154) 
mladen2  0.780000 0.000000 0.780000 ( 0.792159) 
phrogz  0.218000 0.000000 0.218000 ( 0.220044) 

Todo el código como punto de referencia se adaptó para eliminar sort.uniq para una comparación justa.

0
array = [505, 506, 507, 600, 601, 602, 603, 1643, 1644, 1645, 1646, 1647, 1648, 1649, 1650, 1651, 1652, 1653, 1654] 
array.inject([]){ |a, e| a[-1] && a[-1].last && a[-1].last == e-1 ? a[-1] = (a[-1].first..e) : a << (e..e); a } 
#=> [505..507, 600..603, 1643..1654] 

y para las matrices no ordenados puede preclasificar que:

array.sort!.uniq! 
+0

~~ No se puede encadenar 'ordenar!' !!!! ~~ Lo sentimos, 'tipo!' Parece que es seguro encadenar por alguna razón. La mayoría de los otros métodos de mutación que terminan en '!' No son (como gsub !, uniq !, etc.) Así que me encoge cuando veo a alguien usando el valor de retorno de cualquier método que termine en '!' –

+0

@JackCasey, por supuesto que puedes encadena cualquiera de los métodos enumerados (¡ordenar !, gsub! uniq!). Pero podría no tener sentido en algunos casos – fl00r

+0

Claro, quise decir que a veces es una trampa :) –

Cuestiones relacionadas