1

I am filtering an array for every value the is the same as the key provided. Im certain there is a one shot reduce method someone better than me can condense this down to, but alas filter map filter map.

So I submit to an array an object that says [{k:v}, {k2:otherv}] and find all the elements that are not that and then return those object keys.

The code below returns:

  [
    {k: v1},
    {k: v2},
    {k: v3}
  ]
   [
    {k2: v4},
    {k2: v5},
    {k2: v6}
   ]
 ]

And obviously to map over it correctly id like it to look like [{k:v1, k2:v4}, {k:v2,k2:v5}, {k:v3, k2:v6}]

I've tried several examples from:

How can I merge two object arrays by index in JavaScript?

and

Combine same-index objects of two arrays

but short of writing every object key possible into each of these, none of what I've tried works.

const blogkeys = cont
   .filter((k) => k.type === "blogs")
   .map(({ key, content }) => {
    if (key.includes(".")) {
     let objkey = key.substr(key.indexOf(".") + 1, key.length);

     let obj = { [objkey]: content };

     let arrName = key.substr(0, key.indexOf("."));

     let pushedObj = { [arrName]: [{ ...obj }] };

     return pushedObj;
    } else {
     let obj = { [key]: content };
     return obj;
    }
   });

this creates the keys we are looking for in the parent array


  const everyOtherBlog = blogkeys.map((blogkey) => {
   const returned = blogs
    .filter(
     (f) =>
      !JSON.stringify(f).includes(
       JSON.stringify(blogkey).replace("{", "").replace("}", "")
      )
    )
    .map(({ _doc }) => {
     let obj = {};

     Object.keys(_doc)
      .filter((f) => f === Object.keys(blogkey)[0])
      .map((a) => {
       obj = Object.assign(obj, { [a]: _doc[a] });

       return obj;
      });

     return obj[0];
    });

   return returned;
  });

This returns the data set you see.

Here is what blogkeys looks like :

[0] [
[0]   { title: ' stuff' },
[0]   {
[0]     p1: ' stuff '
[0]   }
[0] ]

which is made from

  {
[0]     _id: '606a4049d4812928986afc10',
[0]     contentId: '60443ced4e233336f8306b5b',
[0]     type: 'blogs',
[0]     key: 'title',
[0]     content: 'stuff'
[0]   },

and a blog looks something like

{
 title: '',
 p1:''
}

Everyone here provided alot of cool stuff that ended up not helping me because of how i was feeding the data in, when i fixed that i realized i didnt need any fancy zips just good old object.fromEntries. Ill leave this up though cause some of these are very interesting.

Any help would be great

Mickey Gray
  • 440
  • 2
  • 11
  • Could you add an example specifically of an input array and the exact output value you want? This is interesting but I'm not feeling like I'm getting it quite yet. – Ryan Apr 06 '21 at 00:36
  • 1
    You can use `{...obj1, ...obj2}` to combine two objects without knowing the keys. – Barmar Apr 06 '21 at 00:42

4 Answers4

4

two arrays

You can use map to implement zip and then map again to perform your tranform. This solution works for only two input arrays -

const zip = (a, b) =>
  a.map((x, i) => [x, b[i]])

const foo =
  [{a:1},{a:2},{a:3}]
  
const bar = 
  [{b:4},{b:5},{b:6}]
  
const result = 
  zip(foo, bar).map(o => Object.assign({}, ...o))

console.log(JSON.stringify(result))
[{"a":1,"b":4},{"a":2,"b":5},{"a":3,"b":6}]

many arrays, any size

Above, you will run into strange output if a or b is longer than the other. I think a better approach is to use generators though. It works for any number of input arrays of any size -

const iter = t =>
  t?.[Symbol.iterator]()

function* zip (...its)
{ let r, g = its.map(iter)
  while (true)
  { r = g.map(it => it.next())
    if (r.some(v => v.done)) return
    yield r.map(v => v.value)
  }
}

const foo =
  [{a:1},{a:2},{a:3}]
  
const bar = 
  [{b:4},{b:5},{b:6}]

const qux =
  [{c:7},{c:8}]

const result =
  Array.from(zip(foo, bar, qux), o => Object.assign({}, ...o))

console.log(JSON.stringify(result))

This does the zipping and transformation in a single pass, without the need map afterward -

[{"a":1,"b":4,"c":7},{"a":2,"b":5,"c":8}]

without generators

If you don't like generators but still want the flexibility offered by the solution above, we can write a simple zip2 -

const zip2 = ([a, ...nexta], [b, ...nextb]) =>
  a == null || b == null
    ? []                                   // empty
    : [ [a, b], ...zip2(nexta, nextb) ]    // recur

And then the variadiac zip which accepts any amount of arrays of any size -

const zip = (t, ...more) =>
  more.length
    ? zip2(t, zip(...more)).map(([a, b]) => [a, ...b]) // flatten
    : t.map(a => [a])                                  // singleton

Now we can zip any amount of arrays -

const foo =
  [{a:1},{a:2},{a:3}]

const bar =
  [{b:4},{b:5},{b:6}]

const qux =
  [{c:7},{c:8}]

const result =
  zip(foo, bar, qux).map(o => Object.assign({}, ...o))

console.log(JSON.stringify(result))

Expand the snippet below to verify the result in your own browser -

const zip2 = ([a, ...nexta], [b, ...nextb]) =>
  a == null || b == null
    ? []
    : [ [a, b], ...zip2(nexta, nextb) ]

const zip = (t, ...more) =>
  more.length
    ? Array.from(zip2(t, zip(...more)), ([a, b]) => [a, ...b])
    : t.map(a => [a])

const foo =
  [{a:1},{a:2},{a:3}]

const bar =
  [{b:4},{b:5},{b:6}]

const qux =
  [{c:7},{c:8}]

const result =
  zip(foo, bar, qux).map(o => Object.assign({}, ...o))

console.log(JSON.stringify(result))
[{"a":1,"b":4,"c":7},{"a":2,"b":5,"c":8}]
Mulan
  • 129,518
  • 31
  • 228
  • 259
  • ...thank you i think you caused me physical pain looking at that... but thank you. – Mickey Gray Apr 06 '21 at 00:58
  • not sure what you mean. i updated the first `zip` to use `map` instead of `reduce` as it makes more sense. still the generator approach is recommended – Mulan Apr 06 '21 at 00:59
  • just a joke the iterator looks complicated is all – Mickey Gray Apr 06 '21 at 01:00
  • i tried the generator and the issue i keep running into is returned is 1 array. so i split the array and passed it into the generator and got... literally the console log of ``` [ {k: v1}, {k: v2}, {k: v3} ] [ {k2: v4}, {k2: v5}, {k2: v6} ] ]``` – Mickey Gray Apr 06 '21 at 02:39
  • Every code in this answer is runnable in your browser and produces the output as described. If you are getting a different result it's because your input does not match. – Mulan Apr 06 '21 at 02:42
1

Here's a fairly straightforward solution using .reduce() that will accept any number of arrays of various lengths.

const
  foo = [{ a: 1 }, { a: 2 }, { a: 3 }],
  bar = [{ b: 4 }, { b: 5 }, { b: 6 }],
  qux = [{ c: 7 }, { c: 8 }],

  zip = (...arrs) =>
    arrs.reduce((a, arr) => {
      arr.forEach((x, i) => Object.assign((a[i] = a[i] || {}), x));
      // or using logical nullish assignment
      // arr.forEach((x, i) => Object.assign((a[i] ??= {}), x));
      return a;
    }, []);

  result = zip(foo, bar, qux);

console.log(JSON.stringify(result))
// [{ a: 1, b: 4, c: 7 }, { a: 2, b: 5, c: 8 }, { a: 3, b: 6 }]
pilchard
  • 12,414
  • 5
  • 11
  • 23
  • what is ??= because vscode doesnt like it – Mickey Gray Apr 06 '21 at 02:15
  • [nullish coalescing assignment](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_nullish_assignment) – Mulan Apr 06 '21 at 02:18
  • As Thank You noted it's the [Logical nullish assignment (??=)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_nullish_assignment) operator. I've edited the snippet to use a [Logical OR (||)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_OR) short circuit. – pilchard Apr 06 '21 at 10:12
  • vscode accepted what mdn sugested of a[i] ?? (a[i] = {}) – Mickey Gray Apr 06 '21 at 14:32
  • That's great, and yes, assuming you're using node, v14 supports nullish coalescing(??) while v15 adds support for logical nullish assignment(??=) – pilchard Apr 06 '21 at 15:04
1

You can try this too with map and reduce, this is just another alternative

function merge(...args) {
    // finding highest length Array to not skip missing elements from other arrays
    // for skipping missing elements use "acc.length < ele.length"
    const maxArray = args.reduce((acc, ele) => acc.length > ele.length ? acc : ele);

    //Iterating over highest length array
    return maxArray.map((ele, index) => 

    //merging all the instances in arrays with same index
    args.reduce((acc, group) => Object.assign(acc, group[index]), {})
    );
}

merge([ {k: 'v1'}, {k: 'v2'}, {k: 'v3'} ], [ {k2: 'v4'}, {k2: 'v5'}, {k2: 'v6'} ]);
// [{"k":"v1","k2":"v4"},{"k":"v2","k2":"v5"},{"k":"v3","k2":"v6"}]

merge([ {k: 'v1'}, {k: 'v2'}], [ {k2: 'v4'}, {k2: 'v5'}, {k2: 'v6'} ])
// [{"k":"v1","k2":"v4"},{"k":"v2","k2":"v5"},{"k2":"v6"}]

merge([ {k: 'v1'}, {k: 'v2'}, {k: 'v3'} ], [ {k2: 'v4'}, {k2: 'v5'}])
//[{"k":"v1","k2":"v4"},{"k":"v2","k2":"v5"},{"k":"v3"}]
Ravikumar
  • 2,085
  • 12
  • 17
0

I wanted to share what I ended up doing cause it worked well with both nested arrays and simple object arrays and is formatted for getting info straight from an await from mongo db, sadly its just a filter map tho.

blog obj is { title:"stuff", p1:"stuff" }

and the return is the zipped array.

  const everyOtherBlog = Object.values(blogObj).map((val) => {
   const b = blogs
    .filter((f) => !JSON.stringify(f).includes(val))
    .map(({ _doc }) => {
     const keys = Object.keys(_doc).filter((k) =>
      Object.keys(blogObj).includes(k)
     );

     const entryObj = Object.fromEntries(keys.map((k) => [k, _doc[k]]));

     return entryObj;
    });

   return b[0];
  });
Mickey Gray
  • 440
  • 2
  • 11