范围数组的索引数组

ruby的范围非常酷。 我最终得到这样的数组:

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

然后必须删除它们的一部分。 为此我:

 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] 

它们被操纵,因此一些数字被排除在外,其他数字可能被添加。 我最终可能会这样:

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

如何将其转换回紧凑的范围数组? 似乎反函数应该存在? 我希望它能返回这样的东西:

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

谢谢!

(新的和改进的。在冰箱里保鲜最多两周!):

 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] 

function性,不易读的解决方案:

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

一个很好的一个:

 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| ji!=1}.map{|a| a.first..a.last} end end [505, 506, 507, 600, 1647, 1648, 1649, 1650, 1651, 1654].split_by{|i,j| ji!=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] 

这是Wayne Conrads算法的直接小with,它有一个小的调整,使其适用于其他类型的范围,例如字母

 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 

执行此操作的结果是

 [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”]

这是一个答案(改编自此代码 ),其速度是此处发布的其他代码的两倍多。 此外,只有这个答案和@Steve的答案处理非整数数组。

 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 

以下是基准测试结果:

  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) 

所有基准代码都经过调整以删除sort.uniq以进行公平比较。

我从来没有在Ruby语言中看到任何东西,但是这里有一些代码可以帮助你自己做:

http://snippets.dzone.com/posts/show/4677

 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 ps .. a[0] s=a[1] end end left=a.index(s) pa[left..-1][0]..a[left..-1][-1] end to_range(ar) 
 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] 

对于未排序的数组,您可以预先分配它:

 array.sort!.uniq!