如何按排序顺序获取重叠范围的计数?

时间:2015-12-07 14:26:23

标签: arrays ruby range

假设我有一个排序的包含范围数组:

a = [1012..1014, 1016..1020, 1017..1022, 1021..1035, 1040..1080]

我希望输出一个数组数组,每个数组的第一个元素是一个范围,第二个元素是重叠计数,如下所示:

[[1012..1014, 1], [1016..1016, 1], [1017..1020, 2], [1021..1022, 2], [1023..1035, 1], [1040..1080, 1]]

例如,范围1017..1020包含在两个范围1016..10201017..1022中,因此其数量为两个。

5 个答案:

答案 0 :(得分:2)

<强>代码

require 'set'

def range_info(a)
  covered_by = a.each_with_object(Hash.new { |h,k| h[k]=Set.new }) { |r,h|
    r.each { |n| h[n] << r } }
  a.flat_map { |r| r.to_a }.
    uniq.
    slice_when { |b,c| c > b+1 }.
    flat_map { |r| r.to_a.slice_when { |b,c| covered_by[b] != covered_by[c] } }.
    flat_map { |enum| enum.to_a.map { |a| [a.first..a.last, covered_by[a.first].size] } }
end

示例

a = [1012..1014, 1016..1020, 1017..1022, 1021..1035, 1040..1080]

range_info(a)
  #=> [[1012..1014, 1], [1016..1016, 1], [1017..1020, 2], [1021..1022, 2],
  #    [1023..1035, 1], [1040..1080, 1]] 

<强>解释

首先创建哈希covered_by,其密钥等于a中至少一个范围覆盖的数字,其中covered_by[n]等于a中所有范围的集合覆盖密钥n

covered_by = a.each_with_object(Hash.new { |h,k| h[k]=Set.new }) { |r,h|
  r.each { |n| h[n] << r } }
  #=> {1012=>#<Set: {1012..1014}>, 1013=>#<Set: {1012..1014}>,
  #    ...
  #    1016=>#<Set: {1016..1020}>, 1017=>#<Set: {1016..1020, 1017..1022}>,
  #    ...
  #    1079=>#<Set: {1040..1080}>, 1080=>#<Set: {1040..1080}>} 

有关Hash.new { |h,k| h[k]=[] }的解释,请参阅我的回答here,与Hash.new { |h,k| h[k]=Set.new }类似。

接下来,获取一组增加的非重叠范围,这些范围涵盖a中一个或多个范围所涵盖的相同数字:

arr = a.flat_map { |r| r.to_a }.uniq.slice_when { |b,c| c > b+1 }
  #=> [1012..1014, 1016..1035, 1040..1080] 

接下来,将arr中的每个范围拆分为枚举器,这些枚举器将生成​​a中相同范围所涵盖的连续数字数组:

b = arr.flat_map { |r| r.to_a.slice_when { |b,c| covered_by[b] != covered_by[c] } }
  #=> [#<Enumerator: #<Enumerator::Generator:0x007fd1ea854558>:each>,
  #    #<Enumerator: #<Enumerator::Generator:0x007fd1ea8543c8>:each>,
  #    #<Enumerator: #<Enumerator::Generator:0x007fd1ea854238>:each>] 

我们可以通过将它们转换为数组来查看b的元素:

b.map(&:to_a)
  #=> [[[1012, 1013, 1014]],
  #    [[1016], [1017, 1018, 1019, 1020], [1021, 1022],
  #     [1023, 1024, 1025, 1026, 1027, 1028, 1029, 1030, 1031, 1032, 1033,
  #      1034, 1035]],
  #    [[1040, 1041, 1042, 1043, 1044, 1045, 1046, 1047, 1048, 1049, 1050,
  #      1051, 1052, 1053, 1054, 1055, 1056, 1057, 1058, 1059, 1060, 1061,
  #      1062, 1063, 1064, 1065, 1066, 1067, 1068, 1069, 1070, 1071, 1072,
  #      1073, 1074, 1075, 1076, 1077, 1078, 1079, 1080]]] 

最后,flat_map这些数组到包含范围和a中涵盖范围所有元素的范围数的数组:

c = b.flat_map { |enum| enum.to_a.map { |a| [a.first..a.last, covered_by[a.first].size] } }
  #=> [[1012..1014, 1], [1016..1016, 1], [1017..1020, 2], [1021..1022, 2],
  #    [1023..1035, 1], [1040..1080, 1]] 

答案 1 :(得分:1)

以下是我对此问题的看法。它可能效率不高 - 复杂度O(n 2 - 尽管如此,它仍然是一种解决方案。

我找到范围是否是另一个范围的子范围的方法是执行以下步骤:

  1. 将两个范围转换为数组,然后使用Array#|运算符
  2. 进行连接
  3. 对通过组合两个范围获得的数组进行排序。
  4. 如果一个范围是另一个范围的子范围,那么,当使用to_a转换为数组时,包含子范围的范围将等于组合排序数组。
  5. 以下是插图:

    r1 = 2..3
    r2 = 1..4
    
    p a = r1.to_a | r2.to_a
    #=> [2, 3, 1, 4]
    p a = a.sort
    #=> [1, 2, 3, 4]
    p a == r1.to_a
    #=> [1,2,3,4] == [2,3] 
    #=> false
    p a == r2.to_a
    #=> [1,2,3,4] == [1,2,3,4]
    #=> true
    

    基于上述方法,这里是完整的代码。虽然我不确定问题中给出的范围示例列表是否有任何重叠范围,因此,我已经举了我自己的例子。

    h = {}
    r_a = [1016..1020, 1017..1020, 1021..1035, 1040..1080]
    r_a.each {|r| h[r] = 1}
    
    (0...r_a.length).each do |i|
        (0...r_a.length).each do |j|
            if (i != j)
                range_outer = r_a[i]
                range_inner = r_a[j]
    
                first,*rest,last = (range_outer.to_a | range_inner.to_a).to_a.sort
                combined_range = Range.new(first, last)
    
                if range_inner == combined_range
                    h[range_outer] += 1 
                end
            end
        end
    end
    p h
    #=> {1016..1020=>1, 1017..1020=>2, 1021..1035=>1, 1040..1080=>1}
    

答案 2 :(得分:0)

以下解决方案在有限的场合下工作:当范围的最小值和范围的最大值永远不相同时。 (即,如果有x..100,那么就没有100..y。也没有z..z。)

break_points = a.flat_map{|r| [r.min - 1, r.min, r.max, r.max + 1]}.uniq.sort
a.flat_map do
  |r|
  break_points
  .select{|i| r.min <= i and i <= r.max}
  .each_slice(2)
  .map{|min, max| min..max}
end
.group_by(&:itself)
.map{|k, v| [k, v.length]}

答案 3 :(得分:0)

如果您要测试所提供范围内的所有子范围,您可以尝试这样的事情(仅考虑从每个原始范围的最小值开始的子范围):

a = [1012..1014, 1016..1020, 1017..1022, 1021..1035, 1040..1080]
test_inputs = a.each_with_object([]) do |original, expanded| 
  original.size.times.each{ |i| expanded << Range.new(original.min, original.min+i) }
end

output = test_inputs.each_with_object([]) do |input, result|
  appears = a.select{|x| x.min <= input.min}.select{|x| x.max >= input.max}.count
  result << [input, appears]
end

答案 4 :(得分:0)

这是我解决问题的方法。让

a = [1012..1014, 1016..1020, 1017..1022, 1021..1035, 1040..1080]

第1步:展平此数组,然后计算每个元素

b = a.map(&:to_a).inject(:+).sort.group_by{|i| i }.map{|k,v| [k,v.count] }

# => [[1012, 1], [1013, 1], [1014, 1], [1016, 1], [1017, 2], [1018, 2], [1019, 2], [1020, 2], [1021, 2], [1022, 2], [1023, 1], ...

第2步:将nil添加为断点

c = b.each_with_index do |e, i| 
  if e.nil? || b[i+1].nil? then next end
  if b[i][0] + 1  != b[i+1][0] || b[i][1] != b[i+1][1] then b.insert(i+1,nil) end
end 

# => [[1012, 1], [1013, 1], [1014, 1], nil, [1016, 1], nil, [1017, 2], [1018, 2], [1019, 2], [1020, 2], [1021, 2], [1022, 2], nil, [1023, 1], ...

第3步:按分界点拆分获得的数组,并将它们分组到范围

d = c.split{|e| e.nil?}.map{|e| [(e.first[0]..e.last[0]), e.first[1]]}

# => [[1012..1014, 1], [1016..1016, 1], [1017..1022, 2], [1023..1035, 1], [1040..1080, 1]]

更新

因为split是Rails的一个方法,所以我有一个纯Ruby的替代方法。

第1步:与上述相同

第2步:将数组拆分为小组,如下所示

c = []
j = 0
b.each_with_index do |e, i| 
  if c[j].nil? then c[j] =[] end
  c[j] << b[i]
  if b[i+1] && (b[i][0] + 1 != b[i+1][0] || b[i][1] != b[i+1][1]) then j+=1 end
end

# p c => [
#          [[[1012, 1], [1013, 1], [1014, 1]], 
#          [[1016, 1]], 
#          [[1017, 2], [1018, 2], [1019, 2], [1020, 2], [1021, 2], [1022, 2]], 
#           ...
#        ]

第3步:将每个组转换为范围

d = c.map{|e| [(e.first[0]..e.last[0]), e.first[1]]}

# => [[1012..1014, 1], [1016..1016, 1], [1017..1022, 2], [1023..1035, 1], [1040..1080, 1]]