假设我有一个排序的包含范围数组:
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..1020
和1017..1022
中,因此其数量为两个。
答案 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 ) - 尽管如此,它仍然是一种解决方案。
我找到范围是否是另一个范围的子范围的方法是执行以下步骤:
Array#|
运算符to_a
转换为数组时,包含子范围的范围将等于组合排序数组。以下是插图:
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]]