【问题标题】:Dynamic Object Layout动态对象布局
【发布时间】:2013-06-11 22:55:37
【问题描述】:

我有一个包含 50 个对象的数组,每个对象的宽度和高度都不同。我的目标是创建面板来容纳这些对象并保持它们井然有序,但是这个面板不能超过 300 像素的宽度或 600 像素的高度,所以我必须在这个面板中尽可能多地放置对象,如果它们不适合然后我需要创建一个新面板,直到没有更多项目要附加。
因此,例如,如果我有一个尺寸为 150x75 的项目,那么我只能放置另一个相同尺寸的对象,如果下一个项目没有这个尺寸,那么我创建一个空对象来占用这些空白空间。一个例子可以在Codepen.io找到 现在只要每个项目的高度为 75px 就可以了,但是当高度超过这个值时就会出现问题。几天来,我一直在尝试解决这个问题,但这个特殊的任务似乎会影响整个设计。这是我检查物品是否合适的逻辑:

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;
}

逻辑有问题吗?如果我重新排序这些物品,我现在可以解决它,但它们必须保持有序。完整代码可见广告Codepen.io

【问题讨论】:

  • 你应该看看Masonry的源代码masonry.desandro.com
  • @stevekohls - 您的编辑不一定得到保证 - 您假设问题是特定于语言的,但问题的编写方式实际上是一个通用的架构设计问题,而不是特定于语言的问题被用来实现它。
  • 我这样做是因为我以前用 php 尝试过确切的逻辑。在这种情况下,我使用的是 javascript,因为我在 codepen.io 中进行测试
  • @ChrisStratton 点。 lomas09 感谢您的澄清。您可能想在您的问题中声明它不是特定于 javascript 的。
  • 感谢您的建议

标签: arrays layout tetris bin-packing


【解决方案1】:

更新

关于你的代码,我不明白是什么问题,因为不清楚。我想给你一些建议:
- 当您需要与某人一起工作时(例如,如果您需要帮助,或在项目中等),您应该分享您所知道的相同的东西。
-在这种情况下,语言不是规范,你应该尽可能地抽象代码,伪代码是一个好的开始。
-您可以通过一些示例以及图像和方案来描述您遇到的问题。
- 如果您添加特定的编码来代表问题,您应该尊重该编码。

目前还不清楚您的代码是如何工作的,因为它似乎是 js 和伪代码之间的混合体。据我所知,第一个逻辑错误似乎与空间划分(行列)有关:例如,如果您有 4 个元素 1x1(行 x 列),它们将填充 1 行但是,如果您有 1 个元素 1x1 ,1 个元素 2x1(行 x 列)、1 个元素 1x1(行 x 列)和另一个元素 2x1(行 x 列),它们完成了一行,但您也会在下面的行中找到一些片段,而您没有似乎在您的代码中注册了这种行为。

希望大家理解清楚,这里是jsfiddle,这里是代码:

<!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>

我想澄清几点

  1. 对象尺寸是某物的倍数还是只是随机的?
  2. 您需要在哪里添加填充块,在您找不到其他具有相同高度且宽度总和为 300 的块的附近?
  3. 根据您要搜索的对象数量,一个对象可以容纳多个或更少的对象,这显然会增加所需的时间。

无论如何,为了呈现一些东西,我编写了这个算法 (jsfiddle),它可能有用,也可能没用,这取决于你在搜索什么:

<!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) 对象的尺寸是常数整数值的倍数:例如,如果 const_int_val = 75px,则可能的宽度可以是 75、150、225 和 300(最多四列),并且可能高度可以是 75、150、225、300 和 375(最多 5 行)
  • 2) 对象必须填充 300px 的宽度和 600px 的高度(在这种情况下),并且不能超过。只有 4 列和 8 行可用
  • *这需要具备的一个重要特征是对象需要保持有序。它应该有点像 First Fit bin Packing 算法。您无法遍历对象数组以找到适合的对象,您只能检查以下对象,如果不适合则创建并清空对象以填充该空间
  • @lomas09 300 和 600 是 const 的倍数?您在第一条评论中说 5 行,在第二条评论中说 8 行。行取决于常量?要填充的尺寸是多少:宽度、高度(如果是宽度,则第二个元素必须具有相同的高度,如果是高度,则第二个元素必须具有相同的宽度)?怎么订的?按高度(如果按宽度相等)、按宽度(如果按高度相等)或例如按高度和宽度之和排序?你应该更清楚..
  • 每个 bin 的最大尺寸为 300 x 600。但是每个对象的最大宽度只能是 300,最大高度只能是 375,(4 列 x 5 行)。尺寸将按宽度填充。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-07-12
  • 1970-01-01
  • 1970-01-01
  • 2014-07-02
  • 2015-05-28
相关资源
最近更新 更多