Reputation: 3101
Let's say I have two arrays:
let A = [a,b,c,d]
let B = [c,d,e]
where each of the letters is an object that has several properties, one of which is an id
which is unique in my domain. The merged result would then be
[a,b,c,d,e]
where [c,d]
originate from B.
Using an elegant approach, how would I merge these two arrays in a way that any element in B
would overwrite any existing one in A
and all others remain untouched. So it's a union with B
elements taking precedence in case of a conflict.
I have two ideas (using ES6 and lodash):
//remove same elements first, then join arrays
let ids = new Set(B.map(e => e.id));
let newState = _.reject(A, constraint => ids.has(constraint.id));
return newState.concat(B);
//convert both to hashmap, join, then take values
let B_map = _.keyBy(B, 'id');
let A_map = _.keyBy(A, 'id');
return {...A_map, ...B_map}.values();
Is there a shorter / more concise / more readable version? Maybe one without external dependencies? I'm essentially looking for something like
Where equality between any element is defined by the id
property (or a comparator function in v2).
Upvotes: 4
Views: 1623
Reputation: 328
Here is a solution that follows more of the redux styled approach...
// imported redux actions (these are simply strings)
import {
SOME_DEFINED_ACTION_CASE
} from '../actions/someActions';
const initialState = {
reduxList: []
}
// reducer function
export default function someReducer(state = initialState, action) {
switch (action.type) {
case SOME_DEFINED_ACTION_CASE: {
let ids = new Set(action.payload.map(e => e.id));
let newState = state.reduxList.filter(a => !ids.has(a.id)).concat(action.payload);
return Object.assign({}, state, {
reduxList: newState
});
}
}
}
Upvotes: 0
Reputation: 191976
Use lodash's _.differenceBy(A, B)
to remove all items that exist in B
from A
, and then combine with B
items. This will preserve the order of A items before B items.
const A = [{"id":"a","arr":"A"},{"id":"b","arr":"A"},{"id":"c","arr":"A"},{"id":"d","arr":"A"}];
const B = [{"id":"c","arr":"B"},{"id":"d","arr":"B"}];
const result = [..._.differenceBy(A, B, 'id'), ...B];
console.log(result);
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js"></script>
Upvotes: 0
Reputation: 31682
Since you are already using lodash, you can use _.unionBy
which merges the arrays using a criterion by which uniqueness is computed:
let result = _.unionBy(B, A, "id");
Start with B
before A
, so that in case of duplicates, B
values are taken instead of A
ones.
Example:
let A = [
{ id: "a", arr: "A" },
{ id: "b", arr: "A" },
{ id: "c", arr: "A" },
{ id: "d", arr: "A" }
];
let B = [
{ id: "b", arr: "B" },
{ id: "d", arr: "B" }
];
let result = _.unionBy(B, A, "id");
console.log(result);
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js"></script>
Note: This messes up the order of the items, the duplicates come first, then the rest.
Upvotes: 3
Reputation: 12990
Without external dependencies, you can use filter
to extract elements from A
that don't have ids in B
and concat
that with B
:
const A = [{id: 1, name: 'x'}, {id: 2, name: 'y'}, {id: 3, name: 'z'}];
const B = [{id: 2, name: 'hello'}];
let ids = new Set(B.map(e => e.id));
let newState = A.filter(a => !ids.has(a.id)).concat(B);
console.log(newState);
Upvotes: 4