Extending and then Sorting Underscore js - javascript

I was trying to figure it out how can I extend and then sort the items by the created extension variable.
if(score === 'Overall Score'){
// let midtermSort = _.sortBy(overAll, 'overall_score');
_.each(students, function(elem) {
_.extend(elem, {overall_score : (elem.midterm_score + elem.final_score) / 2});
_.sortBy(elem, 'overall_score');
console.log(elem.firstname + " " + elem.overall_score);
});
}
As you can see on my code, I iterate to the students and then extend a new column w/c is overall_score. So right now I need to sort the items via overall_score.
Here's what I got:
As you can see the overall score does not SORT them properly. Anything i was doing wrong? Please help.
UPDATE Side Note:
I tried to mixed it up with each function and it works but it was a long process. Any idea how to refactor it a little bit?
if(score === 'Overall Score'){
let overAllScore = _.each(students, function(elem) {
_.extend(elem, {overall_score : (elem.midterm_score + elem.final_score) / 2});
});
let sorted = _.sortBy(overAllScore, 'overall_score');
_.each(sorted, function(elem) {
console.log(elem.firstname + " " + elem.final_score);
});
}

This will do what you want, assuming of course that you wanted them listed from lowest to highest overall score. Note that I've indented the code for readability:
var score = 'Overall Score';
var students = [
{
firstname: 'Frank',
midterm_score: 80,
final_score: 80
},
{
firstname: 'Julie',
midterm_score: 50,
final_score: 65
},
{
firstname: 'Eddie',
midterm_score: 100,
final_score: 73
},
{
firstname: 'Bill',
midterm_score: 60,
final_score: 67
}
];
if (score === 'Overall Score') {
_.each(
_.sortBy(
_.map(
students,
function(elem) {
return _.extend(elem, {overall_score : (elem.midterm_score + elem.final_score) / 2});
}
),
'overall_score'
),
function(elem) {
console.log(elem.firstname + " " + elem.overall_score);
}
);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>

_.sortBy, according to the documentation, returns a sorted copy of the Array. This is unlike the standard Array.sort().
You need to keep this value:
elem = _.sortBy(elem, ...)
Or, if you want to keep the entire dataset, use map instead of each:
students = _.map(students, function(elem) {
...
return _.sortBy(elem, ...)
})

Related

Determine top value from a JSON array

I'm trying to create a chart which uses an input set of data which I then turn into percentages so that the chart is always between the range of -100 and 100.
From the array of JSON data I want to work out the max number (positive value, generally) and min number (likely to be a negative but turned into a positive using a - before the variable).
I then want to work out which of those two 'positive' values is higher and mark that as the top value.
I then for each value from the data, want to divide the (timeline_value by the top_value) multiplied by 100 and rounded.
For each of those values, I need to add them to individual arrays. One of them is to calculate the rectangle width chartWidth, one to work out the rect start position chartStart (for positive values its 152, for negative values, it is 148 minus chartWidth).
For chartColor, I want to have it predefined that revenue is always green (#28CE6D), expenses is always red (#DF3456) and profit is always blue (#4DC7EC). Below is the data set that is driving my chart that I need to create from the input data:
var chartStart = [152, 84, 152]
var chartWidth = [100, 64, 36]
var chartNames = ["$7,110 Revenue", "$4,539 Expenses", "$2,571 Profit"]
var chartColor = ["#28CE6D", "#DF3456", "#4DC7EC"]
Here's the bit I'm stuck on where I'm trying to create the above set from. Thanks
var input_data = {
timeline_balances: [
{
timeline_name: "Revenue",
timeline_value: 7110,
currency: "$"
},
{
timeline_name: "Expenses",
timeline_value: -4539,
currency: "$"
},
{
timeline_name: "Profit",
timeline_value: 2571,
currency: "$"
}
]
}
var max_value = input_data.timeline_balances.timeline_value.max_value
var min_value = -input_data.timeline_balances.timeline_value.min_value
if (max_value > min_value) {
top_value = max_value;
} else {
top_value = min_value;
}
You have to use a loop to get values from the object as the object is inside an array
var input_data = {
timeline_balances: [{
timeline_name: "Revenue",
timeline_value: 7110,
currency: "$"
},
{
timeline_name: "Expenses",
timeline_value: -4539,
currency: "$"
},
{
timeline_name: "Profit",
timeline_value: 2571,
currency: "$"
}
]
}
var top_value=-10;
input_data.timeline_balances.forEach(e=>{
var max_value = e.timeline_value
var min_value = -1*e.timeline_value
if (max_value > min_value) {
if(max_value>top_value)
top_value = max_value;
} else {
if(min_value>top_value)
top_value = min_value;
}
})
console.log(top_value)
We can utilize a custom sort on the array. In our custom sort function we'll use Math.abs() to get the value regardless of sign and sort using that.
In the example below I've done the sort in descending order, so the first value in the array has the highest absolute value. (I've tweaked your negative value to make it our maximum for the purposes of this example)
var input_data = {
timeline_balances: [{
timeline_name: "Revenue",
timeline_value: 7110,
currency: "$"
},
{
timeline_name: "Expenses",
timeline_value: -14539,
currency: "$"
},
{
timeline_name: "Profit",
timeline_value: 2571,
currency: "$"
}
]
}
/*Make a copy of the object as sort happens in place*/
/*See: https://stackoverflow.com/questions/122102/what-is-the-most-efficient-way-to-deep-clone-an-object-in-javascript*/
var absolute_sorted_data = JSON.parse(JSON.stringify(input_data));
/*Sort by absolute value - decending*/
absolute_sorted_data.timeline_balances.sort(function(a,b){
return Math.abs(b.timeline_value) - Math.abs(a.timeline_value);
});
/*First value has the highest absolute value*/
var max_val = Math.abs(absolute_sorted_data.timeline_balances[0].timeline_value);
console.log(max_val);
/*Update chart arrays - also set new properties in your object.
This is cleaner but your charting engine may not support that*/
var chartStart = []; //[152, 84, 152]
var chartWidth = []; //[100, 64, 36]
var chartNames = []; //["$7,110 Revenue", "$4,539 Expenses", "$2,571 Profit"]
var chartColor = []; //["#28CE6D", "#DF3456", "#4DC7EC"]
input_data.timeline_balances.forEach(function(el){
var val = el.timeline_value;
el.chartStart = val >= 0 ? 152 : 84;
el.chartWidth = Math.round(val/max_val*100);
el.chartName = `${el.currency}${Math.abs(val)} ${el.timeline_name}`;
switch(el.timeline_name) {
case "Revenue":
el.chartColor = "#28CE6D";
break;
case "Expenses":
el.chartColor = "#DF3456";
break;
case "Profit":
el.chartColor = "#4DC7EC";
break;
}
chartStart.push(el.chartStart);
chartWidth.push(el.chartWidth);
chartNames.push(el.chartName);
chartColor.push(el.chartColor);
});
console.log(input_data);
{//
<script>
'use strict';
let input_data =
{
timeline_balances :
[
{
timeline_name : "Revenue",
timeline_value : 7110,
currency : "$"
},
{
timeline_name : "Expenses",
timeline_value : -4539,
currency : "$"
},
{
timeline_name : "Profit",
timeline_value : 2571,
currency : "$"
}
]
};
let maxValue = 0;
let minValue = 0;
for (let key in input_data.timeline_balances) {
{
//alert(input_data.timeline_balances[key].timeline_value)
if(maxValue < input_data.timeline_balances[key].timeline_value)
maxValue = input_data.timeline_balances[key].timeline_value;
if(minValue > input_data.timeline_balances[key].timeline_value)
minValue = input_data.timeline_balances[key].timeline_value;
}
} //end for loop
alert("Maximum value: " + maxValue);
alert("Minimum value: " + minValue);
//}

Optimise Round Up/Down Function

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));

JavaScript: Different Results with && and &, with numbers

So, I'm picking up JavaScript since I just seems to enjoy it. I bought an online course, and now facing a challenge in my code. It's a pretty simple one, so hopefully one of the experts out there can help the poor fella (me).
I have the following code, which to my knowledge, should be proper:
var johnAge = 25;
var markAge = 30;
var steveAge = 40;
var johnHeight = 170;
var markHeight = 175;
var steveHeight = 150;
var John, Mark, Steve;
John = johnHeight + 5 * johnAge;
Mark = markHeight + 5 * markAge;
Steve = steveHeight + 5 * steveAge;
console.log('John: ' + John);
console.log('Mark: ' + Mark);
console.log('Steve: ' + Steve);
if (John > Mark && Steve) {
console.log('John wins!');
} else if (Mark > Steve && John) {
console.log('Mark wins!');
} else if (Steve > John && Mark) {
console.log('Steve wins!');
} else {
console.log("it's a draw.");
}
Here is my problem:
John: 295
Mark: 325
Steve: 350
Steve wins!
Now, this game is about having the one with the highest points to win the game. Steve is obviously the winner based on his score.
The problem is with this part right here:
} else if (Mark > Steve && John) {
console.log('Mark wins!');
} else if (Steve > John && Mark) {
console.log('Steve wins!');
It will show that 'Mark' won the game if and when i make the following changes:
} else if (Mark > John && Steve) {
console.log('Mark wins!');
}
I simply just switched locations between 'John' and 'Steve'. If John comes first, it shows that 'Mark won the game', and if Steve comes firs, it moves on and executes the next 'else if' which is 'Steve won the game'.
What I don't understand is why changing positions between my variables causing such huge difference even though I'm using the logical and &&.
This problem doesn't seem to exist when I use binary and &. Though, as far as I've read about this, binary and isn't preferable in coding and doesn't have much real applications where it can be used.
So if possible, I'd like to fix this my using && instead of relying on &.
I really don't understand the changes and their cause.
Thank you in advance for helping and guiding me.
~Cheers
You need to check the player against both of the others like this:
The way you have it now your if statement is Steve > John(true) && Mark(true).
var johnAge = 25;
var markAge = 30;
var steveAge = 40;
var johnHeight = 170;
var markHeight = 175;
var steveHeight = 150;
var John, Mark, Steve;
John = johnHeight + 5 * johnAge;
Mark = markHeight + 5 * markAge;
Steve = steveHeight + 5 * steveAge;
console.log('John: ' + John);
console.log('Mark: ' + Mark);
console.log('Steve: ' + Steve);
if (John > Mark && John > Steve ) {
console.log('John wins!');
} else if (Mark > Steve && Mark > John) {
console.log('Mark wins!');
} else if (Steve > John && Steve > Mark) {
console.log('Steve wins!');
} else {
console.log("it's a draw.");
}
You can do this, you just have to make an improved > operator that suits your needs. But please stay tuned until the end of this answer. We'll address some other things that we really need to talk about
const gtAll = (x, y, ...ys) =>
ys.length === 0
? x > y
: x > y && gtAll (x, ...ys)
console.log ('5 is greater than 4, 3, and 2', gtAll (5, 4, 3, 2))
// true
console.log ('5 is greater than 4, 3, and 7', gtAll (5, 4, 3, 7))
// false
In your program you could use it like below.
if (gtAll (John, Mark, Steve))
console.log ("John wins")
else if (gtAll (Mark, Steve, John))
console.log ("Mark wins")
else if (gtAll (Steve, John, Mark))
console.log ("Steven wins")
else
console.log ("draw")
But if we zoom out a little bit, there's a bigger problem to solve with your program. Let's look at some of the things that are sticking out.
Have a bunch of vars named fooColor, fooSize, fooAdjective? You're probably looking for an Object.
var johnAge = 25
var johnHeight = 170
var john = { name: 'John', age: 25, height: 170 }
Assigning multiple vars for values of the same kind ie John, Mark, Steve? You're probably looking for an Array.
var John = { ... }
var Mark = { ... }
var Steve = { ... }
const people =
[ { name: 'John', age: 25, height: 170 }
, { name: 'Mark', age: 30, height: 175 }
, { name: 'Steve', age: 40, height: 150 }
]
Does it feel like you're starting to repeat yourself? Use functions to perform repeated tasks
John = johnHeight + 5 * johnAge;
Mark = markHeight + 5 * markAge;
Steve = steveHeight + 5 * steveHeight;
const score = p =>
p.height + 5 * p.age
console.log (score (people [0])) // John
// 295
console.log (score (people [1])) // Mark
// 325
console.log (score (people [2])) // Steve
// 350
console.log (people.map (score))
// [ 295, 325, 350 ]
Here's a rewrite of your program. Most importantly notice that we didn't even end up using gtAll as because there's a better way to think about the problem now that our people are represented as objects in an array. And remember to use functions where work is repeated!
const people =
[ { name: 'John', age: 25, height: 170 }
, { name: 'Mark', age: 30, height: 175 }
, { name: 'Steve', age: 40, height: 150 }
]
const score = (p) =>
p.height + 5 * p.age
const determineWinner = (contested, challenger) =>
{
if (score (challenger) > score (contested))
return challenger
else if (score (challenger) === score (contested))
return { ...contested, name: "tie" }
else
return contested
}
const winner =
people.reduce (determineWinner)
console.log ("the winner is", winner)
// the winner is { name: 'Steve', age: 40, height: 150 }
console.log ("the winner is", winner.name)
// the winner is Steve
Critically, notice that we can easily add another person to people and our program doesn't have to change in order to determine a winner. For example, if we added Charlie
var charlieAge = 60
var charlieHeight = 180
var Charlie = charlieHeight + 5 * charlieAge
Then we would have to go and alter all of the if statements. You can see how this simply does not scale
if (gtAll (John, Mark, Steve))
if (gtAll (John, Mark, Steve, Charlie))
console.log ("John wins")
else if (gtAll (Mark, Steve, John))
else if (gtAll (Mark, Steve, John, Charlie))
console.log ("Mark wins")
else if (gtAll (Steve, John, Mark))
else if (gtAll (Steve, John, Mark))
console.log ("Steven wins")
else if (gtAll (Charlie, John, Mark, Steve))
console.log ("Charlie wins")
else
console.log ("draw")
Compare that to simply adding Charlie to our people array
const people =
[ { name: 'John', age: 25, height: 170 }
, { name: 'Mark', age: 30, height: 175 }
, { name: 'Steve', age: 40, height: 150 }
, { name: 'Charlie', age: 60, height: 180 }
]
const winner =
people.reduce (determineWinner)
console.log (winner.name)
// Charlie
References for this answer
Object
Array
Array.prototype.map
Array.prototype.reduce
Rest parameter
Spread syntax
You want to use comparisons on both sides of the && operator:
if(Mark > Steve && Mark > John)
The && operator treats its operands as booleans. The expression if(Mark > Steve && John) means
if both are true:
Mark is greater than Steve, and
John has a true value when John is coerced to a boolean (namely, for a number, when John is nonzero)
This is obviously not what you wanted to test for.

Whats wrong with my javascript Object?

I have a javascript "object." Im using the word object to make things easier. Which is here:
var character = {
name: "",
myClass: "",
health: 20,
maxHealth: 20,
};
Say i have a game, and the game has fights, and after each fight you gain a health point. which is done with:
character.maxHealth += 1;
However... When i tried to do this, i ended up getting 201 as the maxHealth or 2032 or 203232 or whatever number i wanted to add to the max health was just adding as if it was a string. through my eyes in looks like an integer to me but i must be mistaken. if anyone can give me a hand it would be really appreciated. That is an example of what i have. the actual code is:
var character = {
name: "",
myClass: "",
health: 20,
maxHealth: 20,
stamina: 10,
maxStamina: 10,
mana: 5,
maxMana: 5,
physStrength: 3,
minAttack: 0,
mentStrength: 3,
physDefense: 3,
mentDefense: 3,
exp: 0,
punch: function() {
toggleAttackButtons(0);
this.minAttack = this.physStrength/3;
var damage = Math.floor(Math.random() * this.physStrength) + this.minAttack;
addString("You punched and did " + damage + " damage.");
myEnemy.health -= damage;
updateStats();
setTimeout(function(){
myEnemy.attack();
toggleAttackButtons(1);
updateStats();
}, 1000);
},
kick: function(){
toggleAttackButtons(0);
this.minAttack = this.physStrength/3;
var damage = Math.floor(Math.random() * this.physStrength) + this.minAttack;
addString("You kicked and did " + damage + " damage.");
myEnemy.health -= damage
updateStats();
setTimeout(function(){
myEnemy.attack();
toggleAttackButtons(1);
updateStats();
}, 1000);
},
};
and this is where im incrementing the number:
var updateStats = function() {
document.getElementById("charHealth").innerHTML = "Health: " + character.health + " / " + character.maxHealth;
document.getElementById("enemHealth").innerHTML = "Health: " + myEnemy.health + " / " + myEnemy.maxHealth;
if(myEnemy.health <= 0){
myEnemy.health = 0;
character.maxHealth += 1;
removeFightScreen(1);
}
if(character.health <= 0){
removeFightScreen(2);
}
};
I understand the object is messy i plan on rewriting it in the future to be a lot more efficient. im just roughing it up right now.
I found myself the answer. Im sorry for my lack of code evidence, however it contains cookies, so when i write the cookies using the character stats like health and maxHealth, they end up being converted into strings to fit the cookie. Therefore i need to convert them back to integers. thank you guys for your help. Next time i will add in all the code i just felt that being there was several hundred lines of code i didnt want to go through it all.

Dynamically-changing variables random intergers

The numbers themselves aren't relevant. I have a list of variables that are used to track a moving vehicle.
UTC Time:, Latitude:, Longitude:, Speed:, Heading:, Steering:, Odometer:(PPM), GPS Status:, STWS Status:
Like i said the numbers aren't relevant, and neither is the math. I just need to simulate dynamically changing integers for each variable. For instance, Speed:25. then the 25 become a 26, then a 28, then a 15 and so on. I will implement this code, and then set the min and max for each variable.
I just need to show customers that the vehicle tracking system monitor can display changing values for each variable.
Thank you.
$("#the_span_id").text(n); and hook it up to a JavaScript timer event.
It sounds like what you need is a jQuery countdown. Take a look at this link:
http://www.tripwiremagazine.com/2011/04/9-cool-jquery-countdown-scripts.html
You could do something along the following lines:
var DynVar = {
variables: [],
timer: 0,
numIntervals: 0,
counter: 0,
updateVar: function(v) {
v.value = Math.random() * (v.max - v.min) + v.min;
},
createVar: function(name, min, max) {
var v = {"name": name, "min": min, "max": max};
DynVar.updateVar(v);
DynVar.variables.push(v);
return v;
},
update: function() {
for (i = 0; i < DynVar.variables.length; ++i) {
var v = DynVar.variables[i];
DynVar.updateVar(v);
console.log(DynVar.counter + ": " + v.name + ": " + v.value);
}
if (DynVar.counter++ >= DynVar.numIntervals) {
clearInterval(DynVar.timer);
}
},
start: function(interval, numIntervals) {
DynVar.counter = 0;
DynVar.numIntervals = numIntervals;
DynVar.timer = setInterval(DynVar.update, interval);
}
};
DynVar.createVar("speed", 10, 30);
DynVar.createVar("latitude", 20, 22);
DynVar.start(1000, 3);

Categories