【问题标题】:JavaScript sorting and grouping items in arrayJavaScript 对数组中的项目进行排序和分组
【发布时间】:2017-07-31 07:54:28
【问题描述】:

我正在尝试在 JavaScript 中进行一些映射。我正在尝试检查arr 中的类型是否存在于datasetarr 中,如果存在,我会在datasetarr 中获取索引并增加该索引的数量。如果不存在,我在datasetarr 中添加一个新条目。这是我的代码:

var datasetarr = [];
var pos;
for(var i = 0; i < arr.length; i++){
    console.log('unsorted ' + arr[i].type + ' ' + arr[i].quantity);
    if(datasetarr.indexOf(arr[i].type) > -1){
        pos = datasetarr.indexOf(arr[i].type);
        datasetarr[pos].quantity += arr[i].quantity;
    }else{
        datasetarr.push({type: arr[i].type, quantity: arr[i].quantity});
    }
}

for(var i = 0; i < datasetarr.length; i++){
    console.log('sorted ' + datasetarr[i].type + ' ' + datasetarr[i].quantity);
}

输出应该是:

kitchen appliance 20
home entertainment 8
batteries & lightings 4
home appliance 12

然后,我可以获取每个类型和数量并存储到数组中以供我使用图表进行绘制。

但是,使用上面的代码,我得到的东西与未排序的完全一样。任何想法我的逻辑的哪一部分出错了?

【问题讨论】:

  • 如果您将图像替换为实际数组(作为代码)会更容易为您提供帮助..
  • 如果你能添加一些有用的代码,比如array Aarray Bdatasetarr会更容易。或者更好的是,在 fiddle、sn-p 或类似中的工作示例。
  • 使用 arr 和正确的命名约定而不是数组 A/B 编辑问题
  • 所以基本上你想通过typequantity 值对数组arr 进行分组? (并在datasetarr 中输出结果)
  • @gmo 是的,这就是我想要实现的目标。抱歉英语不好

标签: javascript arrays sorting grouping


【解决方案1】:

使用对象进行分组。

var categories = {};

function incrementBy(category, value) {
  if (!categories[category]) {
    categories[category] = 0;
  }
  categories[category] += value;
}

var datasetarr = [];
var pos;
for(var i = 0; i < arr.length; i++){
  console.log('unsorted ' + arr[i].type + ' ' + arr[i].quantity);
  incrementBy(arr[i].type, arr[i].quantity)
}

for(var category in categories){
  console.log('sorted ' + category + ' ' + categories[category]);
}

如果需要,您可以分解对象,将其分解为数组并在需要时对其进行排序。

一个更简洁的例子,它可能会让你更好地理解:

var arr = [{type: 'kitchen appliance', quantity: 2},
    {type: 'home entertainment', quantity: 2},
    {type: 'home entertainment', quantity: 3},
    {type: 'batteries & lightings', quantity: 2},
    {type: 'home entertainment', quantity: 2},
    {type: 'home appliance', quantity: 5},
    {type: 'kitchen appliance', quantity: 4},
    {type: 'kitchen appliance', quantity: 5},
    {type: 'kitchen appliance', quantity: 3},
    {type: 'kitchen appliance', quantity: 4},
    {type: 'kitchen appliance', quantity: 1},
    {type: 'home entertainment', quantity: 1},
    {type: 'home appliance', quantity: 5},
    {type: 'batteries & lightings', quantity: 2},
    {type: 'kitchen appliance', quantity: 2},
    {type: 'home appliance', quantity: 2}];


function group(array) {
    var categories = {};

    function incrementBy(category, value) {
        if (!categories[category]) {
            categories[category] = 0;
        }
        categories[category] += value;
    }

    array.forEach(function (value, index, arr) {
        incrementBy(value.type, value.quantity)
    });

    return categories;
}

function print(categories) {
    for (var category in categories) {
        console.log('%s: %s', category, categories[category]);
    }
}

print(group(arr));

这里是group 隐藏分组的解决方案,它可以容纳您自己的实现:

function group(array) {
    var categories = {};

    function incrementBy(object) {
        var category = categories[object.type];
        if (!category) {
            category = categories[object.type] = {};
        }
        var subCategory = category[object.subType];
        if (!subCategory) {
            subCategory = category[object.subType] = {};
        }
        subCategory += object.value;
    }

    array.forEach(function (value, index, arr) {
        incrementBy(value, value.quantity)
    });

    return categories;
}

根据您的用例,您还可以展平结构:

function incrementBy(object) {
    var key = [object.type, object.subType].join('/');
    var category = categories[key];
    if (!category) {
        category = categories[key] = {};
    }
    subCategory += object.value;
}

但是有不同的地图可能是有意义的:

function groupings(array) {
    var groupings = {
      types: {},
      subTypes: {},
      paths: {}
    };

    function incrementBy(object) {
        var category = groupings['types'][object.type];
        if (!category) {
            category = groupings['types'][object.type] = {};
        }
        category += object.value;

        var subCategory = groupings['subTypes'][object.subType];
        if (!subCategory) {
            subCategory = groupings['subTypes'][object.subType] = {};
        }
        subCategory += object.value;


        var key = [object.type, object.subType].join('/');
        var path = groupings['paths'][key];
        if (!path) {
            path = groupings['paths'][key] = {};
        }
        path += object.value;
    }

    array.forEach(function (value, index, arr) {
        incrementBy(value, value.quantity)
    });

    return categories;
}

为避免聚合信息丢失,您可以简单地创建一个更复杂的数据结构:

function groupByAndSumBy(data, groupByProperty, sumByProperty) {
  var accumulator = {};

  data.forEach(function(object, index, array) {
      var localAcc = accumulator[groupByProperty]
            = accumulator[groupByProperty] || { items: [] };
      localAcc[sumByProperty]
            = (localAcc[sumByProperty] || 0) + object[sumByProperty];
      localAcc.items.push(object);
  });

  return accumulator;
}

function groupByMerchantNameAndSumByTotalSales(data) {
    return groupByAndSumBy(data, 'merchantName', 'totalSales');
}

这将创建一个聚合,其中还包含输入数组的子集,从而可以更详细地查看数据。

【讨论】:

  • 您在哪里声明类别?在incrementBy函数和for循环函数里面都有吗?
  • A category 基本上是 arr[i].type。所以它只定义了调用的方法。
  • 对不起,我的意思是上面的代码是两个独立的函数,对吧?一个函数中的两个 for 循环和另一个函数中的 incrementBy。它们都使用类别对象,因此我在两个函数中都声明了该对象。但不知何故,它现在没有打印任何东西。
  • 我添加了一个更广泛的示例。我希望它有助于更​​好地跟进。
  • 我添加了一个示例,它将原始项目保留在一个额外的数组中。 branchName 是数据的桶,totalSales 是累积值,items 包含用于计算给定 totalSales 的所有元素,branchName 是输入数据的真实子集。
【解决方案2】:

一般来说,我会建议 lodash 处理这类东西

result = _(arr)
    .groupBy('type')
    .mapValues(xs => _.sumBy(xs, 'quantity'))
    .value();

然而,在这种特殊情况下,一个普通的 JS 解决方案同样简单:

let result = {};

for (let item of arr)
    result[item.type] = (result[item.type] || 0) + item.quantity;

【讨论】:

  • 这个问题没有标记lodashecmascript-6,但是如果你要使用ES6,你不妨for (let { type, quantity } of arr)
  • @PatrickRoberts:截至 2017 年,我想默认情况下假设 ES6 处理 javascript 问题是安全的。
  • 我不同意。如果提问者使用的是 ES5(var 是一个很大的提示,除了没有使用 ES6 特性),普遍的共识是用 ES5 回答。无论如何,我都赞成你的答案,因为我认为使用 lodash 很方便,但我主要是给你建议以改进你的 ES6 答案。
【解决方案3】:

这是因为,您的 If 条件仅检查结果数组 datasetarr 中的索引值,该数组已经为空,以便推送下一个值,

所以你的

        if(datasetarr.indexOf(arr[i].type) > -1){
        pos = datasetarr.indexOf(arr[i].type);
        datasetarr[pos].quantity += arr[i].quantity;
    }

永远不会执行。 这就是您获得与输入相同的输出的原因。

【讨论】:

  • 嗯,那我应该怎么解决呢?
  • 而不是 if 循环调用一个函数来仅将 distinct 类别推送到结果数组 datasetarr 并保持在每次调用函数时添加相同的数量。还是你要我给你写逻辑??
【解决方案4】:

你可以像这样写一个通用的聚合函数,并通过指定分类和聚合的键来调用它,分别是typequantity。最后,您指定reducer 函数,它只是一个汇总和a + b

function aggregate (array, predicate, value, reducer) {
  // group values by predicate
  var groups = array.reduce(function (groups, item) {
    if (item[predicate] in groups) {
      groups[item[predicate]].push(item[value])
    } else {
      groups[item[predicate]] = [item[value]]
    }

    return groups
  }, {})

  // aggregate each group of values by reducer
  return Object.keys(groups).map(function (group) {
    var summary = {}
    summary[predicate] = group
    summary[value] = groups[group].reduce(reducer, 0)
    return summary
  })
}

// input array
var arr = [{ type: 'kitchen appliance', quantity: 2 }, { type: 'home entertainment', quantity: 2 }, { type: 'home entertainment', quantity: 3 }, { type: 'batteries & lightings', quantity: 2 }, { type: 'home entertainment', quantity: 2 }, { type: 'home appliance', quantity: 5 }, { type: 'kitchen appliance', quantity: 4 }, { type: 'kitchen appliance', quantity: 5 }, { type: 'kitchen appliance', quantity: 3 }, { type: 'kitchen appliance', quantity: 4 }, { type: 'kitchen appliance', quantity: 1 }, { type: 'home entertainment', quantity: 1 }, { type: 'home appliance', quantity: 5 }, { type: 'batteries & lightings', quantity: 2 }, { type: 'kitchen appliance', quantity: 2 }, { type: 'home appliance', quantity: 2 }]

// output array
var sums = aggregate(arr, 'type', 'quantity', function (a, b) { return a + b })

// formatting
var output = sums.map(function (item) {
  return item.type + ' ' + item.quantity
})

// string output
console.log(output.join('\n'))

【讨论】:

  • 我明白了,但有什么方法可以从输出中获取类型和数量?例如 output.type 或 output.quantity?因为我将使用它们来绘制图表。到目前为止,输出形成为一个字符串 right
  • 我刚刚向你展示了如何,它在最后两个语句中......你确实声明这是你想要的输出格式,所以如果这不是你想要的,你应该说你需要什么第一名。
  • 就像我想从输出中获取所有类型并存储到一个临时数组中,以及从输出中获取所有数量并存储到另一个临时数组中。然后我将使用这两个临时数组来绘制图表。目前输出本身是一个字符串,所以 output.type, output.quantity 返回我 undefined
  • @EmmaHannah 请阅读代码中的 cmets。你想要的聚合数组在sums 中,aggregate() 的输出,我已经多次解释过了。根据问题中的要求,格式化的字符串行在输出中。
【解决方案5】:

基于 OP 问题:

我正在尝试检查来自 arr 的类型是否存在于 datasetarr 中,如果存在,我会在 datasetarr 中获取索引并增加该索引的数量。如果不存在,我在 datasetarr 中添加一个新条目。

以及 cmets 中的说明:

问:所以基本上你想按类型和总和值对数组 arr 进行分组? (并在 datasetarr 中输出结果)
A:是的,这就是我想要达到的目标。

我认为最好和最简单的方法就是这样做,检查是否存在并采取相应的行动。

var datasetarr = [];
for (var i = arr.length - 1; i >= 0; i--) {
  if (datasetarr.hasOwnProperty(arr[i]["type"])) {
    // exist -> increment
    datasetarr[arr[i]["type"]] = datasetarr[arr[i]["type"]] + arr[i]["quantity"];
  } else {
    // no exist -> add new
    datasetarr[arr[i]["type"]] = arr[i]["quantity"];
  }
}

datasetarr 变量的预期输出为:

datasetarr = {
    "batteries & lightings" => 4,
    "home appliance" => 12,
    "home entertainment" => 8,
    "kitchen appliance" => 21
];

【讨论】:

    猜你喜欢
    • 2021-08-13
    • 2014-06-20
    • 1970-01-01
    • 2018-02-27
    • 2021-05-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-25
    相关资源
    最近更新 更多