1
\$\begingroup\$

I have a code snippet which performs an SQL-style join on objects in two arrays based on a specific field (in the code below, .id).

let preprocessed_array = [
  {id: 1,category:'a'},
  {id: 2,category:'b'},
  {id: 3,category:'b'}
] 

let new_array = [
  {id: 4},
  {id: 1,category:'b'},
  {id: 5}
] 

 for(let i=0;i< preprocessed_array.length;i++)
 {
   for(let j=0;j<new_array.length;j++)
   {
     if( preprocessed_array[i].id===new_array[j].id)
     {
       new_array[j]=preprocessed_array[i]
     }
   }
 }

 console.log(new_array)

Although this snippet is straightforward, I don't like the imperative style. Is there a more declarative way to do this just with javascript / ECMA6 arrays?

\$\endgroup\$
2
  • \$\begingroup\$ Wouldn't a Join mean that when the id is already found, you get the all the categories for the shared id? Why is for example the item with id one only assigned with category a at the end? \$\endgroup\$
    – Icepickle
    Sep 14, 2017 at 7:07
  • \$\begingroup\$ Indeed, a joing would not be enough to get to the results I want. But it would be the core to a more declarative solution. \$\endgroup\$ Sep 14, 2017 at 7:24

2 Answers 2

2
\$\begingroup\$

Build an index and use it to map the second array:

const index = new Map(source.map(el => [el.id, el]));
const result = picks.map(el => (index.get(el.id) || el));

The larger the arrays, the more effective the index becomes.
If this code is a bottleneck, it can be rewritten to build the index on-the-go (memoization).

P.S. source stands for preprocessed_array and picks for new_array in the original code.

\$\endgroup\$
1
  • \$\begingroup\$ this answer is much better than mine +1 \$\endgroup\$ Sep 15, 2017 at 19:08
1
\$\begingroup\$

You can use map and find to do the same thing "declaratively", but in my opinion that's just a stupid buzzword and yours is more readable..

let preprocessed_array = [
  {id: 1,category:'a'},
  {id: 2,category:'b'},
  {id: 3,category:'b'}
] 

let new_array = [
  {id: 4},
  {id: 1,category:'b'},
  {id: 5}
] 

new_array = new_array.map(function(a) {
  var match = preprocessed_array.find(function(b) {
    return b.id === a.id;
  });
  return match === undefined ? a : match;
});

console.log(new_array)

\$\endgroup\$
8
  • \$\begingroup\$ You are right. Your solution is slightly more declarative, but at the same time considerably less readable. I was looking for a fully declarative style, as in sql. Now I realize, that the array objects don't provide that. So are there libraries comparable to python pandas to achieve what I want? \$\endgroup\$ Sep 14, 2017 at 7:22
  • 1
    \$\begingroup\$ @user1934212 - there are indeed. jSQL is an SQL implementation written in js by yours truly, but doesn't yet support joins, alaSQL is a more fully featured one. linq is basically the same thing without actaully being an SQL implementation.. \$\endgroup\$ Sep 14, 2017 at 7:27
  • \$\begingroup\$ Since I have a strong .NET background, I will look into the linq library first. Thanks for your help! \$\endgroup\$ Sep 14, 2017 at 7:37
  • \$\begingroup\$ I personally prefer the map/find variation, but agree that the benefit is somewhat lost with your current version. I would prefer function join(a, b) { return a.map(item => b.find(({id}) => id === item.id) || item); } \$\endgroup\$
    – Gerrit0
    Sep 14, 2017 at 15:05
  • \$\begingroup\$ @Gerrit0 I did that initially but the problem with that is if find returns a zero or null from the preprocessed array it will erroneously return the item from the other array. It should really check for undefined explicitly. \$\endgroup\$ Sep 14, 2017 at 15:11

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.