如何将重叠结构与其中的范围合并

时间:2017-10-11 20:46:25

标签: ruby

我有一个类Format,它是一个包含有关文本格式的信息的数据结构:

Format = Struct.new(:from, :to, :attributes)

(我不在乎是Format.new(from, to, attrs)还是Format.new(range, attrs)。)

Format个实例f1f2中,我想定义一个操作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)]

attribute1attribute2是可以添加的任何内容,在这种情况下是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

2 个答案:

答案 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]>]