数组动态分组多个条件

时间:2018-02-01 21:46:30

标签: javascript arrays

const task = [
    { x: 1, y: 20 },
    { x: 2, y: 30 },
    { x: 1, y: 50 },
    { x: 2, y: 20 },
    { x: 1, y: 10 },
    { x: 9, y: 40 },
    { x: 1, y: 30 },
    { x: 3, y: 5 }
];

有两个条件:

  • 分组限制可以是max z
  • y的总和可以是max t

例如z=2t=60(我将这些值作为参数传递给函数),结果应为:

let result = [
    [{ x: 1, y: 20 },{ x: 2, y: 30 }],
    [{ x: 1, y: 50 }],
    [{ x: 2, y: 20 },{ x: 1, y: 10 }],
    [{ x: 9, y: 40 }],
    [{ x: 1, y: 30 },{ x: 3, y: 5 }]
];

我已设法使用for循环实现,但我对可能的功能解决方案感到好奇。任何帮助都会得到满足。

7 个答案:

答案 0 :(得分:4)

稍微更具功能性的方法将包括一些非常简单的帮助程序。让我们以函数ySum开头,它返回给定参数y中所有group属性的总和:

const ySum = (group) => group.reduce((sum, { y: currY }) => sum + currY, 0);

接下来,让我们定义一个函数,它接受一个任务和一个组,告诉我们给定的任务是否适合给定的组:

从评论中更新对于空组,此函数始终返回true,以防止在y > 60执行特定任务时出现空数组问题

const taskFitsInGroup = (task, group) => {

    if (group.length === 0) {
        return true;
    }

    return (group.length < 2) && (ySum(group) + task.y <= 60);
};

注意:您当然可以将常量260提取到变量/参数。

Array.prototype.reduce是您需要的数据转换的理想选择。在实施addTask之后,以下行应该满足您的要求:

const result = tasks.reduce(addTask, [[]]);

只剩下要做的事情是创建我们上面提到addTask的{​​{1}}函数。使用我们已经创建的辅​​助函数,这实际上非常简单:

reduce

工作示例:

&#13;
&#13;
const addTask = (accumulation, task) => {

    const lastGroup = accumulation[accumulation.length - 1];

    if (taskFitsInGroup(task, lastGroup)) {
        lastGroup.push(task);
    } else {
        accumulation.push([task]);
    }

    return accumulation;
};
&#13;
&#13;
&#13;

答案 1 :(得分:3)

这是另一个功能性的ES6解决方案:

&#13;
&#13;
function chunk(task, z, t) {
    return task.reduce( ({sum, arr, i}, {y}, j) => 
        (j > i && sum + y > t) || j - i >= z
            ? {sum: y, arr: [...arr, arr[arr.length-1].splice(j-i)], i: j}
            : {sum: sum + y, arr, i}
    , {sum: 0, arr: [task.slice()], i: 0}).arr;
}

const task = [{x:1,y:20}, {x:2,y:30}, {x:1,y:50}, {x:2,y:20},{x:1,y:10},{x:9,y:40},{x:1,y:30},{x:3,y:40}];

const result = chunk(task, 2, 60);

console.log(JSON.stringify(result));
&#13;
&#13;
&#13;

注意:注意额外的j > i条件,即使y大于限制,也要确保结果中的每个子数组都至少有一个元素。否则你可能会有一个无限循环 - 永远不能放置具有太大y - 值的元素。

解释

该功能从创建reduce回调的起始值开始:

{sum: 0, arr: [task.slice()], i: 0}

arr属性表示到目前为止的结果,它是原始数组,其所有元素组合在一起。 slice是确保我们拥有数组副本并且不会改变原始数据所必需的。{/ p>

sum属性将跟踪最后一个块中的总和,直到reduce循环的当前索引。

i属性告诉原始数组中的索引将最后一个分成新块。

reduce回调包含以下参数:

  • {sum, arr, i}:这是上述对象的解构表示。 reduce回调将始终返回具有这三个属性的对象,因此它们将在下一次迭代中再次作为回调的参数提供。
  • {y}:再次进行解构以提取y数组中当前元素的task值。
  • jtask数组
  • 中的当前索引

然后短表达式语法用于箭头函数(=>):它后面没有语句块,只是一个表达式,它是回调的返回值。

此表达式使用三元运算符来决定当前元素是否可以保留在当前块中,在这种情况下,sumarr只需更新i属性可以保持不变。短ES6对象表示法用于表示arr属性将获得arr值。 i

相同
: {sum: sum + y, arr, i}

另一种情况是必须拆分新块:

? {sum: y, arr: [...arr, arr[arr.length-1].splice(j-i)], i: j}

此处splice方法用于从i-j第一个元素之后的当前最后一个块中删除所有元素。删除的元素将放回一个新数组中,作为它的最后一个新元素。其余部分以...arr复制。我必须承认,这不是纯粹的功能,因为arr的最后一个子阵列正在用splice进行变异。但是使用slice编写相同内容需要更多代码。

决定采取哪两项措施的条件反映了您指定的条件:y > tj - i >= z。我已经解释了为什么我将j > i添加到第一个条件。

答案 2 :(得分:2)

这是使用.reduce的一个潜在解决方案:

&#13;
&#13;
var task = [{x:1,y:20}, {x:2,y:30}, {x:1,y:50}, {x:2,y:20},{x:1,y:10},{x:9,y:40},{x:1,y:30},{x:3,y:40}];

// grouping limit can be max z
// sums of y can be max t

// the result should be:
// [[{x:1,y:20}, {x:2,y:30}],[{x:1,y:50}], [{x:2,y:20},{x:1,y:10}],[{x:9,y:40}],[{x:1,y:30},{x:3,y:5}]];

function sumY(arr) {
    return arr.reduce((sum, item) => sum + item.y, 0);
}

function group(maxSize, maxSum, arr) {
    return arr.reduce((groups, item) => {
        const last = groups[groups.length - 1];
        (!last || last.length >= maxSize || sumY(last) + item.y > maxSum)
            ? groups.push([item])
            : last.push(item); 
        return groups;
    }, []);
}

console.log(group(2, 60, task));
&#13;
&#13;
&#13;

答案 3 :(得分:2)

您可以为实际组使用辅助数组,并将属性y与实际对象的y属性相加

group.reduce((s, { y }) => s + y, o.y)
^^^^^                                  array
              ^                        accumulator for sum
                 ^^^^^                 destructuring to get y property
                           ^^^^^       build sum and return value
                                  ^^^  start accumulator with actual y prop

用于检查给定值。

function group(array, z, t) {
    var result = [];
    array.reduce(function (group, o, i) {
        var sum = group.reduce((s, { y }) => s + y, o.y);
        if (i && sum <= t && group.length < z) {
            group.push(o);
        } else {
            result.push(group = [o]);
        }
        return group;
    }, []);
    return result;
}

var task = [{ x: 1, y: 20 }, { x: 2, y: 30 }, { x: 1, y: 50 }, { x: 2, y: 20 }, { x: 1, y: 10 }, { x: 9, y: 40 }, { x: 1, y: 30 }, { x: 3, y: 5 }],
    result = group(task, 2, 60);
    
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

答案 4 :(得分:1)

const group = (arr, z, t, result = [[]], count = 0, y = 0) => 
  (arr.reduce((acc, curr) => (
    count++, y += curr.y,
    count > z || y >= t 
      ? (count = 0, y = curr.y, result[result.push([curr]) - 1])
      :  (acc.push(curr), acc)
  ), result[0]), result);

这是一种纯粹的功能性方法。使用group(task, 2, 60)进行调用。但是我仍然更喜欢旧的for循环:

 const result = [];
 let group = [], z = 2, t = 60, count = 0, y = 0;

for(const task of tasks){
  if(count++ >= z || (y += task.y) >= t){
    count = 0;
    y = task.y;
    result.push(group);
    group = [];
  }
  group.push(task);
}
if(group.length) result.push(group);

答案 5 :(得分:1)

您可以使用reduce()方法,并将一个变量用于当前数组编号,将一个变量用于当前y和值。

const task = [{x:1,y:20}, {x:2,y:30}, {x:1,y:50}, {x:2,y:20},{x:1,y:10},{x:9,y:40},{x:1,y:30},{x:3,y:40}];

const group = (data, z, t) => {
  let n = 0, tTemp = 0;
  return data.reduce(function(r, e) {
    if ((r[n] && r[n].length >= z) || (tTemp + e.y) > t) n += 1, tTemp = e.y;
    else tTemp += e.y;
    r[n] = (r[n] || []).concat(e)
    return r;
  }, [])
}

const result = group(task, 2, 60)
console.log(result)

答案 6 :(得分:1)

const task = [{x:1,y:20}, {x:2,y:30}, {x:1,y:50}, {x:2,y:20},{x:1,y:10},{x:9,y:40},{x:1,y:30},{x:3,y:40}],
z = 2, t = 60;

const group = task =>
  task.reduce((a, e) => (
    !!a.length && a[a.length - 1].length < z &&
    a[a.length - 1].reduce((s, {y}) => s + y, e.y) <= t ?
    a[a.length - 1].push(e) : a.push([e])
  ) && a, [])

console.log(JSON.stringify(group(task)))