为cols添加新的摘要行,它们具有两个不同cols的相同价位,并处理时间差

时间:2018-06-20 14:10:41

标签: javascript arrays algorithm matrix

我有这个矩阵算法:

输入:

const input = [
    ['Camry', 'Toyota', 'Jan', 'Nowhere Town', '50'],
    ['Camry', 'Toyota', 'Feb', 'Nowhere Town', '70'],
    ['Camry', 'Toyota', 'Jan', 'Random City', '3000'],
    ['Prius', 'Toyota', 'Jan', 'Nowhere Town', '60'],
    ['Prius', 'Toyota', 'Jan', 'Random Town', '60'],
    ['Prius', 'Toyota', 'Mar', 'Nowhere Town', '50'],
    ['Civic', 'Honda', 'Jan', 'Nowhere Town', '10'],
    ['Civic', 'Honda', 'Feb', 'Nowhere Town', '10'],
    ['Civic', 'Honda', 'Mar', 'Random Town', '10'],
    ['Civic', 'Honda', 'Mar', 'Random Town', '20'],
]

预期输出:

const output = [
    ['S', 'Camry', 'Toyota', 'Jan', '3050'],
    ['D', 1, 'Camry', 'Nowhere Town', '50'],
    ['D', 2, 'Camry', 'Random City', '3000'],
    ['S', 'Camry1', 'Toyota', 'Feb', '70'],
    ['D', 1, 'Camry1', 'Nowhere Town', '70'],
    ['S', 'Prius', 'Toyota', 'Jan', '120'],
    ['D', 1, 'Prius', 'Nowhere Town', '60'],
    ['D', 2, 'Prius', 'Random Town', '60'],
    ['S', 'Prius1', 'Toyota', 'Mar', '50'],
    ['D', 1, 'Prius1', 'Nowhere Town', '50'],
    ['S', 'Civic', 'Honda', 'Jan', '10'],
    ['D', 1, 'Civic', 'Nowhere Town', '10'],
    ['S', 'Civic1', 'Honda', 'Feb', '10'],
    ['D', 1, 'Civic1', 'Nowhere Town', '10'],
    ['S', 'Civic2', 'Honda', 'Mar', '30'],
    ['D', 1, 'Civic2', 'Random Town', '10'],
    ['D', 2, 'Civic2', 'Random Town', '20'],
]

换句话说:如果行包含相同的品牌,相同的制造和相同的月份,则在总销售额的顶部添加一个摘要行并为每个详细信息行添加列出的顺序。

如果月制作行的后面分别为SD行添加一个额外的数字字符串(1、2,...) >晚于表中的第一个

这个问题与我以前的问题here类似,但是这个问题需要额外的逻辑来处理月份的差异。

这是我使用的旧代码:

const groupReport = arr => {
    const result = [].concat(...arr
        .reduce((m, [brand, make, month, town, amount]) => {
            var key = [brand, make, month].join('|'),
                data = m.get(key) || [['S', brand, make, month, '0']];

            data.push(['D', data.length, brand, town, amount]);
            data[0][4] = (+data[0][4] + +amount).toString();
            return m.set(key, data);
        }, new Map)
        .values()
    )
    return result
}

旧代码返回以下结果:

const oldOutput = [
    ['S', 'Camry', 'Toyota', 'Jan', '3050'],
    ['D', 1, 'Camry', 'Nowhere Town', '50'],
    ['D', 2, 'Camry', 'Random City', '3000'],
    ['S', 'Camry', 'Toyota', 'Feb', '70'],
    ['D', 1, 'Camry', 'Nowhere Town', '70'],
    ['S', 'Prius', 'Toyota', 'Jan', '120'],
    ['D', 1, 'Prius', 'Nowhere Town', '60'],
    ['D', 2, 'Prius', 'Random Town', '60'],
    ['S', 'Prius', 'Toyota', 'Mar', '50'],
    ['D', 1, 'Prius', 'Nowhere Town', '50'],
    ['S', 'Civic', 'Honda', 'Jan', '10'],
    ['D', 1, 'Civic', 'Nowhere Town', '10'],
    ['S', 'Civic', 'Honda', 'Feb', '10'],
    ['D', 1, 'Civic', 'Nowhere Town', '10'],
    ['S', 'Civic', 'Honda', 'Mar', '30'],
    ['D', 1, 'Civic', 'Random Town', '10'],
    ['D', 2, 'Civic', 'Random Town', '20'],
]

如何改进旧代码以处理新逻辑,还是可以采用新方法?

2 个答案:

答案 0 :(得分:2)

您可以聚合查找哈希,然后循环遍历直到获得月数据并生成所需的输出(理想情况是递归,但我是反复进行的):

var input = [
    ['Camry', 'Toyota', 'Jan', 'Nowhere Town', '50'],
    ['Camry', 'Toyota', 'Feb', 'Nowhere Town', '70'],
    ['Camry', 'Toyota', 'Jan', 'Random City', '3000'],
    ['Prius', 'Toyota', 'Jan', 'Nowhere Town', '60'],
    ['Prius', 'Toyota', 'Jan', 'Random Town', '60'],
    ['Prius', 'Toyota', 'Mar', 'Nowhere Town', '50'],
    ['Civic', 'Honda', 'Jan', 'Nowhere Town', '10'],
    ['Civic', 'Honda', 'Feb', 'Nowhere Town', '10'],
    ['Civic', 'Honda', 'Mar', 'Random Town', '10'],
    ['Civic', 'Honda', 'Mar', 'Random Town', '20'],
]

var dataObj = input.reduce((all, [brand, make, month, city, count]) => {

    if (!all.hasOwnProperty(brand)) all[brand] = {};

    if (!all[brand].hasOwnProperty(make)) all[brand][make] = {};

    if (!all[brand][make].hasOwnProperty(month)) all[brand][make][month] = [];

    all[brand][make][month].push({city, count});

    return all;

}, {});


var result = Object.keys(dataObj).reduce((all, brand) => {

    Object.keys(dataObj[brand]).forEach(make => {

        Object.keys(dataObj[brand][make]).forEach((month, j) => {

            const id = j || '';

            dataObj[brand][make][month]
                .reduce((final, {city, count}, i) => {

                    final[0][4] += Number(count);
                    final.push(['D', i + 1, `${brand}${id}`, make, month, count]);
                    return final;

                },[['S', `${brand}${id}`, make, month, 0]])
                .forEach(item => all.push(item));

        });

    });

    return all;

}, []);

console.log(result);

答案 1 :(得分:2)

您的这对问题很好地显示了“期望输入->输出”这类问题如何使那些喜欢代码难题的人返回花哨的单子(我对此感到内)如此有建设性...只要需求发生变化,就很难弄清楚到底发生了什么!


我将尽力解释如何创建持久的程序:

您的两种行类型

首先定义标题是什么,行是什么,以及构造它们所需的信息

const header = (model, make, month, monthIndex, totalCost) =>
  ["D", model + (monthIndex || ""), make, month, totalCost + ""];

const row = (model, make, month, monthIndex, carIndex, cost) =>
  ["S", carIndex + 1, model + (monthIndex || ""), make, month, cost];

分组到各个部分

现在,我们需要通过modelmakemonth对汽车进行分组,以获取正确数量的标题及其所需的信息。我们将使用groupBy实用程序,您可以在整个代码中重复使用该实用程序,而该实用程序并不特定于此程序。基础知识:

  • 输入:
    • 为元素string返回a -> string的函数
    • 元素列表[a]
  • 输出:
    • 一个对象,其元素按其字符串键分组:{ key: [a] }

许多库都实现了groupBy,但是您也可以自己定义一个库。

嵌套表

现在我们有了groupBy,我们可以在您的行中创建一棵树,如下所示:

const tableTree = { "Camry": { "Toyota": { "Jan": [ /* car rows */ ] } } };

您可以通过减少对象的条目来为此数据结构编写一个转换器,并且应该更加清楚逻辑是什么以及如何更改它。

平整嵌套表

您可以像这样循环遍历这棵树:

Object.values(tableTree)
  .forEach(models => Object.values(models)
    .forEach(months => Object.values(months)
      .forEach(cars => { /* create rows */ }))
    )
  );

运行示例:

将它们放在一起:

const { map, groupBy, elAt, prop, sum } = utils();

// APP
// Properties:
const getModel = prop(0);
const getMake = prop(1);
const getMonth = prop(2);
const getCost = prop(4);

// Groupers:
const groupByModel = groupBy(getModel);
const groupByMake = groupBy(getMake);
const groupByMonth = groupBy(getMonth);


// Leveled grouper:
const makeTable = carInput => 
  map (map (groupByMonth)) 
      (map (groupByMake) 
           (groupByModel(carInput)));

// Get to the cars and make Sections
const flattenTable = table => {
  const rows = [];
  
  Object.values(table)
    .forEach(models => Object.values(models)
      .forEach(months => Object.values(months)
        .forEach((cars, mNr) => 
          rows.push(...Section(cars, mNr))
      )
    )
  );
          
  return rows;
};

// Row types (these will be so much nicer is you use objects...):
const Header = (model, make, month, totalCost, mNr) => 
  ["D", model + (mNr || ""), make, month, totalCost + ""];
  
const Row = mNr => (car, cNr) =>
  ["S", cNr + 1, getModel(car) + (mNr || ""), getMake(car), getMonth(car), getCost(car)];

const Section = (cars, mNr) => {
  const [c] = cars;
  const tCost = cars.map(getCost).reduce(sum);
  return [
    Header(getModel(c), getMake(c), getMonth(c), tCost, mNr),
    ...cars.map(Row(mNr))
  ];
};


// Test data:
const input = [
    ['Camry', 'Toyota', 'Jan', 'Nowhere Town', '50'],
    ['Camry', 'Toyota', 'Feb', 'Nowhere Town', '70'],
    ['Camry', 'Toyota', 'Jan', 'Random City', '3000'],
    ['Prius', 'Toyota', 'Jan', 'Nowhere Town', '60'],
    ['Prius', 'Toyota', 'Jan', 'Random Town', '60'],
    ['Prius', 'Toyota', 'Mar', 'Nowhere Town', '50'],
    ['Civic', 'Honda', 'Jan', 'Nowhere Town', '10'],
    ['Civic', 'Honda', 'Feb', 'Nowhere Town', '10'],
    ['Civic', 'Honda', 'Mar', 'Random Town', '10'],
    ['Civic', 'Honda', 'Mar', 'Random Town', '20'],
];


// Run App:
console.log(
  flattenTable(makeTable(input))
    .map(row => `[${row.join(", ")}]`));


function utils() { 
  return {
    groupBy: getKey => xs => xs
      .map(x => [getKey(x), x])
      .reduce(
        (gs, [k, x]) => Object.assign(
          gs, 
          { [k]: (gs[k] || []).concat([x]) }
        ),
        {}
      ),
    map: f => obj => Object.entries(obj)
      .reduce(
        (o, [k, v]) => Object.assign(o, { [k]: f(v) }),
        {}
      ),
    prop: k => o => o[k],
    sum: (x, y) => +x + +y
  }
};