2

I have an array of 50 objects, each width a different width and height. My objective is to create panels to hold this objects and keep them in order, but this panel cannot exceed a width of 300 pixels or a height of 600 pixels, so I have to fit as many objects as I can within this panel and if they don't fit then I need to create a new panel until there are no more items to append.
So for example if I have an item with the dimensions of 150x75, then I could only place another object of the same dimensions, and if the next item does not have this dimensions then I create an empty object to take up these empty space. An example can be found at Codepen.io Now this works as long as the height of each item is 75px, but the problem arises when the height exceeds this. I have been trying to solve this problem for a few days now, but this particular task seems to throw of the entire design. Here is my logic to check if the item fits:

layout.prototype.createPanel = function(){
//create a new panel
var panel = '<div style="float:left;margin:5px;border:2px solid black;min-width:'+this.WIDTH+'px;min-height:'+this.HEIGHT+'px;">';
var h = 0;
//as long as the panel height does not exceed the constant height
while(h < this.HEIGHT){
    //this.commands += h+'<br>';
    var w = 0;
    panel += '<div>'; //create a new row
    //as long as the panel width does not exceed the constant width
    while(w < this.WIDTH){
        var el = this.items[0]; //grab the first item for checking purposes
        var fits = false;
        //if the current column is the first one, then just check if the item fits vertically
        if(w < this.int_width){
            //as long as the item's height plus the current height of this panel is less than the static height, then it fits
            fits = h + el.height <= this.HEIGHT ? true : false;
        }
        //else check if it fits horizontal and vertically
        else{
            if(this.fitsHorizontal(w,el.width) && h + el.height <= this.HEIGHT){
                fits = true;
            }
            else{
                fits = false;
            }
        }
        //if the item fits, remove it from the array and append it to the panel
        if(fits){
            el = this.items.shift();
            panel += el.html;
            w+= el.width;   //update the width
            this.desired_height = el.height;    //update the new desired height     

            //if this shifted item was the last one then terminate execution to avoid any infinite loops
            if(this.items.length == 0){
                this.has_items = false;
                break;
            }
        }
        //else create an empty element to fill up the space needed
        else{
            panel += '<div style="float:left;width:'+(this.WIDTH - w)+'px;height:'+this.desired_height+'px;background-color:#ccc;margin:1px;"></div>';
            el.height = this.int_height;
            break;
        }
    }
    panel += '</div>'; //close the row

    //if this shifted item was the last one then terminate execution to avoid any infinite loops
    if(!this.has_items){
        this.has_items = false;
        break;
    }
    h+=this.desired_height;
}
panel += '</div>'; //close the panel
return panel;
}
layout.prototype.fitsHorizontal = function(current_width,item_width){
//if the current width plus the item width is less than the constant width them the item fits horizontal
if(current_width + item_width <= this.WIDTH){
    return true;
}
return false;
}
layout.prototype.fitsVertical = function(current_height,item_height){
if(current_height + item_height <= this.HEIGHT && item_height == this.desired_height){
    return true;
}
return false;
}

Is there anything wrong with the logic? I now I could solve it if I reorder the items, but they have to stay in order. The full code can be found ad Codepen.io

lomas09
  • 1,114
  • 4
  • 12
  • 28
  • You should look at the source code of Masonry http://masonry.desandro.com/ – juandopazo Jun 14 '13 at 14:53
  • @stevekohls - your edit is not necessarily warranted - you ASSUME that the problem is language specific, but the way the question is written it is actually a generic architectural design issue, rather than one specific to the language being used to implement it. – Chris Stratton Jun 14 '13 at 17:34
  • I did this because I have tried the exact logic with php before. I am using javascript in this case because I was testing in codepen.io – lomas09 Jun 14 '13 at 18:29
  • @ChrisStratton point taken. lomas09 thanks for the clarification. You might want to state in your question that it's not javascript-specific. – stevekohls Jun 14 '13 at 20:43
  • (ot) Should you add the "tetris" tag? – Alessandro Gabrielli Jun 15 '13 at 10:39

1 Answers1

0

UPDATE

About your code, I can't understand what is the problem because it is not clear. I would like to give you some tips:
-When you need to work with someone (like here if you need help, or in a project, etc), you should share the same identical things that you know.
-In this case, where the language is not a specification, you should abstract the code as much as you can, pseudo-code is a good begin.
-you can describes the problems you met, from some example but also from images and schemes.
-If you add a specific codification to represent the problem you should respect that codification.

It is not really clear how your code works because it seems an hybrid between js and a pseudo-code. For what I can understand, the first logic error it seems to be related to the space division (rows columns): for example if you have 4 elements 1x1 (rows x columns) they will fill 1 row but, if you have 1 element 1x1,1 element 2x1 (rows x columns), 1 element 1x1 (rows x columns) and another element 2x1 (rows x columns), they complete a row, but you will also find some pieces in the row below, and you doesn't seems to registers this kind of behaviour in your code.

eg

I hope to have understood well, here the jsfiddle and here the code:

<!DOCTYPE html>
<head>
<style>
.pad div{
    margin:1px;
}
.pad{
    background-color:#FFF;
    border:#000 1px solid;
}
</style>
<script src="http://code.jquery.com/jquery-1.9.1.js"></script>
<script src="http://code.jquery.com/ui/1.10.3/jquery-ui.js"></script>
<body>
<script>
var pieceN=50;
var pieces = [];
const multip = 75;
var actualmaxheight=0;
const h=600;
const w=300;
const maxrows = 5;
const maxcols = 4;
var id="";
var actualcontent=0;
$('body').append('<div id="content'+actualcontent+'" class="pad" style="'+
                        'height:'+(h+4)+'px;width:'+(w+4)+'px"></div>');
id='content'+actualcontent;
actualcontent++;
//creating objects n times
for(var i=0; i<(pieceN); i++){
    color=(Math.floor(Math.random()*16777215)).toString(16);
    if(color.length==5){ color=color+'0'};
    if(color.length==4){ color=color+'00'};
    //for faster thing I'll use an already sorted array
    eit = Math.floor((Math.random()*maxrows)+1);
    wid = Math.floor((Math.random()*maxcols)+1);
    pieces.push({
        'height': eit*multip,
        'width': wid*multip,
        'rows': eit,
        'cols': wid,
        'color':'#'+color
    });
}
var copy=pieces;
var i =0;
//while i is less then the number of the objects -1
//the last element will be inserted afther this cycle
//it can be also inserted in the cylce, anyway it's only 1 loop
while(i<pieces.length-1){
    //if there is enough height for the element in the container
    if(h-actualmaxheight>=parseInt(pieces[i].height)){ 
        //the width fill te container width (insert)
        if(parseInt(pieces[i].width)==w){
            document.getElementById(id).innerHTML+=
                '<div id="'+pieces.length+'" style="'+
                'width:'+pieces[i].width+'px;'+
                'height:'+pieces[i].height+'px;'+
                'background-color:'+pieces[i].color+';'+
                'float:left"></div>';
            actualmaxheight+=parseInt(pieces[i].height);
        }else{//if the width don't fill te container
          //if this elem+nextone width fill the container width (insert x 2)
          if(((parseInt(pieces[i].width)+parseInt(pieces[i+1].width))==w) && (parseInt(pieces[i].height)==parseInt(pieces[i+1].height))){
              document.getElementById(id).innerHTML+=
                  '<div id="'+i+'" style="'+
                  'width:'+pieces[i].width+'px;'+
                  'height:'+pieces[i].height+'px;'+
                  'background-color:'+pieces[i].color+';'+
                  'float:left"></div>'+
                  '<div id="'+(i+1)+'" style="'+
                  'width:'+pieces[i+1].width+'px;'+
                  'height:'+pieces[i+1].height+'px;'+
                  'background-color:'+pieces[i+1].color+';'+
                  'float:left"></div>';
              actualmaxheight+=parseInt(pieces[i].height);
              i++;//because 2 inserted
          }else{
              //else we insert 1 elem + 1 filling element
              document.getElementById(id).innerHTML+=
                  '<div id="'+i+'" style="'+
                  'width:'+pieces[i].width+'px;'+
                  'height:'+pieces[i].height+'px;'+
                  'background-color:'+pieces[i].color+';'+
                  'float:left"></div>'+
                  '<div id="'+(pieces.length+100)+'" style="'+
                  'width:'+(w-pieces[i].width)+'px;'+
                  'height:'+pieces[i].height+'px;'+
                  'background-color:'+pieces[i].color+';'+
                  'float:left"></div>';
              actualmaxheight+=parseInt(pieces[i].height);
          }
        }
    i++;//we surely inserted an elem
    }else{
        //else the container have not enough height for this elem
        //fill the height with a black block and don't increase i
        //because we inserted nothing
        document.getElementById(id).innerHTML+=
            '<div id="'+(pieces.length+1000)+'" style="'+
            'width:'+w+'px;'+
            'height:'+(h-actualmaxheight)+'px;'+
            'background-color:#000000;'+
            'float:left;color:#FFFFFF">here to fill H</div>';
        $('body').append('<div id="content'+actualcontent+'" class="pad" style="'+
                        'height:'+(h+6+actualcontent*1.2)+'px;width:'+(w+4)+'px;color:#FFFFFF"></div>');
        id='content'+actualcontent++; //id of the container updated
        actualmaxheight=0;//reset height
    }
}
//insert last element
i=pieces.length-1;//get its array index
if(h-actualmaxheight>=pieces[i].height){//there is enough height in this container
    //the elem fill the width
    if(parseInt(pieces[i].width)==w){
      actualmaxheight+=pieces[i].height;
      document.getElementById(id).innerHTML+=
          '<div id="'+pieces.length+'" style="'+
          'width:'+pieces[i].width+'px;'+
          'height:'+pieces[i].height+'px;'+
          'background-color:'+pieces[i].color+';'+
          'float:left"></div>'+
          '<div id="'+(pieces.length+1000)+'" style="'+
          'width:'+w+'px;'+
          'height:'+(h-actualmaxheight)+'px;'+
          'background-color:#000000;color:#FFFFFF;'+
          'float:left">here to fill H</div>';
    }else{ //the elem don't fill the width
      actualmaxheight+=pieces[i].height;
      document.getElementById(id).innerHTML+=
          '<div id="'+pieces.length+'" style="'+
          'width:'+pieces[i].width+'px;'+
          'height:'+pieces[i].height+'px;'+
          'background-color:'+pieces[i].color+';'+
          'float:left"></div>'+
          '<div id="'+(pieces.length+10000)+'" style="'+
          'width:'+(w-pieces[i].width)+'px;'+
          'height:'+pieces[i].height+'px;'+
          'background-color:'+pieces[i].color+';'+
          'float:left"></div>'+
          '<div id="'+(pieces.length+1000)+'" style="'+
          'width:'+w+'px;'+
          'height:'+(h-actualmaxheight)+'px;'+
          'background-color:#000000;color:#FFFFFF;'+
          'float:left">here to fill H</div>';
    }
}else{//there is not enough height in this container
    if(parseInt(pieces[i].width)==w){//the elem fill the width
      //fill the height with a black block
      document.getElementById(id).innerHTML+=
          '<div id="'+(pieces.length+1000)+'" style="'+
          'width:'+pieces[i].width+'px;'+
          'height:'+(h-actualmaxheight)+'px;'+
          'background-color:#000000;color:#FFFFFF;'+
          'float:left">here to fill H</div>';
      //create another container and add the elem + black block to fill the height
      actualmaxheight=parseInt(pieces[i].height);
          $('body').append('<div id="content'+actualcontent+'" class="pad" style="'+
          'height:'+(h+4)+'px;width:'+(w+4)+'px">'+
          '<div id="'+pieces.length+'" style="'+
          'width:'+pieces[i].width+'px;'+
          'height:'+pieces[i].height+'px;'+
          'background-color:'+pieces[i].color+';'+
          'float:left"></div>'+
          '<div id="'+(pieces.length+1000)+'" style="'+
          'width:'+w+'px;'+
          'height:'+(h-actualmaxheight)+'px;'+
          'background-color:#000000;color:#FFFFFF;'+
          'float:left">here to fill H</div>'+
          '</div>');
    }else{//the elem don't fill the width
      //fill the height with a black block to fill the height
      document.getElementById(id).innerHTML+=
          '<div id="'+(pieces.length+1000)+'" style="'+
          'width:'+w+'px;'+
          'height:'+(h-actualmaxheight)+'px;'+
          'background-color:#000000;color:#FFFFFF;'+
          'float:left">here to fill H</div>';
          actualmaxheight=parseInt(pieces[i].height);
      //create another container and add the elem + 1 elem to fill the width 
      //+ 1 black block to fill the height
      $('body').append('<div id="content'+actualcontent+'" class="pad" style="'+
          'height:'+(h+4)+'px;width:'+(w+4)+'px">'+
          '<div id="'+pieces.length+'" style="'+
          'width:'+pieces[i].width+'px;'+
          'height:'+pieces[i].height+'px;'+
          'background-color:'+pieces[i].color+';'+
          'float:left"></div>'+
          '<div id="'+(pieces.length+100)+'" style="'+
          'width:'+(w-pieces[i].width)+'px;'+
          'height:'+pieces[i].height+'px;'+
          'background-color:'+pieces[i].color+';'+
          'float:left"></div>'+
          '<div id="'+(pieces.length+1000)+'" style="'+
          'width:'+w+'px;'+
          'height:'+(h-actualmaxheight)+'px;'+
          'background-color:#000000;color:#FFFFFF;'+
          'float:left">here to fill H</div>'+
          '</div>');
    }
}
/*
//print the elments
for(var i=0; i<(copy.length); i++){
    $('body').append('<div id="el'+i+'"'+ 
        'style="width:'+copy[i].width+'px;'+
        'height:'+copy[i].height+'px;'+
        'background-color:'+copy[i].color+';float:left"></div>'
    );
}
*/
</script>
</body>
</html>

I would like to clear some points

  1. Does the object dimensions are multiple of something or they are just random?
  2. Where you need to add a filling piece, near every pieces for which you can't find another piece with same height and 300 as sum of widths?
  3. an object can be fit with many object or less depending on how many object you want to search, this clearly increase the time needed.

Anyway to present something, I wrote this algorithm (jsfiddle) that can or cannot be useful, depending on what are you searching :

<!DOCTYPE html>
<head>
<style>
.pad div{
    margin:1px;
}
.pad{
    background-color:#FFF;
    border:#000 1px solid;
}
</style>
<script src="http://code.jquery.com/jquery-1.9.1.js"></script>
<script src="http://code.jquery.com/ui/1.10.3/jquery-ui.js"></script>
<body>
<script>
var pieceN=50;
var pieces = [];
var actualmaxheight=0;
var h=600;
var w=300;
var flag=0;

$('body').append('<div id="content0" class="pad" style="'+
                        'height:'+(h+4)+'px;width:'+(w+4)+'px"></div>');
//creating objects n times
for(var i=0; i<(pieceN); i++){
    color=(Math.floor(Math.random()*16777215)).toString(16);
    if(color.length==5){ color=color+'0'};
    //for faster thing I'll use an already sorted array
    pieces.push({
        'height': Math.floor((Math.random()*600)+1),
        'width':Math.floor((Math.random()*150)+1),
        'color':'#'+color
    });
}
var copy=pieces;
id='content0';
//for all the elements aren't placed(i remove every placed element)
for(i=0;i<pieces.length;i++){
    flag=0;
    k=0;
    //while k<pieces.length confront every piece[k] with piece[i]
    while(k<pieces.length){
        //if exist 2 piece with sum of width==w(300), the same height and the actual container have enough height, let's add them 
        if(parseInt(pieces[i].width)+parseInt(pieces[k].width)==w && pieces[i].height==pieces[k].height && ((h-actualmaxheight)>parseInt(pieces[i].height))){
            document.getElementById(id).innerHTML+='<div id="e'+(i+k)+'"'+ 
            'style="width:'+pieces[i].width+'px;'+
            'height:'+pieces[i].height+'px;'+
            'background-color:'+pieces[i].color+';float:left"></div>'+
            '<div id="e'+(k+i)+'2"'+ 
            'style="width:'+pieces[k].width+'px;'+
            'height:'+pieces[k].height+'px;'+
            'background-color:'+pieces[k].color+';float:left"></div>';
            actualmaxheight+=parseInt(pieces[i].height);
            //remove them from the array
            pieces = $.grep( pieces, function(n,index){
                return index != k;
            });
            pieces = $.grep( pieces, function(n,index){
                return index != i;
            });
            flag=1;
        }
        k++;
    }
    //if we inserted 0 elements in the while before, we have 2 reason
    //1) there aren't 2 elements which satisfy the rules
    //in this case we add the element with an element created to fill the empty space orizontal
    if(flag==0){
        for(j=0;j<pieces.length;j++){
            if((h-actualmaxheight)>parseInt(pieces[j].height)){
                document.getElementById(id).innerHTML+='<div id="e'+i+'"'+ 
                'style="width:'+pieces[j].width+'px;'+
                'height:'+pieces[j].height+'px;'+
                'background-color:'+pieces[j].color+';float:left"></div>'+
                '<div id="e'+i+'2"'+ 
                'style="width:'+(w-parseInt(pieces[j].width))+'px;'+
                'height:'+pieces[j].height+'px;'+
                'background-color:'+pieces[j].color+';float:left"></div>';
                actualmaxheight+=parseInt(pieces[j].height);
                pieces = $.grep( pieces, function(n,index){
                    return index != j;
                });
            }
        }
    }
    //2) there isn't an element which can be conained in the space remained in the container
    //in this case we add a black stripe to fill the space vertical
    if(actualmaxheight<h){
        document.getElementById(id).innerHTML+='<div id="e'+j+i*k+'"'+ 
        'style="width:300px;'+
        'height:'+(h-actualmaxheight)+'px;'+
        'background-color:#000000;float:left;color:#FFFFFF">i fill the space</div>';                
    }
    //then we create a new container to repeat until all the elements are placed
    $('body').append('<div class="pad" id="content'+j+'"'+
        'style="margin-top:10px;'+
        'height:'+(h+4)+'px;width:'+(w+4)+'px"></div>');
    id='content'+j;
    actualmaxheight=0;
    i=0;
}
//print the elments
for(var i=0; i<(copy.length); i++){
    $('body').append('<div id="el'+i+'"'+ 
        'style="width:'+copy[i].width+'px;'+
        'height:'+copy[i].height+'px;'+
        'background-color:'+copy[i].color+';float:left"></div>'
    );
}
</script>
</body>
</html>
  • 1) the object's dimensions are multiples of a constant integer value: for example if the const_int_val = 75px, then the possible widths can be 75, 150, 225 & 300 (Four columns max) and the possible heights can be 75, 150, 225, 300 & 375 (Five rows max) – lomas09 Jun 19 '13 at 14:56
  • 2)The objects have to fill up a width of 300px and a height of 600px (in this case) and can't go beyond that. There are only 4 columns and 8 rows available – lomas09 Jun 19 '13 at 14:59
  • *One important feauture this needs to have is that the objects need to stay in order. It should be sort of like the First Fit bin Packing algorithm. You cannot loop through the array of objects to find one that fits, you could only check the following one and if it fits greatm if it doesnt then create and empty object to fill up that space – lomas09 Jun 19 '13 at 15:01
  • @lomas09 300 and 600 are multiple of the const? You said 5 rows in the first comment and 8 in the second. rows depend on the const? What's the dimensions to be filled: width, height (if width, the second element must have same height, if height, the second element must have same width)? Ordered how? order by height (if equals by width), by width (if equal by height) or, for example, by sum of height and width? You should be more clear.. – Alessandro Gabrielli Jun 19 '13 at 15:47
  • Each bin can have maximum dimensions of 300 x 600. However each object's max-width can only be 300 and the max-height can only be 375, (4 cols x 5 rows). The dimensions are to be filled by width. – lomas09 Jun 19 '13 at 16:06
  • Now the order depends of the object's index. Take these objects as examples: $objects[0] = ("width" => 225, "height" => 75, "cols" => 3, "rows" => 1); $objects[1] = ("width" => 150, "height" => 75, "cols" => 2, "rows" => 1); $objects[2] = ("width" => 150, "height" => 150, "cols" => 2, "rows" => 2); $objects[3] = ("width" => 300, "height" => 225, "cols" => 4, "rows" => 3);$objects[4] = ("width" => 300, "height" => 75, "cols" => 4, "rows" => 1); – lomas09 Jun 19 '13 at 16:07
  • Then the bin should look like these: first row: $object[0] & an empty element to fill up that space (75 x 75) – lomas09 Jun 19 '13 at 16:11
  • second row: $object[1] & empty object (225 x 75) – lomas09 Jun 19 '13 at 16:12
  • third to fourth row: $object[2] & empty object (150 x 150) – lomas09 Jun 19 '13 at 16:12
  • fifth to eight row: $object[3] and then create another bin to hold $object[4] and so on... – lomas09 Jun 19 '13 at 16:14
  • But if for example $object[4] had the dimensions of 75 x 75, you cannot grab it and put it alongside with $object[0] even thought it fits. The objects have to remain in their index value – lomas09 Jun 19 '13 at 16:16
  • @lomas09 $objects[1] = ("width" => 150, "height" => 75, "cols" => 2, "rows" => 1); and second row: $object[1] & empty object (225 x 75) ? second row should be object[1] $ empty object(150x75), right? – Alessandro Gabrielli Jun 19 '13 at 17:23
  • Yes, my bad $ empty object(150x75) – lomas09 Jun 19 '13 at 17:30
  • @lomas09 ok, so when you add an block near another one, if the block is empty or the block came from the array, they must have the same height? And whichever is the constant value, every object have max cols = 300/const and max rows (600/const)+const? or just 4 and 5? or the const will never change? – Alessandro Gabrielli Jun 19 '13 at 17:36
  • yes the objects have to have the same height if they are next to each other. The Maximum cols and rows for an object is always going to be 4 and 5 respectively. And the constant will probably never change, so it will stay at 75 for now – lomas09 Jun 19 '13 at 18:00
  • I apologize for the bad explaining and it won't happen again. However your code and logic are just what I was expecting and needed. Thank You. – lomas09 Jun 20 '13 at 14:46
  • @lomas09 ok, happy to know that it works. Try, if you have time, to understand what you did wrong, gl – Alessandro Gabrielli Jun 20 '13 at 15:14