pascalwhoop
pascalwhoop

Reputation: 3101

Merge two arrays, avoiding duplicates using immutable pattern in redux

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

Answers (4)

Tristan Heilman
Tristan Heilman

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

Ori Drori
Ori Drori

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

ibrahim mahrir
ibrahim mahrir

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

slider
slider

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

Related Questions