Repository: benmaier/binpacking

Issue:
Additional empty bin returned when all values exceed bin size
When using `to_constant_volume` if all given values exceed the bin size, then an additional empty bin is returned.

Examples:

    # input 1
    values = [24, 42]
    bins = to_constant_volume(values, 20)
    
    # output 1
    bins = [[], [42], [24]]


    # input 2
    values = {"a": 24, "b": 42}
    bins = to_constant_volume(values, 20)
    
    # output 2
    bins = {{}, {"a": 42}, {"b": 24}}


Discussion:

Patch:
diff --git a/.gitignore b/.gitignore
index b48773d..8a9883a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,4 +13,10 @@
 # Python egg metadata, regenerated from source files by setuptools.
 /*.egg-info
 
+# Python virtual environment
+.venv
+
+# JetBrains project
+.idea
+
 .DS_Store
diff --git a/binpacking/to_constant_volume.py b/binpacking/to_constant_volume.py
index 7f0171d..46e3cfa 100644
--- a/binpacking/to_constant_volume.py
+++ b/binpacking/to_constant_volume.py
@@ -20,11 +20,11 @@ def to_constant_volume(d,V_max,weight_pos=None,key=None,lower_bound=None,upper_b
     Distributes a list of weights, a dictionary of weights or a list of tuples containing weights
     to a minimal number of bins which have a fixed volume.
     INPUT:
-    --- d: list containing weights, 
+    --- d: list containing weights,
            OR dictionary where each (key,value)-pair carries the weight as value,
-           OR list of tuples where one entry in the tuple is the weight. The position of 
+           OR list of tuples where one entry in the tuple is the weight. The position of
               this weight has to be given in optional variable weight_pos
-         
+
     optional:
     ~~~ weight_pos: int -- if d is a list of tuples, this integer number gives the position of the weight in a tuple
     ~~~ key: function -- if d is a list, this key functions grabs the weight for an item
@@ -51,7 +51,7 @@ def to_constant_volume(d,V_max,weight_pos=None,key=None,lower_bound=None,upper_b
             key = lambda x: x[weight_pos]
         if key is None:
             raise ValueError("Must provide weight_pos or key for tuple list")
-    
+
     if isinstance(d, list) and key:
         new_dict = {i: val for i, val in enumerate(d)}
         d = {i: key(val) for i, val in enumerate(d)}
@@ -93,7 +93,7 @@ def to_constant_volume(d,V_max,weight_pos=None,key=None,lower_bound=None,upper_b
 
     #iterate through the weight list, starting with heaviest
     for item,weight in enumerate(weights):
-        
+
         if isdict:
             key = keys[item]
 
@@ -108,7 +108,10 @@ def to_constant_volume(d,V_max,weight_pos=None,key=None,lower_bound=None,upper_b
             b = candidate_bins[candidate_index]
 
         #if this weight doesn't fit in any existent bin
-        else:
+        elif item > 0:
+            # note! if this is the very first item then there is already an
+            # empty bin open so we don't need to open another one.
+
             # open a new bin
             b = len(weight_sum)
             weight_sum = np.append(weight_sum, 0.)
@@ -117,14 +120,18 @@ def to_constant_volume(d,V_max,weight_pos=None,key=None,lower_bound=None,upper_b
             else:
                 bins.append([])
 
-        #put it in 
+        # if we are at the very first item, use the empty bin already open
+        else:
+            b = 0
+
+        #put it in
         if isdict:
             bins[b][key] = weight
         else:
             bins[b].append(weight)
 
         #increase weight sum of the bin and continue with
-        #next item 
+        #next item
         weight_sum[b] += weight
 
     if not is_tuple_list:
@@ -137,7 +144,7 @@ def to_constant_volume(d,V_max,weight_pos=None,key=None,lower_bound=None,upper_b
                 new_bins[b].append(new_dict[key])
         return new_bins
 
-         
+
 if __name__=="__main__":
 
     a = np.random.power(0.01,size=10000)


Test patch:
diff --git a/binpacking/tests/constant_volume.py b/binpacking/tests/constant_volume.py
index 4026f53..7b63ccd 100644
--- a/binpacking/tests/constant_volume.py
+++ b/binpacking/tests/constant_volume.py
@@ -29,4 +29,9 @@ def test_key_func():
     for bin_ in bins:
         for item in bin_:
             assert 'x' in item
-            assert 'y' in item
\ No newline at end of file
+            assert 'y' in item
+
+def test_no_fit():
+    values = [42, 24]
+    bins = to_constant_volume(values, 20)
+    assert bins == [[42], [24]]