Find the best match in an array - javascript

I have got the following array:
let x = [
{ name: "Bad", value: 2 },
{ name: "Critical", value: 1 },
{ name: "High", value: 5 },
{ name: "Medium", value: 5 },
];
The expectation is to look for "Critical" first, if the array has it, return that, else look for "High" then look for "Medium" and so on.

You can store the priorities in an array and then loop over the array and for every priority check if there's an object and whenever an object is found, return it. If the entire priorities array is exhausted then return null (or whatever you want).
const arr = [
{ name: "Bad", value: 2 },
{ name: "Critical", value: 1 },
{ name: "High", value: 5 },
{ name: "Medium", value: 5 },
],
priorities = ["Critical", "High", "Medium", "Bad"],
search = (arr, priorities) => {
for (let p of priorities) {
const obj = arr.find(({ name }) => name === p);
if (obj) {
return obj;
}
}
return null;
};
console.log(search(arr, priorities));
You can also sort the array based on the priority.
Create a Map that stores the priorities.
Sort arr based on the priorities stored in the map.
const arr = [
{ name: "Bad", value: 2 },
{ name: "Critical", value: 1 },
{ name: "High", value: 5 },
{ name: "Medium", value: 5 },
],
priorities = new Map([
["Critical", 4],
["High", 3],
["Medium", 2],
["Bad", 1],
]),
sortedArr = [...arr].sort(
(a, b) => priorities.get(b.name) - priorities.get(a.name)
);
console.log(sortedArr);

let value = x.find(x => x.name == "Critical")
|| x.find(x => x.name == "High")
|| x.find(x => x.name == "Medium") ...

First, define the order in which you want to look for items, then use that to sort the items in the array. Lastly, find based on all the items in your sort order. The first match will be returned, since the items are sorted.
const x = [{name: 'Bad',value: 2}, {name: 'High', value: 5}, {name: 'Medium', value: 5}, {name: 'Critical', value: 1}],
order = ['Critical','High','Medium'],
//sort according to above order
output = x.sort(
({name:na},{name:nb}) =>
order.findIndex(v => v === na) - order.findIndex(v => v === nb)
)
//Now find items matching name in order
.find( ({name}) => order.includes(name) );
console.log( output );
NOTE
Since your data is already sorted in the desired order, I moved the element with name = "Critical" to the end of the array.
If the data will always be sorted according to the priority you want to find the items, then no sorting is needed.

Related

How to filter object array based on various values within a json object

Given the following array of objects:
let list = [
{id: 100, desc: 'Apple'},
{id: 555, desc: 'Banana'},
{id: 110, desc: 'Orange'},
{id: 120, desc: 'Strawberry'}
]
and the following:
let myObj = {
"res": {
"myId": 555,
"allIds": [
{
"subId": 100
},
{
"subId": 120
}
]
}
}
I need to filter the list array above so that it doesn't include the myId value and any of the subId values within the allIds array.
So based on this requirement after filtering, I expect to only see the following remaining value within the list array, i.e.:
let list = [
{id: 110, desc: 'Orange'}
]
as id: 110 doesn't equal myId and doesn't exist within the allIds array.
I tried the following which works for just myId:
let filteredArr = {list.filter((el) => el.id !== myObj.res.myId)}
but unsure how to also exclude/filter the subId's within the allIds array as well?
I tried the following which works for just myId:
let filteredArr = {list.filter((el) => el.id !== myObj.res.myId)}
but unsure how to also exclude/filter the subId's within the allIds array as well?
First, those {...} around list.filter(...) don't belong there. The { after a = starts an object initializer, but that's not valid content for an object initializer.
Use && ("and") and then myObj.res.allIds.every to see if the subId of every element in myObj.res.allIds is not a match for el.id:
let filteredArr = list.filter(
(el) => el.id !== myObj.res.myId && myObj.res.allIds.every(({ subId }) => subId !== el.id)
);
Live Example:
let list = [
{ id: 100, desc: "Apple" },
{ id: 555, desc: "Banana" },
{ id: 110, desc: "Orange" },
{ id: 120, desc: "Strawberry" },
];
let myObj = {
res: {
myId: 555,
allIds: [
{
subId: 100,
},
{
subId: 120,
},
],
},
};
let filteredArr = list.filter(
(el) => el.id !== myObj.res.myId && myObj.res.allIds.every(({ subId }) => subId !== el.id)
);
console.log(filteredArr);
That's assuming that myObj.res.allIds is fairly short, so it's okay to re-traverse it for (nearly) every element of list. If it's not short, you might want to create a Set containing all of the disallowed id valuse (myObj.res.myId and the subId values) first, since lookup time for the has method of a Set is guaranteed to be sublinear (whereas every will be linear):
const disallowed = new Set([
myObj.res.myId,
...myObj.res.allIds.map(({subId}) => subId),
]);
let filteredArr = list.filter((el) => !disallowed.has(el.id));
Live Example:
let list = [
{ id: 100, desc: "Apple" },
{ id: 555, desc: "Banana" },
{ id: 110, desc: "Orange" },
{ id: 120, desc: "Strawberry" },
];
let myObj = {
res: {
myId: 555,
allIds: [
{
subId: 100,
},
{
subId: 120,
},
],
},
};
const disallowed = new Set([
myObj.res.myId,
...myObj.res.allIds.map(({subId}) => subId),
]);
let filteredArr = list.filter((el) => !disallowed.has(el.id));
console.log(filteredArr);
You'll need to collate a list of ids that you want to filter out of your myObj object and simply filter the list. Something like this...
const blackList = [ myObj.res.myId, ...myObj.res.allIds.map(o => o.subId) ]
const filteredArr = list.filter(o => !blackList.includes(o.id))
console.log(filteredArr)

Filter an array of objects by a N-length map of props with 3 states for each prop

I'm doing a complex filter, for which I have an initial list of objects with unique ids.
And a map with N properties with a list of corresponding object copies for each prop; and three states for each prop: idle: 0, show: 1, hide: 2.
For now I managed to do this with lodash's differenceBy and intersectionBy. My filter function takes in an array of objects and mutates the array, by checking and filtering the array with every map prop.
But concerning efficiency and growing number of complexity, should this kind of problem be solved differently?
For example:
If this filter is applied to a big array of hex colors (length 100, 1000 or more)
colors [1, 2, 3, ...1000]
And prop map has a growing number of props, like tags, by which a user can mark colors and show/hide them on filter. Or at some point new prop states will be added.
prop1 0, 1, 2, ...10
prop2 0, 1, 2, ...10
prop3 0, 1, 2, ...10
...
prop100 0, 1, 2, ...10
Should this kind of problem be solved via graph or matrix algorithms or some other method respectively? And, if yes, to what I should look into?
My code for optimisation and efficiency concerns:
const propMap = [
{ name: 'prop1', value: 0, items: [] },
{ name: 'prop2', value: 1, items: [ { id: 1}, { id: 2} ] },
{ name: 'propN', value: 2, items: [ { id: 2} ] },
];
const someArr = [
{ id: 1}, { id: 2}, { id: 3}, { id: 4},{ id: 5},
]
function filterByPropMap (arr) {
// Filter hidden from array
propMap.forEach(prop => {
if (prop.value === 2) {
arr = _.differenceBy(arr, prop.items, 'id');
}
});
// Filter intersecting objects to show
propMap.forEach(prop => {
if (prop.value === 1) {
arr = _.intersectionBy(arr, prop.items, 'id');
}
});
return [...arr];
}
console.log(filterByPropMap(someArr));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js"></script>
In general, you very often don't need Lodash. Consider the following, which uses only vanilla ES6.
It returns { id: 1 } twice, because it looks like the deduplication is an unintended side effect of your code. At least you never do so explicitly.
const propMap = [
{ name: 'prop1', value: 0, items: [] },
{ name: 'prop2', value: 1, items: [ { id: 1}, { id: 2} ] },
{ name: 'propN', value: 2, items: [ { id: 2} ] },
];
const someArr = [
{ id: 1}, { id: 2}, { id: 3}, { id: 4},{ id: 1},
];
function filterByPropMap(arr) {
const hiddenItems = propMap
.filter(p => p.value === 2)
.map(p => p.items)
.flat();
const intersectingItems = propMap
.filter(p => p.value === 1)
.map(p => p.items)
.flat();
const isEqual = (a, b) => a.id === b.id;
return arr
.filter(v => !hiddenItems.some(h => isEqual(h, v)) &&
intersectingItems.some(i => isEqual(i, v)));
}
console.log(filterByPropMap(someArr));

Get count from Array of arrays

I have an array of arrays below. With ES6, how can I get a count of each value Good, Excellent & Wow into a new array e.g [{name: Good, count: 4} {name: Excellent, count: 5}, {name:Wow, count:2}] in dynamic style. I am attempting to use Object.assign but I am failing to "unique" out the count of the key plus instead, I need to use an array as I am trying to render this out on the front end. Do I need to use reduce? how?
let k = 0
const stats = {}
const remarks = [
[{name: "Good"}],
[{name: "Good"}, {name: "Excellent"}],
[{name: "Good"}, {name: "Excellent"}, {name: "Wow"}],
[{name: "Good"}, {name: "Excellent"}, {name: "Wow"}],
[{name: "Excellent"}],
[{name: "Excellent"}]
]
remarks.forEach((arr) => {
arr.map((e) => {
Object.assign(stats, { [e.name]: k = k + 1 })
})
})
console.log(stats);
Output:
stats: {Good: 8, Excellent: 11, Wow: 9}
Which is Incorrect plus I need to use an array.
Expected output:
[{name: Good, count: 4} {name: Excellent, count: 5}, {name:Wow, count:2}]
Flatten the array of arrays and reduce it starting with an object like : { Good: 0, Excellent: 0, Wow: 0}
then .map the Object.entries of the result to transform it to an array :
const remarks = [
[{ name: "Good" }],
[{ name: "Good" }, { name: "Excellent" }],
[{ name: "Good" }, { name: "Excellent" }, { name: "Wow" }],
[{ name: "Good" }, { name: "Excellent" }, { name: "Wow" }],
[{ name: "Excellent" }],
[{ name: "Excellent" }]
];
const result = Object.entries(
remarks.flat().reduce(
(all, { name }) => {
all[name] += 1;
return all;
},
{ Good: 0, Excellent: 0, Wow: 0 }
)
).map(([name, count]) => ({ name, count }));
console.log(result);
You can try below logic:
var data = [[{name: "Good"}],[{name: "Good"}, {name:"Excellent"}],[{name: "Good"}, {name:"Excellent"}, {name:"Wow"}],[{name: "Good"}, {name:"Excellent"}, {name:"Wow"}],[{name:"Excellent"}],[{name:"Excellent"}]]
var nData = [];
(data || []).forEach( e => {
(e || []).forEach(ei => {
var i = (index = nData.findIndex(d => d.name === ei.name)) >=0 ? index : nData.length;
nData[i] = {
name: ei.name,
count : (nData[i] && nData[i].count ? nData[i].count : 0)+1
}
});
});
console.log(nData);
Hope this helps!
You can use reduce, then convert the result into an array of objects:
const counts = remarks.reduce((result, list) => {
list.forEach(remark => {
result[remark.name] = (result[remark.name] || 0) + 1;
});
}, {});
const finalResult = [];
for (let name in counts) {
finalResult.push({name, count: counts[name]});
}
You could achieve this pretty easily by:
1) Flattening the nested array into 1 single level array.
2) Iterating over the flat array and create a "count map" by using Array.prototype.reduce
For example:
const remarks = [
[{
name: 'Good'
}],
[{
name: 'Good'
}, {
name: 'Excellent'
}],
[{
name: 'Good'
}, {
name: 'Excellent'
}, {
name: 'Wow'
}],
[{
name: 'Good'
}, {
name: 'Excellent'
}, {
name: 'Wow'
}],
[{
name: 'Excellent'
}],
[{
name: 'Excellent'
}]
]
const flatten = arr => arr.reduce((accum, el) => accum.concat(el), [])
const map = flatten(remarks).reduce((accum, el) => {
if (accum[el.name]) {
accum[el.name] += 1;
} else {
accum[el.name] = 1;
}
return accum;
}, {});
console.log(map)
First find the counts using reduce than pass that to another function to get the desired view structure:
const Good = 1,
Excellent = 2,
Wow = 3;
const remarks = [
[{name: Good}],
[{name: Good}, {name:Excellent}],
[{name: Good}, {name:Excellent}, {name:Wow}],
[{name: Good}, {name:Excellent}, {name:Wow}],
[{name:Excellent}],
[{name:Excellent}]
];
/*
[{name: Good, count: 4} {name: Excellent, count: 5}, {name:Wow, count:2}]
*/
function counts(remarks) {
return remarks.flat().reduce((acc, v) => {
const name = v.name;
let count = acc[name] || 0;
return {
...acc,
[name]: count + 1
}
}, {});
}
function view(counts) {
return Object.keys(counts).map(key => {
let count = counts[key];
return { name: key, count };
})
}
console.log(view(counts(remarks)));
Any time you are making a smaller set of data, or transforming data, in JavaScript reduce should be the first method you attempt to use. In this case, you may want to pair it with an indexer (hence preloading with an array of index and an array of result).
This works in one pass without needing to know the name values up front.
const remarks = [
[{name: "Good"}],
[{name: "Good"}, {name: "Excellent"}],
[{name: "Good"}, {name: "Excellent"}, {name: "Wow"}],
[{name: "Good"}, {name: "Excellent"}, {name: "Wow"}],
[{name: "Excellent"}],
[{name: "Excellent"}]
];
const stats = remarks.reduce((p,c) => (
c.forEach( ({name}) => {
if(!p[0].hasOwnProperty(name)){
p[1].push({name:name,count:0});
p[0][name] = p[1].length - 1;
}
p[1][p[0][name]].count++;
}),p),[{},[]])[1];
console.log(stats);
A slightly more concise and definitely less readable approach (but it's worth to mention) could be:
const remarks = [
[{ name: "Good" }],
[{ name: "Good" }, { name: "Excellent" }],
[{ name: "Good" }, { name: "Excellent" }, { name: "Wow" }],
[{ name: "Good" }, { name: "Excellent" }, { name: "Wow" }],
[{ name: "Excellent" }],
[{ name: "Excellent" }]
];
const stats = Object.entries(
remarks
.flat()
.reduce((acc, {name}) => (acc[name] = -~acc[name], acc), {})))
).map(([name, count]) => ({ name, count }));
console.log(stats);
It uses the comma operator in the reducer to returns the accumulator; and the bitwise operator NOT to create a counter without the needs to initialize the object upfront with all the names.
const flattenedRemarks = _.flatten(remarks);
const groupedRemarks = _.groupBy(flattenedRemarks, (remark) => remark.name);
const remarkCounts = _.mapValues(groupedRemarks, (group) => group.length);
const data = {
"mchale": {
"classes":["ESJ030", "SCI339"], // get the length
"faculty":["Hardy", "Vikrum"] // get the length
},
"lawerence":{
"classes":["ENG001"], // get the length
"faculty":["Speedman", "Lee", "Lazenhower"] // get the length
}
};
const count = Object.keys(data).map(campusName => {
const campus = data[campusName];
return Object.keys(campus).map(key => campus[key].length).reduce((p, c) => p + c, 0);
}).reduce((p, c) => p + c, 0);
console.log(count);

how to calculate a sum of an array in an object

I had a variable like that
const data = {
code: 1,
items: [
{ nickname: 1, name: [
{id : "A"},
{id : "B"}
]
},
{
nickname: 2, name: [
{id: "A"},
{id: "C"}
]
}
]
}
after that, I want to show how many characters: A:2, B:1, C:1
You can do that is following steps:
Use flatMap() on the array data.items
Inside flatMap() use map() to convert all the object to their id and return it from flatMap(). This way you will array ["A","B","A","C"]
Then use reduce() and get an object with count of all the letters.
const data = { code: 1, items: [ { nickname: 1, name: [ {id : "A"}, {id : "B"} ] }, { nickname: 2, name: [ {id: "A"}, {id: "C"} ] } ] }
const res = data.items.flatMap(x =>
x.name.map(a => a.id)
).reduce((ac,a) => (ac[a] = ac[a] + 1 || 1,ac),{});
console.log(res)
const data = {
code: 1,
items: [
{
nickname: 1,
name: [
{ id: "A" },
{ id: "B" }
]
},
{
nickname: 2,
name: [
{ id: "A" },
{ id: "C" }
]
}
]
};
const res = data.items.reduce((acc, next) => {
next.name.forEach(({ id }) => {
acc[id] = acc[id] + 1 || 1;
});
return acc;
}, {});
console.log(res);
You can do that in a single shot using reduce.
Reducing data.items will allow you to add to the accumulator (initially an empty object), the value of the currently looped name property item.
The result will be an object owning all the occurences of each encountered letter in the name property of each array.
Relevant lines explained:
data.items.reduce((acc, next) will call the reduce method on data.items. acc is the reduce accumulator (initially an empty object), next is the currently looped item of data.items.
next.name.forEach(({id}) in this line, we loop the name property of the currently looped item (data.items[n]). ({id}) is a short syntax to acquire the id property of the looped item in the foreach. It's equivalent to (item => item.id).
acc[id] = acc[id] + 1 || 1; tries to increase the property [id] of the accumulator (example: "A" of {}) by 1. If it does not exist, it sets the value to 1.
return acc; returns the accumulator.
You could iterate name and take id in a loop for assigning the count.
const
data = { code: 1, items: [{ nickname: 1, name: [{ id : "A" }, { id : "B" }] }, { nickname: 2, name: [{ id: "A" }, { id: "C" }] }] },
result = data.items.reduce(
(r, { name }) => (name.forEach(({ id }) => r[id] = (r[id] || 0 ) + 1), r),
{}
);
console.log(result);

How to check if all objects of array are included another array?

I am trying to check if object array A includes objects from B.
let A = [
{ name: "Max" },
{ name: "Jhon" },
{ name: "Naton" },
]
let B = [
{ name: "Max" },
{ name: "Naton" },
]
So B has two objects that is in array A. How to check this ?
I am trying to achieve it with includes :
for(let entry of this.b){
if(this.a.includes(entry)){
console.log('includes');
}
}
But I get false on includes.
The method Array.includes() compare the entries of the array with the given value. Because your array entries are objects, it will not match. You have to loop at the array yourself and make the comparison.
Array.some() loops on an array and returns true if you returns true at least one. This method is useful when you want to verify something. In our example, we want to verify if the array a contains the b entry.
const a = [{
name: 'Max',
},
{
name: 'Jhon',
},
{
name: 'Naton',
},
];
const b = [{
name: 'Max',
},
{
name: 'Naton',
},
{
name: 'Daddy',
},
];
console.log(b.map(x => a.some(y => y.name === x.name)));
If I break it down :
const a = [{
name: 'Max',
},
{
name: 'Jhon',
},
{
name: 'Naton',
},
];
const b = [{
name: 'Max',
},
{
name: 'Naton',
},
{
name: 'Daddy',
},
];
// Loop on every entry of the b array
b.forEach((x) => {
// x here represent one entry
// first it will worth { name: 'Max' }, then { name: 'Naton' } ...
// for each value we are going to look at a if we can find a match
const isThereAMatch = a.some((y) => {
// y here is worth one entry of the a array
if (y.name === x.name) return true;
return false;
});
if (isThereAMatch === true) {
console.log(`We have found ${x.name} in a`);
} else {
console.log(`We have not found ${x.name} in a`);
}
});
You have to use another loop, then check the property name:
var a = [
{name: "Max"},
{name: "Jhon"},
{name: "Naton"},
];
var b = [
{name: "Max"},
{name: "Naton"},
];
for(let entry of b){
for(let entry2 of a){
if(entry2.name == entry.name){
console.log('includes', entry.name);
}
}
}
OR: You can use string version of object to check with includes():
var a = [
{name: "Max"},
{name: "Jhon"},
{name: "Naton"},
];
var b = [
{name: "Max"},
{name: "Naton"},
];
var aTemp = a.map(i => JSON.stringify(i));
var bTemp = b.map(i => JSON.stringify(i));
for(let entry of bTemp){
if(aTemp.includes(entry)){
console.log('includes', entry);
}
}
When you use Array#includes() method it will always return false because it's comparing objects which aren't equal because they aren't referencing the same object.
You should compare objects properties and not whole objects, you can do it using Array#some() method like this:
for (let entry of this.b) {
if (this.b.some(x => x.name === entry.name)) {
console.log('includes');
}
}
Demo:
A = [{
name: "Max"
},
{
name: "Jhon"
},
{
name: "Naton"
},
]
B = [{
name: "Max"
},
{
name: "Naton"
},
]
//Filter objects that exists in both arrays
let result = A.filter(el=> B.some(x => x.name === el.name));
console.log(result);

Categories