4

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).

3
  • the wanted result is [a, b] ... or? Commented Dec 12, 2018 at 17:16
  • or are you asking for the result to be [a,b,c,d] where c,d are from B? Commented Dec 12, 2018 at 17:29
  • I improved the question a bit to clarify the result. Commented Dec 13, 2018 at 14:47

4 Answers 4

4

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);

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

2 Comments

I chose this answer as it is using only 2 instead of 1 line with vanilla javascript. However, Ibrahims is the most appropriate when considering that I use lodash. Thanks!
I posted another solution that follows the more redux styled approach.
3

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.

1 Comment

This one-liner is actually what I discovered today, a day later. I guess there are going to be two right answers. This one and the one by @slider who offers a vanilla solution.
0

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>

2 Comments

Good, but the unionBy seems to be a tad more intuitive, as it creates a union with duplicates removed by an iteratee.
The order of the Union is the problem - B before A.
0

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
            });
        }
    }
}

Comments

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.