binpack 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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: []
|