2010-09-16 6 views
10

Les gammes en ruby ​​sont plutôt cool. je me retrouve avec des tableaux comme celui-ci:Tableau d'index au tableau de gammes

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

et ont ensuite enlever les restes d'entre eux. Pour cela, je:

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] 

Ils sont manipulés, donc certains nombres sont exclus et d'autres peuvent être ajoutés. Je peux finir avec ceci:

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

Comment puis-je convertir ce nouveau dans une gamme compacte de gammes? Il semble que la fonction inverse devrait exister? Je pense qu'elle doit retourner quelque chose comme ceci:

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

Merci!

+1

plus sur des solutions: http://stackoverflow.com/questions/12360108 – tokland

+1

'(234..25)' est un plage invalide. '(234..25) .to_a => []'. –

Répondre

14

(Nouveau et amélioré reste frais dans votre réfrigérateur. jusqu'à deux semaines):

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 Bonne solution, mais la valeur dans l'autre devrait être 'span [0 ..- 2] + [spans.last.first ..n] '(Edité pour corriger) –

+0

@Steve Weet, Merci. Bonne prise. –

+0

Excellent - merci! Vérifiez également la généralisation de Steve Weet et la réponse de Mladen Jablanović plus bas! –

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

solution fonctionnelle, non-très lisible:

(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} 

Et nice one:

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

c'est assez élégant :) –

+0

SpaceBeforeModifierKeyword de RuboCop a détecté qu'il n'y avait pas d'espace avant 'if' dans' a.push ([]) si yield (i, j) '. Je ne savais pas que c'était Ruby valide. O_o –

+0

Rubocop suggère également d'utiliser 'each_with_object' plutôt que' injecter'. –

5

Ceci est un lit d'enfant droite de l'algorithme Wayne Conrads avec un petit coup sec pour le faire fonctionner pour d'autres types de gammes, par exemple alphabétique

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 

Et les résultats de l'exécution de ce sont

 
[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

C'est très cool. Imaginez essayer de le faire dans une langue qui ne fait pas de dactylographie. –

5

est ici une réponse (adapté de this code) qui est plus de deux fois plus vite que l'autre code affiché ici. En outre, seule cette réponse et the one by @Steve gérer des tableaux de non-entiers.

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 

Voici les résultats de référence:

    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) 

Tout le code benchmarkée a été adapté pour supprimer sort.uniq pour une comparaison équitable.

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] 

et pour les tableaux non classés, vous pouvez le tri préliminaire il:

array.sort!.uniq! 
+0

~~ Vous ne pouvez pas enchaîner 'trier!' !!!! ~~ Désolé en fait 'trier!' On dirait qu'il est sécuritaire de chaîner pour une raison quelconque. La plupart des autres méthodes de mutation se terminant par '!' Ne le sont pas (comme gsub !, uniq !, etc) Donc je me faufile quand je vois quelqu'un utilisant la valeur de retour de n'importe quelle méthode se terminant par '!' –

+0

@JackCasey, bien sûr vous pouvez enchaîner les méthodes listées (sort !, gsub! uniq!). Mais ça peut être sans signification dans certains cas – fl00r

+0

Bien sûr, je voulais dire que c'est un piège parfois :) –