How to remove a particular elements from an array in javascript? [duplicate] - javascript
This question already has answers here:
How to get the difference between two arrays in JavaScript?
(84 answers)
Closed 5 months ago.
I am looking for an efficient way to remove all elements from a javascript array if they are present in another array.
// If I have this array:
var myArray = ['a', 'b', 'c', 'd', 'e', 'f', 'g'];
// and this one:
var toRemove = ['b', 'c', 'g'];
I want to operate on myArray to leave it in this state: ['a', 'd', 'e', 'f']
With jQuery, I'm using grep() and inArray(), which works well:
myArray = $.grep(myArray, function(value) {
return $.inArray(value, toRemove) < 0;
});
Is there a pure javascript way to do this without looping and splicing?
Use the Array.filter() method:
myArray = myArray.filter( function( el ) {
return toRemove.indexOf( el ) < 0;
} );
Small improvement, as browser support for Array.includes() has increased:
myArray = myArray.filter( function( el ) {
return !toRemove.includes( el );
} );
Next adaptation using arrow functions:
myArray = myArray.filter( ( el ) => !toRemove.includes( el ) );
ECMAScript 6 sets can permit faster computing of the elements of one array that aren't in the other:
const myArray = ['a', 'b', 'c', 'd', 'e', 'f', 'g'];
const toRemove = new Set(['b', 'c', 'g']);
const difference = myArray.filter( x => !toRemove.has(x) );
console.log(difference); // ["a", "d", "e", "f"]
Since the lookup complexity for the V8 engine browsers use these days is O(1), the time complexity of the whole algorithm is O(n).
var myArray = [
{name: 'deepak', place: 'bangalore'},
{name: 'chirag', place: 'bangalore'},
{name: 'alok', place: 'berhampur'},
{name: 'chandan', place: 'mumbai'}
];
var toRemove = [
{name: 'deepak', place: 'bangalore'},
{name: 'alok', place: 'berhampur'}
];
myArray = myArray.filter(ar => !toRemove.find(rm => (rm.name === ar.name && ar.place === rm.place) ))
The filter method should do the trick:
const myArray = ['a', 'b', 'c', 'd', 'e', 'f', 'g'];
const toRemove = ['b', 'c', 'g'];
// ES5 syntax
const filteredArray = myArray.filter(function(x) {
return toRemove.indexOf(x) < 0;
});
If your toRemove array is large, this sort of lookup pattern can be inefficient. It would be more performant to create a map so that lookups are O(1) rather than O(n).
const toRemoveMap = toRemove.reduce(
function(memo, item) {
memo[item] = memo[item] || true;
return memo;
},
{} // initialize an empty object
);
const filteredArray = myArray.filter(function (x) {
return toRemoveMap[x];
});
// or, if you want to use ES6-style arrow syntax:
const toRemoveMap = toRemove.reduce((memo, item) => ({
...memo,
[item]: true
}), {});
const filteredArray = myArray.filter(x => toRemoveMap[x]);
If you are using an array of objects. Then the below code should do the magic, where an object property will be the criteria to remove duplicate items.
In the below example, duplicates have been removed comparing name of each item.
Try this example. http://jsfiddle.net/deepak7641/zLj133rh/
var myArray = [
{name: 'deepak', place: 'bangalore'},
{name: 'chirag', place: 'bangalore'},
{name: 'alok', place: 'berhampur'},
{name: 'chandan', place: 'mumbai'}
];
var toRemove = [
{name: 'deepak', place: 'bangalore'},
{name: 'alok', place: 'berhampur'}
];
for( var i=myArray.length - 1; i>=0; i--){
for( var j=0; j<toRemove.length; j++){
if(myArray[i] && (myArray[i].name === toRemove[j].name)){
myArray.splice(i, 1);
}
}
}
alert(JSON.stringify(myArray));
Lodash has an utility function for this as well:
https://lodash.com/docs#difference
How about the simplest possible:
var myArray = ['a', 'b', 'c', 'd', 'e', 'f', 'g'];
var toRemove = ['b', 'c', 'g'];
var myArray = myArray.filter((item) => !toRemove.includes(item));
console.log(myArray)
I just implemented as:
Array.prototype.exclude = function(list){
return this.filter(function(el){return list.indexOf(el)<0;})
}
Use as:
myArray.exclude(toRemove);
You can use _.differenceBy from lodash
const myArray = [
{name: 'deepak', place: 'bangalore'},
{name: 'chirag', place: 'bangalore'},
{name: 'alok', place: 'berhampur'},
{name: 'chandan', place: 'mumbai'}
];
const toRemove = [
{name: 'deepak', place: 'bangalore'},
{name: 'alok', place: 'berhampur'}
];
const sorted = _.differenceBy(myArray, toRemove, 'name');
Example code here: CodePen
If you cannot use new ES5 stuff such filter I think you're stuck with two loops:
for( var i =myArray.length - 1; i>=0; i--){
for( var j=0; j<toRemove.length; j++){
if(myArray[i] === toRemove[j]){
myArray.splice(i, 1);
}
}
}
Now in one-liner flavor:
console.log(['a', 'b', 'c', 'd', 'e', 'f', 'g'].filter(x => !~['b', 'c', 'g'].indexOf(x)))
Might not work on old browsers.
This is pretty late but adding this to explain what #mojtaba roohi has answered. The first block of code will not work as each array is having a different object, i.e. df[0] != nfl[2]. Both objects look similar but are altogether different, which is not the case when we use primitive types like numbers.
let df = [ {'name': 'C' },{'name': 'D' }]
let nfl = [ {'name': 'A' },{'name': 'B' },{'name': 'C' },{'name': 'D' }]
let res = nfl.filter(x => df.indexOf(x)<0)
console.log(res)
Here is the working code:
let df = [{'name': 'C' },{'name': 'D' }]
let nfl = [ {'name': 'A' },{'name': 'B' },{'name': 'C' },{'name': 'D' }];
let res = nfl.filter((o1) => !df.some((o2) => o1.name === o2.name));
console.log(res)
If you're using Typescript and want to match on a single property value, this should work based on Craciun Ciprian's answer above.
You could also make this more generic by allowing non-object matching and / or multi-property value matching.
/**
*
* #param arr1 The initial array
* #param arr2 The array to remove
* #param propertyName the key of the object to match on
*/
function differenceByPropVal<T>(arr1: T[], arr2: T[], propertyName: string): T[] {
return arr1.filter(
(a: T): boolean =>
!arr2.find((b: T): boolean => b[propertyName] === a[propertyName])
);
}
Proper way to remove all elements contained in another array is to make source array same object by remove only elements:
Array.prototype.removeContained = function(array) {
var i, results;
i = this.length;
results = [];
while (i--) {
if (array.indexOf(this[i]) !== -1) {
results.push(this.splice(i, 1));
}
}
return results;
};
Or CoffeeScript equivalent:
Array.prototype.removeContained = (array) ->
i = #length
#splice i, 1 while i-- when array.indexOf(#[i]) isnt -1
Testing inside chrome dev tools:
19:33:04.447 a=1
19:33:06.354 b=2
19:33:07.615 c=3
19:33:09.981 arr = [a,b,c]
19:33:16.460 arr1 = arr
19:33:20.317 arr1 === arr
19:33:20.331 true
19:33:43.592 arr.removeContained([a,c])
19:33:52.433 arr === arr1
19:33:52.438 true
Using Angular framework is the best way to keep pointer to source object when you update collections without large amount of watchers and reloads.
I build the logic without using any built-in methods, please let me know any optimization or modifications.
I tested in JS editor it is working fine.
var myArray = [
{name: 'deepak', place: 'bangalore'},
{name: 'alok', place: 'berhampur'},
{name: 'chirag', place: 'bangalore'},
{name: 'chandan', place: 'mumbai'},
];
var toRemove = [
{name: 'chirag', place: 'bangalore'},
{name: 'deepak', place: 'bangalore'},
/*{name: 'chandan', place: 'mumbai'},*/
/*{name: 'alok', place: 'berhampur'},*/
];
var tempArr = [];
for( var i=0 ; i < myArray.length; i++){
for( var j=0; j<toRemove.length; j++){
var toRemoveObj = toRemove[j];
if(myArray[i] && (myArray[i].name === toRemove[j].name)) {
break;
}else if(myArray[i] && (myArray[i].name !== toRemove[j].name)){
var fnd = isExists(tempArr,myArray[i]);
if(!fnd){
var idx = getIdex(toRemove,myArray[i])
if (idx === -1){
tempArr.push(myArray[i]);
}
}
}
}
}
function isExists(source,item){
var isFound = false;
for( var i=0 ; i < source.length; i++){
var obj = source[i];
if(item && obj && obj.name === item.name){
isFound = true;
break;
}
}
return isFound;
}
function getIdex(toRemove,item){
var idex = -1;
for( var i=0 ; i < toRemove.length; i++){
var rObj =toRemove[i];
if(rObj && item && rObj.name === item.name){
idex=i;
break;
}
}
return idex;
}
//Using the new ES6 Syntax
console.log(["a", "b", "c", "d", "e", "f", "g"].filter(el => !["b", "c", "g"].includes(el)));
// OR
// Main array
let myArray = ["a", "b", "c", "d", "e", "f", "g"];
// Array to remove
const toRemove = ["b", "c", "g"];
const diff = () => (myArray = myArray.filter((el) => !toRemove.includes(el)));
console.log(diff()); // [ 'a', 'd', 'e', 'f' ]
// OR
const diff2 = () => {
return myArray = myArray.filter((el) => !toRemove.includes(el));
};
console.log(diff2()); // [ 'a', 'd', 'e', 'f' ]
A High performance and immutable solution
Javascript
const excludeFromArr = (arr, exclude) => {
const excludeMap = exclude.reduce((all, item) => ({ ...all, [item]: true }), {});
return arr.filter((item) => !excludeMap?.[item]);
};
Typescript:
const excludeFromArr = (arr: string[], exclude: string[]): string[] => {
const excludeMap = exclude.reduce<Record<string, boolean>>((all, item) => ({ ...all, [item]: true }), {});
return arr.filter((item) => !excludeMap?.[item]);
};
Related
How to get the intersection of two sets while recognizing equal set values/items not only by reference but by their equal structures and entries too?
I have two deal with two Set instances. const set1 = new Set([ { name: 'a' }, { name: 'b', lastname: 'bb' }, { name: 'c' }, { name: 'd' }, ]); const set2 = new Set([ { name: 'b' }, { name: 'd' }, ]); Any object within a set will feature several and also distinct keys and values. The goal is to find structurally equal objects (same keys and values) in both sets, which is ... The intersection of equal data items in/of set1 and set2. In the following example the expected result is [ { name: 'd' } ] ... console.log([...set1].filter(item => set2.has(item))); ... but it logs an empty array / [] instead. An object features more than 20 keys so one has to compare them one by one, which can not be done in a hard coded way. How could one achieve a generic approach for an intersection of two lists of structurally equal data items?
You can do something like this: const set1 = new Set([ {name: 'a'}, {name: 'b', lastname: 'bb'}, {name: 'c'}, {name: 'd'} ]); const set2 = new Set([ {name: 'b'}, {name: 'd'} ]); set1.forEach((value) => { if (![...set2].some((o) => Object.entries(o).every(([k, v], _, arr) => (Object.keys(value).length === arr.length && value[k] === v)))) { set1.delete(value); } }) console.log([...set1]); What this does, is to iterate through set1 and if the item at the current iteration is not the same as any item in set2 (![...set2].some(..)), it is deleted. The items are considered the same if they have the same number of keys and if the values at the same key are strictly equal. This only works if the values of the objects in the sets are primitives, if they are not, you'll have to change value[k] === v to an appropriate comparison.
One could write a generic solution which compares pure, thus JSON conform, data structures regardless of any object's nesting depth/level and (creation time) key order. Such a function would be self recursive for Array item (order matters) and Object property (key order does not matter) comparison. Otherwise values are compared strictly. function isDeepDataStructureEquality(a, b) { let isEqual = Object.is(a, b); if (!isEqual) { if (Array.isArray(a) && Array.isArray(b)) { isEqual = (a.length === b.length) && a.every( (item, idx) => isDeepDataStructureEquality(item, b[idx]) ); } else if ( a && b && (typeof a === 'object') && (typeof b === 'object') ) { const aKeys = Object.keys(a); const bKeys = Object.keys(b); isEqual = (aKeys.length === bKeys.length) && aKeys.every( (key, idx) => isDeepDataStructureEquality(a[key], b[key]) ); } } return isEqual; } const objA = { // `objA` equals `objB`. name: 'foo', value: 1, obj: { z: 'z', y: 'y', a: { name: 'bar', value: 2, obj: { x: 'x', w: 'w', b: 'b', }, arr: ['3', 4, 'W', 'X', { name: 'baz', value: 3, obj: { k: 'k', i: 'i', c: 'c', }, arr: ['5', 6, 'B', 'A'], }], }, }, arr: ['Z', 'Y', 1, '2'], }; const objB = { // `objB` equals `objA`. arr: ['Z', 'Y', 1, '2'], obj: { z: 'z', y: 'y', a: { obj: { x: 'x', w: 'w', b: 'b', }, arr: ['3', 4, 'W', 'X', { obj: { k: 'k', i: 'i', c: 'c', }, name: 'baz', value: 3, arr: ['5', 6, 'B', 'A'], }], name: 'bar', value: 2, }, }, name: 'foo', value: 1, }; const objC = { // `objC` equals neither `objA` nor `objB`. arr: ['Z', 'Y', 1, '2'], obj: { z: 'z', y: 'y', a: { obj: { x: 'x', w: 'w', b: 'b', }, arr: ['3', 4, 'W', 'X', { obj: { k: 'k', i: 'i', c: 'C', // the single difference to `objA` and `objB`. }, name: 'baz', value: 3, arr: ['5', 6, 'B', 'A'], }], name: 'bar', value: 2, }, }, name: 'foo', value: 1, }; console.log( 'isDeepDataStructureEquality(objA, objB) ?..', isDeepDataStructureEquality(objA, objB) ); console.log( 'isDeepDataStructureEquality(objA, objC) ?..', isDeepDataStructureEquality(objA, objC) ); console.log( 'isDeepDataStructureEquality(objB, objC) ?..', isDeepDataStructureEquality(objB, objC) ); console.log( 'isDeepDataStructureEquality(objB, objA) ?..', isDeepDataStructureEquality(objB, objA) ); .as-console-wrapper { min-height: 100%!important; top: 0; } Based on the above implementation of isDeepDataStructureEquality one can solve the OP's task, that actually looks for the intersection of two list structures, by additionally providing a getIntersectionOfDeeplyEqualDataStructures functionality ... function getIntersectionOfDeeplyEqualDataStructures(a, b) { return [...(a ?? [])] .reduce((collector, sourceItem) => { const { target, intersection } = collector; const targetIndex = target.findIndex(targetItem => isDeepDataStructureEquality(targetItem, sourceItem) ); if (targetIndex >= 0) { // collect the intersection of // both, source (a) and target (b). intersection.push(target[targetIndex]); } return collector; }, { target: [...(b ?? [])], intersection: [], }).intersection; } const set1 = new Set([ { name: 'a' }, { name: 'b', lastname: 'bb' }, { name: 'c' }, { name: 'd' } ]); const set2 = new Set([ { name: 'b' }, { name: 'd' }, ]); console.log( "getIntersectionOfDeeplyEqualDataStructures(set1, set2) ...", getIntersectionOfDeeplyEqualDataStructures(set1, set2) ); const set3 = new Set([ { name: 'a' }, { name: 'b', lastname: 'bb' }, { name: 'c' }, { name: 'd', list: ['foo', 1, null, false, 0, { foo: { bar: { baz: 'bizz', buzz: '' } } }], }, ]); const set4 = new Set([ { list: ['foo', 1, null, false, 0, { foo: { bar: { buzz: '', baz: 'bizz' } } }], name: 'd', }, { name: 'C' }, { lastname: 'bb', name: 'b' }, { name: 'aa' } ]); console.log( "getIntersectionOfDeeplyEqualDataStructures(set3, set4) ...", getIntersectionOfDeeplyEqualDataStructures(set3, set4) ); .as-console-wrapper { min-height: 100%!important; top: 0; } <script> function isDeepDataStructureEquality(a, b) { let isEqual = Object.is(a, b); if (!isEqual) { if (Array.isArray(a) && Array.isArray(b)) { isEqual = (a.length === b.length) && a.every( (item, idx) => isDeepDataStructureEquality(item, b[idx]) ); } else if ( a && b && (typeof a === 'object') && (typeof b === 'object') ) { const aKeys = Object.keys(a); const bKeys = Object.keys(b); isEqual = (aKeys.length === bKeys.length) && aKeys.every( (key, idx) => isDeepDataStructureEquality(a[key], b[key]) ); } } return isEqual; } </script> Edit As for Titus' approach ... set1.forEach(value => { if ( ![...set2].some(o => Object.entries(o).every(([k, v], _, arr) => (Object.keys(value).length === arr.length && value[k] === v) ) ) ) { set1.delete(value); } }); ... which works for flat objects only, though already agnostic to key insertion order, one could optimize the code by ... ... not creating the keys array of the most outer currently processed object again and again with every nested some and every iteration. thus, something like ... const valueKeys = Object.keys(value); ... before the if clause, already helps improving the code. ... inverting the nested some and every logic which does result in a more efficient way of ... deleting every flat data-item from the processed set which does not equal any flat data-item from the secondary set. On top of that, one could implement a function statement which not only helps code-reuse but also makes the implementation independent from outer scope references. For instance, the primary set which is operated and going to be mutated can be accessed as such a function's third parameter. But most important for outer scope independency is the also available thisArg binding for any set's forEach method. Thus any function statement or function expression can access e.g. the other/secondary set via this in case the latter was passed as the forEach's 2nd parameter. Also an improved wording supports a better readability of the code ... //the function naming of cause is exaggerated. function deleteItemFromSourceWhichDoesNotEqualAnyItemFromBoundTarget(sourceItem, _, sourceSet) { const targetSet = this; const sourceKeys = Object.keys(sourceItem); if ( // ... for any data-item from the (bound) target-set ... [...targetSet].every(targetItem => // ... which does not equal the currently processed data-item from the source-set ... Object.entries(targetItem).some(([targetKey, targetValue], _, targetEntries) => sourceKeys.length !== targetEntries.length || sourceItem[targetKey] !== targetValue ) ) ) { // ... delete the currently processed data-item from the source-set. sourceSet.delete(sourceItem); } } const set1 = new Set([ { name: 'a' }, // - to be kept. { name: 'b', lastname: 'bb' }, // - to be kept. { name: 'c' }, // - to be deleted. { name: 'd', nested: { name: 'a' } }, // - to be kept, but fails ... ]); // ... due to not being flat. const set2 = new Set([ { name: 'd', nested: { name: 'a' } }, // - should equal, but doesn't. { name: 'a' }, // - does equal. { lastname: 'bb', name: 'b' }, // - does equal. { name: 'e' }, // - doesn't equal. ]); // `set1` is going to be mutated. set1.forEach(deleteItemFromSourceWhichDoesNotEqualAnyItemFromBoundTarget, set2); console.log( 'mutated `set1` now (almost) being equal to the intersection of initial `set1` and `set2` ...', [...set1] ); .as-console-wrapper { min-height: 100%!important; top: 0; }
const set1 = new Set([ {name: 'a'}, {name: 'b', lastname: 'bb'}, {name: 'c'}, {name: 'd'} ]); const set2 = new Set([ {name: 'b'}, {name: 'd'} ]); const names = [...set2].map(s2 => s2.name); console.log([...set1].filter(item => names.includes(item.name))); const set1 = new Set([ {name: 'a'}, {name: 'b', lastname: 'bb'}, {name: 'c'}, {name: 'd'}, {name: 'e'} ]); const set2 = new Set([ {name: 'c', lastname: 'ccc'}, {name: 'd'}, {name: 'b', lastname: 'cc'}, {name: 'e'} ]); console.log([...set1].filter(item => { const s2Arr = [...set2]; const itemKeys = Object.keys(item); for(let i = 0; i < s2Arr.length; i++){ const s2Obj = s2Arr[i]; const s2ObjKeys = Object.keys(s2Obj); if(s2ObjKeys.length == itemKeys.length){ let oneSame = true; for(let j = 0; j < s2ObjKeys.length; j++){ const s2ObjKey = s2ObjKeys[j]; if(item[s2ObjKey] != s2Obj[s2ObjKey]){ oneSame = false; } } if(oneSame) return true; } } return false; }));
Convert object to string while join array in javascript
I want to convert array to string & if array contain object then need to convert in string. array = [ 'a', 'b', { name: 'John doe', age: 25 } ] My code: const convertedArray = array.join(' '); Output like below: a b "{"name":"john", "age":22, "class":"mca"}"
You can use array reduce function. Inside the reduce callback check if the current object which is under iteration is an object or not. If it is an object then use JSON.stringify and concat with the accumulator. const array = [ 'a', 'b', { name: 'John doe', age: 25 } ]; const val = array.reduce((acc, curr) => { if (typeof curr === 'object') { acc += JSON.stringify(curr); } else { acc += `${curr} ` } return acc; }, ''); console.log(val) Using JSON.stringify on the entire array will have starting and ending [ and ] respectively, which is not what you are looking const array = [ 'a', 'b', { name: 'John doe', age: 25 } ]; console.log(JSON.stringify(array))
Simple ! Try following : var arr = [ 'a', 'b', { name: 'John doe', age: 25 } ] var newArr = arr.map(i => typeof i === "object" ? JSON.stringify(i) : i) console.log(newArr) output : ['a', 'b', '{"name":"John doe","age":25}']
Remove duplicated combination in array based on index
I have the following data array: const data = [ { value: [ 'a', 'b', 'a', 'a' ] }, { value: [ 'c', 'c', 'd', 'c' ] } ]; So there's is 4 combination here based on index: combination 1 : a - c (index 0 in each value arrays) combination 2 : b - c (index 1 in each value arrays) combination 3 : a - d (index 2 in each value arrays) combination 4 : a - c (index 3 in each value arrays) As you can see the first and the last combinations are the same, so i want to remove the second occurrence from each array, the result should be: [ { value: [ 'a', 'b', 'a' ] }, { value: [ 'c', 'c', 'd' ] } ]
You can zip the values arrays from both objects to form an array which looks like: ["a-c", "b-c", ...] As these are now strings, you can turn this array into a Set using new Set(), which will remove all duplicate occurrences. You can then turn this set back into an array which you can then use .reduce() on to build you array of objects from. For each value you can obtain the list of values by using .split() on the '-', and from that, populate your reduced array. See example below: const data = [{ value: [ 'a', 'b', 'a', 'a' ] }, { value: [ 'c', 'c', 'd', 'c' ] } ]; const unq = [...new Set( data[0].value.map((_,c)=> data.map(({value})=>value[c]).join('-')) )]; const res = unq.reduce((acc, str) => { const values = str.split('-'); values.forEach((value, i) => acc[i].value.push(value)); return acc; }, Array.from({length: data.length}, _ => ({value: []}))) console.log(res); Limitations of the above method assume that you won't have a - character as your string value. If this is an issue, you can consider using a different delimiter, or find unique values within your array using .filter() instead of a Set.
You could save a lookup object for unique pairs of value based with index Given your input is, below solution could help you const data = [ { value: ["a", "b", "a", "a"], }, { value: ["c", "c", "d", "c"], }, ] const lookup = {} data[0].value.forEach((_, index) => { lookup[`${data[0].value[index]}-${data[1].value[index]}`] = true }) const res = Object.keys(lookup).reduce( (acc, key) => { const [val1, val2] = key.split("-") acc[0].value.push(val1) acc[1].value.push(val2) return acc }, [{ value: [] }, { value: [] }] ) console.log(res)
Below is a two step solution with a generator function and a single pass. const data = [ { value: [ 'a', 'b', 'a', 'a' ] }, { value: [ 'c', 'c', 'd', 'c', ] } ]; const zipDataValues = function* (data) { const iterators = data.map(item => item.value[Symbol.iterator]()) let iterations = iterators.map(iter => iter.next()) while (iterations.some(iteration => !iteration.done)) { yield iterations.map(iteration => iteration.value) iterations = iterators.map(iter => iter.next()) } } const filterOutDuplicateCombos = function (values) { const combosSet = new Set(), resultData = [{ value: [] }, { value: [] }] for (const [valueA, valueB] of values) { const setKey = [valueA, valueB].join('') if (combosSet.has(setKey)) { continue } combosSet.add(setKey) resultData[0].value.push(valueA) resultData[1].value.push(valueB) } return resultData } console.log( filterOutDuplicateCombos(zipDataValues(data)) ) // [ { value: [ 'a', 'b', 'a' ] }, { value: [ 'c', 'c', 'd' ] } ] Here is a reference on generators and iterators
Filter combinations + sorting by the first occurrence: const data = [{ value: ['a', 'b', 'a', 'a'] },{ value: ['c', 'c', 'd', 'c'] }]; var res = {}, i, t; for (i = 0; i < data[0].value.length; ++i) { res[data[0].value[i]] = res[data[0].value[i]] || {}; res[data[0].value[i]][data[1].value[i]] = true; } data[0].value = []; data[1].value = []; for (i in res) { for (t in res[i]) { data[0].value[data[0].value.length] = i; data[1].value[data[1].value.length] = t; } } console.log(data);
Most efficient way to dedupe 2 arrays by value A and sort by value B?
I have an array containing objects that have an identifier and the sort value. When an endpoint I'm querying returns an array with the updated objects, I need to merge it with the existing array, the incoming array's duplicated identifier objects taking precedence over the existing array but for them both to be sorted alongside each other. e.g arrayA = [A:1, B:4, C:6] arrayB = [D:2, A:3, C:5, G:7] result = [D:2, A:3, B:4, C:5, G:7] So far I can't think of any solution that doesn't include me deduping the arrays first and sorting them second, which seems wildly inefficient for the long lists that I am going to be working with. What would be the most efficient way of going about this?
You can perform the usual merge step that mergesort uses, except you can skip elements in arrayA that are already in arrayB. To perform this check quickly, you can first add all the ids from arrayB to a Set. This will result in a final runtime of O(n + m) (with extra space for the Set) instead of O((n + m) log(n + m): function linearMerge(A, B) { const res = []; const newIds = new Set(B.map(o => o.id)); A = A.filter(o => !newIds.has(o.id)); let i = 0, j = 0; while (i < A.length && j < B.length) { if (A[i].val < B[j].val) res.push(A[i++]); else res.push(B[j++]); } while (i < A.length) res.push(A[i++]); while (j < B.length) res.push(B[j++]); return res; } const arrayA = [{id: 'A', val: 1}, {id: 'B', val: 4}, {id: 'C', val: 6}]; const arrayB = [{id: 'D', val: 2}, {id: 'A', val: 3}, {id: 'C', val: 5}, {id: 'G', val: 7}]; const result = linearMerge(arrayA, arrayB); console.log(result); // [D:2, A:3, B:4, C:5, G:7] Of course, your original approach also works but can be improved if you use a Set to remove duplicates: function nLogNMerge(A, B) { const newIds = new Set(B.map(o => o.id)); return [...A.filter(o => !newIds.has(o.id)), ...B] .sort((a, b) => a.val - b.val); } const arrayA = [{id: 'A', val: 1}, {id: 'B', val: 4}, {id: 'C', val: 6}]; const arrayB = [{id: 'D', val: 2}, {id: 'A', val: 3}, {id: 'C', val: 5}, {id: 'G', val: 7}]; const result = nLogNMerge(arrayA, arrayB); console.log(result); // [D:2, A:3, B:4, C:5, G:7] I tested both approaches where arrayA and arrayB have 4500 entries, and the linear merge indeed outperforms the second approach (about "20%" faster). You can find the test here: https://jsperf.com/merge-sorted-arrays-with-duplicates/1
sorting array manually using a reference array [duplicate]
This question already has answers here: How to sort in specific order - array prototype(not by value or by name) (4 answers) Closed 3 years ago. const order = ['b', 'c', 'a']; const objects = [ { name: 'a' }, { name: 'b' }, { name: 'c' }, ]; Trying to figure out the most efficient way to sort the objects array by name using the manual order array.
Here is a quick use of sort plus indexOf. const order = ['b', 'c', 'a']; const objects = [ { name: 'a' }, { name: 'b' }, { name: 'c' }, ]; const sortedObjects = objects.sort((o1, o2) => order.indexOf(o1.name) - order.indexOf(o2.name)); console.log(sortedObjects); With cached indices: const order = ['b', 'c', 'a'].reduce((acc, elt, index) => (acc[elt] = index, acc), {}); const objects = [ { name: 'a' }, { name: 'b' }, { name: 'c' }, ]; const sortedObjects = objects.sort((o1, o2) => order[o1.name] - order[o2.name]); console.log(sortedObjects);
You can cache the indices using Object.entries() and Object.fromEntries() to re-arrange the order object into a lookup table: const order = ['b', 'c', 'a']; const objects = [ { name: 'a' }, { name: 'b' }, { name: 'c' }, ]; const lut = Object.fromEntries( Object.entries(order).map(entry => entry.reverse()) ); objects.sort((a, b) => lut[a.name] - lut[b.name]); console.log(objects);