我有一个类Format
,它是一个包含有关文本格式的信息的数据结构:
Format = Struct.new(:from, :to, :attributes)
(我不在乎是Format.new(from, to, attrs)
还是Format.new(range, attrs)
。)
在Format
个实例f1
和f2
中,我想定义一个操作merge_formats(f1, f2)
,它合并重叠格式,例如,当应用语法高亮和选择时相同的文本区域。类似地,对于范围是分离的情况,仅在一侧重叠,等等。它由以下图表描述:
INPUT:
+---+---+---+---+---+---+---+---+---+
format1 = | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | , attributes1
+---+---+---+---+---+---+---+---+---+
+---+---+---+---+
format2 = | 4 | 5 | 6 | 7 | , attributes2
+---+---+---+---+
RESULT:
+---+---+---+
part1 = | 1 | 2 | 3 |, attributes1
+---+---+---+
+---+---+---+---+
part2 = | 4 | 5 | 6 | 7 | , attributes1 + attributes2
+---+---+---+---+
+---+---+
part3 = | 8 | 9 | , attributes1
+---+---+
特定应用程序应如下所示:
merge_formats(Format.new(1, 9, attributes1), Format.new(4, 7, attributes2))
# => [Format.new(1, 3, attributes1), Format.new(4, 7, attributes1 + attributes2), Format.new(8, 9, attributes1)]
attribute1
和attribute2
是可以添加的任何内容,在这种情况下是flags(整数,因此是|
运算符)。如果他们是:attribute1
和:attribute2
,那么作为添加"操作将是Array.push
,结果可能是:
# => [Format.new(1, 3, [:attribute1]), Format.new(4, 7, [:attribute1, :attribute2]), Format.new(8, 9, [:attribute1])]
有一个很好的Ruby方法如何解决这个问题?
我遇到了this,其中一个答案巧妙地使用flat_map
来检测边缘,但在我的情况下这不可用,因为其他信息丢失了。我想出了这个,我不喜欢它:
Format = Struct.new(:from, :to, :attributes) do
def self.compare(f1, f2)
if f2.from < f1.from && f2.to > f1.to
:includes
elsif f2.from >= f1.from && f2.to <= f1.to
:inside
elsif f2.from < f1.from && (f1.from..f1.to).include?(f2.to)
:left
elsif (f1.from..f1.to).include?(f2.from) && f2.to > f1.to
:right
else
:outside
end
end
def self.merge(f1, f2)
case compare(f1, f2)
when :includes
[Format.new(f2.from, f1.from-1, f2.attributes),
Format.new(f2.from, f1.to, f1.attributes | f2.attributes),
Format.new(f1.to+1, f2.to, f2.attributes),
]
when :inside
if f2.from == f1.from && f2.to == f1.to
[Format.new(f2.from, f2.to, f1.attributes | f2.attributes)]
else
[Format.new(f1.from, f2.from-1, f1.attributes),
Format.new(f2.from, f2.to, f1.attributes | f2.attributes),
Format.new(f2.to+1, f1.to, f1.attributes),
]
end
when :left
r = [Format.new(f2.from, f1.from-1, f2.attributes),
Format.new(f1.from, f2.to, f1.attributes | f2.attributes)]
r << Format.new(f2.to+1, f1.to, f1.attributes) if f2.to != f1.to
r
when :right
r = []
r << Format.new(f1.from, f2.from-1, f1.attributes) if f2.from != f1.from
r << Format.new(f2.from, f1.to, f1.attributes | f2.attributes)
r << Format.new(f1.to+1, f2.to, f2.attributes)
r
else
if f2.from < f1.from
[f2, f1]
else
[f1, f2]
end
end
end
end
答案 0 :(得分:1)
一种可能的解决方案(我不是专家Rubyist):
# file .../split_range/lib/t.rb
class SplitRange
# Answer a hash.
# There will be two or three of the following keys :
# :ar1left : part of range1 outside and left of range2
# :br2left : part of range2 outside and left of range1
# :ccommon : part common to range1 and range2
# :dr1right : part of range1 outside and right of range2
# :er2right : part of range2 outside and right of range1
# Note : letters a b c d e have been added to help writing test expectations
# in the same order as the result of merge.
# The value is an array of numbers from ranges.
def partition(p_range1, p_range2)
# puts "r1=#{p_range1} r2=#{p_range2}"
group1 = p_range1.group_by{ | e | e < p_range2.first ? :ar1left : p_range2.cover?(e) ? :ccommon : :dr1right }
group2 = p_range2.group_by{ | e | e < p_range1.first ? :br2left : p_range1.cover?(e) ? :ccommon : :er2right }
group1.merge(group2)
end
end # class SplitRange
class Format < Struct.new(:from, :to, :attributes)
def self.merge(f1, f2)
formats = []
groups = SplitRange.new.partition(f1.from..f1.to, f2.from..f2.to)
#puts groups
groups.sort_by{ | k, v | k}.each do | key, value |
case key
when :ar1left
formats << Format.new(value.first, value.last, f1.attributes)
when :br2left
formats << Format.new(value.first, value.last, f2.attributes)
when :ccommon
formats << Format.new(value.first, value.last, f1.attributes | f2.attributes)
when :dr1right
formats << Format.new(value.first, value.last, f1.attributes)
when :er2right
formats << Format.new(value.first, value.last, f2.attributes)
end
end
formats
end
end # class Format
if __FILE__ == $0
then
f1 = Format.new(1, 9, [:foo])
f2 = Format.new(4, 7, [:bar])
puts Format.merge(f1, f2)
end
执行:
$ ruby -w lib/t.rb
#<struct Format from=1, to=3, attributes=[:foo]>
#<struct Format from=4, to=7, attributes=[:foo, :bar]>
#<struct Format from=8, to=9, attributes=[:foo]>
我写了两个RSpec测试,一个用来测试SplitRange
:
# file .../split_range/spec/t_split_spec.rb
require 't'
RSpec.describe SplitRange do
describe '#partition' do
let(:rleft) { 1..5 }
let(:rcenter) { 10..20 }
let(:rright) { 24..26 }
let(:rovleft) { 8..12 }
let(:rinside) { 12..16 }
let(:rovright) { 18..24 }
let(:rleft_a) { rleft.to_a }
let(:rcenter_a) { rcenter.to_a }
let(:rright_a) { rright.to_a }
let(:rovleft_a) { rovleft.to_a }
let(:rinside_a) { rinside.to_a }
let(:rovright_a) { rovright.to_a }
def helperCommon(a1, a2)
# answers elements common to both arrays
a1.select { | e | a2.include?(e) }
end
context 'non overlapping' do
it 'values of r1 smaller than r2 go to :ar1left' do
groups = SplitRange.new.partition(rleft, rcenter)
expect(groups[:ar1left]).to eq(rleft_a)
end
it 'values of r2 smaller than r1 go to :br2left' do
groups = SplitRange.new.partition(rcenter, rleft)
expect(groups[:br2left]).to eq(rleft_a)
end
it 'values of r1 greater than r2 go to :dr1right' do
groups = SplitRange.new.partition(rright, rcenter)
expect(groups[:dr1right]).to eq(rright_a)
end
it 'values of r2 greater than r1 go to :er2right' do
groups = SplitRange.new.partition(rcenter, rright)
expect(groups[:er2right]).to eq(rright_a)
end
end # context 'non overlapping'
context 'overlapping r2 from left to right' do
it 'values of r2 smaller than r1 go to :br2left' do
groups = SplitRange.new.partition(rcenter, rovleft)
expect(groups[:br2left]).to eq(rovleft_a - rcenter_a)
end
it 'values of r2 (left) common to r1 go to :ccommon' do
groups = SplitRange.new.partition(rcenter, rovleft)
expect(groups[:ccommon]).to eq(helperCommon(rcenter_a, rovleft_a))
end
it 'values of r2 (inside) common to r1 go to :ccommon' do
groups = SplitRange.new.partition(rcenter, rinside)
expect(groups[:ccommon]).to eq(helperCommon(rcenter_a, rinside_a))
end
it 'values of r2 (right) common to r1 go to :ccommon' do
groups = SplitRange.new.partition(rcenter, rovright)
expect(groups[:ccommon]).to eq(helperCommon(rcenter_a, rovright_a))
end
it 'values of r2 greater than r1 go to :er2right' do
groups = SplitRange.new.partition(rcenter, rovright)
expect(groups[:er2right]).to eq(rovright_a - rcenter_a)
end
end # context 'overlapping r2 from left to right'
context 'overlapping r1 from left to right' do
it 'values of r1 smaller than r2 go to :ar1left' do
groups = SplitRange.new.partition(rovleft, rcenter)
expect(groups[:ar1left]).to eq(rovleft_a - rcenter_a)
end
it 'values of r1 (left) common to r2 go to :ccommon' do
groups = SplitRange.new.partition(rovleft, rcenter)
expect(groups[:ccommon]).to eq(helperCommon(rovleft_a, rcenter_a))
end
it 'values of r1 (inside) common to r2 go to :ccommon' do
groups = SplitRange.new.partition(rinside, rcenter)
expect(groups[:ccommon]).to eq(helperCommon(rinside_a, rcenter_a))
end
it 'values of r1 (right) common to r2 go to :ccommon' do
groups = SplitRange.new.partition(rovright, rcenter)
expect(groups[:ccommon]).to eq(helperCommon(rovright_a, rcenter_a))
end
it 'values of r1 greater than r2 go to :dr1right' do
groups = SplitRange.new.partition(rovright, rcenter)
expect(groups[:dr1right]).to eq(rovright_a - rcenter_a)
end
end # context 'overlapping r1 from left to right'
end # describe '#partition'
end # describe SplitRange
和一个测试merge
:
# file .../split_range/spec/t_merge_spec.rb
require 't'
RSpec.describe Format do
describe '.merge' do
=begin
let(:rleft) { 1..5 }
let(:rcenter) { 10..20 }
let(:rright) { 24..26 }
let(:rovleft) { 8..12 }
let(:rinside) { 12..16 }
let(:rovright) { 18..24 }
=end
context 'moving r1 over r2' do
it 'non overlapping r1 on the left of r2' do
f1 = Format.new(1, 5, [:foo])
f2 = Format.new(10, 20, [:bar])
expect(Format.merge(f1, f2)).to eq([Format.new(1, 5, [:foo]), Format.new(10, 20, [:bar])])
end
it 'overlapping r1 on the left of r2' do
f1 = Format.new(8, 12, [:foo])
f2 = Format.new(10, 20, [:bar])
expect(Format.merge(f1, f2)).to \
eq([Format.new(8, 9, [:foo]), Format.new(10, 12, [:foo, :bar]), Format.new(13, 20, [:bar])])
end
it 'overlapping r1 inside r2' do
f1 = Format.new(12, 16, [:foo])
f2 = Format.new(10, 20, [:bar])
expect(Format.merge(f1, f2)).to \
eq([Format.new(10, 11, [:bar]), Format.new(12, 16, [:foo, :bar]), Format.new(17, 20, [:bar])])
end
it 'overlapping r1 on the right of r2' do
f1 = Format.new(18, 24, [:foo])
f2 = Format.new(10, 20, [:bar])
expect(Format.merge(f1, f2)).to \
eq([Format.new(10, 17, [:bar]), Format.new(18, 20, [:foo, :bar]), Format.new(21, 24, [:foo])])
end
it 'non overlapping r1 on the right of r2' do
f1 = Format.new(24, 26, [:foo])
f2 = Format.new(10, 20, [:bar])
expect(Format.merge(f1, f2)).to eq([Format.new(10, 20, [:bar]), Format.new(24, 26, [:foo])])
end
end # context 'moving r1 over r2'
context 'moving r2 under r1' do
it 'non overlapping r2 on the left of r1' do
f1 = Format.new(10, 20, [:foo])
f2 = Format.new(1, 5, [:bar])
expect(Format.merge(f1, f2)).to eq([Format.new(1, 5, [:bar]), Format.new(10, 20, [:foo])])
end
it 'overlapping r2 on the left of r1' do
f1 = Format.new(10, 20, [:foo])
f2 = Format.new(8, 12, [:bar])
expect(Format.merge(f1, f2)).to \
eq([Format.new(8, 9, [:bar]), Format.new(10, 12, [:foo, :bar]), Format.new(13, 20, [:foo])])
end
it 'overlapping r2 inside r1' do
f1 = Format.new(10, 20, [:foo])
f2 = Format.new(12, 16, [:bar])
expect(Format.merge(f1, f2)).to \
eq([Format.new(10, 11, [:foo]), Format.new(12, 16, [:foo, :bar]), Format.new(17, 20, [:foo])])
end
it 'overlapping r2 on the right of r1' do
f1 = Format.new(10, 20, [:foo])
f2 = Format.new(18, 24, [:bar])
expect(Format.merge(f1, f2)).to \
eq([Format.new(10, 17, [:foo]), Format.new(18, 20, [:foo, :bar]), Format.new(21, 24, [:bar])])
end
it 'non overlapping r2 on the right of r1' do
f1 = Format.new(10, 20, [:foo])
f2 = Format.new(24, 26, [:bar])
expect(Format.merge(f1, f2)).to eq([Format.new(10, 20, [:foo]), Format.new(24, 26, [:bar])])
end
end # context 'moving r1 over r2'
end # describe '.merge'
end # describe Format
执行:
$ rspec -fd
Format
.merge
moving r1 over r2
non overlapping r1 on the left of r2
overlapping r1 on the left of r2
overlapping r1 inside r2
overlapping r1 on the right of r2
non overlapping r1 on the right of r2
moving r2 under r1
non overlapping r2 on the left of r1
overlapping r2 on the left of r1
overlapping r2 inside r1
overlapping r2 on the right of r1
non overlapping r2 on the right of r1
SplitRange
#partition
non overlapping
values of r1 smaller than r2 go to :ar1left
values of r2 smaller than r1 go to :br2left
values of r1 greater than r2 go to :dr1right
values of r2 greater than r1 go to :er2right
overlapping r2 from left to right
values of r2 smaller than r1 go to :br2left
values of r2 (left) common to r1 go to :ccommon
values of r2 (inside) common to r1 go to :ccommon
values of r2 (right) common to r1 go to :ccommon
values of r2 greater than r1 go to :er2right
overlapping r1 from left to right
values of r1 smaller than r2 go to :ar1left
values of r1 (left) common to r2 go to :ccommon
values of r1 (inside) common to r2 go to :ccommon
values of r1 (right) common to r2 go to :ccommon
values of r1 greater than r2 go to :dr1right
Finished in 0.0093 seconds (files took 0.11066 seconds to load)
24 examples, 0 failures
所有测试都是绿色的(Ruby 2.4,RSpec 3.6)。
答案 1 :(得分:1)
解决方案的结构
我已经定义了
Format = Struct.new(:range, :attributes)
和类Format
的实例,例如
Format.new(10..20, :BAR)
这里@attribute
等于符号,但它可以是任何Ruby对象。
然后我将构造并返回Format
的实例数组,例如
Format.new(12..15, [:FOO, :BAR])
这被解释为Format
的{{1}}原始实例@attributes
等于:FOO
和:BAR
的值@range
覆盖了12..15
{ {1}}。此外,Format
(@attributes
的值为数组)的这些实例具有问题所要求的非重叠范围。未指定数组中元素的顺序(@attributes
的值)。
然后可以根据需要操纵@attributes
的值(数组)。例如,[3,5]
可能会转换为3|5
。
<强>代码强>
def merge_formats(*formats)
fmod = formats.map { |e| Format.new(e.range, [e.attributes]) }.
sort_by { |e| e.range.begin }
a = []
while fmod.any?
b = []
while fmod.any? && (b.empty? || (fmod.first.range.begin == b.first.range.begin))
b << fmod.shift
end
next_end = b.min_by { |f| f.range.end }.range.end
next_end = [next_end, fmod.first.range.begin-1].min if fmod.any?
a << Format.new(b.first.range.begin..next_end, b.map { |f| f.attributes.first })
while b.any?
f = b.shift
fmod.unshift(Format.new(next_end+1..f.range.end, f.attributes)) if
f.range.end > next_end
end
end
a
end
<强>实施例强>
Format = Struct.new(:range, :attributes)
f1 = Format.new(10..20, :FOO)
f2 = Format.new(12..16, :BAR)
merge_formats(f1, f2)
#=> [#<struct Format range=10..11, attributes=[:FOO]>,
# #<struct Format range=12..16, attributes=[:FOO, :BAR]>,
# #<struct Format range=17..20, attributes=[:FOO]>]
f1 = Format.new(12..16, :BAR)
f2 = Format.new(10..11, :FOO)
merge_formats(f1, f2)
#=> [#<struct Format range=10..11, attributes=[:FOO]>,
# #<struct Format range=12..16, attributes=[:BAR]>]
f1 = Format.new(12..16, :BAR)
f2 = Format.new(10..20, :FOO)
f3 = Format.new(14..24, :BAZ)
f4 = Format.new(15..18, :QUX)
merge_formats(f1, f2, f3, f4)
#=> [#<struct Format range=10..11, attributes=[:FOO]>,
# #<struct Format range=12..13, attributes=[:FOO, :BAR]>,
# #<struct Format range=14..14, attributes=[:BAR, :FOO, :BAZ]>,
# #<struct Format range=15..16, attributes=[:BAZ, :FOO, :BAR, :QUX]>,
# #<struct Format range=17..18, attributes=[:QUX, :FOO, :BAZ]>,
# #<struct Format range=19..20, attributes=[:BAZ, :FOO]>,
# #<struct Format range=21..24, attributes=[:BAZ]>]