Aggregate same key values into an array and avoid undefined

别等时光非礼了梦想. 提交于 2021-01-07 06:22:59

问题


I am trying to aggregate the same key values into an array by value.

so for example I have an array of objects, like so

const data = [{foo: true},{foo: false},{bar: true},{buzz: false}]

when they get aggregated the array transforms into

[
foo: {true: [{foo: true}], false: [{foo: false}]},
bar: {true: [{bar: true}]},
buzz: {false: [{buzz: false}]}
]

the array entries is the original object.

Now I know the keys that I want to group by.. they are foo, bar, buzz and fizz.

But fizz is not part of the original array, so the return is undefined, like so

[
foo: {true:[{foo: true}], false: [{foo: false}]},
bar: {true: [{bar: true}]},
buzz: {false: A[{buzz: false}]}
fizz: {undefined: [{foo: true},{foo: false},{bar: true},{buzz: false}]}
], 

how do I reduce the original array without including the fizz value that is undefined?

code here:

 let v = [];
let types = ['foo', 'bar', 'buzz', 'fizz' ]
    for (let x = 0; x < types.length; x++) {
        let data = data.reduce((acc, i) => {
            if (!acc[i[types[x]]]) {
                acc[i[types[x]]] = [i]
            }
            else if (Array.isArray(acc[i[types[x]]])) {
                acc[i[types[x]]].push(i);
            }
            else if (typeof acc[i[types[x]]] === 'object') {
                acc[i[types[x]]] = [acc[i[types[x]]]]
                acc[i[types[x]]].push(i)
            }
            return acc;
        }, {})
        v.push({ [types[x]]: data });
    }

    return v;

回答1:


You were close, you just need to check if the property you were adding was undefined before adding. You can also check if the reduced object has any properties before adding to the result object.

Note that this may not be the most efficient way of doing it, but sometimes it's better to understand the code than it is to have highly efficient code.

const data = [{
  foo: true
}, {
  foo: false
}, {
  bar: true
}, {
  buzz: false
}];
let v = [];
let types = ['foo', 'bar', 'buzz', 'fizz']
for (let x = 0; x < types.length; x++) {
  let reduced = data.reduce((acc, i) => {
    //                        /* Added this type check            */
    if (!acc[i[types[x]]] && typeof i[types[x]] !== 'undefined') {
      acc[i[types[x]]] = [i]
    } else if (Array.isArray(acc[i[types[x]]])) {
      acc[i[types[x]]].push(i);
    } else if (typeof acc[i[types[x]]] === 'object') {
      acc[i[types[x]]] = [acc[i[types[x]]]]
      acc[i[types[x]]].push(i)
    }
    return acc;
  }, {});
  // Doesn't add a property for the type if there are no data
  if (Object.keys(reduced).length) {
    v.push({
      [types[x]]: reduced
    });
  }
}

console.log(v);



回答2:


Have a look at how Array.prototype.reduce works. It might be the right method to build your approach upon.

A generic way of solving the OP's problem was to iterate the provided data array. For each item one would extract its key and value. In case the item's key is listed (included) in another provided types array, one would continue creating a new data structure and collecting the currently processed item within the latter.

One does not want to iterate the types array for it will cause a unnecessarily complex lookup for the data items, each time a type item is going to be processed.

Thus a generically working (better code reuse) reduce method might be the best solution to the OP's problem ...

const sampleDataList = [
  { foo: true },
  { foo: false },
  { bar: true },
  { baz: false },
  { buzz: false },
  { baz: false },
  { bar: true }
];

// foo: {true: [{foo: true}], false: [{foo: false}]},
// bar: {true: [{bar: true}]},
// buzz: {false: [{buzz: false}]}

function collectItemIntoInclusiveKeyValueGroup(collector, item) {
  const { inclusiveKeyList, index } = collector;
  const firstItemEntry = Object.entries(item)[0];

  const key = firstItemEntry[0];
  const isProceedCollecting = (       // proceed with collecting ...
                                      //
    !Array.isArray(inclusiveKeyList)  // - either for no given list
    || inclusiveKeyList.includes(key) // - or if item key is listed.
  );
  if (isProceedCollecting) {

    let keyGroup = index[key];        // access the group identified
    if (!keyGroup) {                  // by an item's key, ... or ...
                                      // ...create it in case ...
      keyGroup = index[key] = {};     // ...it did not yet exist.
    }
    const valueLabel = String(firstItemEntry[1]); // item value as key.

    let valueGroupList = keyGroup[valueLabel];    // acces the group list
    if (!valueGroupList) {                        // identified by an item's
                                                  // value, ...or create it in
      valueGroupList = keyGroup[valueLabel] = []; // case it did not yet exist.
    }
    // push original reference into a grouped
    // key value list, as required by the OP.
    valueGroupList.push(item);
  }
  return collector;
}

console.log(
  "'foo', 'bar', 'buzz' and 'fizz' only :",
  sampleDataList.reduce(collectItemIntoInclusiveKeyValueGroup, {

    inclusiveKeyList: ['foo', 'bar', 'buzz', 'fizz'],
    index: {}

  }).index
);
console.log(
  "'foo', 'bar' and 'baz' only :",
  sampleDataList.reduce(collectItemIntoInclusiveKeyValueGroup, {

    inclusiveKeyList: ['foo', 'bar', 'baz'],
    index: {}

  }).index
);
console.log(
  "all available keys :",
  sampleDataList.reduce(collectItemIntoInclusiveKeyValueGroup, {

    index: {}

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



回答3:


Try something like:

    const data = [{foo: true},{foo: false},{bar: true},{buzz: false}];
    let v = [];
    let types = ['foo', 'bar', 'buzz', 'fizz' ];
        for (let x = 0; x < types.length; x++) {
          let filteredlist = data.filter(function (d) {
            return Object.keys(d)[0] == types[x];
          });
          let isTrue = 0;
          let isFalse = 0;
          if (filteredlist.length > 0) {
            for (let i = 0; i < filteredlist.length; i++) {
              let trueOrfalse = eval("filteredlist[i]." + types[x]);
              if (trueOrfalse) {
                isTrue++;
              } else {
                isFalse++;
              }
            }
            v.push(types[x], {true: isTrue, false: isFalse});
          }
        }
    console.log(v);



回答4:


Assuming you only want to count the number of each key (e.g. true or false) you can use the following code.

I've written this as a function named 'aggregate' so that it can be called multiple times with different arguments.

const initialData = [{foo: true},{foo: true},{foo: false},{bar: true},{buzz: false}];
const types = ['foo', 'bar', 'buzz', 'fizz'];

const aggregate = (data, types) => {
  const result = {};

  data.forEach(item => {
    // Extract key & value from object
    // Note: use index 0 because each object in your example only has a single key
    const [key, value] = Object.entries(item)[0];
    // Check if result already contains this key
    if (result[key]) {
      if (result[key][value]) {
        // If value already exists, append one
        result[key][value]++;
      } else {
        // Create new key and instantiate with value 1
        result[key][value] = 1;
      }
    } else {
      // If result doesn't contain key, instantiate with value 1
      result[key] = { [value]: 1 };
    }
  });
  
  return result;
};

console.log(aggregate(initialData, types));

This will output the following (note I've added another {foo: true} to your initialData array for testing).

The output should also be an object (not array) so that each key directly relates to its corresponding value, as opposed to an Array which will simply place the value as the next item in the Array (without explicitly linking the two).

{
  foo: { true: 2, false: 1 },
  bar: { true: 1 },
  buzz: { false: 1 }
}


来源:https://stackoverflow.com/questions/63958748/aggregate-same-key-values-into-an-array-and-avoid-undefined

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!