From: Adam Shelly Date: 2006-01-16T08:56:26+09:00 Subject: Re: [QUIZ][SOLUTION] Packing (#62) My solution so far. I use 'BoxSorter' to keep all the boxes in a hash indexed by both dimensions so its easy to find one that fits. The 'TrunkSet' keeps track of the loaded boxes using arrays of characters. The algorithm is kind of the opposite of Ilmari's: it iterates over empty spaces in the trunk, looking for boxes that fit, instead of iterating over the boxes. -Adam ----------------------------------------------------------------- require 'delegate' class Box def initialize w,h @w,@h=w,h end def [] n n==0 ? @w :@h end def rotate! @w,@h = @h,@w end def other n n==@h ? @w :@h end def <=> box2 #compares the non-matching dimension if @w == box2[0] then @h <=> box2[1] elsif @w== box2[1] then @h <=> box2[0] elsif @h == box2[0] then @w <=> box2[1] else @w <=> box2[0] end end end class BoxSorter def initialize boxset @h = Hash.new boxset.each{|b|@h[b[0]]||=[];@h[b[0]]<0 set = @h[w] box = set.find{|b| b.other(w)<=h } if set if box self.remove box box.rotate! if box[0] != w return box end w-=1; end end def remove box @h.delete_if {|k,v| v.delete(box); v.empty? } end end class TrunkSet < DelegateClass(Array) def initialize w,h @width,@height=w,h super [] grow end def grow @openrow=0 self<< Array.new(@height){Array.new(@width){" "}} end def first_open_row loop do break if last[@openrow].find{|s| s==' '} grow if @height == (@openrow +=1) end last[@openrow] end def first_open_space gaps,lastchar = [],nil first_open_row.each_with_index do |c,i| if c==' ' if c==lastchar then gaps[-1][0]+=1 else gaps << [1,i]; end end lastchar = c end gaps.max end def pad_out last[@openrow].map!{|c| if c==' ' then '+' else c end } first_open_row end def add_box box, col size,height = box[0],box[1] (0..height).each do |row| fillchar = (row == height) ? ['+','-'] : ['|','#'] if nil != (fillrow = last[@openrow+row]) fillrow[col-1] = fillchar[0] if (col-1>=0 ) size.times {|i| fillrow[col+i] = fillchar[1] } fillrow[col+size] = fillchar[0] if ( col+size < @width ) end end end def rows_remaining @height-@openrow end def has_no_boxes? last.each{|r| return false if r.find{|c| c == '#'} } true end end class Packer def initialize size, boxes @loose_boxes = BoxSorter.new(boxes) @trunks = TrunkSet.new(*size) end def pack_a_box column,nextbox = nil,nil loop do space_available,column = @trunks.first_open_space nextbox = @loose_boxes.find_best_fit(space_available, @trunks.rows_remaining) break if nextbox @trunks.pad_out #if no box fits, need to fill row with pads end @trunks.add_box(nextbox,column) end def pack until @loose_boxes.size == 0 pack_a_box end (@trunks.rows_remaining).times { @trunks.pad_out } end def show @trunks.pop if @trunks.has_no_boxes? @trunks.each do |bin| bin.each { |row|puts row.join } puts "" end puts "#{@trunks.size} loads" end end class PackerParser attr_reader :binsize, :blocks def initialize file @binsize = file.readline.chomp @blocks = file.readline.chomp.split " " end def size @binsize.match(/(\d*)x(\d*)/) [$1.to_i,$2.to_i] end def boxes @blocks.map{|s| s.match(/(\d*)x(\d*)/);Box.new($1.to_i,$2.to_i)} end end if __FILE__ == $0 pp = PackerParser.new(ARGF) puts pp.binsize puts pp.blocks.join(' ') pk = Packer.new(pp.size, pp.boxes) pk.pack pk.show end