binpack 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/Rakefile +1 -0
- data/Readme.md +61 -0
- data/binpack.gemspec +20 -0
- data/lib/binpack.rb +77 -0
- data/lib/binpack/version.rb +3 -0
- metadata +53 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/Readme.md
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
# Introduction
|
2
|
+
This is a gem adapted from a solution for 2-dimensional bin packing by Ilmari Heikkinen. From the post:
|
3
|
+
|
4
|
+
> It's a greedy heuristic algorithm that tries to fit each box into the trunk, largest first.
|
5
|
+
> If no box fits, a new trunk is created.
|
6
|
+
> I'm representing trunks as 2D tables of squares (with one unit of hidden padding around), then fill it starting from top left.
|
7
|
+
> The fitterfirst finds a row with enough empty space to fit the box and then checks if the next box-height rows also contain the space.
|
8
|
+
> If they do, the box is drawn to the rows.
|
9
|
+
> Printing is easy because the trunk already is in printable format.
|
10
|
+
|
11
|
+
## Usage
|
12
|
+
# Create an array of random items
|
13
|
+
items = []
|
14
|
+
12.times do |i|
|
15
|
+
items << Binpack::Item.new("Associated object #{i+1}", (rand(10)+2)/2.0, (rand(10)+2)/2.0)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Pack the array of items into a bin where the default bin size is 16x10 with a padding of 1
|
19
|
+
bins = Binpack::Bin.pack(items, [], Binpack::Bin.new(16, 10, 1))
|
20
|
+
|
21
|
+
|
22
|
+
Visual output example:
|
23
|
+
|
24
|
+
puts bins.join("\n\n")
|
25
|
+
|
26
|
+
22222_0000_11_cc
|
27
|
+
22222_0000_11_cc
|
28
|
+
22222_0000_11_cc
|
29
|
+
___________11_cc
|
30
|
+
99_888_aaa____cc
|
31
|
+
99_888__________
|
32
|
+
99_____b_aaa____
|
33
|
+
99_0_7_b________
|
34
|
+
99_0_7_b_0______
|
35
|
+
___0_7___0______
|
36
|
+
|
37
|
+
Array of items, their locations locations, and whether or not they had to be rotated 90º
|
38
|
+
|
39
|
+
puts bins[0].items.inspect
|
40
|
+
|
41
|
+
[
|
42
|
+
[#<Binpack::Item:0x007f84bb14c5c0 @obj="Associated object 12", @width=5.0, @height=3.0, @rotated=false>, 1, 1],
|
43
|
+
[#<Binpack::Item:0x007f84bb031438 @obj="Associated object 1", @width=4.5, @height=3.0, @rotated=false>, 7, 1],
|
44
|
+
[#<Binpack::Item:0x007f84bb14c6b0 @obj="Associated object 11", @width=2.5, @height=4.5, @rotated=false>, 12, 1],
|
45
|
+
[#<Binpack::Item:0x007f84bb031000 @obj="Associated object 3", @width=2.0, @height=5.0, @rotated=false>, 15, 1],
|
46
|
+
[#<Binpack::Item:0x007f84bb030dd0 @obj="Associated object 4", @width=2.0, @height=5.0, @rotated=false>, 1, 5],
|
47
|
+
[#<Binpack::Item:0x007f84bb14c890 @obj="Associated object 9", @width=3.5, @height=2.5, @rotated=false>, 4, 5],
|
48
|
+
[#<Binpack::Item:0x007f84bb14c980 @obj="Associated object 8", @width=3.5, @height=1.5, @rotated=false>, 8, 5],
|
49
|
+
[#<Binpack::Item:0x007f84bb030b00 @obj="Associated object 5", @width=1.5, @height=3.0, @rotated=false>, 4, 8],
|
50
|
+
[#<Binpack::Item:0x007f84bb0311e0 @obj="Associated object 2", @width=1.0, @height=4.0, @rotated=false>, 6, 8],
|
51
|
+
[#<Binpack::Item:0x007f84bb14c7a0 @obj="Associated object 10", @width=1.0, @height=3.5, @rotated=false>, 8, 7],
|
52
|
+
[#<Binpack::Item:0x007f84bb14cb60 @obj="Associated object 6", @width=3.5, @height=1.0, @rotated=false>, 10, 7],
|
53
|
+
[#<Binpack::Item:0x007f84bb02a070 @obj="Associated object 7", @width=1.0, @height=2.5, @rotated=true>, 10, 9]
|
54
|
+
]
|
55
|
+
|
56
|
+
## Notes
|
57
|
+
|
58
|
+
1. The packing assumes a border of @padding. If you wish to put images on an 11"x17"
|
59
|
+
piece of paper with a 1" border, you will need to make the bin 16x10 with a padding of 1.
|
60
|
+
2. When layout out your objects, be sure to note whether or not the item had to be rotated to fit.
|
61
|
+
3. The algorithm uses string matching to determine placement so if higher precision than integral is required, you'll need to use a multiplier on everything.
|
data/binpack.gemspec
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "binpack/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "binpack"
|
7
|
+
s.version = Binpack::VERSION
|
8
|
+
s.authors = ["Kelley Reynolds"]
|
9
|
+
s.email = ["kelley.reynolds@rubyscale.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{2 dimensional bin packing library}
|
12
|
+
s.description = %q{2 dimensional bin packing library adapted from a RubyQuiz solution by Ilmari Heikkinen}
|
13
|
+
|
14
|
+
s.rubyforge_project = "binpack"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
end
|
data/lib/binpack.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
#require "binpack/version"
|
2
|
+
|
3
|
+
module Binpack
|
4
|
+
class Item
|
5
|
+
attr_reader :width, :height, :rotated, :obj
|
6
|
+
|
7
|
+
def initialize(obj, width, height, rotated=false)
|
8
|
+
@obj, @width, @height, @rotated = obj, width, height, rotated
|
9
|
+
end
|
10
|
+
|
11
|
+
def rotate
|
12
|
+
self.class.new(@obj, @height, @width, !@rotated)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Bin
|
17
|
+
attr_reader :width, :height, :padding, :items
|
18
|
+
|
19
|
+
def initialize(width, height, padding=1)
|
20
|
+
@width, @height, @padding = width, height, padding.to_i
|
21
|
+
@items = []
|
22
|
+
@rows = (@padding..@height + (@padding * 2)).map{ "_"*(@width + (@padding * 2)) }
|
23
|
+
end
|
24
|
+
|
25
|
+
def add(item)
|
26
|
+
try_adding(item) or try_adding(item.rotate)
|
27
|
+
end
|
28
|
+
alias_method "<<".to_sym, :add
|
29
|
+
|
30
|
+
def try_adding(item)
|
31
|
+
itemrow = "_" * (item.width + (@padding * 2))
|
32
|
+
@rows.each_with_index {|r,i|
|
33
|
+
break if i > @rows.size - (item.width + @padding * 2)
|
34
|
+
next unless r.include?(itemrow)
|
35
|
+
idxs = @rows[i + @padding, item.height + @padding].map { |s| s.index(itemrow) }
|
36
|
+
next unless idxs.all?
|
37
|
+
idx = idxs.max
|
38
|
+
next unless @rows[i, item.height + (@padding*2)].all? { |s| s[idx,itemrow.size] == itemrow }
|
39
|
+
g = rand(16).to_s(16)
|
40
|
+
@rows[i + @padding, item.height].each{ |s|
|
41
|
+
s[idx + @padding, item.width] = "#{g}" * item.width
|
42
|
+
}
|
43
|
+
@items.push([item, idx + @padding, i + @padding])
|
44
|
+
return item
|
45
|
+
}
|
46
|
+
nil
|
47
|
+
end
|
48
|
+
|
49
|
+
def empty?
|
50
|
+
@items.empty?
|
51
|
+
end
|
52
|
+
|
53
|
+
def to_s
|
54
|
+
@rows[@padding..(-1 - @padding)].map{ |r| r[@padding..(-1 - @padding)] }.join("\n")
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.pack(items, bins=[], default_bin=nil)
|
58
|
+
default_bin ||= self.new(16, 10)
|
59
|
+
raise "Expected an array" if !bins.kind_of?(Array)
|
60
|
+
|
61
|
+
items = items.sort_by { |item| item.width*item.height }.reverse
|
62
|
+
bins << self.new(default_bin.width, default_bin.height, default_bin.padding) if bins.empty?
|
63
|
+
until items.empty?
|
64
|
+
fitting = items.find { |item| bins.last.add item }
|
65
|
+
if fitting
|
66
|
+
items.delete_at(items.index(fitting))
|
67
|
+
elsif bins.last.empty?
|
68
|
+
raise "Can't fit #{items.inspect} into the bins"
|
69
|
+
else
|
70
|
+
bins << self.new(default_bin.width, default_bin.height, default_bin.padding) unless items.empty?
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
bins
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
metadata
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: binpack
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Kelley Reynolds
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-04-24 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: 2 dimensional bin packing library adapted from a RubyQuiz solution by
|
15
|
+
Ilmari Heikkinen
|
16
|
+
email:
|
17
|
+
- kelley.reynolds@rubyscale.com
|
18
|
+
executables: []
|
19
|
+
extensions: []
|
20
|
+
extra_rdoc_files: []
|
21
|
+
files:
|
22
|
+
- .gitignore
|
23
|
+
- Gemfile
|
24
|
+
- Rakefile
|
25
|
+
- Readme.md
|
26
|
+
- binpack.gemspec
|
27
|
+
- lib/binpack.rb
|
28
|
+
- lib/binpack/version.rb
|
29
|
+
homepage: ''
|
30
|
+
licenses: []
|
31
|
+
post_install_message:
|
32
|
+
rdoc_options: []
|
33
|
+
require_paths:
|
34
|
+
- lib
|
35
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
36
|
+
none: false
|
37
|
+
requirements:
|
38
|
+
- - ! '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ! '>='
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
requirements: []
|
48
|
+
rubyforge_project: binpack
|
49
|
+
rubygems_version: 1.8.15
|
50
|
+
signing_key:
|
51
|
+
specification_version: 3
|
52
|
+
summary: 2 dimensional bin packing library
|
53
|
+
test_files: []
|