Trying to store many rectangles without duplicate areas efficiently in ruby

痞子三分冷 提交于 2019-12-13 08:59:13

问题


I'd like to store several rectangles in one variable in ruby. It's in order to add rectangles in which the screen must be refreshed. I tried it with a class Rectangle with top, left, height and with, and stored rectangle variables in an array. But it's much too slow when iterating over it and calculating with it.

When adding a new rectangle, there shouldn't be any duplicates on the screen to refresh, and it shouldn't take much time to calculate in order to remove duplicates (no time in the best case).

Sorry, I'm not a native English speaker and many thanks for your ideas!

require 'Rectangles'

class Rectangle

attr_accessor :top,:left,:height,:width

def initialize(options={})
    @top=options[:top]
    @left=options[:left]
    @height=options[:height]
    @width=options[:width]
end

def bottom
    @top+@height-1
end

def right
    @left+@width-1
end

def coveredBy?(rectangle)
    return false if @top<rectangle.top
    return false if @left<rectangle.left
    return false if bottom>rectangle.bottom
    return false if right>rectangle.right
    return true
end

def intersection!(rectangle)
    if @top<rectangle.top
        @top=rectangle.top
    end
    if @left<rectangle.left
        @left=rectangle.left
    end
    if bottom>(t=rectangle.bottom)
        @height=rectangle.bottom+1-@top
    end
    if right>(t=rectangle.right)
        @width=rectangle.right+1-@left
    end
end

def &(rectangle)
    if @top>=rectangle.top
        top=@top
    else
        top=rectangle.top
    end
    if @left>=rectangle.left
        left=@left
    else
        left=rectangle.left
    end
    if bottom<=rectangle.bottom
        height=bottom+1-top
    else
        height=rectangle.bottom+1-top
    end
    return nil if height<=0
    if right<=rectangle.right
        width=right+1-left
    else
        width=rectangle.right+1-left
    end
    return nil if width<=0
    return Rectangle.new(
        :top=>top,
        :left=>left,
        :height=>height,
        :width=>width
    )
end

def -(rectArg)
    return [] if empty?
    return [self] if bottom<rectArg.top
    return [self] if @top>rectArg.bottom
    return [self] if right<rectArg.left
    return [self] if @left>rectArg.right

    ret=[]
    line=@top
    if @top<rectArg.top
        ret[ret.count]=Rectangle.new(
            :top=>line,
            :left=>@left,
            :height=>[rectArg.top-@top,@height].min,
            :width=>@width
        )
        line=ret.first.bottom+1
    end
    if @left<rectArg.left
        ret[ret.count]=Rectangle.new(
            :top=>line,
            :left=>@left,
            :height=>[rectArg.bottom+1-line,bottom+1-line].min,
            :width=>[rectArg.left-@left,@width].min
        )
    end
    if right>rectArg.right
        ret[ret.count]=Rectangle.new(
            :top=>line,
            :left=>rectArg.right+1,
            :height=>[rectArg.bottom+1-line,bottom+1-line].min,
            :width=>[right-rectArg.right,@width].min
        )
    end
    if bottom>rectArg.bottom
        ret[ret.count]=Rectangle.new(
            :top=>rectArg.bottom+1,
            :left=>@left,
            :height=>[bottom-rectArg.bottom,@height].min,
            :width=>@width
        )
    end
    ret
 end

# Mit return true geht es deutlich schneller als mit ||.
def outside?(rectangle)
    return true if empty?
    return true if rectangle.empty?
    return true if bottom<rectangle.top
    return true if @top>rectangle.bottom
    return true if right<rectangle.left
    return true if @left>rectangle.right
    return false
 end


 # nebeneinander oder Überschneidung
public def touching?(rectangle)
    horizontalRange=((rectangle.left-1..(rectangle.left+rectangle.width)))
    verticalRange=((rectangle.top-1..(rectangle.top+rectangle.height)))
    horizontalRange.any?{|column|
        column==@left||column==@left+@width
    } &&
    verticalRange.any?{|line|
        line==@top||line==@top+@height
    }
 end

def ==(rectangle)
    #rectangle.kind_of?(self.class) &&
    @top==rectangle.top &&
    @height==rectangle.height &&
    @left==rectangle.left &&
    @width==rectangle.width
end

def !=(rectangle)
    !(self==rectangle)
end

def empty?
    return true if top.nil?
    return true if left.nil?
    return true if height.nil?
    return true if height<=0
    return true if width.nil?
    return true if width<=0
    # @empty=@height<=0 || @width<=0 if @empty.nil?
    # @empty
end

public def area
    # @area||=@height*@width
    # @area
    @height*@width
end

# nur wenn sie passgenau nebeneinander liegen
# (Überschneidungen darf es nicht geben)
public def mergeHorizontally(rectangle)
    return nil if @top!=rectangle.top
    return nil if @height!=rectangle.height
    if @left+@width==rectangle.left
        return Rectangle.new(
            :top=>@top,
            :left=>@left,
            :height=>@height,
            :width=>@width+rectangle.width
        )
    elsif rectangle.left+rectangle.width==@left
        return Rectangle.new(
            :top=>rectangle.top,
            :left=>rectangle.left,
            :height=>rectangle.height,
            :width=>rectangle.width+@width
        )
    else
        return nil
    end
end

# nur wenn sie passgenau nebeneinander liegen
# (Überschneidungen darf es nicht geben)
public def mergeVertically(rectangle)
    return nil if @left!=rectangle.left
    return nil if @width!=rectangle.width
    if @top+@height==rectangle.top
        # return nil
        return Rectangle.new(
            :top=>@top,
            :left=>@left,
            :height=>@height+rectangle.height,
            :width=>@width
        )
    elsif rectangle.top+rectangle.height==@top
        # return nil
        return Rectangle.new(
            :top=>rectangle.top,
            :left=>rectangle.left,
            :height=>rectangle.height+@height,
            :width=>rectangle.width
        )
    else
        return nil
    end
end

public def to_rects
    Rectangles.new([self])
end

public def >(rectangle)
    return true if @height>rectangle.height
    return true if @width>rectangle.width
    return false
end

public def +(rectArg)
    # @@log<<"Rectangle#+  ==================================================="
    # @@log<<"rectArg: #{rectArg.class}"
    newTop=[@top,rectArg.top].min
    newLeft=[@left,rectArg.left].min
    newHeight=[bottom,rectArg.bottom].max+1-newTop
    newWidth=[right,rectArg.right].max+1-newLeft
    Rectangle.new(
        :top=>newTop,
        :left=>newLeft,
        :height=>newHeight,
        :width=>newWidth
    )
end

end


回答1:


You have to define your own method to find out if objects are equal.

class Rectangle
  attr_accessor :top, :left, :width, :height

  def initialize(top, left, width, height)
    @top = top
    @left = left
    @width = width
    @height = height
  end

  def ==(rect)
    @top == rect.top &&
    @left == rect.left &&
    @width == rect.width &&
    @height == rect.height
  end
end

rect1 = Rectangle.new(10, 10, 20, 30)
rect2 = Rectangle.new(20, 20, 10, 15)
rectangles = [rect1, rect2]

rect3 = Rectangle.new(30, 30, 10, 10)
rectangles.include?(rect3) # false

rect_dup = rect1.dup
rectangles.include?(rect_dup) # true

Edit: Added benchmark

require 'benchmark'

rectangles = []

1.upto(1000) do |i|
  rectangles << Rectangle.new(i, i, 10, 10)
end

Benchmark.bm do |x|
  x.report(:missing_rectangles) do
    1.upto(1000) do |i|
      rect = Rectangle.new(i, i + 5, 10, 10)
      rectangles.include?(rect)
    end
  end
end

Result:

                    user       system     total    real
missing_rectangles  0.110000   0.000000   0.110000 (  0.107022)


来源:https://stackoverflow.com/questions/39297163/trying-to-store-many-rectangles-without-duplicate-areas-efficiently-in-ruby

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!