装箱问题 - 确定给定范围的一组值的最佳分组

Posted

技术标签:

【中文标题】装箱问题 - 确定给定范围的一组值的最佳分组【英文标题】:Bin packing problem - Determine optimal grouping of set of values for a given range 【发布时间】:2021-02-19 13:12:35 【问题描述】:

我的问题与我尝试根据组合大小(媒体文件)进行分组和合并的文件有关。一般来说,我有一个程序可以将多个文件组合成多组合并文件。

用户可以选择任意数量的文件供程序合并。

程序将合并文件,并输出合并后的文件。

合并后的文件必须在一定的大小范围内。就我而言,2.2gb 到 4.2gb。

假设用户选择了 10 个文件。 我需要该程序以尽可能最佳的方式组合文件。如果最后一个文件是 800mb,并且不适合与前三个文件合并,但文件数组可以重新组织以合并 800mb 文件,我需要程序来这样做。

这里有一个抽象的程序来说明:

console.log(output(2.2, 4.2, [1, .5, .4, 1.2, 2.2, 1.1, 1.7, 1.8, .2, .8]));

function output(lowerRange, upperRange, values) 

    let groups = [];
    
    
    // This works, but would output with a hanging .8 at the end. 
    groups = [ [1, .5, .4, 1.2], [2.2, 1.1], [1.7, 1.8, .2], [.8] ];

    // This reorganizes to incorporate .8 in a more optimal way by shifting things around.
    groups = [ [1, .5, .4, 1.2], [2.2, 1.1, .8], [1.7, 1.8, .2] ];

    return groups;

非常感谢任何帮助。

【问题讨论】:

哇,真的是很大的挑战,我喜欢这个,我正在努力并给你一个回应。你的函数中的values var 是什么? @EstebanMANSART 感谢您的积极反馈。函数中的values参数表示在console.log中调用函数时作为第三个参数传递的值数组@ Facepalm,我没有看到你的第一行 XD 最后一个问题,如果文件大小大于upperRange,我们创建一个只有这个的子数组,还是根本不处理? 这是一个bin packing problem,是一个 NP 难题。 【参考方案1】:

如果我理解得很好,我想你可以这样做: 我希望这就是你要找的东西,编码真的很有趣:)

你可以运行代码sn-p查看结果

// ========== Function definition ==========
function output(storage, filesToStore, min, max) 

    for (file of filesToStore) 
      
      if (storage.length == 0) 
        let chunk = []
        storage.push(chunk)
      
      
      let fileIsStored = false
      for (chunk of storage) 
        if (hasChunkEnoughAvailableSpace(chunk, file, min, max)) 
          chunk.push(file)
          fileIsStored = true
        
      
      
      if (!fileIsStored) 
        storage.push([file])
      
    


function hasChunkEnoughAvailableSpace(chunk, fileSize, min, max) 
  let totalSize = chunk.reduce((a,b)=>a+b, 0) // This is just a "sum"
  
  return fileSize < max - totalSize; // Is file size less than remaining space



// ========== Core ==========

let storage = [];

// I juste generate random numbers, but that is the same thing as file sizes
let filesToStore = Array.from(length: 40, () => Math.floor(Math.random() * 60));

// For my needs, just adapte it at yours
let min = 20
let max = 35


output(storage, filesToStore, min, max)
console.log(storage);

【讨论】:

不好,我的朋友。用这个数组[.4, .5, .6, .7, .7, .8, .9, .9, 1, 1.2, 1.4] 试试你的代码,你会看到一个挂着的1.4。尽管如此,你的代码还是有进步的! 好的,明白了,所以当最后一个值不能进入其他堆栈但小于minValue => 失败。嗯,很有趣,我不确定是否有时间继续,但如果是的话,我会尽力给你一个答案;)【参考方案2】:

我不是数学天才,我知道这些垃圾箱包装问题的解决方案可能会变得非常复杂。但是,在这种情况下,随机化输入非常有效。

所以,随机化初始数组,然后根据上下限对数组进行线性分组,然后检查 bin 是否有效。

在这里,我有一个包含 50 个初始值的数组。我允许它最多尝试 10000 次尝试,但它通常会在前 500 次尝试中找到一个有效的 bin,并具有给定的约束(最小值:2.2,最大值:4.2)。很多时候它要快得多。

经过多次测试,我没有一个阴性结果:

const testArr = [1, .5, .4, 1.2, 3, 1.3, 3, .8, .9, 1.2,
                2.8, 2.1, .7, .3, .7, 1.8, 1.1, .9, .4, 1.2, 1,
                2, 1.1, 1.3, .9, 1.8, 1.7, 1.1, 1.3, 2.8, 2.6, 1.4,
                2.9, 2.6, 1.1, .5, .2, 1.8, 2.5, 3.4, 2, 2, 2, 4, 3.1,
                1.3, 3, 3, 3.5, 2.3];

console.log("Array length: " + testArr.length);
let numAttempts = 10000;
let rndArray, getBin, binIsValid = false;

for (i=1; i<=numAttempts; i++) 
    
    rndArray = shuffleArray(testArr);
    
    getBin = binOutput(4.2, rndArray);
    binIsValid = isBinValid(2.2, 4.2, getBin);
    
    if (binIsValid === true) 
        console.log("Completed in " + i + " attempts.");
        break;
     



if (binIsValid === true) 
    console.log(getBin);
 else 
    console.log("No valid bin produced in " + numAttempts + " attempts.");


function binOutput(upperRange, rndVals) 
    let newGroup = [], groupTotal = 0;

    return rndVals.reduce((acc, val, i) => 
        groupTotal = groupTotal + val;
        if (groupTotal < upperRange) 
            newGroup.push(val);
            if (i === (rndVals.length-1)) 
                acc.push(newGroup);
            
         else 
            acc.push(newGroup);
            newGroup = [];
            newGroup.push(val);
            groupTotal = val;
            if (i === (rndVals.length-1)) 
                acc.push(newGroup);
            
        
        return acc;
    , []);


function isBinValid(lRange, uRange, bin) 
    let binValid = true, groupTotal = 0;
    bin.forEach(group => 
        groupTotal = getGroupTotal(group);
        if (groupTotal <= lRange || groupTotal >= uRange) binValid = false;
    );
    return binValid;


function getGroupTotal(groupArr) 
    return groupArr.reduce((a, b) => a + b, 0);


function shuffleArray(array) 
    for (let i = array.length - 1; i > 0; i--) 
        let j = Math.floor(Math.random() * (i + 1));
        let temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    
    return array;

【讨论】:

以上是关于装箱问题 - 确定给定范围的一组值的最佳分组的主要内容,如果未能解决你的问题,请参考以下文章

如何从给定的一组值中进行选择,四舍五入?

确定一个值是不是在 Java 中的一组值中的最快方法是啥?

仅从一组值中检索给定标识符和一个列值的多个条目中的行

用R中的组中的非NA字符替换一组值的NA [重复]

检查数组是不是至少包含 PHP 中的一组值

从具有相同 id 的一组值中选择最大值