范围数组的索引数组

时间:2010-09-16 16:26:13

标签: ruby arrays range

红宝石中的范围非常酷。 我最终得到了这样的数组:

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)]

谢谢!

7 个答案:

答案 0 :(得分:14)

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

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 :(得分:8)

功能性,非常易读的解决方案:

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

还不错:

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]

答案 2 :(得分:5)

这是韦恩康拉德算法的一个简单的小方法,只需稍加调整即可使其适用于其他类型的范围,例如:字母

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"]

答案 3 :(得分:5)

这是一个答案(改编自this code),其速度是此处发布的其他代码的两倍多。此外,只有这个答案和the one by @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进行公平比较。

答案 4 :(得分:1)

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

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

答案 5 :(得分: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)

答案 6 :(得分: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]

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

array.sort!.uniq!