Complementing the another answer, use map
/find
is a good solution, but depending on the size of the arrays, it may not climb as well.
That’s because, according to the language specification, the method find
always starts the search at the beginning of the array. So if the arrays start to get too big, it starts to become inefficient, since for each element of an array, you need to search the other, always starting from the beginning.
Of course, for small arrays and/or being processed a few times, the difference is irrelevant and imperceptible (after all, for few data, everything is fast). But anyway, one way to avoid these searches being done at all times is to create a temporary object that holds references to the original objects, using as key the common information (in this case, the id
).
With this, the "search" for the object becomes only one lookup simple on this object. Then, discard the keys and take only the values of it (which in this case, will be in an array, as desired). So:
const mergedScreensAllCompanies = [
{ id: 1, description: 'Cadastro de usuários' },
{ id: 2, description: 'Cadastro de filiais' }
];
const userScreens = [
{ id: 1, user_id: 1, screen_id: 1, foo: 'a' },
{ id: 2, user_id: 2, screen_id: 2, foo: 'b' }
];
// objeto temporário, mapeia cada id para o respectivo objeto
let merged = {};
for (let i = 0; i < mergedScreensAllCompanies.length; i++) {
merged[mergedScreensAllCompanies[i].id] = mergedScreensAllCompanies[i];
}
// atualiza os objetos com os dados do outro array
for (let i = 0; i < userScreens.length; i++) {
if (merged[userScreens[i].screen_id]) {
merged[userScreens[i].screen_id] = { ...merged[userScreens[i].screen_id], ...userScreens[i] };
}
}
// descarta as chaves, obtém o array com os objetos atualizados
merged = Object.values(merged);
console.log(merged);
Although it seems to "do more things" (after all, it has two for
), actually does less things. The map
traverse the array once, and for each element it does a search with find
, running through the other array. It’s okay that most of the time it doesn’t go through everything, since it stops as soon as it finds the element, but still, we will have many elements being traveled several times, which in the end ends up generating a lot more iterations (and as we will see below, this can make all the difference).
Already the above code only goes through each of the arrays once (and there is a loop at the end, within Object.values
). Although it seems that the code does more things, it’s actually doing less. The difference is that with map
and find
, these extra things are "hidden" within these functions.
But again reinforcement that, for a few small arrays, the difference will be tiny and probably you won’t even notice. In this case, it becomes more a matter of taste and "style": many prefer to program in a more "functional" way and think using map
(and his "brothers" filter
, reduce
, etc) the code becomes more expressive, etc. I just wanted to leave an alternative, in case the arrays start to increase a lot and this starts to be a problem (and it is always good to know other ways of doing, to "open the head" and not get stuck in a single way to solve the problems).
For the record, other alternatives are:
eliminate the use of spread to update the object:
let merged = {};
for (let i = 0; i < mergedScreensAllCompanies.length; i++) {
merged[mergedScreensAllCompanies[i].id] = mergedScreensAllCompanies[i];
}
for (let i = 0; i < userScreens.length; i++) {
if (merged[userScreens[i].screen_id]) {
merged[userScreens[i].screen_id].user_id = userScreens[i].user_id;
merged[userScreens[i].screen_id].screen_id = userScreens[i].screen_id;
merged[userScreens[i].screen_id].foo = userScreens[i].foo;
}
}
merged = Object.values(merged);
Use for...of
:
let merged = {};
for (const screen of mergedScreensAllCompanies) {
merged[screen.id] = screen;
}
for (const userScreen of userScreens) {
if (merged[userScreen.screen_id]) {
merged[userScreen.screen_id] = { ...merged[userScreen.screen_id], ...userScreen };
}
}
merged = Object.values(merged);
The first one is even faster (because it doesn’t cost you to create another object, which is what happens when you use the spread), but on the other hand, the code becomes more verbose (which may or may not be a problem, it goes to each one - it can be a problem if there are many properties to copy, for example). The second, although a little slower (but still faster than map
/find
), is a little more succinct - and in my opinion, more expressive - than the for
"traditional" (but again, this is very subjective).
Finally, here’s the test I did, and the fastest option was for
without spread (item 1 above), followed by for
normal (the first option I put), then for..of
, and finally map
/find
and Lodash (this was by far the slowest).
I also tested on Node with Benchmark.js and the result was the same:
sem spread x 2,412 ops/sec ±1.51% (87 runs sampled)
for normal x 671 ops/sec ±4.05% (75 runs sampled)
for of x 651 ops/sec ±2.78% (82 runs sampled)
map/find x 15.15 ops/sec ±3.30% (41 runs sampled)
lodash x 11.88 ops/sec ±5.90% (33 runs sampled)
Fastest is sem spread
What counts are ops/sec (transactions per second): the more, the better
Reinforcing once again that if you don’t have a performance problem (perhaps because they are small arrays and/or few runs), it won’t make much of a difference (depending on the case, for small arrays, the performance of map
can be equal or even better). Even because this is just a "random" test, what really counts is the test with real data, to know if this is really a problem...
The solution needs to be given with lodash or can only use native methods?
– Augusto Vasques
@Augustovasques can be with lodash or without, is that with lodash I believe the code gets smaller, so I tried with it
– veroneseComS