数组中的重叠范围

时间:2015-03-10 23:31:24

标签: ruby

我想在ruby 1.9.3 ver中编写一个程序。它收集唯一值范围,然后计算这些范围内的数字量。
例如,让我们使用3个范围(1..3),(6..8)和(2..4)。它将返回具有两个范围(1..4),(6..8)和数量 - 7的数组。 我写了以下代码:

z= []
def value_ranges(start, finish, z)
  range = (start..finish)
  arr = z
  point = nil
  if arr.empty?
    point = nil
  else
    arr.each { |uniq|
      if overlap?(uniq,range) == true
        point = arr.index(uniq)
        break
      else
        point = nil
      end
    }
  end
  if point != nil
    if arr[point].first >= start && arr[point].end <= finish
      range = (start..finish)
    elsif arr[point].first >= start
      range = (start..arr[point].end)
    elsif arr[point].end <= finish
      range = (arr[point].first..finish)
    else
      range = (arr[point].first..arr[point].end)
    end
    arr[point] = range
  else
    arr << range
  end
  print arr
end

def overlap?(x,y)
  (x.first - y.end) * (y.first - x.end) >= 0
end

当程序满足与已收集的两个范围重叠的范围时出现问题。
例如(1..5)(7..9)(11..19),下一个给定范围是(8..11)。
它应链接两个范围并返回以下结果 - (1..5),(7..19)。

我不知道如何在不创建无限循环的情况下重新检查整个数组。另外,计算范围内数量的最佳方法是什么?

1 个答案:

答案 0 :(得分:1)

以下是两种类似Ruby的方法。

arr = [1..3, 6..8, 2..4]

#1高效方法

首先计算合并范围:

a = arr[1..-1].sort_by(&:first).each_with_object([arr.first]) do |r,ar|
  if r.first <= ar.last.last
    ar[-1] = ar.last.first..[ar.last.last,r.last].max
  else
    ar << r
  end
end
  #=> [1..4, 6..8]

然后计算这些范围内的元素总数:

a.reduce(0) { |tot,r| tot + r.size }
  #=> [1..4, 6..8].reduce(0) { |tot,r| tot + r.size }
  #=> 7

说明

b = arr[1..-1]
  #=> [6..8, 2..4]
c = b.sort_by(&:first)      
  #=> [2..4, 6..8]
enum = c.each_with_object([1..3])
  #=> #<Enumerator: [2..4, 6..8]:each_with_object([1..3])>

枚举器enum的内容将传递到块中,并由Enumerator#each分配给块变量,后者将调用Array#each。我们可以通过将枚举器转换为数组来查看枚举器的内容:

enum.to_a
  #=> [[2..4, [1..3]], [6..8, [1..3]]]

我们可以使用Enumerator#next逐步完成枚举器。 each传递给块的枚举器的第一个元素是[2..4, [1..3]]。这将分配给块变量,如下所示:

r, ar = enum.next
   #=> [2..4, [1..3]] 
r  #=> 2..4 
ar #=> [1..3] 

我们现在执行块计算

  if r.first <= ar.last.last
    #=> 2 <= (1..3).last
    #=> 2 <= 3
    #=> true
    ar[-1] = ar.last.first..[ar.last.last,r.last].max
      #=> ar[-1] = 1..[3,4].max
      #=> ar[-1] = 1..4
      #=> 1..4
  else # not executed this time
    ar << r
  end

这不是那么神秘。所以我不必一直说“ar的最后一个范围”,让我来定义:

ar_last = ar.last
  #=> 1..3

首先,因为我们开始按每个范围的开头对范围进行排序,我们知道当enum的每个元素都传递到块中时:

ar_last.first <= r.first

对于传递到块中的enum的每个元素:

r.first <= ar_last.last

我们将r.lastar_last.last进行比较。有两种可能性需要考虑:

  • r.last <= ar_last.last,在这种情况下,两个范围重叠,因此ar_last不会改变;和
  • r.last > ar_last.last,在这种情况下,ar_last的上端必须增加到r.last

下面,

2 = r.first <= ar_last.last = 3
4 = r.last  >  ar_last.last = 3

因此ar_last已从1..3更改为1..4

each现在将enum的最后一个元素传递到块中:

r, ar = enum.next
   #=> [6..8, [1..4]] 
r  #=> 6..8 
ar #=> [1..4] 

if r.first <= ar.last.last
  #=> (6 <= 4) => false this time
  ...
else # executed this time
  ar << r
    #=> ar << (6..8)
    #=> [1..4, 6..8] 
end

a = ar #=> [1..4, 6..8]

这次r.first > ar_last.last,意味着范围r不重叠ar_last,因此我们将r追加到arar_last现在等于r

最后:

a.reduce(0) { |tot,r| tot + r.size }
  #=> [1..4, 6..8].reduce(0) { |tot,r| tot + r.size }  
  #=> 7

我们也可以写:

a.map(&:size).reduce(:+)

#2简单但效率低下

这是一个简单但不是特别有效的方法,它使用Enumerable#slice_when,在v2.2中新建。

arr = [(1..3), (6..8), (2..4)]

计算满意的范围:

a = arr.flat_map(&:to_a)
       .uniq
       .sort
       .slice_when { |i,j| i+1 != j }
       .map { |ar| (ar.first..ar.last) }
  #=> [1..4, 6..8] 

这些范围内的元素总数按#1

计算

说明

以下是步骤:

b = arr.flat_map(&:to_a)
  #=> [1, 2, 3, 6, 7, 8, 2, 3, 4]
c = b.uniq
  #=> [1, 2, 3, 6, 7, 8, 4]
d = c.sort
  #=> [1, 2, 3, 4, 6, 7, 8]
e = d.slice_when { |i,j| i+1 != j }
  #=> #<Enumerator: #<Enumerator::Generator:0x007f81629584f0>:each> 
a = e.map { |ar| (ar.first..ar.last) }
  #=> [1..4, 6..8]

我们可以通过将枚举器转换为数组来查看枚举器e的内容:

e.to_a
  #=> [[1, 2, 3, 4], [6, 7, 8]]