2011-12-24 8 views
7

me gustaría tomar la entrada tales como:¿Cómo resumo una matriz de enteros como una matriz de rangos?

[1,2,4,5,6,7,9,13] 

y convertirlo en algo como lo siguiente:

[[1,2],[4,7],[9,9],[13,13]] 

Cada sub-matriz representa un rango de números enteros.

+2

¿Estás preguntando si hay código para hacer esto ya? ¿Estás preguntando porque estás tratando de hacer tu propio y tener problemas para implementarlo? – bobbymcr

+0

Estoy rodando el mío. Parece que siempre hay formas interesantes de implementar este tipo de cosas en Ruby. – Larsenal

+0

¿Con qué condiciones se supone que se construirán los rangos? – cvshepherd

Respuesta

19

enfoque funcional utilizando Enumerable#chunk:

xs.enum_for(:chunk).with_index { |x, idx| x - idx }.map do |diff, group| 
    [group.first, group.last] 
end 
# => [[1, 2], [4, 7], [9, 9], [13, 13]] 

Cómo funciona: una vez indexados, elementos consecutivos de la matriz tienen el mismo x - idx, por lo que utilizar ese valor a trozo (agrupación de elementos consecutivos) la matriz de entrada. Finalmente, solo tenemos que tomar los primeros y últimos elementos de cada grupo para construir los pares.

+0

Esto se ve muy bien. Olvidé por completo el nuevo método de fragmento. – cvshepherd

+2

Y para dar un paso más, '.map {| min, max | min == max? min: min .. max} 'dará como resultado:' [1..2, 4..7, 9, 13] '. –

+0

O bien, cambie '[pares.primero [0], pares.último [0]]' a 'pares.primero [0] .. pares.último [0]' para obtener rangos en todas las posiciones: '[1. .2, 4..7, 9..9, 13..13] '. –

0

Otro enfoque

def summarize(x) 
    x.inject([]) do |acc, value| 
    if acc.last && acc.last[1] + 1 == value 
     acc.last[1] = value 
     acc 
    else 
     acc << [value,value] 
    end 
    end 
end 

Al igual que en el método de Larsenal pero utilizando inyectar para manejar las cosas aburridas.

3

Hmm, bueno, no es tokland's obra maestra, pero creo que puede ser una buena solución sencilla ...

[1,2,4,5,6,7,9,13].inject([]) do |m, v| 
    if m.last.to_a.last == v.pred 
    m[-1][-1] = v 
    else 
    m << [v, v] 
    end 
    m 
end 
4

Esto es casi directamente de la documentación enumerable#slice_before método:

ar = [1,2,4,5,6,7,9,13] 
prev = ar[0] 
ar.slice_before{|e|prev,prev2 = e,prev; prev2.succ != e}.map{|a|a.first..a.last} 
#=> [1..2, 4..7, 9..9, 13..13] 

Esto debería funcionar con caracteres, fechas, cualquier cosa con un método .succ.

2

Una solución aún más fácil que @tokland's very nice one está utilizando chunk_while:

xs.chunk_while { |a, b| a + 1 == b }.map do |seq| 
    [seq.first, seq.last] 
end 

Nota: chunk_while se introdujo en Ruby 2.3

Cuestiones relacionadas