javascript - 为列添加新的摘要行共享 2 个不同列的相同值并处理时间差

标签 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'],
]

换言之:如果行包含相同的Brand、相同的Make 和相同的Month,则在顶部添加一个包含总销售额的摘要行并为每个详细信息行添加列出的订单。

如果< strong>Month 晚于表中的第一个 Month

这个问题类似于我的老问题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'],
]

我如何改进旧代码来处理新逻辑,或者我可以采用新方法吗?

最佳答案

你的一对问题很好地展示了“预期输入 -> 输出”这类问题如何从喜欢代码谜题的人(我自己对此感到内疚)返回花哨的单行代码那种建设性的......每当需求发生变化时,很难弄清楚发生了什么!


我将尝试解释您可以做些什么来创建一个持久的程序:

你的两种行类型

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

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 对我们的汽车进行分组,以到达正确的位置 header 数量及其所需信息。我们将使用一个 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
  }
};

关于javascript - 为列添加新的摘要行共享 2 个不同列的相同值并处理时间差,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50950277/

相关文章:

javascript - array.shift() 在 axios 响应中不起作用?

javascript - 是否可以在 Jasmine.Js 的 toEqual 中检查多种类型?

algorithm - 如何将 5 个点均匀分布到不规则形状上?

algorithm - 汇编程序通过问题

java - 如何避免在递归中使用全局/类级别变量?

javascript - $(window).width() 与 $(document).width() 之间的区别

javascript - 搜索反斜杠

javascript - Array.prototype 导致错误

c++ - 如何使用数组将多个字符串文字传递给函数(无 vector )

javascript - 是否有可能在 javascript 的排序函数中导致无限循环?