I am creating a class that takes an array of elements and splits them into an arbitrary number of buckets so that the sum of the sizes of the elements in each bucket is balanced.
I use a naive approach when I just add the next item to the bucket with the lowest overall size until the number of items runs out. This works great for most scenarios, but sometimes fails due to a lack of "planning." I illustrated this with the following example.
the code:
Class SplitCollection:
class SplitCollection
attr_reader :buckets
def initialize(collection, buckets = 2)
@collection = collection
@buckets = Array.new(buckets) { Bucket.new }
split
end
private
def split
@collection.each do |item|
@buckets.min { |x, y| x.total <=> y.total } << item
end
end
end
Bucket Class:
class Bucket
def initialize
@items = []
end
def total
@items.reduce(0) { |sum, item| sum + item.size }
end
def <<(item)
@items << item
end
end
Item Class:
class Item
attr_reader :size
def initialize(size)
@size = size
end
end
Here is some code that demonstrates the scenario when the approach fails:
sizes = [2, 4, 2, 2, 4, 2]
collection = sizes.map { |size| Item.new(size) }
SplitCollection.new(collection).buckets.map(&:total)
SplitCollection.new(collection, 3).buckets.map(&:total)
SplitCollection.new(collection, 4).buckets.map(&:total)
I'm looking for:
- an alternative approach to solving the same problem or
- a way to increase my existing approach for handling all scripts.