6

I've got a problem, and I'll try and describe it in as simplest terms as possible.

Using a combination of PHP and MySQL I need to fulfil the following logic problem, this is a simplified version of what is required, but in a nutshell, the logic is the same.

Think boxes. I have lots of small boxes, and one big box. I need to be able to fill the large box using lots of little boxes.

So lets break this down.

I have a table in MySQL which has the following rows

Table: small_boxes
id | box_size
=============
1  | 100
2  | 150
3  | 200
4  | 1000
5  | 75
..etc

This table can run up to the hundreds, with some boxes being the same size

I now have a requirement to fill one big box, as an example, of size 800, with all the combinations of small_boxes as I find in the table. The big box can be any size that the user wishes to fill.

The goal here is not efficiency, for example, I don't really care about going slightly under, or slightly over, just showing the different variations of boxes that can possibly fit, within a tolerance figure.

So if possible, I'd like to understand how to tackle this problem in PHP/MySQL. I'm quite competent at both, but the problem lies in how I approach this.

Examples would be fantastic, but I'd happily settle for a little info to get me started.

Big-G
  • 219
  • 1
  • 11
  • is it always possible? meaning is there always going to be a combination of small 'boxes' that fit inside a big box? – Max Hudson Jul 09 '12 at 15:29
  • There will always be a combination of small boxes that fit into a larger box. The worst case scenario is that there'll be a large number of the "largest" small boxes that'll fit into the large box, and if the large box is less than the "smallest" small box, and not within tolerance, I can catch this using an initial MySQL query which is simple to do. – Big-G Jul 09 '12 at 15:40

3 Answers3

3

You should probably look into the glorious Knapsack problem

Ugo Méda
  • 1,205
  • 8
  • 23
  • Thanks for letting me know this is an actual documented mathematical problem. Further research has led me to this : http://rosettacode.org/wiki/Knapsack_problem/0-1#PHP – Big-G Jul 09 '12 at 15:46
  • I'll proceed from here and update this. I don't think this will answer my question fully, but it's definitely a very good start. – Big-G Jul 09 '12 at 15:47
2

https://codegolf.stackexchange.com/questions/3731/solve-the-knapsack-problem

Read up on this.

Hopefully you've taking algebra 2..

Here is some PHP code that might help you out:

http://rosettacode.org/wiki/Knapsack_problem/0-1#PHP

Community
  • 1
  • 1
Max Hudson
  • 9,961
  • 14
  • 57
  • 107
1

Thanks to maxhd and Ugo Meda for pointing me in the right direction!

As a result I've come to something very close to what I need. I'm not sure if this even falls into the "Knapsack problem", or whichever variation thereof, but here's the code I've come up with. Feel free to throw me any constructive criticism!

In order to try and get some different variants of boxes inside the knapsack, I've removed the largest item on each main loop iteration, again, if there's a better way, let me know :)

Thanks!

class knapsack {
    private $items;
    private $knapsack_size;
    private $tolerance = 15; //Todo : Need to make this better, perhaps a percentage of knapsack
    private $debug = 1;

    public function set_knapsack_size($size){
        $this->knapsack_size = $size;
    }

    public function set_items($items){
        if(!is_array($items)){
            return false;
        }

        //Array in the format of id=>size, ordered by largest first
        $this->items = $items;
    }

    public function set_tolerance($tolerance){
        $this->tolerance = $tolerance;
    }

    private function remove_large_items(){
        //Loop through each of the items making sure we can use this sized item in the knapsack
        foreach($this->items as $list_id=>$list){
            //Lets look ahead one, and make sure it isn't the last largest item, we will keep largest for oversize.
            if($list["size"] > $this->knapsack_size && (isset($this->items[$list_id+1]) && $this->items[$list_id+1]["size"] > $this->knapsack_size)){
                unset($this->items[$list_id]);
            }else{
                //If we ever get here, simply return true as we can start to move on
                return true;
            }
        }

        return true;
    }

    private function append_array($new_data,$array){
        if(isset($array[$new_data["id"]])){
            $array[$new_data["id"]]["qty"]++;
        }else{
            $array[$new_data["id"]]["qty"] = 1;
        }

        return $array;
    }

    private function process_items(&$selected_items,$knapsack_current_size){
        //Loop the list of items to see if we can fit it in the knapsack
        foreach($this->items as $list){
            //If we can fit the item into the knapsack, lets add it to our selected_items, and move onto the next item
            if($list["size"] <= $knapsack_current_size){

                $this->debug("Knapsack size is : ".$knapsack_current_size." - We will now take ".$list["size"]." from it");
                $selected_items = $this->append_array($list,$selected_items);
                $knapsack_current_size -= $list["size"];

                //Lets run this method again, start recursion
                $knapsack_current_size = $this->process_items($selected_items,$knapsack_current_size);
            }else{
                //Lets check if we can fit a slightly bigger item into the knapsack, so we can eliminate really small items, within tolerance
                if(($list["size"] <= $knapsack_current_size + $this->tolerance) && $knapsack_current_size > 0){
                    $this->debug("TOLERANCE HIT : Knapsack size is : ".$knapsack_current_size." - We will now take ".$list["size"]." from it");
                    $selected_items = $this->append_array($list,$selected_items);
                    $knapsack_current_size -= $list["size"];
                }
            }

            //Lets see if we have to stop the recursion
            if($knapsack_current_size < 0){
                return $knapsack_current_size;
            }
        }
    }

    private function debug($message=""){
        if(!$this->debug){
            return false;
        }

        echo $message."\n";
    }

    public function run(){
        //If any of the variables have not been set, return false
        if(!is_array($this->items) || !$this->knapsack_size){
            return false;
        }

        //Lets first remove any items that may be too big for the knapsack
        $this->remove_large_items();

        //Lets now check if we still have items in the array, just incase the knapsack is really small
        if(count($this->items) == 0){
            return false;
        }

        //Now that we have a good items list, and we have no items larger than the knapsack, lets move on.
        $variants = array();
        foreach($this->items as $list_id=>$list){
            $this->debug();
            $this->debug("Finding variants : ");

            $selected_items = array();
            $this->process_items($selected_items,$this->knapsack_size);
            $variants[] = $selected_items;

            //Remove the largest variant, so we get a new set of unique results
            unset($this->items[$list_id]);
        }

        return $variants;
    }
}

$products = array(
    array("id"=>1,"size"=>90),
    array("id"=>2,"size"=>80),
    array("id"=>3,"size"=>78),
    array("id"=>4,"size"=>66),
    array("id"=>5,"size"=>50),
    array("id"=>6,"size"=>42),
    array("id"=>7,"size"=>36),
    array("id"=>8,"size"=>21),
    array("id"=>9,"size"=>19),
    array("id"=>10,"size"=>13),
    array("id"=>11,"size"=>7),
    array("id"=>12,"size"=>2),
);

$knapsack = new knapsack();
$knapsack->set_items($products);
$knapsack->set_knapsack_size(62);
$result = $knapsack->run();

var_dump($result);
Big-G
  • 219
  • 1
  • 11