Variation of the backpack ... in python
Asked Answered
L

1

14

I have a conceptual problem where I have several packages, each package contains a number of elements inside. Elements are of type A or type B. I want to distribute the packages in a finite number of bins in such a way that the distribution between A and B does not differ wildly among bins.

The problem is quite complex, hence I will try to explain it with hard constraints and a conceptual example.

Constraints

A package can only be used once
A package must be used entirely
The bins should have relatively equal distributions between `A` and `B` (max 5% deviation from the original ratio)
A package can be spread across all the bins in the given batch
I want to end up with as little as batches (size <= 3 bins) as possible

Example (Conceptual)

Plate 1: 92 * `A`
Plate 2: 92 * `A`
Plate 3: 64 * `A`
Plate 4: 42 * `A`, 50 * `B`
Plate 5: 12 * `A`, 49 * `B`
Plate 6: 92 * `B`

Total distribution as such is 302 * A and 191 * B yielding 493 samples in total, the resulting ratios then are 61.25% of A and 38.75% of B

Desired result:

A minimized set of batches, where each batch contains at most 3 bins (length <= 92) with let's say between 52 and 60 of type A and between 32 and 40 of type B (the combined total not above 92) per bin.

Question

What algorithm or method would one suggest to tackle this problem, a simple suggested scheme will do (considering that what I have been trying so far (see below) does not get very far)

The idea behind my attempts thus far

data = ... # This is a list containg all values in a tuple format of `(unit, [(type, location)])` format
while len(data) > 0:
   batch = []
   counter1 = 0
   counter2 = 0
   for i in data:
      for j in i[1]:
         if j[0] == 'A':
            counter1 += 1
         else:
            counter2 += 1
   ratio1 = counter1/(counter1+counter2)
   ratio2 = counter2/(counter1+counter2)
   # Now we know the maximum number of A and B per batch
   counter3 = 0 # This keeps track of howmany type `A` we have in current batch
   counter4 = 0 # This keeps track of howmany type `B` we have in current batch
   while counter3 < ratio1:
      for i in data:
         for j in i[1]:
            if Counter(elem[0] for elem in j)['A'] < (ratio1 - counter3) and Counter(elem[0] for elem in j)['B'] < (ratio2 - counter4):
               # Add this unit (from data) to the batch
               batch.append(i)
               counter3 += Counter(elem[0] for elem in j)['A']
               counter4 += Counter(elem[0] for elem in j)['B']
               # Remove the used unit from data

This is also where I am stuck, this currently does not attempt to minimize the number of bins, nor does it check the ratios. Additionally, I have the nagging idea that the way that I am trying to do this is no where near the smart way of solving such a problem.

Loop answered 20/7, 2015 at 21:44 Comment(14)
What exactly is the question?Arlenaarlene
What is the question?Balderdash
How about next time you finish writing it before you publish it?Haunted
I apologize for that, I was trying to have participants in the chat read it while editing in an attempt to make it clearer.Loop
When you say "variation of the backpack", do you mean the integer knapsack problem? If so, the best accepted solution as I remember it is A-star-search, sometimes called "heuristic search".Zoometry
Close, but not completely. I have multiple knapsacks and I want to have relatively comparable distributions between A and B in all knapsacks after all the blocks/packages have been distributed. Additionally, not all knapsacks are created at the start but they are created in batches.Loop
@BasJansen I think it's still a space search problem. That means, in my experience, you should start with a BFS implementation and then add a heuristic optimizer and perhaps some state pruning. If you want an optimal solution I think you're going to have to check every possibility: a greedy algorithm won't find the best answer, or even an answer, quickly.Zoometry
"I want to end up with as little as batches (of 3 bins) as possible". Does this mean: "A batch always contains 3 bins" or a batch has min 3 bins or max 3 bins? In addition, can you confirm it is batches you want to minimise - i.e. you want as few batches as possible, rather than bins?Comb
@JRichardSnape I edited the text to state that a batches are length <= 3 bins and that indeed it is number of batches that has to be minimized instead of the bins (as by the constraints).Loop
I think, the problem statement needs more work. What does "a package can only be used once mean"? (We know all the packages when we need to decide how to distribute? Offline vs. Online problem) Each package needs to be completely within one batch? Can multiple packages be packaged together in one batch? Is the 5% deviation-ratio a fixed constant or part of the target-function? (the latter: multi-objective optimization -> which objective is how important?) How important is the performance? Evaluation will be difficult without a good instance-generator(most instances are too easy; some are hard)Pointsman
I think @Zoometry is the closest answer to what you are looking for. You should research knapsack algorithms, for which you will find many different approaches, from dynamic programming to diverse meta-heuristics. For small instances you could even try mixed-integer programming, but if you intend to have big instances it may not work later.Chlorite
@BasJansen, how big is your problem? I.e. how many packages are you expecting to have and how many elements of each type do you think you might have? What's the exact goal - do you simply want to minimize number of batches while keeping everything under constraints? Your problem looks like Integer Programming to me.Cautious
"A package can be spread across all the bins in the given batch" - does this mean the contents of a package can be freely distributed? Could you provide an optimal and an invalid solution to the example problem, and make sure to use the words "plate", "bin", "batch", "package"? @BasJansenStalinabad
Your desired output is not valid: The absolute minimum number of batches which your example can fit is 2 (6 batches, as 493/92 is 5.36), which gives an average of 31.8 B per bin... which cannot satisfy a minimum of 32 B per bin. If packages can be spread across batches, then there is a trivial solution by pooling and evenly chunking all As & Bs.Hexagonal
S
1
#quick generator to rotate bin numbers
def getBin(maxBin):
    number = -1
    while True:
        number +=1 
        if number >= maxBin:
            number = 0
        yield number

batches = []
data = ....

#calculate the minimum number of bins we need
numberOfBins = (len(data))/ 92 + 1 

aBinPlacement = getBin(numberOfBins)
bBinPlacement = getBin(numberOfBins)

bins = numberOfBins * [[]]

#the ratio will be maintained because we rotate bins by type
for datum in data:
    if datum[0] == 'A':
        bins[aBinPlacement.next()].append(datum)
    else:
        bins[bBinPlacement.next()].append(datum)

batches.extend(bins)
Scrunch answered 19/9, 2015 at 3:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.