-2

I would like to efficiently replace an object in an array with another object based on a property of that object in JavaScript. Here is an example below:

const oldEmployees = [
{id: 1, name: 'Jon', age: 38}, 
{id: 2, name: 'Mike', age: 35},
{id: 3, name: 'Shawn', age: 40}
];

const newEmployee = {id: 2, name: 'Raj', age: 32};

I would like the output to be:

[
{id: 1, name: 'Jon', age: 38}, 
{id: 2, name: 'Raj', age: 32},
{id: 3, name: 'Shawn', age: 40}
]

What would be the most efficient method to do this?

9
  • On which property do you want to base this? Commented Sep 18, 2023 at 20:28
  • Do you want to modify the existing array, or do you want to construct a new array for the output? Commented Sep 18, 2023 at 20:29
  • And could you expand on your need for efficiency? This is a simple operation on small data. Commented Sep 18, 2023 at 20:31
  • I would like to base it on the id in the above example. Commented Sep 18, 2023 at 20:31
  • Maybe the word efficiency was used incorrectly. More just the best way to go about it Commented Sep 18, 2023 at 20:31

2 Answers 2

-1

Use Array::map() to get a copy of the array with the replaced value:

const oldEmployees = [
{id: 1, name: 'Jon', age: 38}, 
{id: 2, name: 'Mike', age: 35},
{id: 3, name: 'Shawn', age: 40}
];

const newEmployee = {id: 2, name: 'Raj', age: 32};

console.log(oldEmployees.map(old => newEmployee.id === old.id ? newEmployee : old));

EDIT

There spawned some discussion with TheMaster regarding that his solution with Map is more efficient.

There are several arguments from my side:

  • The OP asked to replace 1 array item at a time
  • For replacing several employees Map should be utilized with the new employees, no sense to map the old employees
  • Initializing a map is expensive task so if data is small Array::find() could work faster (in our example it works faster than Map on the old employees)
  • Benchmark something under 10ms could make not much sense, since often performance precision is limited in browsers due security reason
  • Using iterators also expensive
Cycles: 50000 / Chrome/117
--------------------------------------------------------------------
10 employees
    Alexander Map             313/min  1.0x  332  381  372  319  313
    Alexander Array::find()   432/min  1.4x  433  452  432  463  474
    TheMaster                 799/min  2.6x  932  799  832  822  877
1 employee
    Alexander                 123/min  1.0x  123  148  127  124  128
    TheMaster                 777/min  6.3x  838  831  777  823  847
--------------------------------------------------------------------
https://github.com/silentmantra/benchmark
Cycles: 50000 / Firefox/117
--------------------------------------------------------------------------
10 employees
    Alexander Map              877/min  1.0x   891   877   949   935   951
    Alexander Array::find()   1494/min  1.7x  2727  1494  2544  2495  1528
    TheMaster                 3603/min  4.1x  3626  3611  3687  3603  3665
1 employee
    Alexander                  474/min  1.0x   474   612   641   605   592
    TheMaster                 3607/min  7.6x  3607  3757  3689  3711  3731
--------------------------------------------------------------------------
https://github.com/silentmantra/benchmark

<script benchmark="50000">

const i = 1000,//mock 1000 old employees
  j = 10;//mock 10 new employees
const oldEmployees = new Array(i).fill(0).map((_, i) => ({
  id: ++i,
  name: 'Jon',
  age: Math.floor(Math.random() * 100),
}));

const newEmployeesMap = new Map();
while(true){
  const id = Math.floor(Math.random() * i);
  newEmployeesMap.set(id, {
    id,
    name: 'Raj',
    age: Math.floor(Math.random() * 100),
  });
  if(newEmployeesMap.size === j) break;
}
const newEmployees = [...newEmployeesMap.values()];

const newEmployee = structuredClone(oldEmployees[0]);
 
// @group 10 employees

// @benchmark TheMaster

const oldEmployeesMap = oldEmployees.reduce(
  (a, c) => a.set(c.id, c),
  new Map()
);
newEmployees.forEach((newEmployee) => {
  oldEmployeesMap.set(newEmployee.id, newEmployee);
});
[...oldEmployeesMap.values()];

// @benchmark Alexander Map

const map = new Map;
newEmployees.forEach(employee => map.set(employee.id, employee));
oldEmployees.map((old, newEmployee) => (newEmployee = map.get(old.id)) ? newEmployee : old);

// @benchmark Alexander Array::find()
oldEmployees.map((old, newEmployee) => (newEmployee = newEmployees.find(e => e.id === old.id)) ? newEmployee : old);

// @group 1 employee

// @benchmark TheMaster
{
const oldEmployeesMap = oldEmployees.reduce(
  (a, c) => a.set(c.id, c),
  new Map()
);
oldEmployeesMap.set(newEmployee.id, newEmployee);
[...oldEmployeesMap.values()];
}
// @benchmark Alexander

oldEmployees.map(old => (newEmployee.id === old.id) ? newEmployee : old);




</script>
<script src="https://cdn.jsdelivr.net/gh/silentmantra/benchmark/loader.js"></script>

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

4 Comments

Hmm this returns back a boolean
@navin1551 do you need replace the original array or get a copy with the replaced value? if original you don't need to return it, since you already have the original array
Hi Alexander, I need a copy with replaced value
@navin1551 updated my answer, could be interesting
-1

If ids are unique, a better way is to store them in a Map or a object with ids as key.

/*<ignore>*/console.config({maximize:true,timeStamps:false,autoScroll:false});/*</ignore>*/ 
const oldEmployees = [
  { id: 1, name: 'Jon', age: 38 },
  { id: 2, name: 'Mike', age: 35 },
  { id: 3, name: 'Shawn', age: 40 },
];

const newEmployee = { id: 2, name: 'Raj', age: 32 };
const oldEmployeesMap = oldEmployees.reduce(
  (a, c) => a.set(c.id, c),
  new Map()
);
oldEmployeesMap.set(newEmployee.id, newEmployee);
console.log([...oldEmployeesMap.values()]);
<!-- https://meta.stackoverflow.com/a/375985/ -->    <script src="https://gh-canon.github.io/stack-snippet-console/console.min.js"></script>

Performance analysis for 1000 old employees and adding 10 new employees

 
const { performance: p } = window;
const i = 1000,//mock 1000 old employees
  j = 10;//mock 10 new employees
const oldEmployees = new Array(i).fill(0).map((_, i) => ({
  id: ++i,
  name: 'Jon',
  age: Math.floor(Math.random() * 100),
}));

const newEmployees = new Array(j).fill(0).map((_, i) => ({
  id: Math.floor(Math.random() * i),
  name: 'Raj',
  age: Math.floor(Math.random() * 100),
}));

//my method
p.mark('A');
const oldEmployeesMap = oldEmployees.reduce(
  (a, c) => a.set(c.id, c),
  new Map()
);
newEmployees.forEach((newEmployee) => {
  oldEmployeesMap.set(newEmployee.id, newEmployee);
});
const out1 = [...oldEmployeesMap.values()];
p.mark('B');

//Alex method
p.mark('C');
const out2 = newEmployees.reduce(
  (a, newEmployee) =>
    a.map((old) => (newEmployee.id === old.id ? newEmployee : old)),
  oldEmployees
);
p.mark('D');
console.log({ out1, out2 });
atob = p.measure('TheM', 'A', 'B');
ctod = p.measure('Alex', 'C', 'D');
console.log({ atob, ctod });
<!-- https://meta.stackoverflow.com/a/375985/ -->    <script src="https://gh-canon.github.io/stack-snippet-console/console.min.js"></script>

5 Comments

it will map all elements which is wasted
@AlexanderNenashev You mean memory? Won't only a reference to the object and not a actual copy be stored in the Map? Regardless, I think Map should be the default here. I think array of objects can be discarded.
totally wasted map filling, sorry. just look, iterate the array and you can update the item without any map already since you get the item while looping. just Array::map() and that's it. moreover you use Map::values() - that an intermediate iterator, it's very slow...
@AlexanderNenashev Added a performance analysis using Mocks. Overall, Map is a better way to store this data as it scales better.
added benchmarks and arguments to my answer

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.