Javascript : Sort the array - javascript

i need to sort the array of people according to who has the most interests in common with Sharon. The person with the most interests matching should be on top. getSortedList() function that sorts the lists of people - whoever has the most similar interests with the 'sharon' object (3 matching interests) should be on top
const interestList = [
"gaming",
"driving",
"football",
"fishing",
"painting",
"cooking",
"singing",
"shopping",
"running",
"clubbing"
];
const people = [
{ name: "Ahmad", interests: ["shopping", "painting", "cooking"] },
{ name: "Betty", interests: ["running", "painting", "football"] },
{ name: "Charlie", interests: ["gaming", "football", "painting"] },
{ name: "Diana", interests: ["fishing", "singing", "driving"] },
{ name: "Ethan", interests: ["gaming", "clubbing", "cooking"] },
{ name: "Farhan", interests: ["cooking", "driving", "fishing"] },
{ name: "Gwen", interests: ["singing", "fishing", "gaming"] },
{ name: "Helen", interests: ["football", "clubbing", "shopping"] },
{ name: "Imelda", interests: ["painting", "running", "football"] },
{ name: "Josef", interests: ["shopping", "running", "cooking"] },
{ name: "Khan", interests: ["fishing", "running", "clubbing"] },
{ name: "Lionel", interests: ["gaming", "singing", "driving"] }
];
const sharon = {
name: "Sharon",
interests: ["football", "painting", "gaming"]
};
function getSortedList() {
let output = people.slice();
return person;
}
function printPeople() {
let list = getSortedList();
list.unshift(sharon);
list.forEach(person => {
person.interest1 = person.interests[0];
person.interest2 = person.interests[1];
person.interest3 = person.interests[2];
delete person.interests;
});
console.log("Friend Matching Script Output:");
console.table(list);
console.table(getSortedList());
}
printPeople();

You can try something like this. If you need to modify your sort to beyond just the count, you can tweak the sort method.
let people = [
{ name: "Ahmad", interests: ["shopping", "painting", "cooking"] },
{ name: "Betty", interests: ["running", "painting", "football"] },
{ name: "Charlie", interests: ["gaming", "football", "painting"] },
{ name: "Diana", interests: ["fishing", "singing", "driving"] },
{ name: "Ethan", interests: ["gaming", "clubbing", "cooking"] },
{ name: "Farhan", interests: ["cooking", "driving", "fishing"] },
{ name: "Gwen", interests: ["singing", "fishing", "gaming"] },
{ name: "Helen", interests: ["football", "clubbing", "shopping"] },
{ name: "Imelda", interests: ["painting", "running", "football"] },
{ name: "Josef", interests: ["shopping", "running", "cooking"] },
{ name: "Khan", interests: ["fishing", "running", "clubbing"] },
{ name: "Lionel", interests: ["gaming", "singing", "driving"] }
];
const sharon = {
name: "Sharon",
interests: ["football", "painting", "gaming"]
};
people.forEach((person, index)=> {
let count = 0;
person.interests.forEach(int => {
sharon.interests.forEach(interest => {
if(int === interest) {
count++
people[index]['count'] = count;
}
})
})
})
people.sort((a,b) => {
if(a.count >= b.count) {
return -1;
} else {
return 1;
}
})

Determine count of overlapping interests, then apply sort.
people.map(item => {
let count = 0;
sharon.interests.forEach(interest => {if (item.interests.lastIndexOf(interest) > -1) ++count});
item.counts = count;
return item;
}).sort((a,b) => a.counts < b.counts ? 1 : (a.counts > b.counts ? -1 : 0))

Usually, the easiest way to sort something is when they are in number form. Currently, you only have data in the form of arrays and objects, which is difficult to sort by itself. Instead, as you want to sort by the number of common interests between two people, we can use this number to help us sort your array of objects.
Thus, the first step to solving your problem is to figure out a way to find the number of "overlapping" interests one object has with another. This can be done by using a method such as this:
const get_overlapping = interests =>
sharon.interests.filter(hobby => interests.includes(hobby)).length;
The above method will loop through sharon's interests (using .filter()), and only keep those which appear (.includes()) in the passed in interests array. As .filter returns an array of overlapping interests with sharon's, we can then simply get the .length of this array to find the number of overlapping interests.
The next step is to now use our method of get_overlapping to sort the list. Javascript provides a .sort() method, where we can supply a function to it which defines how it should sort.
If the supplied function (which has two parameters a and b, both representing elements in your array) returns a number less than 0, then it will make a come before b in the array, if the number returned is 0 then a and b will stay and if the number returned is greater than 0 then b will come before a. Using this idea, we can sort the people array by using the following:
get_overlapping(b) - get_overlapping(a)
where b now represents the interests array of one object in the array, and a represents the interests array of another object in your array
Using this idea, we can use this in your getSortedList method:
const people = [
{ name: "Ahmad", interests: ["shopping", "painting", "cooking"]},
{ name: "Betty", interests: ["running", "painting", "football"] },
{ name: "Charlie", interests: ["gaming", "football", "painting"] },
{ name: "Diana", interests: ["fishing", "singing", "driving"] },
{ name: "Ethan", interests: ["gaming", "clubbing", "cooking"] },
{ name: "Farhan", interests: ["cooking", "driving", "fishing"] },
{ name: "Gwen", interests: ["singing", "fishing", "gaming"] },
{ name: "Helen", interests: ["football", "clubbing", "shopping"] },
{ name: "Imelda", interests: ["painting", "running", "football"] },
{ name: "Josef", interests: ["shopping", "running", "cooking"] },
{ name: "Khan", interests: ["fishing", "running", "clubbing"] },
{ name: "Lionel", interests: ["gaming", "singing", "driving"] }
];
const sharon = {
name: "Sharon",
interests: ["football", "painting", "gaming"]
};
const get_overlapping = interests =>
sharon.interests.filter(i => interests.includes(i)).length;
const getSortedList = () => {
const output = people.slice(); // make a copy of people so that we don't modify the people array
output.sort(({interests: a}, {interests: b}) => get_overlapping(b) - get_overlapping(a));
return output; // return sorted array
}
console.log(getSortedList());
If you are looking to implement a sorting method yourself, there are plenty of sorting algorithms out there which you can take a shot at writing. Some are easier to implement and understand than others (eg: selection sort, insertion sort, etc...), however, they may not be as efficient to implement.

Related

Remove duplicates from an array of objects based on time created

I have an array of objects and there are some duplicate objects
const data = [
{
"id": "1011",
"name": "abc",
"Dob": "3/2/11",
"timeCreated": "16:03:41"
},
{
"id": "1012",
"name": "xys",
"Dob": "6/5/12",
"timeCreated": "01:05:21"
},
{
"id": "1011",
"name": "xyz",
"Dob": "3/2/11",
"timeCreated": "17:03:41"
},
{
"id": "1011",
"name": "xyz",
"Dob": "3/2/11",
"timeCreated": "15:03:41"
}
]
I am removing duplicates in the array by using some()
let arr = [];
data.forEach(obj => {
if (!arr .some(o => o.id === obj.id)) {
arr.push({ ...obj})
}
});
I need help filtering it and only keeping the latest object based off of "timeCreated"
so the data looks something like this:
{
"id": "1012",
"name": "xys",
"Dob": "6/5/12",
"timeCreated": "01:05:21"
},
{
"id": "1011",
"name": "xyz",
"Dob": "3/2/11",
"timeCreated": "17:03:41"
},
]
you can do that :
const data =
[ { id: '1011', name: 'abc', Dob: '3/2/11', timeCreated: '16:03:41' }
, { id: '1012', name: 'xys', Dob: '6/5/12', timeCreated: '01:05:21' }
, { id: '1011', name: 'xyz', Dob: '3/2/11', timeCreated: '17:03:41' }
, { id: '1011', name: 'xyz', Dob: '3/2/11', timeCreated: '15:03:41' }
]
const arr = data.reduce((result,obj)=>
{
let row = result.find(x=>x.id===obj.id)
if (!row)
result.push({...obj})
else if (row.timeCreated < obj.timeCreated)
Object.assign(row,obj)
return result
},[])
console.log( arr )
.as-console-wrapper {max-height: 100%!important;top:0 }
Late to the party, but here's a shorter, maybe faster, more efficient solution that just involves a sort and filter operation
let tmp=[], arr = data.sort((a, b) => +b.timeCreated.replaceAll(':', '') - +a.timeCreated.replaceAll(':', ''))
.filter(o => (!tmp.includes(o.id) && tmp.push(o.id)));
How it works: Pretty simply actually. It first sorts the array by timeCreated descending. It does this by (on the fly) transforming the 'HH:MM:SS' string into the number HHMMSS (+b.timeCreated.replaceAll(':', '')), then comparing. Then it takes the sorted array and filters it through the temporary array tmp, which stores ids each iteration - and if the id is already in there (and we know that is the latest according to the timeCreated) we filter it out. This is all handled by the wonderfully simple ternary: .filter(o => (!tmp.includes(o.id) && tmp.push(o.id)), which says if we've already seen that id, return false, otherwise make a note of it
Why it's cool - For most use cases (small data sets), there isn't a significant difference between functional iterators like map, reduce, forEach, filter, sort - however this is thinking out of the box. Rather than build datasets and reduce them down, this smartly chops it to size first - using only 2 operations.
const data = [{ id: '1011', name: 'abc', Dob: '3/2/11', timeCreated: '16:03:41' }, { id: '1012', name: 'xys', Dob: '6/5/12', timeCreated: '01:05:21' }, { id: '1011', name: 'xyz', Dob: '3/2/11', timeCreated: '17:03:41' }, { id: '1011', name: 'xyz', Dob: '3/2/11', timeCreated: '15:03:41' }]
let tmp=[], arr = data.sort((a, b) => +b.timeCreated.replaceAll(':', '') - +a.timeCreated.replaceAll(':', '')).filter(o => (!tmp.includes(o.id) && tmp.push(o.id)));
console.log(arr)

Javascript .find() not working for nested object array [duplicate]

This question already has answers here:
Curly Brackets in Arrow Functions
(3 answers)
Closed 1 year ago.
I have two nested object arrays, one is an array describing a school (id, name, tests it conducts along with their counts), and another is an array of school teachers corresponding to the schools in the first array. Here is some sample data for both arrays:
import { ObjectId } from 'mongodb'
monthlySchoolTests = [{
schoolId: ObjectId('804d8527f390ghz26e3426j6'),
schoolName: "School1"
schoolTests: [
{ testName: "Chemistry", count: 15 },
{ testName: "Music", count: 8 }
]
},
{
schoolId: ObjectId('804ef074384b3d43f125ghs5'),
schoolName: "School2"
schoolTests: [
{ testName: "Math", count: 3 },
{ testName: "Physics", count: 12 },
{ testName: "Biology", count: 10 }
]
}
]
schoolTeachers = [{
schoolId: ObjectId('804ef074384b3d43f125ghs5')
schoolName: "School2"
teachers: [
{ name: "Michael", email: "michael#school2.edu" },
{ name: "Jane", count: "jane#school2.edu" },
{ name: "Lukas", count: "lukas#school2.edu" }
]
},
{
schoolId: ObjectId('804d8527f390ghz26e3426j6')
schoolName: "School1"
teachers: [
{ name: "Cleo", email: "cleo#school1.edu" },
{ name: "Celine", count: "celine#school1.edu" },
{ name: "Ike", count: "ike#school1.edu" }
]
}
]
I obtained the second array by passing as an argument an array of schoolIds that I got from the query results for the first array, so I know that both arrays have about the same number of objects (assuming queries don't return null values). I am trying to link the test information in the first array with the school teacher in the second array but the .find() method is not working for me. Here is my code:
monthlySchoolTests.map(testObj => {
const selectedSchoolTeachers = schoolTeachers.find(teacherObj => {
String(testObj.schoolId) === String(teacherObj.schoolId)
})
console.log(selectedSchoolTeachers)
})
For some reason, I only get undefined even though I have tested this by mapping through each object in the first array and asserting that there is a match for every schoolId in the second array. (console.log yields true).
Sorry for any errors as this is my first time posting. Would appreciate any help!
Well, you could just quick-generate a combined array that had everything you need in it:
let combined = monthlySchoolTests.map(e => ({...e, teachers: schoolTeachers.filter(t => t.schoolId === e.schoolId)[0].teachers}))
const ObjectId = (v) => v; // for testing
let monthlySchoolTests = [{
schoolId: ObjectId('804d8527f390ghz26e3426j6'),
schoolName: "School1",
schoolTests: [
{ testName: "Chemistry", count: 15 },
{ testName: "Music", count: 8 }
]
},
{
schoolId: ObjectId('804ef074384b3d43f125ghs5'),
schoolName: "School2",
schoolTests: [
{ testName: "Math", count: 3 },
{ testName: "Physics", count: 12 },
{ testName: "Biology", count: 10 }
]
}
]
let schoolTeachers = [{
schoolId: ObjectId('804ef074384b3d43f125ghs5'),
schoolName: "School2",
teachers: [
{ name: "Michael", email: "michael#school2.edu" },
{ name: "Jane", count: "jane#school2.edu" },
{ name: "Lukas", count: "lukas#school2.edu" }
]
},
{
schoolId: ObjectId('804d8527f390ghz26e3426j6'),
schoolName: "School1",
teachers: [
{ name: "Cleo", email: "cleo#school1.edu" },
{ name: "Celine", count: "celine#school1.edu" },
{ name: "Ike", count: "ike#school1.edu" }
]
}
]
let combined = monthlySchoolTests.map(e => ({...e, teachers: schoolTeachers.filter(t => t.schoolId === e.schoolId)[0].teachers}))
console.log(combined)

How to efficiently construct new object array using unique values from arrays of children inside an array of parent objects

The need is to take objects like this:
[ { "first":
{ "children" : [{ "name": "abc", "detail":"123"},
{ "name": "def", "detail":"456"}
]
}},
{ "second":
{ "children" : [{ "name": "ghi", "detail":"123"},
{ "name": "jkl", "detail":"456"}
]
}},
{ "third":
{ "children" : [{ "name": "mno", "detail":"123"},
{ "name": "pqr", "detail":"456"}
]
}},
{ "fourth":
{ "children" : [{ "name": "stu", "detail":"123"},
{ "name": "vwx", "detail":"456"}
]
}},
{ "fifth":
{ "children" : [{ "name": "yz", "detail":"123"},
{ "name": "abc", "detail":"456"}
]
}},
{ "sixth":
{ "children" : [{ "name": "def", "detail":"123"},
{ "name": "ghi", "detail":"456"}
]
}}
]
and then create a flattened array of unique values (options for a select) from the name field of the children that looks like this:
[{"value":"abc", "label":"abc"},
{"value":"def", "label":"def"},
{"value":"ghi", "label":"ghi"},
{"value":"jkl", "label":"jkl"},
{"value":"mno", "label":"mno"},
{"value":"pqr", "label":"pqr"},
{"value":"stu", "label":"stu"},
{"value":"vwx", "label":"vwx"},
{"value":"yz", "label":"yz"}
]
The code below is working, but it looks like it is inefficient because it appears to make many passes over the array:
[
...new Set(
[].concat.apply([], bases.map((base) => {
if (!base.children || base.children.length === 0) return;
return base.children}
)).map((child) => child.name)
)
].map((optName) => {return {value: optName, label: optName};})
If it is possible, how can this same result be achieved without as many iterations across the array.
Firstly, as a rule of thumb, you shouldn't worry too much about performance until you have a reason to do so.
Secondly, chaining the array prototype functions (e.g. map, forEach, filter) will require multiple iterations by design.
Thirdly, there's no reason to assume multiple iterations is slower than a single iteration if the work done within the iterations is the same anyways. I.e. incrementing an index and comparing it with an array length isn't going to be the bottleneck compared to pushing objects into arrays and check set entries.
Here's a (IMO) cleaner snippet to extract unique names from your array:
let bases = [{
children: [{
name: "abc",
detail: "123"
},
{
name: "def",
detail: "456"
}
]
}, {
children: [{
name: "abc" ,
detail: "123"
},
{
name: "xyz" ,
detail: "456"
}
]
},
{}
];
let output = bases
.flatMap(b => b.children || [])
.map(c => c.name)
.filter((v, i, a) => a.indexOf(v) === i) // filter unique values
.map(name => ({
value: name,
label: name,
}));
console.log(output);
Now if you really want to do all this in a single iteration, that too is possible, but harder to read:
let bases = [{
children: [{
name: "abc",
detail: "123"
},
{
name: "def",
detail: "456"
}
]
}, {
children: [{
name: "abc" ,
detail: "123"
},
{
name: "xyz" ,
detail: "456"
}
]
},
{}
];
let output = [];
let seenNames = {};
for (base of bases) {
if (!base.children)
continue;
for (child of base.children) {
let name = child.name;
if (seenNames[name])
continue;
seenNames[name] = true;
output.push({
value: name,
label: name,
});
}
}
console.log(output);
You could take Array#flatMap for getting a flat representation of data for using unique values and map new objects.
var data = [{ first: { children: [{ name: "abc", detail: "123" }, { name: "def", detail: "456" }] } }, { second: { children: [{ name: "ghi", detail: "123" }, { name: "jkl", detail: "456" }] } }, { third: { children: [{ name: "mno", detail: "123" }, { name: "pqr", detail: "456" }] } }, { fourth: { children: [{ name: "stu", detail: "123" }, { name: "vwx", detail: "456" }] } }, { fifth: { children: [{ name: "yz", detail: "123" }, { name: "abc", detail: "456" }] } }, { sixth: { children: [{ name: "def", detail: "123" }, { name: "ghi", detail: "456" }] } }],
result = Array.from(
new Set(data
.flatMap(Object.values)
.flatMap(({ children }) => children.map(({ name }) => name))
),
value => ({ value, label: value })
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Filtering object based on partial string matching in JavaScript

I'm trying to sort this JavaScript object so only certain rows are returned, the object:
const baseHeroes = [
{ name: "Batman", powers: ["Strength", "Intelligence"] },
{ name: "Superman", powers: ["Strength", "Flying"] },
{ name: "Spiderman", powers: ["Spider Sense", "Webs"] },
{ name: "The Flash", powers: ["Speed"] },
{ name: "Captain America", powers: ["Strength", "Shield"] },
];
I want to return only the rows that have the power "Strength" in the powers section, but to return the full row with all data, so I'd want to have this when finished:
const strongHeroes = [
{ name: "Batman", powers: ["Strength", "Intelligence"] },
{ name: "Superman", powers: ["Strength", "Flying"] },
{ name: "Captain America", powers: ["Strength", "Shield"] },
];
Any help would be greatly appreciated.
Array.filter is your friend here. Alongside that, .includes is also your friend:
Array.filter
Array.includes
const baseHeroes = [
{ name: "Batman", powers: ["Strength", "Intelligence"] },
{ name: "Superman", powers: ["Strength", "Flying"] },
{ name: "Spiderman", powers: ["Spider Sense", "Webs"] },
{ name: "The Flash", powers: ["Speed"] },
{ name: "Captain America", powers: ["Strength", "Shield"] },
];
const results = baseHeroes.filter( h => h.powers.includes('Strength') );
console.log(results);
There are other things you might want to think about such as case sensitivity and the possibility of .powers being null or something other than an array, but this should give you a general idea of how to go about it.
I'm extremely new to JavaScript, but the following seems to work.
const strongHeroes = [];
const baseHeroes = [
{ name: "Batman", powers: ["Strength", "Intelligence"] },
{ name: "Superman", powers: ["Strength", "Flying"] },
{ name: "Spiderman", powers: ["Spider Sense", "Webs"] },
{ name: "The Flash", powers: ["Speed"] },
{ name: "Captain America", powers: ["Strength", "Shield"] },
];
for (var i = 0; i < baseHeroes.length; i++) {
if (baseHeroes[i].powers.indexOf("Strength") != -1) {
strongHeroes.push(baseHeroes[i]);
}
}
Basically it searches through the original array, and if the powers contains the string "Strength" it returns the location of the start of Strength, and if it doesn't find it, returns -1. So if we find Strength in the powers, push that array to a new array.

filter array for unique items with item count

Helo,
I am using the following code to return a unique list of categories.
stadium_cats: function () {
let stadiums =[
{"name":"Elland road","category":"football","country":"England"},
{"name":"Principality stadiums","category":"rugby","country":"Wales"},
{"name":"Twickenham","category":"rugby","country":"England"},
{"name":"Eden Park","category":"rugby","country":"New Zealand"}
];
var categories = stadiums.map(function(obj) {return obj.category});
categories = categories.filter(function(v,i) {return categories.indexOf(v) == i; });
return categories;
}
When I run the above I get an array of unique stadium.category values.
Could someone help me extend it, so that instead of an array returned I get an array of objects as follows:
[{"name":"football","count":"1"}{"name":"rugby","count":"3"}]
I would like both the name and how many times it was featured?
Is this at all possible?
Many thanks,
Dave
You can do this with forEach loop and object as optional parameter.
let stadiums = [{"name":"Elland road","category":"football","country":"England"},{"name":"Principality stadiums","category":"rugby","country":"Wales"},{"name":"Twickenham","category":"rugby","country":"England"},{"name":"Eden Park","category":"rugby","country":"New Zealand"}];
var result = [];
stadiums.forEach(function(e) {
if(!this[e.category]) {
this[e.category] = {name: e.category, count: 0}
result.push(this[e.category]);
}
this[e.category].count++;
}, {});
console.log(result);
Just one line with ES6:
let stadiums = [{ name: 'Elland road', category: 'football', country: 'England' }, { name: 'Principality stadiums', category: 'rugby', country: 'Wales' }, { name: 'Twickenham', category: 'rugby', country: 'England' }, { name: 'Eden Park', category: 'rugby', country: 'New Zealand' }];
let result = [...new Set(stadiums.map(s => s.category))].map(c => ({ name: c, count: stadiums.filter(s => s.category === c).length }));
console.log(result);
function stadium_cats() {
let stadiums = [{
"name": "Elland road",
"category": "football",
"country": "England"
}, {
"name": "Principality stadiums",
"category": "rugby",
"country": "Wales"
}, {
"name": "Twickenham",
"category": "rugby",
"country": "England"
}, {
"name": "Eden Park",
"category": "rugby",
"country": "New Zealand"
}];
var cat_arr = [];
var cats = {};
var stadium;
for (var i = 0; i < stadiums.length; i++) {
stadium = stadiums[i];
if (typeof cats[stadium.category] == 'undefined') {
cats[stadium.category] = {
name: stadium.category,
count: 0
};
cat_arr.push(cats[stadium.category]);
}
cats[stadium.category].count++;
}
return cat_arr;
}
console.log(stadium_cats());
While you asked for an ES6 version, you could use a closure for this.
let stadiums = [{ name: "Elland road", category: "football", country: "England" }, { name: "Principality stadiums", category: "rugby", country: "Wales" }, { name: "Twickenham", category: "rugby", country: "England" }, { name: "Eden Park", category: "rugby", country: "New Zealand" }],
result = [];
stadiums.forEach((hash => a => {
if (!hash[a.category]) {
hash[a.category] = { name: a.category, count: 0 };
result.push(hash[a.category]);
}
hash[a.category].count++;
})(Object.create(null)));
console.log(result);

Categories