I'm practicing my javascript and I've come across the following array.
const people = [
{
name: 'Carly',
yearOfBirth: 2018,
},
{
name: 'Ray',
yearOfBirth: 1962,
yearOfDeath: 2011
},
{
name: 'Jane',
yearOfBirth: 1912,
yearOfDeath: 1941
},
]
i'm trying to find the oldest person in the array but I keep getting the wrong person.here's my code
let findTheOldest = function(people) {
const oldest = people.sort((a,b) => (a.yearOfDeath - a.yearOfBirth) > (b.yearOfDeath - b.yearOfBirth) ? -1 : 1);
return oldest[0];
}
so it keeps saying that 'Carly' is the oldest person rather than 'Ray'? How would I go about it? note that 'Carly' has no yearOfDeath and therefore she is still alive.
You can use reduce, and use the current year for people without a death date:
const people = [{name:"Carly",yearOfBirth:2018},{name:"Ray",yearOfBirth:1962,yearOfDeath:2011},{name:"Jane",yearOfBirth:1912,yearOfDeath:1941}];
const findTheOldest = function(people) {
const thisYear = new Date().getFullYear();
return people.reduce((res, person) => {
const age = (person.yearOfDeath || thisYear) - person.yearOfBirth;
return age > res.age ? { person, age } : res;
}, { person: null, age: 0 }).person;
}
console.log(findTheOldest(people)); // Ray
As an Engineer, most proofs I study at college assume something and we work it out, then at the end of the proof, Maths will tell you if your assumption was right.
We'll assume we have a function called getAge(person) that has a signature as follows.
// this performance improvement of storing the year was suggested by #blex
let currentYear = new Date().getFullYear();
let getAge = (person) => {
return (person.yearOfDeath ? person.yearOfDeath : currentYear) - person.yearOfBirth
};
Basically, if the person doesn't have a .yearOfDeath, he's still alive and the current year 2020 at the time of writing this answer.
and we have a getOldest(people) that has a signature as follows.
let getOldest = people => {
/** Keep in mind that people can be mutated and you
need to avoid this here
*/
// An assumption. It can be right or wrong.
let oldest_person = people[0];
// The first method (slice) returns a shallow copy
// the second one (splice) removes the oldest_person
// removing it makes the loop count decrease by one. Nothing else.
// we don't need a deep copy, we won't alter the people.
people = (people.slice()).splice(1);
// You could save the getAge(oldest_person) in a variable
// instead of computing it each time
// I wanted this to be as readable as possible.
for (let person of people){
if (getAge(person) > getAge(oldest_person)){
// Congrats! we have a new older person!
oldest_person = person;
}
}
return oldest_person;
};
This has a worst-case time complexity of o(n).
For illustration, let's benchmark this.
let people = []
let init = () => {
let randomInteger = (min, max) => {
// return random integer between min, max, found this on stackoverflow
return Math.floor(Math.random() * (max - min + 1)) + min;
}
for (let i = 0; i < 10000000; i++){ // 10m person
let dateOfBirth = parseInt('19' + randomInteger(10,99));
let dateOfDeath = parseInt('20' + randomInteger(10, 99));
let person = {
name: `person_${i}`,
dateOfBirth, // same as dateOfBirth: dateOfBirth,
dateOfDeath, // same logic
}
people.push(person); // add it to people
}
}
init();
start = performance.now(); // time in millisecs
getOldest(people);
end = performance.now(); // time in millisecs after getting the oldest person
console.log((end - start ) * Math.pow(10, -3)) // time elapsed is around 0.2 secs.
To use sort to find the oldest, you need to include a default specifying the current year for people without a yearOfDeath. Below I've done this in a helper function called "age".
Using sort if your only purpose is to find a maximum can be inefficient though, particularly if you're dealing with a lot of data: try using reduce, as per other answer.
const people = [
{
name: "Carly",
yearOfBirth: 2018,
},
{
name: "Ray",
yearOfBirth: 1962,
yearOfDeath: 2011,
},
{
name: "Jane",
yearOfBirth: 1912,
yearOfDeath: 1941,
},
];
let findTheOldest = function (people) {
const age = (x) => (x.yearOfDeath || new Date().getFullYear()) - x.yearOfBirth;
const oldest = people.sort((a, b) =>
age(a) > age(b) ? -1 : 1
);
return oldest[0];
};
console.log(findTheOldest(people));
Related
Let's start with an example.
I have a list of fruits and it's nutritions stored in JS objects. Then I get a vegetable which is not in the list but has same types of values as the keys in the fruits object.
How can I get the closest fruit from the fruit object to the vegetable given vegetable if 1) the nutrition values (sugar,salt,...) has same values (sugar = salt) 2) the nutrition values (sugar,salt,...) have different values (sugar > salt, so basicly check only for sugar). I know that's not a good explanation, but let's check the example below.
let fruits = {
apple:{
sugar:12,
salt:5
},
banana:{
sugar:13,
salt:3
},
}
let cucumber = {
sugar:12,
salt:3
}
let closestFruitToCucumber = closest(cucumber, fruits)
// apple (if checking only sugar)
// banana (if both sugar and salt are valued the same)
function closest(vegetable,fruitList){
// here is the part which I am looking for.
}
I can post the code, which I have tried. There were plenty of them but none of them worked at all.
Following up on my comment:
I guess that you want to calculate the absolute difference for every matching key in the objects, and then sum these differences for each comparison, finally selecting the comparison with the lowest sum.
I'd use a functional approach so that you can refine your result by a query parameter (property key(s)). This approach will continue to work even if the two objects don't share the same property keys, and it will also allow you to easily refactor if the standard property keys change in your data:
const defaultKeys = ['salt', 'sugar'];
function getSimilarityScore (a, b, keys = defaultKeys) {
let score = 0;
for (const key of keys) score += Math.abs((a[key] ?? 0) - (b[key] ?? 0));
return score;
}
function closest (objList, obj, keys = defaultKeys) {
let minScore = Number.POSITIVE_INFINITY;
let result;
for (const o of Object.values(objList)) {
const score = getSimilarityScore(obj, o, keys);
if (score >= minScore) continue;
minScore = score;
result = o;
}
return result;
}
const fruits = {
apple: {
salt: 5,
sugar: 12,
},
banana: {
salt: 3,
sugar: 13,
},
};
const cucumber = {
salt: 3,
sugar: 12,
};
const closestToCucumber = closest(fruits, cucumber);
const closestToCucumberBySalt = closest(fruits, cucumber, ['salt']);
const closestToCucumberBySugar = closest(fruits, cucumber, ['sugar']);
console.log(closestToCucumber === fruits.banana); // true
console.log(closestToCucumberBySalt === fruits.banana); // true
console.log(closestToCucumberBySugar === fruits.apple); // true
You can implement a function for this purpose and compute the sum of absolute differences, finding the minimum such sum and returning the fruit corresponding this value. I returned its name, but if you want a different return format, let me know.
let fruits = {
apple:{
sugar:12,
salt:5
},
banana:{
sugar:13,
salt:3
},
}
let cucumber = {
sugar:12,
salt:3
}
function getClosestFruit(fruits, vegetable) {
let output = undefined;
let score = 0;
for (let key in fruits) {
let fruit = fruits[key];
let currentScore = Math.abs(fruit.sugar - vegetable.sugar) + Math.abs(fruit.salt - vegetable.salt);
if ((!output) || (score > currentScore)) {
output = key;
score = currentScore;
}
}
return output;
}
console.log(getClosestFruit(fruits, cucumber));
What is the best way to loop over a collection of data. The problem i am facing is the low performance. See the example code snippets. Following two methods has the performance issue.
interface Day {
date: number;
disabled: boolean;
}
// sample data
const monthDays: Day[] = Array.from({ length: 30 }, (v: unknown, k: number) => ({ date: k + 1, disabled: false }));
const disabledDates: number[] = Array.from({ length: 30 }, (v, k) => k + 1);
//set disabled dates
// method 1
let counter = 0;
for (let day of monthDays) {
day.disabled = disabledDates.some(d => d === day.date);
counter++;
}
console.log(counter) // logs 30
// method 2
counter = 0;
for (let day of monthDays) {
for (let date of disabledDates) {
counter++;
if (day.date === date) {
day.disabled = true;
break;
}
}
}
console.log(counter); // logs 494
In the app i am working , i need to iterate on array 3 to 4 times . This results in low performance of the app. Can anyone suggest whats is the best way to loop.
Instead of checking each element again with .some you can spread your values into an Set and check with set.has() if its inside witch is much faster
Your time complexity drops from O(n^2) to O(n)
// sample data
let monthDays = Array.from({ length: 10000 }, (v, k) => ({ date: k + 1, disabled: false }));
const disabledDates = Array.from({ length: 10000 }, (v, k) => k + 1);
//set disabled dates
// method 1
let start = performance.now()
for (let day of monthDays) {
day.disabled = disabledDates.some(d => d === day.date);
}
console.log(performance.now() - start);
//reset
monthDays = Array.from({ length: 10000 }, (v, k) => ({ date: k + 1, disabled: false }));
start = performance.now();
let set = new Set([ ...disabledDates ]);
for (let day of monthDays) {
if(set.has(day.date)) {
day.disabled = true;
}else {
day.disabled = false;
}
}
console.log(performance.now() - start);
You have the exact same performance characteristics with both method 1 and method 2, however, your way of measuring them is flawed. With method 2, you count the outer and inner iterations made by the code, while with method 1, you only count the outer iterations. However, .some() still does an iteration over the data:
// sample data
const monthDays = Array.from({ length: 30 }, (v, k) => ({ date: k + 1, disabled: false }));
const disabledDates = Array.from({ length: 30 }, (v, k) => k + 1);
//set disabled dates
// method 1
let counter = 0;
for (let day of monthDays) {
day.disabled = disabledDates.some(d => {
counter++;
return d === day.date
});
counter++;
}
console.log(counter) // logs 495
See also on TypeScript Playground
Since you have two nested iterations, both methods exhibit an O(n*m) time complexity, where n is the length of monthDays and m is the length of disabledDates. This is basically quadratic complexity for close values of n and m and actually quadratic for n = m (as is the example).
The way to correct that is to eliminate the nested loops and only process the data once. This is easily achievable by first pre-computing a Set object that contains all disabledDates, that way you don't have to loop over an array only to check if they exist instead using Set#has.
// sample data
const monthDays = Array.from({ length: 30 }, (v, k) => ({ date: k + 1, disabled: false }));
const disabledDates = new Set(Array.from({ length: 30 }, (v, k) => k + 1));
for (const day of monthDays) {
day.disabled = disabledDates.has(day.date);
}
console.log(monthDays);
See also on TypeScript Playground
The one time conversion to a set has a complexity of O(m) and then each time you loop over you only have an O(n) iteration.
If you can create set once only, then repeatedly iterating monthDays don't need to include that operation. Thus the complexity of the operation becomes O(n).
If you need to create a set every time before you loop, then the complexity becomes an O(n+m) which is still better than quadratic and essentially linear.
As a note here, this assumes that .has() is a O(1) operation. This is a reasonable assumption that probably holds true most of the time. However, technically, that's not guaranteed - the specifications define a sub-linear complexity on set operations, so you might have O(log n) complexity, which means that the iteration over monthDays and looking up disabledDates will be O(n * log m). Even then, using a set is probably the easiest optimisation path. If performance is still a problem, then perhaps generating an object as a lookup table could be better:
//this should be generated
const lookup: Record<number, true> = {
1: true,
2: true,
3: true,
/*...*/
}
/*...*/
//fetch from lookup or set `false` if not in the lookup
const disabled: boolean = lookup[day.date] ?? false;
I need to have a method which needs to check in the valid range between -064.000000 to -180.000000 and 142.000000 to 180.000000. The ranges object that I have looks like the following:
"ranges": {
"range1": {
"min": -180,
"max": -64
},
"range2": {
"min": 142,
"max": 180
}
}
So far, this is what I was able to complete but it doesn't seem to work right:
const mustBeInRangeInclusive = ({ userInput, ranges }) => {
let data_num = _.toNumber(userInput);
for (let i = 0; i < ranges.length; i++) {
return result = data_num >= ranges[i][0] && data_num <= ranges[i][1];
}
};
Can someone please help me complete this method to figure out what am I doing wrong?
Expected output:
-63 -> invalid
-64: valid
181 -> invalid
180: valid
141 -> invalid
142: valid
Few edits, as the question code keeps changing a bit.
First problem - accessing objects properties
If you have an array, you access its values by indeces.
let array = [1, 2, 3];
let firstItem = array[0];
let secondItem = array[1];
If you have object, you access its propeties by their names.
let someObject = { 'name': 'John', 'age': 21 };
let name = someObject.name;
name = someObject['name'];
If you have array of objects, you combine both methods.
let arrayOfObjects = [
{ 'name': 'John', 'age': 21 },
{ 'name': 'Sam', 'age': 23 }
]
let firstObjectsName = arrayOfObjects[0].name;
Second problem - exiting the loop on the first iteration
You call return statement as soon as you enter the loop making it impossible to enter the second iteration. You could store result of each iteration in the array and return it in the end.
const mustBeInRangeInclusive = ({ userInput, ranges }) => {
let results = [];
let data_num = _.toNumber(userInput);
for (let i = 0; i < ranges.length; i++) {
results.push(data_num >= ranges[i].min && data_num <= ranges[i].max);
}
return results;
};
This answer based upon the comment request of OP, and does not solve the issue if filters would be an object.
Assuming you can change the definition, using an array of filters would be a lot easier to work with than an object with filters. You can use the array every method to check if every elements matches a criteria. Use some to check if some (one or more) elements matches a criteria.
const userInput = document.getElementById("user-input");
const ranges = [ // <- changed definition to an array
{ "min": 0, "max": 100 },
{ "min": 50, "max": 150 },
];
userInput.addEventListener("change", () => {
const nr = parseInt(userInput.value, 10);
const coversNr = ({min, max}) => min <= nr && nr <= max;
const withinEveryRange = ranges.every(coversNr);
const withinSomeRange = ranges.some(coversNr);
console.log("withinEveryRange //=>", withinEveryRange);
console.log("withinSomeRange //=>", withinSomeRange );
});
<input id="user-input" type="number" />
I am trying to make sure the order of dates entered in an object are in a logical order. Here is my code:
function checkDates(pet) {
const dates = [
pet.birthDate,
pet.saleDate,
pet.acquisitionDate,
pet.deathDate
].filter( (date) => {
// filter out undefined items
return date;
});
// list of dates in their chronological order
const sortedDates = dates.slice(0).sort();
const inOrder = dates.every( (date, i) => {
// check to make sure entered date is the same as the chronological date
return date === sortedDates[i];
});
if (!inOrder) {
throw new ValidationError('The dates are in an illogical order');
}
}
The problem is that saleDate and acquisitionDate do not need to be in that order (as defined in the dates array) - they just need to be more than birthDate and less than deathDate. The different dates are not required, for example, the pet object that gets passed through my look like this:
const pet = {
name: "Sam",
birthDate: "2017-01-01",
acquisitionDate: "2017-02-01",
saleDate: "2017-03-01"
}
Further clarification: If present, birthDate must always come first, and deathDate must always come last. Sale and acquisition must be between birth and death date (if they are present), otherwise, it doesn't matter if sale comes before acquisition or vice-versa.
You are on the right path, but sorting isn't necessarily required:
function checkDates(pet) {
const dates = [
pet.birthDate,
pet.saleDate,
pet.acquisitionDate,
pet.deathDate
].filter(date => date);
const inOrder =
(pet.birthDate ? dates.every(date => date >= pet.birthDate) : true) &&
(pet.deathDate ? dates.every(date => date <= pet.deathDate) : true)
if (!inOrder) {
throw new ValidationError('The dates are in an illogical order');
}
}
You could just iterate the given array, without sorting, becaue all dates have to be in order.
function check({ birthDate, acquisitionDate, saleDate, deathDate }) {
return [birthDate, acquisitionDate, saleDate, deathDate]
.filter(Boolean)
.every((a, i, aa) => !i || aa[i - 1] <= a);
}
console.log(check({ name: "Sam", birthDate: "2017-01-01", acquisitionDate: "2017-02-01", saleDate: "2017-03-01" }));
console.log(check({ name: "Sam", birthDate: "2018-01-01", acquisitionDate: "2017-02-01", saleDate: "2017-03-01" }));
Assuming the deathDate is the only optional property here, but it appears it could be done in two checks:
birthDate is less than the minimum of acquisitionDate/saleDate
if deathDate, deathDate is greater than the maximum of acquisitionDate/saleDate
Something along these lines:
function checkDates(pet) {
const inOrder = (
pet.birthDate < Math.min.apply(null, [pet.acquisitionDate, pet.saleDate]) &&
pet.deathDate ? pet.deathDate > Math.max.apply(null, [pet.acquisitionDate, pet.saleDate]) : true
);
if (!inOrder) {
throw new ValidationError('The dates are in an illogical order')
}
return true;
}
const pet = {
birthDate: "2017-01-01",
acquisitionDate: "2017-02-01",
saleDate: "2017-03-01"
};
console.log(checkDates(pet));
If other properties are optional that would change things a bit but not too drastically.
You are saying that the order of both saleDate and acquisitionDate is not important they just need to be higher than birthDate and lower than deathDate, in that case you can simplify your function to do only these four checks:
function checkDates(pet) {
var birthDate = new Date(pet.birthDate);
var saleDate = new Date(pet.saleDate);
var acquisitionDate = new Date(pet.acquisitionDate);
var deathDate = pet.deathDate ? new Date(pet.deathDate) : Infinity;
var inOrder = (birthDate < saleDate) && (birthDate < acquisitionDate) && (saleDate < deathDate) && (acquisitionDate < deathDate);
if (!inOrder) {
throw new ValidationError('The dates are in an illogical order');
}
}
There's no need for using an array and for the sorting and the looping operations, it's really useless.
Demo:
function checkDates(pet) {
var birthDate = new Date(pet.birthDate);
var saleDate = new Date(pet.saleDate);
var acquisitionDate = new Date(pet.acquisitionDate);
var deathDate = pet.deathDate ? new Date(pet.deathDate) : Infinity;
var inOrder = (birthDate < saleDate) && (birthDate < acquisitionDate) && (saleDate < deathDate) && (acquisitionDate < deathDate);
if (!inOrder) {
throw new ValidationError('The dates are in an illogical order');
}
}
console.log(checkDates({
name: "Sam",
birthDate: "2018-01-01",
acquisitionDate: "2017-02-01",
saleDate: "2017-03-01"
}));
This question already has answers here:
What is the fastest or most elegant way to compute a set difference using Javascript arrays?
(14 answers)
Closed 9 years ago.
I have arrays like;
var john = { name: "John Smith", age: 23 };
var mary = { name: "Mary Key", age: 18 };
var bob = { name: "Bob-small", age: 6 };
var people = [john, mary, bob];
var john2 = { name: "John Smith", age: 23 };
var people2 = [john2];
What I would like to do is subtract people2 from people and get result;
[mary, bob];
How can I achieve this?
TIA
The difference of two sets, A and B, is defined as the set of all those elements of A which are not in B. If we implement it naively, computing the difference of two sets of sizes m and n respectively would take O(m * n) time. Not very efficient:
const john1 = { name: "John Smith", age: 23 };
const john2 = { name: "John Smith", age: 23 };
const mary = { name: "Mary Key", age: 18 };
const bob = { name: "Bob-small", age: 6 };
const people1 = [john1, mary, bob];
const people2 = [john2];
const eqPerson = (p, q) => p.name === q.name && p.age === q.age;
const result = people1.filter(p => people2.every(q => !eqPerson(p, q)));
console.log(result); // [mary, bob]
Fortunately, there's a faster way to compute the set difference for large sets using hashing.
const john1 = { name: "John Smith", age: 23 };
const john2 = { name: "John Smith", age: 23 };
const mary = { name: "Mary Key", age: 18 };
const bob = { name: "Bob-small", age: 6 };
const people1 = [john1, mary, bob];
const people2 = [john2];
const hashPerson = ({ name, age }) => `${name} ${age}`;
const hashSet = new Set(people2.map(hashPerson));
const result = people1.filter(p => !hashSet.has(hashPerson(p)));
console.log(result); // [mary, bob]
The advantage is that creating a hash set takes O(n) time and calculating the difference takes O(m) time. Hence in total it only takes O(m + n) time instead of O(m * n) time. In addition, you can reuse the hash set in the future.
Here is an easy solution:
var diff = people.filter(function(item) {
return !people2.some(function(test){
return test.name === item.name && test.age === item.age;
});
});
Make sure the function passed to people2.some correctly checks that the two objects are equal, since == would fail, as you have references to different objects with identical properties.
So, here goes the final running code. Copy , paste it and check for the output.It is first of all separating the strings (comma-separation). Then it is comparing them individually . Check this link: http://jsfiddle.net/DXRZ4/
<html>
<head>
<script>
var a = new Array(); var i=1; var people = new Array();
function fun() {
var str = "john,john,bob",
l = str.split(",");
for(i=0; i<3; i++) {
a[i] = l[i];
// this will show the comma separated strings
document.writeln(l[i]);
}
for(i=0; i<3; i++) {
t=a[i];
j=a[i+1];
if((a[i]==a[i+1]) || (a[i]==a[i+2])) {
// it will store the position of string which are same
p=i;
}
}
for(i=0; i<3; i++) {
if(p!=i){
document.writeln("Subtracted Strings are:-");
document.writeln(l[i]);
}
}
}
</script>
<body>
<input type="button" name="b1" onClick="fun()">
</body>
</html>