0

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;
2
  • Is this snippet really part of the desired transformation result ?.. foo: {true: Array(1), false: Array(1)} ... If yes, what is the cryptic Array(1) supposed to be? If not, can you please just provide the source data and the result that is expected to be aggregated from the former? Commented Sep 18, 2020 at 19:57
  • Have a look at how Array.prototype.reduce works. It might be the right method to build your approach upon. Commented Sep 18, 2020 at 20:07

4 Answers 4

0

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);

Sign up to request clarification or add additional context in comments.

2 Comments

thank you.. so instead of undefined: Array(4), I just get an empty object. Can you think of a way to not return this item whatsoever?
There you go; basically, check if the object you've build has any properties. If not, don't add it.
0

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; }

1 Comment

@ksenia, what exactly is unclear about the above solution? Or in other words ... what makes a for loop which encloses a reduce process and introduces an additional if-property check and push process at the end of each iteration cycle easier to grasp on?
-1

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);

Comments

-1

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 }
}

1 Comment

thank you, that's not exactly what I wanted to do though. I was looking to return the original object if it contains a key with true in true object and if it contains a key with false, false object. That's what Array(1) is about

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.