column_pack 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d3392aff0cd883b18b176bc81e864c6eb575dcb7
4
+ data.tar.gz: 4973088e62dac89bc4f9096cec5e76431254af0e
5
+ SHA512:
6
+ metadata.gz: 39fab94e8b81156dbe61ef7a7fd0f09c14f1445f0b234c07e9c2c3555af5d147342bac049fbabd5943c47349037df1b7a32286ab6e1f49184242141d2c599c94
7
+ data.tar.gz: be2914d3adc9180b9fe12dc9f3e90fe7d8fc4ebe2ee08344ff88fa8844acae0150457fa81cedbe6f2dcd3b598eca8c4e72adede90ac86cff98170fbf9f1aef68
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2014 YOURNAME
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,32 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'ColumnPack'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+
18
+
19
+
20
+ Bundler::GemHelper.install_tasks
21
+
22
+ require 'rake/testtask'
23
+
24
+ Rake::TestTask.new(:test) do |t|
25
+ t.libs << 'lib'
26
+ t.libs << 'test'
27
+ t.pattern = 'test/**/*_test.rb'
28
+ t.verbose = false
29
+ end
30
+
31
+
32
+ task default: :test
@@ -0,0 +1,9 @@
1
+ require 'column_pack/railtie' if defined? Rails
2
+
3
+ module ColumnPack
4
+ module Rails
5
+ class Engine < ::Rails::Engine
6
+ # Get rails to add app, lib, vendor to load path
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,104 @@
1
+ module ColumnPack
2
+
3
+ # Arranges elements into bins using a simple one dimensional bin packing algorithm.
4
+ class BinPacker
5
+
6
+ # Uses a fixed number of bins (total_bins).
7
+ #
8
+ # Options:
9
+ # :algorithm specify a different bin packing algorithm (default :best_fit_decreasing)
10
+ # available algorithms are :best_fit_decreasing, :best_fit_increasing
11
+ #
12
+ # :shuffle_in_col after packing columns, shuffle the elements in each column (defaults to true)
13
+ #
14
+ def initialize(total_bins, options = {})
15
+ raise ArgumentError.new("Must choose a number of bins greater than zero") if total_bins <= 0
16
+
17
+ @total_bins = total_bins
18
+ @algorithm = options[:algorithm] || :best_fit_decreasing
19
+
20
+ if options.has_key? :shuffle_in_col
21
+ @shuffle_in_col = options[:shuffle_in_col]
22
+ else
23
+ @shuffle_in_col = true
24
+ end
25
+
26
+ @elements = []
27
+ @needs_packing = true
28
+ end
29
+
30
+ # Adds element to be packed.
31
+ def add(size, content)
32
+ raise ArgumentError.new("Bin size must be greater than zero") if size <= 0
33
+
34
+ @elements << {:size => size.to_i, :content => content}
35
+ @needs_packing = true
36
+ end
37
+
38
+ # Returns a packed multi-dimensional array of elements.
39
+ def bins
40
+ pack_all if @needs_packing
41
+ @bins
42
+ end
43
+
44
+ # Total empty space left over by uneven packing.
45
+ def empty_space
46
+ pack_all if @needs_packing
47
+ max = @sizes.each.max
48
+ space = 0
49
+ @sizes.each { |size| space += max - size }
50
+ return space
51
+ end
52
+
53
+ private
54
+ def pack_all
55
+ @bins = Array.new(@total_bins) {Array.new}
56
+ @sizes = Array.new(@total_bins, 0)
57
+
58
+ self.send(@algorithm)
59
+ tall_to_middle
60
+ shuffle_within_cols if @shuffle_in_col
61
+
62
+ @needs_packing = false
63
+ end
64
+
65
+ def best_fit_decreasing
66
+ @elements.sort_by! { |e| e[:size] }
67
+ @elements.reverse!
68
+ best_fit
69
+ end
70
+
71
+ def best_fit_increasing
72
+ @elements.sort_by! { |e| e[:size] }
73
+ best_fit
74
+ end
75
+
76
+ def best_fit
77
+ @elements.each do |element|
78
+ size, col = @sizes.each_with_index.min
79
+ pack(col, element)
80
+ end
81
+ end
82
+
83
+ def shuffle_within_cols
84
+ @bins.each { |bin| bin.shuffle! }
85
+ end
86
+
87
+ # moves the tallest bin to the middle
88
+ def tall_to_middle
89
+ if (@total_bins > 1) && ((@total_bins % 2) != 0)
90
+ size, max_col = @sizes.each_with_index.max
91
+ mid_col = @total_bins / 2
92
+
93
+ temp = @bins[mid_col].clone
94
+ @bins[mid_col] = @bins[max_col]
95
+ @bins[max_col] = temp
96
+ end
97
+ end
98
+
99
+ def pack(col, element)
100
+ @bins[col] << element[:content]
101
+ @sizes[col] += element[:size].to_i
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,54 @@
1
+ module ColumnPack
2
+ # Arranges HTML elements into a fixed number of columns.
3
+ class ColumnPacker
4
+ include ActionView::Context
5
+ include ActionView::Helpers::TagHelper
6
+ include ActionView::Helpers::TextHelper
7
+
8
+ # Uses a fixed number of columns (total_columns).
9
+ #
10
+ # Options:
11
+ # :algorithm specify a different bin packing algorithm (default :best_fit_decreasing)
12
+ # available algorithms are :best_fit_decreasing, :best_fit_increasing
13
+ #
14
+ # :shuffle_in_col after packing columns, shuffle the elements in each column (defaults to true)
15
+ #
16
+ def initialize(total_columns, options = {})
17
+ @bin_packer = BinPacker.new(total_columns, options)
18
+ end
19
+
20
+ def add(height, content)
21
+ @bin_packer.add(height.to_i, content)
22
+ end
23
+
24
+ # Renders all elements into columns.
25
+ def render
26
+ wrap(@bin_packer.bins).html_safe
27
+ end
28
+
29
+ private
30
+ def wrap(bins)
31
+ content_tag :div, :class => "column-pack-wrap" do
32
+ capture do
33
+ bins.each do |bin|
34
+ concat column(bin)
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ def column(bin)
41
+ content_tag :div, :class => "column-pack-col" do
42
+ capture do
43
+ bin.each do |element|
44
+ concat element(element)
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ def element(element)
51
+ content_tag :div, element.html_safe, :class => "column-pack-element"
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,11 @@
1
+ require 'column_pack/view_helpers'
2
+ require 'column_pack/bin_packer'
3
+ require 'column_pack/column_packer'
4
+
5
+ module ColumnPack
6
+ class Railtie < Rails::Railtie
7
+ initializer "column_pack.view_helpers" do |app|
8
+ ActionView::Base.send :include, ViewHelpers
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,3 @@
1
+ module ColumnPack
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,51 @@
1
+ module ColumnPack
2
+ module ViewHelpers
3
+
4
+ # Packs content into columns.
5
+ #
6
+ # pack_in_columns expect a block to be passed:
7
+ #
8
+ # pack_in_columns(3) do
9
+ # pack_element(100) do
10
+ # "A"
11
+ # end
12
+ # end
13
+ #
14
+ # Options:
15
+ # :algorithm specify a different bin packing algorithm (default :best_fit_decreasing)
16
+ # available algorithms are :best_fit_decreasing, :best_fit_increasing
17
+ #
18
+ # :shuffle_in_col after packing columns, shuffle the elements in each column (defaults to true)
19
+ #
20
+ def pack_in_columns(total_columns, options = {})
21
+ @column_packer = ColumnPacker.new(total_columns, options)
22
+
23
+ yield
24
+
25
+ @column_packer.render
26
+ end
27
+
28
+ # Packs a single element with a given height into a column.
29
+ #
30
+ # pack_element should be called withing pack_in_columns's block:
31
+ #
32
+ # pack_in_columns(3) do
33
+ # pack_element(100) do
34
+ # "A"
35
+ # end
36
+ # end
37
+ #
38
+ # Accepts parameter strings or block content (ERB, strings, etc).
39
+ #
40
+ def pack_element(height, content = nil, &block)
41
+ return if @column_packer.nil?
42
+
43
+ if block_given?
44
+ @column_packer.add(height.to_i, capture(&block))
45
+ else
46
+ @column_packer.add(height.to_i, content)
47
+ end
48
+ end
49
+
50
+ end
51
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :column_pack do
3
+ # # Task goes here
4
+ # end
@@ -0,0 +1,139 @@
1
+ require 'test_helper'
2
+
3
+ class BinPackerTest < ActiveSupport::TestCase
4
+ include ColumnPack
5
+
6
+ test "initilizes with three columns" do
7
+ bp = BinPacker.new(3)
8
+ assert_equal BinPacker, bp.class
9
+ assert_equal Array, bp.bins.class
10
+ end
11
+
12
+ test "can't init with a non-positive number of cols" do
13
+ assert_raises ArgumentError do
14
+ bp = BinPacker.new(0)
15
+ end
16
+
17
+ assert_raises ArgumentError do
18
+ bp = BinPacker.new(-100)
19
+ end
20
+ end
21
+
22
+ test "adds element" do
23
+ bp = BinPacker.new(1)
24
+ assert bp.add(400, 'A')
25
+ assert_equal 'A', bp.bins[0][0]
26
+ end
27
+
28
+ test "height must be greater than zero" do
29
+ bp = BinPacker.new(3)
30
+ assert_raises ArgumentError do
31
+ bp.add(0, 'A')
32
+ end
33
+
34
+ assert_raises ArgumentError do
35
+ bp.add(-1, 'A')
36
+ end
37
+ end
38
+
39
+ test "adds two elements" do
40
+ bp = BinPacker.new(2, {:algorithm => :best_fit_decreasing})
41
+ assert bp.add(400, 'A')
42
+ assert bp.add(500, 'B')
43
+
44
+ assert_equal 'A', bp.bins[1][0]
45
+ assert_equal 'B', bp.bins[0][0]
46
+ end
47
+
48
+ test "add six elements" do
49
+ bp = BinPacker.new(3)
50
+ bp.add(600, 'F')
51
+ bp.add(300, 'C')
52
+ bp.add(500, 'E')
53
+ bp.add(400, 'D')
54
+ bp.add(200, 'B')
55
+ bp.add(100, 'A')
56
+ assert_equal 3, bp.bins.length
57
+
58
+ assert_equal Array, bp.bins[0].class
59
+ assert_operator 1, :<, bp.bins[0].length
60
+
61
+ assert_equal Array, bp.bins[1].class
62
+ assert_operator 1, :<, bp.bins[1].length
63
+
64
+ assert_equal Array, bp.bins[2].class
65
+ assert_operator 1, :<, bp.bins[2].length
66
+ end
67
+
68
+ test "can iterate bins" do
69
+ bp = BinPacker.new(3)
70
+ bp.add(600, 'F')
71
+ bp.add(300, 'C')
72
+ bp.add(500, 'E')
73
+ bp.add(400, 'D')
74
+ bp.add(200, 'B')
75
+ bp.add(100, 'A')
76
+
77
+ assert_equal Array, bp.bins.class
78
+
79
+ bp.bins.each do |bin|
80
+ bin.each do |element|
81
+ assert_includes ['A', 'B', 'C', 'D', 'E', 'F'], element.to_s
82
+ end
83
+ end
84
+ end
85
+
86
+ test "empty space" do
87
+ bp = BinPacker.new(3)
88
+ bp.add(200, 'A')
89
+ bp.add(900, 'B')
90
+ assert_equal 1600, bp.empty_space
91
+ end
92
+
93
+ test "test different packing algorithms" do
94
+ bp = BinPacker.new(3, {:algorithm => :best_fit_decreasing})
95
+ bp.add(100, 'A')
96
+ bp.add(900, 'B')
97
+ end
98
+
99
+ test "can turn off shuffling" do
100
+ bp = BinPacker.new(1, {:algorithm => :best_fit_decreasing, :shuffle_in_col => false})
101
+ bp.add(900, 'A')
102
+ bp.add(800, 'B')
103
+ bp.add(700, 'C')
104
+ bp.add(600, 'D')
105
+ bp.add(500, 'E')
106
+ bp.add(400, 'F')
107
+
108
+ assert_equal 'A', bp.bins[0][0]
109
+ assert_equal 'B', bp.bins[0][1]
110
+ assert_equal 'C', bp.bins[0][2]
111
+ assert_equal 'D', bp.bins[0][3]
112
+ assert_equal 'E', bp.bins[0][4]
113
+ assert_equal 'F', bp.bins[0][5]
114
+ end
115
+
116
+ test "big data set" do
117
+ (2..10).each do |num_bins|
118
+ assert bp = BinPacker.new(num_bins)
119
+
120
+ File.readlines(File.expand_path('test/fixtures/files/hundred.txt')).each do |line|
121
+ number, name, = line.split(' ')
122
+ assert bp.add(number.to_i, name)
123
+ end
124
+ end
125
+ end
126
+
127
+ test "six items with a perfect fit" do
128
+ bp = BinPacker.new(3)
129
+ bp.add(100, 'A')
130
+ bp.add(300, 'B')
131
+ bp.add(50, 'C')
132
+ bp.add(350, 'D')
133
+ bp.add(200, 'E')
134
+ bp.add(200, 'F')
135
+
136
+ assert_equal 0, bp.empty_space
137
+ end
138
+
139
+ end