I'm trying to think of a way to make this code simple, with the smallest amount of loops and variables, but I'm having trouble.
I want to get the average object in the array 'numbers', based on the 'value'. I feel that there must be a mathematical way to get the average without finding the closest average in another loop.
Currently I have this mess:
var numbers = [
{ value: 41 },
{ value: 19 },
{ value: 51 },
{ value: 31 },
{ value: 11 }
];
// Find average:
var sum = 0;
for (var i = 0; i < numbers.length; i++) {
sum += numbers[i].value;
}
var average = sum / numbers.length;
// Find closest object to average:
var match, difference;
for (var j = 0; j < numbers.length; j++) {
const diff = Math.abs(average - numbers[j].value);
if (difference === undefined || diff < difference) {
difference = diff;
match = numbers[j];
}
}
// Print junk:
console.log("AVERAGE NUMBER: " + average);
console.log("CLOSEST OBJECT: " + match);
console.log("CLOSEST NUMBER: " + match.value);
I need to retrieve the object because it contains other information that I need to use.
Any help would be highly appreciated!
At least you need two loops for getting the average and the closest item, because you need to visit all items in the first run and you do not have the possibillity to store in advance which item has the closest value.
You could get first the average and then reduce the closest value with the object.
var numbers = [{ value: 41 }, { value: 19 }, { value: 51 }, { value: 31 }, { value: 11 }, { value: 30 }],
average = numbers.reduce((sum, { value }) => sum + value, 0) / numbers.length,
closest = numbers.reduce((a, b) =>
Math.abs(average - a.value) <= Math.abs(average - b.value)
? a
: b
);
console.log(average);
console.log(closest);
Related
I'm trying to write a function that receives a string of integers -I know this is not the ideal but it is how I was asked to do in an exercise-, puts them into an array an then gets the max value, minimal value, reads the entire array and counts how many times the max value was surpassed, and for last gives the position of the minimal value.
This is my code:
function performance(string) {
let arrayScore = string.split(' ') //this part works as I tested before, the numbers of the string are correctly passed to the array
let max = arrayScore[0]
let min = arrayScore[0]
let worstgame = 1
let surpasses = 0
for (let i = 0; i < arrayScore.length; i++) {
if (max < arrayScore[i]) {
max = arrayScore[i]
surpasses++
}
if (min > arrayScore[i]) {
min = arrayScore[i]
worstgame = i + 1
}
}
//max = arrayScore.reduce((a, b) => Math.max(a, b))
//min = arrayScore.reduce((a, b) => Math.min(a, b))
return [surpasses, worstgame, max, min]
}
let score = "10 20 20 8 25 3 0 30 1"
console.log(performance(score)) /*here is the problem: the value 8 is attributed to 'max' -should be 30- and the number of surpasses returns 2 -should be 3-*/
I noticed that I can get the max value by using Math.max as an argument in reduce, but I still don't understand why counting the surpasses and the "if" condition for "max" in the "for" loop are not working.
You compare numbers as strings, so '30' < '8' returns true :). Just use Number.parseInt() in order to get a number from a string (e.g. Number.parseInt(max) < Number.parseInt(arrayScore[i]))
how you can do that
function performance(str) {
let scoreArr = str.split(' ').map(data => Number(data))
return scoreArr.reduce((max, num) => {
return max > num ? max : num;
},0)
}
let score = "10 20 20 8 25 3 0 30 1"
console.log(performance(score))
// 30
As the topic states what is the best way to make it so that when you pass an array of emotions/values, to show the closest value based on a numeric mapping in javascript?.
Assume that 'Glad' is the same thing as 'Happy', and 'Down' is the same thing as 'Sad'. Ithe code I've tried seems incredibly lengthy and gets bloated if I add more emotions/states (i.e. Angry). Aside from the emotions array, any new functions and data structures and variables can be changed/introduced.
for example, I can get a list of emotions:
let emotions = ['Happy','Happy','Sad','Glad','Angry'];
Now I want to return a string that reflects what the 'closest' emotion based on these 5 emotions.
For a better example, let's assume the values correspondent to each emotion is:
Angry = 1, Happy = 2, Sad = 3
I was trying something like:
var numb = 0;
for (var i = 0; i < emotions.length; i++) {
if (numb == 'Angry')
numb += 1;
if (numb == 'Happy' || numb == 'Glad')
numb += 2;
if (numb == 'Sad' || numb == 'Down')
numb += 3;
}
var average = numb / emotions.length;
// check which number is closer to
if (average < 1.5)
return 'Angry';
if (average >= 1.5 && < 2.5)
return 'Happy';
if (average > 2.5)
return 'Sad';
if (average == 1.5)
return 'Angry or Happy';
if (average == 2.5)
return 'Happy or Sad';
My expected result based on this list of emotions is:
2(*Happy*) + 2(*Happy*) + 3(*Sad*) + 2(*Happy|Glad*) + 1(*Angry*) = 10
Then divide by 5 (the emotions array length), resulting in 2.
So the result that should be returned, as string, is "Happy".
Let's say I added a fourth type of emotion/feeling... I would be adding more and more of these conditions, and it gets more complicated in the logic checking for the ranges of the numbers.
I am looking at the list of emotions as a whole, and trying to come up with an overall emotion that represents the whole list.
What is the best way to do this so that the code looks clean and I can support more states without having the lines of code become too long?
What about something like this:
Having two object constants:
emotionsValues: Here you assing a value to each emotion you want, like a score to each.
emotionsRank: Here is the final result of each value, based on average you'll get the result from here.
Now:
Receive the emotions array by parameter.
reduce it based on the value of each mapped emotion (using emotionsValues).
Get the average
See if the floor value + ceil value divided by 2 is equal to the number itself (it means its exactly the half), so use the "emotion or emotion".
OR, if not the half, then round to the nearest and get the correct emotion. Don't forget to check if average is below 1 or bigger the the last rank (3 in this case)
const emotionsValues = {
"Angry": 1,
"Happy": 2,
"Glad": 2,
"Sad": 3,
"Down": 3,
}
const emotionsRank = {
1: "Angry",
2: "Happy",
3: "Sad",
}
function getEmotion(arrayEmot) {
let numb = arrayEmot.reduce((acc, v) => Number(emotionsValues[v]) + acc, 0);
let avg = numb / arrayEmot.length;
let min = Math.floor(avg)
let max = Math.ceil(avg)
if ((min + max) / 2 == avg && min != max) {
return emotionsRank[min] + " or " + emotionsRank[max]
} else {
let rounded = avg < 1 ? 1 : avg > 3 ? 3 : Math.round(avg);
return emotionsRank[rounded];
}
}
let emotionsTest = ['Happy', 'Happy', 'Sad', 'Glad', 'Angry'];
console.log(getEmotion(emotionsTest))
let emotionsTest2 = ['Happy', 'Happy', 'Sad', 'Sad'];
console.log(getEmotion(emotionsTest2))
You may create the function emo to value and its reciprocal one: value to emotionS:
Then you map every emotions found in array to its value
do your standard mathematical stuff
and get back to emotions via the reciprocal function
const emoToValue = {
Glad: 1,
Happy: 1,
Sad: 2
}
const valueToEmos = Object.entries(emoToValue).reduce((acc, [emo, val]) => {
acc[val] = acc[val] || []
acc[val].push(emo)
return acc
}, {})
//compute the average:
function avgEmotion (emotions) {
if (emotions.length == 0) return ''
const avg = emotions.reduce((s, em) => s + emoToValue[em], 0) / emotions.length
return valueToEmos[Math.round(avg)].join(' or ')
}
console.log('str', avgEmotion(['Happy', 'Happy', 'Sad', 'Happy'])) //Glad or Happy
console.log('str', avgEmotion(['Happy', 'Happy', 'Sad', 'Sad'])) //Sad
This function explicitly checks for the "mid" case and also for out of range values (since it's based on indices):
function getEmotion(emotions, value) {
// Out of range
if ( value > emotions.length ) return emotions[emotions.length - 1];
if ( value < 1 ) return emotions[0];
// Determine if decimal is .5
let mid = value % 1 === .5;
// Round the value to the nearest integer
let rounded = Math.round(value);
return mid ? `${emotions[rounded - 2]} or ${emotions[rounded - 1]}` : emotions[rounded - 1];
}
Output:
let emotions = ['Happy', 'Happy', 'Sad', 'Glad', 'Angry'];
console.log(getEmotion(emotions, -23)); // Happy
console.log(getEmotion(emotions, 0)); // Happy
console.log(getEmotion(emotions, 1)); // Happy
console.log(getEmotion(emotions, 2.43)); // Happy
console.log(getEmotion(emotions, 2.5)); // Happy or Sad
console.log(getEmotion(emotions, 3.1)); // Sad
console.log(getEmotion(emotions, 155.65)); // Angry
You could create a set of indices and get the values by filtering with the index.
function getEmotion(emotions, value) {
var values = new Set([value + 0.5, value - 0.5, Math.round(value)]);
return emotions.filter((e, i) => values.has(i + 1)).join(' and ');
}
console.log(getEmotion(['Happy', 'Sad', 'Glad', "Angry"], 1));
console.log(getEmotion(['Happy', 'Sad', 'Glad', "Angry"], 1.5));
console.log(getEmotion(['Happy', 'Sad', 'Glad', "Angry"], 1.7));
So one of our clients (an auctioneer) has a set of weird increments (also know as London increments), where essentially they don't conform to any divisible number, so using something like: Math.round(number / increment) * increment will not work.
The increments
From: 100, To: 299, Increment: 10
From: 300, To: 319, Increment: 20
From: 320, To: 379, Increment: 30
From: 380, To: 419, Increment: 20
And this kind of thing goes on.
So taking a number like: 311 should round up to 320. Now I have this code and it works fine, it also rounds up/down 321 => 350 and 363 => 380 as expected.
My concern is that it is not fast and/or sustainable and with large numbers that need to be rounded it will get slower. This function needs to be as fast as the Math.round() obviously knowing that it won't but as fast as possible. Now as much as I got it working, the way I have done it is essentially looping X amount of times (x being any number, so I have set it to 9999999, and I am hoping someone knows a better way of doing this.
// Get increment amount
window.getIncrement = (num) => {
var num = parseInt(num);
for (var i = 0; i < window.increments.length; i++) {
if (num >= parseInt(window.increments[i].from) && num <= parseInt(window.increments[i].to)) {
return parseInt(window.increments[i].increment);
}
}
}
// Get increment start value
window.getIncrementStartValue = (num) => {
var num = parseInt(num);
for (var i = 0; i < window.increments.length; i++) {
if (num >= parseInt(window.increments[i].from) && num <= parseInt(window.increments[i].to)) {
return parseInt(window.increments[i].from);
}
}
};
// Custom round up function
const roundToNearestIncrement = (increment, number, roundDown) => {
var incrementStart = parseInt(window.getIncrementStartValue(number));
var increment = parseInt(increment), number = parseInt(number);
console.log(incrementStart, increment, number);
// So now we have a start value, check the direction of flow
var lastBelow = false, firstAbove = false;
for (var i = 0; i < 9999999; i++) {
var incrementRounder = incrementStart + (increment * i);
if (incrementRounder === number) { return number; }
if (incrementRounder < number) { lastBelow = incrementRounder; }
if (incrementRounder > number) { firstAbove = incrementRounder; }
if (lastBelow !== false && firstAbove !== false) { break; }
console.log('Loop #' + i + ', Below: ' + lastBelow + ', Above: ' + firstAbove);
}
return !roundDown ? firstAbove : lastBelow;
}
Then you use it like so:
// Example usage
var num = 329;
var inc = getIncrement(num);
console.log('Rounded: ' + roundToNearestIncrement(inc, num) + ', Expected: 350');
Now as I said it works great, but my concern is that it will slow down a Node process if the number uses something large like 1,234,567, or just the highest number of that increment set, because the code will loop until it finds the above and below number, so if anyone has a better idea on how to do this that it will work but not loop?
See screenshot of the one I did before:
You can see it had to loop 1865 times before it found the above and below amounts.
Anyway, any ideas you have would be appreciated.
There are a couple of ways of making this faster
1.You can store a very big hash will all the possible values and the rounding result. This will use a lot of scape, but will be the fastest. This means that you'll a hash similar to this
rounded = []; rounded[0]=0 ... rounded[100] = rounded[101] = ... = rounded[109] = 110 ... and so on.
Of course this solution depends on the size of the table.
2.Build a binary search tree, based on the breakout points and search that tree. If the tree is balanced it will take O(log(n)) for a search.
If I understand the problem correctly:
Pre-build the array of all the thresholds, in ascending order. I imagine it'll look something like [0, 1, 2,..., 320, 350, 380, 400, 420,...];
Then the lookup will be simple:
const findNearestThreshold = (number) => thresholdsArray
.find(threshold => (threshold >= number));
A solution basing just on the increments array.
const steps = [
{ from: 100, increment: 10}, // I don't need 'to' property here
{ from: 300, increment: 20},
{ from: 320, increment: 30},
{ from: 380, increment: 20},
]
const roundUp = x => {
const tooLargeIndex = steps.findIndex(({from}) => from > x);
const { from, increment } = steps[tooLargeIndex - 1];
const difference = x - from;
return from + Math.ceil(difference / increment) * increment;
}
console.log(300, roundUp(300));
console.log(311, roundUp(311));
console.log(321, roundUp(321));
This question already has answers here:
Get the closest number out of an array
(21 answers)
Closed 7 years ago.
I have an ordered array:
btnDrag.pos = [0, 65, 131, 196, 259, 323, 388, 453, 517];
And a function that fires when drag stops:
btnDrag.draggable({
axis: 'x',
containment: 'parent',
stop: function() {
var index = (function(){
var new_x = btnDrag.position().left;
// now, how to find the closest index in btnDrag.pos relative to new_x ?
// return index;
})();
btnDrag.animate({
'left': (btnDrag.pos[index] + 'px')
});
}
});
The array values are points which btnDrag is allowed to stay (in axis 'x').
So, the function must return the closest index with the value to btnDrag go.
Thanks in advance.
Since your array is sorted, the fastest way is to use a modified version of the binary search algorithm:
function closest (arr, x) {
/* lb is the lower bound and ub the upper bound defining a subarray or arr. */
var lb = 0,
ub = arr.length - 1;
/* We loop as long as x is in inside our subarray and the length of our subarray is
greater than 0 (lb < ub). */
while (ub - lb > 1) {
var m = parseInt((ub - lb + 1) / 2); // The middle value
/* Depending on the middle value of our subarray, we update the bound. */
if (arr[lb + m] > x) {
ub = lb + m;
}
else if (arr[lb + m] < x) {
lb = lb + m;
}
else {
ub = lb + m;
lb = lb + m;
}
}
/* After the loop, we know that the closest value is either the one at the lower or
upper bound (may be the same if x is in arr). */
var clst = lb;
if (abs(arr[lb] - x) > abs(arr[ub] - x)) {
clst = ub;
}
return clst; // If you want the value instead of the index, return arr[clst]
}
Here is a fiddle where you can test it: http://jsfiddle.net/Lpzndcbm/4/
Unlike all the solution proposed here this solution runs in O(log(n)) and not in O(n). If you are not familiar with complexity, it means that this algorithm will find the closest value in an array of size N in at most O(log(N)) loop while the others will find it in at most N loop (with N = 10000, it makes a big difference since log(10000) ~ 14 (binary log)).
Note that if you have really small array, this may be slower than the naive algorithm.
There you go :
function closest(list, x) {
var min,
chosen = 0;
for (var i in list) {
min = Math.abs(list[chosen] - x);
if (Math.abs(list[i] - x) < min) {
chosen = i;
}
}
return chosen;
}
Each time, the minimum distance is computed and the chosen value is updated based on the minimum. (http://jsbin.com/dehifefuca/edit?js,console)
Something like this?
var closest = btnDrag.pos.reduce(function (prev, curr) {
return (Math.abs(curr - new_x) < Math.abs(prev - new_x) ? curr : prev);
});
Simple for loop will do it:
var btnDrag = {};
btnDrag['pos'] = [0, 65, 131, 196, 259, 323, 388, 453, 517];
new_x = 425;
var index = -1;
for (var i = 0; i < btnDrag.pos.length; i++)
{
if (i < btnDrag.pos.length-1) //loop till i is at 2 positions from the end.
{
//value has to be less then the selected value + 1
if (new_x < btnDrag.pos[i+1])
{
//calculate the half between the values and add it with the first value
// test if new_x is larger then that value.
if ((btnDrag.pos[i+1] - btnDrag.pos[i])/2 + btnDrag.pos[i] > new_x)
{
index = i;
break;
}
else
{
index = i+1;
break;
}
}
}
else
{
//edge cases.
if (new_x < 0)
{
index = 0;
}
else
{
index = btnDrag.pos.length-1;
}
}
}
document.body.innerHTML = btnDrag['pos'][index] + " (" + index + ")";
This question already has answers here:
Find the number in an array that is closest to a given number
(7 answers)
Closed 9 years ago.
I am fairly new to javascript and I'm having problems finding the most efficient way to calculate the problem below
I have an array of objects. Each object has a time stamp and a total field. I have a number saved as a variable and I want to loop through the array to find the timestamp of the object with the total field closest to my number.
This is a sorted array so the numbers are always increasing so for example the numbers could look like this:
Jan 125
Feb 150
Mar 200
Apr 275
If the number I have is 205 I would like to get the result Mar back.
They are objects taken from a mongoDb so look something like this
{TimeStamp: "2013-06-24 01:00", Delivered: 464, Queued: 39, Total: 503}
{TimeStamp: "2013-07-02 01:00", Delivered: 485, Queued: 37, Total: 522}
{TimeStamp: "2013-07-05 01:00", Delivered: 501, Queued: 41, Total: 542}
{TimeStamp: "2013-07-08 09:48", Delivered: 501, Queued: 64, Total: 565}
If the list is already sorted on the right field, you can use this code to find the minimum distance in O(n):
var data = [
{total: 125, name: 'Jan'},
{total: 150, name: 'Feb'},
{total: 200, name: 'Mar'},
{total: 275, name: 'Apr'}
];
function getClosest(arr, value)
{
var closest, mindiff = null;
for (var i = 0; i < arr.length; ++i) {
var diff = Math.abs(arr[i].total - value);
if (mindiff === null || diff < mindiff) {
// first value or trend decreasing
closest = i;
mindiff = diff;
} else {
// trend will increase from this point onwards
return arr[closest];
}
}
return null;
}
You keep track of the currently closest object and its corresponding (absolute) difference between the total and the searched value.
You keep updating those two values as long as the difference decreases. When that no longer happens you can return immediately, because you know it will never decrease afterwards.
To use it:
getClosest(data, 200);
I've got this helpful generic function:
function min(ary, key) {
return ary.map(function(x) {
return [key ? key(x) : x, x]
}).reduce(function(m, x) {
return x[0] < m[0] ? x : m;
})[1]
}
It finds a minimal element in the array using key as a comparison function. Applied to your problem:
number = ...
closestTimestamp = min(arrayOfRecords, function(record) {
return Math.abs(number - record.total)
}).TimeStamp;
var numbers = [122,231,323,53];
var myNumber = 200;
var difference = 9999;
var nearest = null;
for (i = 0 ; i < numbers.lenght; i++){
var candidate = numbers[i];
var currentDifference = Math.abs(myNumber - candidate);
if (currentDifference < difference) {
nearest = candidate; difference = currentDifference;
}
}
You can use a binary search for that value. Adapted from this answer:
function nearestIndex(arr, compare) { // binary search, with custom compare function
var l = 0,
r = arr.length - 1;
while (l <= r) {
var m = l + ((r - l) >> 1);
var comp = compare(arr[m]);
if (comp < 0) // arr[m] comes before the element
l = m + 1;
else if (comp > 0) // arr[m] comes after the element
r = m - 1;
else // this[m] equals the element
return m;
}
// now, l == r+1
// usually you would just return -1 in case nothing is found
if (l == arr.length) return r;
if (r == 0) return 0;
if (Math.abs(compare(arr[l])) > Math.abs(compare(arr[r]))) // "closer"
return r;
else
return l;
}
var items = […];
var i=nearestIndex(items, function(x){return x.Total-532;}); // compare against 532
console.log(items[i].TimeStamp);