迭代嵌套的数组项,并根据特定键的相同值,收集任何其他条目值作为按键+值分组的合并数据

Iterate nested array items and, upon the same value of a specific key, collect any other entry value as data of a merger which is grouped by key+value

我有一个如下所示的数组:

所以作为一个结构,它应该是这样的:

[
    [
       { classNumber: '2', status: 'A', terms: [] }, 
       { classNumber: '32', status: 'B', terms: [] }, 
       { classNumber: '44', status: 'C', terms: []  }
    ],
    [
        { classNumber: '2', status: 'B', terms: [] }, 
        { classNumber: '31', status: 'A', terms: [] }
    ],
    ....
]

之所以出现这种奇怪的对象数组,是因为在我们的应用程序中的某个时刻,我们正在创建一系列理由来反对使用同一对象的某些东西。

我需要能够合并嵌套的对象数组,使其看起来像这样:

[
     { classNumber: '2', status: [ 'A', 'B' ], terms: [] }, 
     { classNumber: '31', status: [ 'A' ], terms: [] }, 
     { classNumber: '32', status: [ 'B' ], terms: [] }, 
     { classNumber: '44', status: [ 'C' ], terms: [] }
]

但我已经为此苦苦挣扎了几天,寻找一些 lodash 函数,但仍然没有运气...

我完全不知道如何实现这一点。所有示例看起来都更简单,嵌套数组更少。关于如何合并同一对象键的所有道具的任何想法?

非常感谢。

如果我没有遗漏什么,你正在找这个:?

var merged = Array.prototype.concat.apply([], original);

喜欢:

Array.prototype.concat.apply([], [[1,2,3],[4,5], [6]]);
// returns:
// [1, 2, 3, 4, 5, 6]

另一种方式:

var merged = [];
for(var i = 0; i<original.length; i++) {
  for(var j = 0, arr = original[i]; j<arr.length; j++) {
    merged.push(arr[j]);
  }
}
    

更新:

是的,我确实漏掉了一些东西。感谢@PeterSeliger 指出。但是我不想删除这个答案,而是想更新并更正它。

下面的代码未测试


function doMergeItems(prev, current) {
  // Merge content of "current" with "prev"
  // Below is just a simple example, need to be replaced
  // according to the desired merging strategy.
  if(!prev)
    prev = {
      classNumber: current.classNumber,
      status: [],
      terms: [],
    };
  prev.status.push(current.status);
  prev.terms.push(...current.terms);
  return prev;
}


var merged = [];
for(var i = 0; i<original.length; i++) {
  for(var j = 0, arr = original[i]; j<arr.length; j++) {
    var item = arr[j];
    var index = merged.findIndex(function(x){
      return x.classNumber === item.classNumber;
    });
    if(index < 0)
      merged.push(doMergeItems(null, item));
    else
      merged[index] = doMergeItems(merged[index], item);
  }
}

我做的很快,但像这样应该可以完成工作

const doubleNestedArray = [
  [{
    classNumber: '2', status: 'A', terms: ['foo', 'bar'],
  }, {
    classNumber: '32', status: 'B', terms: ['baz'],
  }, {
    classNumber: '44', status: 'C', terms: ['bizz'],
  }], [{
    classNumber: '2', status: 'B', terms: ['bar', 'baz'],
  }, {
    classNumber: '31', status: 'A', terms: ['buzz'],
  }],
];
console.log(
  _.chain(doubleNestedArray)
    .flatten()
    .groupBy(element => element.classNumber)
    .map((value) => ({
        ...value[0],
        status: value.map(element => element.status)
    }))
    .value()
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>

来自 OP 自己的评论...

"For all of them I'm planning to do a generic solution. In the status example, create an array with all the status (at least for now). But first I need to be able to merge that and then I'll decide if I make that operations more complex. Does it have sense?" – Sonhja

处理任何(扁平化)数组项而不考虑此类项的数据结构的通用解决方案很可能必须提出两种折叠方法...

  • 一个通用的 reducer 函数,它 reduces the flattened 数组的 data-items 通过对提供的键(属性 名称)具有相同值的任何数据项进行分组。

  • 自定义实现的合并函数(作为 reduce 方法的 initialValue 的一部分提供),其中可以决定 data-item 的条目 (与分组 属性 名称不同)将被合并到目标数据结构中。

// reducer function which generically
// - groups any data item by a provided key's same value
// - and merges all other entries via a custom merge function.
function groupBySameKeyValueAndMergeProperties(collector, item) {
  const { merge, key, lookup, result } = collector;
  const { [key]: groupValue, ...rest } = item;

  let groupItem = lookup.get(groupValue);
  if (!groupItem) {

    groupItem = { [key]: groupValue };

    lookup.set(groupValue, groupItem);
    result.push(groupItem);
  }
  merge(groupItem, rest);

  return collector;
}

// custom, task specific merge function, according to the OP's goal.
function mergeDataItemEntries(targetItem, sourceItem) {
  Object
    .entries(sourceItem)
    .reduce((target, [key, value], idx, arr) => {

      if (target.hasOwnProperty(key)) {

        // collect value of currently processed entry.
        target[key].push(value);

      } else {

        // initial (one time) array initialization
        // in order to later collect all other values.
        target[key] = [value];
      }
      // here, maybe even a final treatment
      // for the 2 dimensional `terms` array.
      if (idx >= arr.length - 1) {

        // - flattening of the 2 dimensional `terms` array.
        // - a `Set` instance always assures unique values.
        target.terms = [...new Set(target.terms.flat())];
      }
      return target;

    }, targetItem);
}

const sampleData = [
  [{
    classNumber: '2', status: 'A', terms: ['foo', 'bar'],
  }, {
    classNumber: '32', status: 'B', terms: ['baz'],
  }, {
    classNumber: '44', status: 'C', terms: ['bizz'],
  }], [{
    classNumber: '2', status: 'B', terms: ['bar', 'baz'],
  }, {
    classNumber: '31', status: 'A', terms: ['buzz'],
  }],
];
console.log(
  'flattened, merged and sorted data items ...',
  sampleData
    .flat()
    .reduce(groupBySameKeyValueAndMergeProperties, {

      // task specific reduce configuration.
      merge: mergeDataItemEntries,
      key: 'classNumber',
      lookup: new Map,
      result: [],
    })
    .result
    .sort((a, b) =>
      Number(a.classNumber) - Number(b.classNumber)
    )
);
console.log('original data structure ...', sampleData);

console.log(
  '... Bonus .. the pure grouping result without merging and sorting ...',
  sampleData
    .flat()
    .reduce(groupBySameKeyValueAndMergeProperties, {

      merge: (_ => _),
      key: 'classNumber',
      lookup: new Map,
      result: [],

    }).result
);
.as-console-wrapper { min-height: 100%!important; top: 0; }