Sort array by number from other array - javascript

I have two arrays and I want to sort first one based on some values from another array:
const items = [
['music', ['arr']],
['movies', ['arr']],
['quizes', ['arr']],
['series', ['arr']]
];
const categories = [
{ name: "music", priority: 3},
{ name: "movies", priority: 2},
{ name: "quizes", priority: 5},
{ name: "series", priority: 1},
{ name: "sports", priority: 4},
];
I want to sort my first array, by property 'priority' from my second array -> from the biggest one.
Like this:
const expectedResult = [
['quizes', ['arr']],
['music', ['arr']]
['movies', ['arr']],
['series', ['arr']],
];
This is what I tried but without success.
const sorted = items.sort((a,b) => {
const [aKey, aVal] = a;
const [bKey, bVal] = b;
const prio = categories.filter(c => c.name === aKey)[0];
// not sure how to use this prio
return aKey.priority - bKey.priority;
})

You were very close, you just needed to grab b's priority (and also use the priority property). find rather than filter is a good choice:
const sorted = items.sort((a,b) => {
const [aKey] = a;
const [bKey] = b;
const aPriority = categories.find(cat => cat.name === aKey).priority;
const bPriority = categories.find(cat => cat.name === bKey).priority;
return bPriority - aPriority;
});
Live Example
const items = [
["music", ["arr"]],
["movies", ["arr"]],
["quizes", ["arr"]],
["series", ["arr"]]
];
const categories = [
{ name: "music", priority: 3},
{ name: "movies", priority: 2},
{ name: "quizes", priority: 5},
{ name: "series", priority: 1},
{ name: "sports", priority: 4},
];
const sorted = items.sort((a,b) => {
const [aKey] = a;
const [bKey] = b;
const aPriority = categories.find(cat => cat.name === aKey).priority;
const bPriority = categories.find(cat => cat.name === bKey).priority;
return bPriority - aPriority;
});
console.log(sorted);
.as-console-wrapper {
max-height: 100% !important;
}
But repeatedly traversing that array of categories isn't a good idea if items is long. Instead, make a Map of key to priority, then use that:
const catPriorityMap = new Map(categories.map(({name, priority}) => [name, priority]));
const sorted = items.sort((a,b) => {
const [aKey] = a;
const [bKey] = b;
const aPriority = catPriorityMap.get(aKey);
const bPriority = catPriorityMap.get(bKey);
return bPriority - aPriority;
});
Live Example
const items = [
["music", ["arr"]],
["movies", ["arr"]],
["quizes", ["arr"]],
["series", ["arr"]]
];
const categories = [
{ name: "music", priority: 3},
{ name: "movies", priority: 2},
{ name: "quizes", priority: 5},
{ name: "series", priority: 1},
{ name: "sports", priority: 4},
];
const catPriorityMap = new Map(categories.map(({name, priority}) => [name, priority]));
const sorted = items.sort((a,b) => {
const [aKey] = a;
const [bKey] = b;
const aPriority = catPriorityMap.get(aKey);
const bPriority = catPriorityMap.get(bKey);
return bPriority - aPriority;
});
console.log(sorted);
.as-console-wrapper {
max-height: 100% !important;
}
Lookup in a Map is done in sublinear time, whereas finding somethign in an array is done in linear time.

You can use sort() method and check the priority from the categories
const items = [
['music', ['arr']],
['movies', ['arr']],
['quizes', ['arr']],
['series', ['arr']],
];
const categories = [
{ name: 'music', priority: 3 },
{ name: 'movies', priority: 2 },
{ name: 'quizes', priority: 5 },
{ name: 'series', priority: 1 },
{ name: 'sports', priority: 4 },
];
const result = items.sort(([a], [b]) => {
const aPriority = categories.find(({ name }) => name === a).priority;
const bPriority = categories.find(({ name }) => name === b).priority;
return bPriority - aPriority;
});
console.log(result);
Learn more about sort() here.

You really do not want to use find() or filter() inside of the sort method because it is expensive. On every iteration, you are looking up the data in the array. So you are looping a lot. There is better ways to get the index.
Easiest thing is to make a lookup object so you are not having to search the other array over and over for a match. So if you can change categories to an object from the start, it will make your life so much easier.
In the sort I added max value in case the key is not defined. Now this would backfire if you had a value of zero since it is just a truthy check.
const items = [
['music', ['arr']],
['movies', ['arr']],
['quizes', ['arr']],
['series', ['arr']]
];
const categories = {
music: 3,
movies: 2,
quizes: 5,
series: 1,
sports: 4,
};
items.sort(([keyA], [keyB]) => (categories[keyA] || Number.MAX_VALUE) - (categories[keyB] || Number.MAX_VALUE));
console.log(items);
If you can not make the object look like that and you have to use the array, you can convert it from the array to an object. That can be done a few ways. I like to use reduce.
const items = [
['music', ['arr']],
['movies', ['arr']],
['quizes', ['arr']],
['series', ['arr']]
];
const categories = [
{ name: "music", priority: 3},
{ name: "movies", priority: 2},
{ name: "quizes", priority: 5},
{ name: "series", priority: 1},
{ name: "sports", priority: 4},
];
const lookup = categories.reduce((acc, obj) => ({...acc, [obj.name]: obj.priority}), {});
items.sort(([keyA], [keyB]) => (lookup[keyA] || Number.MAX_VALUE) - (lookup[keyB] || Number.MAX_VALUE));
console.log(items);
Now if you are sure that all of the keys will exist in the categories, you can drop the max value
items.sort(([keyA], [keyB]) => lookup[keyA] - lookup[keyB];

You could take an object for the order and sort by delta.
const
items = [['music', ['arr']], ['movies', ['arr']], ['quizes', ['arr']], ['series', ['arr']]],
categories = [{ name: "music", priority: 3 }, { name: "movies", priority: 2 }, { name: "quizes", priority: 5 }, { name: "series", priority: 1 }, { name: "sports", priority: 4 }],
order = Object.fromEntries(
categories.map(({ name, priority }) => [name, priority])
);
items.sort(([a], [b]) => order[a] - order[b]);
console.log(items);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Related

Intersect 3 arrays in Javascript - With snippet

I'm trying to intersect array1 and array2 and find the elements that contain the same name.
Then on array3 I only want to keep the elements that exist on the first intersection by name.
I'm stuck here, I just get true and falses. Any help?
const array1 = [{
name: 'John'
}];
const array2 = [{
name: 'Elisa'
}, {
name: 'John'
}];
const array3 = [{
name: 'Elisa',
age: 10
}, {
name: 'John',
age: 23
}, {
name: 'Maria',
age: 30
}];
const intersectArray = array1.map(elem1 => array2.map(elem2 => elem1.name === elem2.name));
console.log(intersectArray);
const filteredArray = array3.map(elem3 => intersectArray.map(elem => elem.name === elem3.name));
console.log(filteredArray);
The expected result should be:
{ name: 'John', age: 23 }
You can just check against both arrays, instead of first creating an intersection:
const array1 = [{ name: 'John' }];
const array2 = [{ name: 'Elisa' }, { name: 'John' }];
const array3 = [{ name: 'Elisa', age: 10 }, { name: 'John', age: 23 }, { name: 'Maria', age: 30 }];
const result = array3.filter(x =>
array1.some(a => a.name === x.name) &&
array2.some(a => a.name === x.name))
console.log(result);
To do what you require you can use filter() to return only the elements which meet certain criteria. In your case, you can find() within the other array if a matching name is found.
The same logic can be used for both steps:
const array1 = [{ name: 'John' }];
const array2 = [{ name: 'Elisa' }, { name: 'John' }];
const array3 = [{ name: 'Elisa', age: 10 }, { name: 'John', age: 23 }, { name: 'Maria', age: 30 }];
const intersectArray = array1.filter(o1 => array2.find(o2 => o1.name == o2.name));
console.log(intersectArray);
const filteredArray = array3.filter(o3 => intersectArray.find(ia => o3.name === ia.name));
console.log(filteredArray);
If you want to grab the intersection between three arrays, you can filter the first array and find a similar item in the second array with some.
const
intersection = (a, b, f) => a.filter(x => b.some(y => f(x) === f(y))),
intersection2 = (f, ...a) => a.reduce((r, b) => intersection(r, b, f));
const
arr1 = [{ name: 'John' }],
arr2 = [{ name: 'Elisa' }, { name: 'John' }],
arr3 = [{ name: 'Elisa', age: 10 }, { name: 'John', age: 23 }, { name: 'Maria', age: 30 }];
const
getName = ({ name }) => name,
inter1 = intersection(arr3, intersection(arr2, arr1, getName), getName),
inter2 = intersection2(getName, arr3, arr2, arr1);
console.log(inter1); // Nested calls
console.log(inter2); // Reduction
.as-console-wrapper { top: 0; max-height: 100% !important; }

Merge and concat two arrays of objects - JS

I have two array of objects and I want to merge them based on different properties and also concat if something doesnt exist in the list.
This is what I have:
const data1 = [{
name: 'A',
id: 1
}, {
name: 'B',
id: 2
}]
const data2 = [{
city: 'X',
rowID: 1
}, {
city: 'Y',
rowID: 2
}, {
city: 'Z',
rowID: 3
}]
const result = _.map(data1, function(p) {
return _.merge(
p,
_.find(data2, {
rowID: p.id
})
)
})
console.log(result)
//Expected Result
/**[
{
"name": "A",
"id": 1,
"city": "X",
"rowID": 1
},
{
"name": "B",
"id": 2,
"city": "Y",
"rowID": 2
},
{
"name": "",
"id": "",
"city": 'Z',
"rowID": 3
}
]*/
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.20/lodash.min.js"></script>
Please advice.
You could collect the merged object with a Map and get the values.
const
merge = data => {
const
pattern = {},
map = new Map;
data.forEach(([objects, key]) => {
Object.keys(objects[0]).forEach(k => pattern[k] = '');
objects.forEach(o => map.set(o[key], { ...map.get(o[key]), ...o }));
});
return Array.from(map.values(), o => ({ ...pattern, ...o }));
},
data1 = [{ name: 'A', id: 1 }, { name: 'B', id: 2 }],
data2 = [{ city: 'X', rowID: 1 }, { city: 'Y', rowID: 2 }, { city: 'Z', rowID: 3 }],
result = merge([[data1, 'id'], [data2, 'rwoID']]);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Concat the arrays, group them by id or rowId, merge all objects and map the values to empty strings, map the object of groups, and merge each group with the defaults to an object (including default empty id and name).
const { concat, groupBy, mapValues, map, merge } = _
const fn = (getId, ...arrs) => {
const items = concat(...arrs);
const defaults = mapValues(merge({}, ...items), () => '');
return map(
groupBy(items, getId),
group => merge({}, defaults, ...group)
)
}
const data1 = [{"name":"A","id":1},{"name":"B","id":2}]
const data2 = [{"city":"X","rowID":1},{"city":"Y","rowID":2},{"city":"Z","rowID":3}]
const result = fn(o => o.id ?? o.rowID, data1, data2)
console.log(result)
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.20/lodash.min.js"></script>

Merging/grouping objects in ReactJS

I have the following arrays:
[{id:0,name:'Weight',option:'250'},{id:0,name:'Roast',option:'Medium'}]
[{id:0,name:'Weight',option:'250'},{id:0,name:'Roast',option:'Light'}]
I need to merge them in something like:
[{id:0,name:'Weight',options:['250']},{id:0,name:'Roast',options:['Medium','Light']}]
I tried to nest some loops also tried with merge, push and spread operators but I can't solve it
result.forEach((att) => {
let newoptions = [];
console.log('att', att.attributes);
att.attributes.forEach((id, idx) => {
console.log('id', id);
newoptions = [...newoptions, { option: id.option }];
newAttr[idx] = { name: id.name, options: newoptions };
});
});
I recommend you to concat both arrays and then iterate over it. Then reduce the items with Array.prototype.reduce():
const data1 = [{ id: 0, name: "Weight", option: "250" },{ id: 0, name: "Roast", option: "Medium" }];
const data2 = [{ id: 0, name: "Weight", option: "250" },{ id: 0, name: "Roast", option: "Light" }];
const array = [...data1, ...data2]; // concat
const result = array.reduce((o, c) => {
const exist = o.find(item => item.id === c.id && item.name === c.name);
if (!exist) {
const options = array
.filter(item => item.id === c.id && item.name === c.name)
.map(item => item.option);
o.push({ id: c.id, name: c.name, options: Array.from(new Set(options)) });
}
return o;
}, []);
console.log(result);
Use flat and forEach. Build an object with keys as name and aggregate the values.
const process = (...data) => {
const res = {};
data.flat().forEach(({ name, option, id }) => {
res[name] ??= { name, id, options: [] };
!res[name].options.includes(option) && res[name].options.push(option);
});
return Object.values(res);
};
const arr1 = [
{ id: 0, name: "Weight", option: "250" },
{ id: 0, name: "Roast", option: "Medium" },
];
const arr2 = [
{ id: 0, name: "Weight", option: "250" },
{ id: 0, name: "Roast", option: "Light" },
];
console.log(process(arr1, arr2));

Get list of duplicate objects in an array of objects

I am trying to get duplicate objects within an array of objects. Let's say the object is like below.
values = [
{ id: 10, name: 'someName1' },
{ id: 10, name: 'someName2' },
{ id: 11, name: 'someName3' },
{ id: 12, name: 'someName4' }
];
Duplicate objects should return like below:
duplicate = [
{ id: 10, name: 'someName1' },
{ id: 10, name: 'someName2' }
];
You can use Array#reduce to make a counter lookup table based on the id key, then use Array#filter to remove any items that appeared only once in the lookup table. Time complexity is O(n).
const values = [{id: 10, name: 'someName1'}, {id: 10, name: 'someName2'}, {id: 11, name:'someName3'}, {id: 12, name: 'someName4'}];
const lookup = values.reduce((a, e) => {
a[e.id] = ++a[e.id] || 0;
return a;
}, {});
console.log(values.filter(e => lookup[e.id]));
Let's say you have:
arr = [
{ id:10, name: 'someName1' },
{ id:10, name: 'someName2' },
{ id:11, name: 'someName3' },
{ id:12, name: 'someName4' }
]
So, to get unique items:
unique = arr
.map(e => e['id'])
.map((e, i, final) => final.indexOf(e) === i && i)
.filter(obj=> arr[obj])
.map(e => arr[e]);
Then, result will be
unique = [
{ id:10, name: 'someName1' },
{ id:11, name: 'someName3' },
{ id:12, name: 'someName4' }
]
And, to get duplicate ids:
duplicateIds = arr
.map(e => e['id'])
.map((e, i, final) => final.indexOf(e) !== i && i)
.filter(obj=> arr[obj])
.map(e => arr[e]["id"])
List of IDs will be
duplicateIds = [10]
Thus, to get duplicates objects:
duplicate = arr.filter(obj=> dublicateIds.includes(obj.id));
Now you have it:
duplicate = [
{ id:10, name: 'someName1' },
{ id:10, name: 'someName2' }
]
Thanks https://reactgo.com/removeduplicateobjects/
You haven't clarified whether two objects with different ids, but the same "name" count as a duplicate. I will assume those do not count as a duplicate; in other words, only objects with the same id will count as duplicate.
let ids = {};
let dups = [];
values.forEach((val)=> {
if (ids[val.id]) {
// we have already found this same id
dups.push(val)
} else {
ids[val.id] = true;
}
})
return dups;
With lodash you can solve this with filter and countBy for complexity of O(n):
const data = [{ id: 10,name: 'someName1' }, { id: 10,name: 'someName2' }, { id: 11,name: 'someName3' }, { id: 12,name: 'someName4' } ]
const counts = _.countBy(data, 'id')
console.log(_.filter(data, x => counts[x.id] > 1))
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
You could do the same with ES6 like so:
const data = [{ id: 10,name: 'someName1' }, { id: 10,name: 'someName2' }, { id: 11,name: 'someName3' }, { id: 12,name: 'someName4' } ]
const countBy = (d, id) => d.reduce((r,{id},i,a) => (r[id] = a.filter(x => x.id == id).length, r),{})
const counts = countBy(data, 'id')
console.log(data.filter(x => [x.id] > 1))
You can use an array to store unique elements and use filter on values to only return duplicates.
const unique = []
const duplicates = values.filter(o => {
if(unique.find(i => i.id === o.id && i.name === o.name)) {
return true
}
unique.push(o)
return false;
})
With lodash you can use _.groupBy() to group elements by their id. Than _.filter() out groups that have less than two members, and _.flatten() the results:
const values = [{id: 10, name: 'someName1'}, {id: 10, name: 'someName2'}, {id: 11, name:'someName3'}, {id: 12, name: 'someName4'}];
const result = _.flow([
arr => _.groupBy(arr, 'id'), // group elements by id
g => _.filter(g, o => o.length > 1), // remove groups that have less than two members
_.flatten // flatten the results to a single array
])(values);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
An alternative based in #ggorlen solution with new Map() as accumulator (for better performance) and without unary operator ++ (not advised by default in projects with ESLint).
const values = [{ id: 10, name: "someName1" }, { id: 10, name: "someName2" }, { id: 11, name: "someName3" }, { id: 12, name: "someName4" },];
const lookup = values.reduce((a, e) => {
a.set(e.id, (a.get(e.id) ?? 0) + 1);
return a;
}, new Map());
console.log(values.filter(e => lookup.get(e.id) > 1));
Try this
function checkDuplicateInObject(propertyName, inputArray) {
var seenDuplicate = false,
testObject = {};
inputArray.map(function(item) {
var itemPropertyName = item[propertyName];
if (itemPropertyName in testObject) {
testObject[itemPropertyName].duplicate = true;
item.duplicate = true;
seenDuplicate = true;
}
else {
testObject[itemPropertyName] = item;
delete item.duplicate;
}
});
return seenDuplicate;
}
referred from : http://www.competa.com/blog/lets-find-duplicate-property-values-in-an-array-of-objects-in-javascript/

Comparing two Objects by id and creating the new one

So I have two objects with this structure:
const obj1 = { data:
[ {
id: 1,
name: 'Linda'
},
{
id: 2,
name: 'Mark'
}
];
const obj2 = [
{
id: 1,
salary: "2000, 60 USD"
},
undefined
],
[
{
id: 2,
salary: "4000, 50 USD"
},
undefined
]
I need to make a function to combine both of these into one object, based on id.
So the final results would be:
const finalObj = { data:
[ {
id: 1,
name: 'Linda',
salary: "2000, 60 USD"
},
{
id: 2,
name: 'Mark',
salary: "4000, 50 USD"
}
];
I have checked other questions, but could not find anything that would help. It can be done with lodash afaik, but don't know how.
I have tried the following:
finalObj = obj1.data.map(x => {
return {
...x,
...obj2
}
But it didn't map correctly.
Thanks.
EDIT: Updated obj2 response.
You can array#concat both your array and then using array#reduce and an object lookup with id, merge your objects. Then return all the values from this object.
const obj1 = { data: [{ id: 1, name: 'Linda' }, { id: 2, name: 'Mark' }]},
obj2 = { data: [{ id: 1, salary: "2000, 60 USD"}, { id: 2, salary: "4000, 50 USD"}]},
result = Object.values(obj1.data.concat(obj2.data).reduce((r,o) => {
r[o.id] = r[o.id] || {};
r[o.id] = {...r[o.id], ...o};
return r;
},{}));
console.log(result);
You could take a Map for collecting all properties of the same id in an object. Later get the values of the map.
const join = o => o && map.set(o.id, Object.assign(map.get(o.id) || {}, o));
var obj1 = { data: [{ id: 1, name: 'Linda' }, { id: 2, name: 'Mark' } ]},
obj2 = [{ id: 1, salary: "2000, 60 USD" }, undefined, { id: 2, salary: "4000, 50 USD" }, undefined],
map = new Map,
result;
obj1.data.forEach(join);
obj2.forEach(join);
result = { data: Array.from(map.values()) };
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Another way
const res = {
...obj1, // take the initial object
data: obj1.data.map(item => ({ // rewrite data property by iterate each item
// and merge item with corresponding
// object from obj2
...item, // take the item object
...obj2.find(({ id }) => id === item.id) // find corresponding object
}))
};
Here is an approach which would combine the objects and not overwrite the properties but only add the ones that are missing as well as avoid the undefined etc:
const names = {data: [{ id: 1, name: 'Linda' },{ id: 2, name: 'Mark' }]}
const salaries = [{ id: 1, salary: "2000, 60 USD" }, undefined]
var result = _.mapValues(names.data, x => {
let hit = _.find(salaries, y => y ? y.id === x.id : null)
return hit ? _.defaults(x, hit) : x
})
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
We are using mapValues to get to the values of names.data and look through them and for each of them get a hit in the salaries. If the hit exists we default the props of the hit with the current data object and return. Hope this helps.

Categories

Resources