How to check to which Tier our Number belongs to? - javascript

So we have that :
{TierLowerBound : 2,
TierUpperBound : 5,
TierName : 'Tier 1',
TierDiscount : 0.15},
{TierLowerBound : 6,
TierUpperBound : 10,
TierName : 'Tier 2',
TierDiscount : 0.40}
And given number for example let i=5;
What can be the best way to find out to which of the tiers our number i belongs to?
I created simple function isBetween, some objects etc. but I am asking, because there are maybe some great ways in JS to deal with that kind of situation?
I'm using foreach loop to check every tier, so it's not as efficient as switch, but switch relays on fixed values, so it would be hard to evalute for instance this situation :
lowerBound = 5;
upperBound = 10;
number = 7;
Looking forward for an answer:)
ANSWER
getDiscount : function(number){
let foundTier = tier.tiersArray.filter(function(object){
let isBetween = number >= object.TierLowerBound &&
( number <= object.TierUpperBound || object.TierUpperBound ==
'noLimit')
return isBetween;
}).values().next().value;
return foundTier ? foundTier.TierDiscount : 0;
}

You can use filter:
const yourArrayOfObjects = [
{
TierLowerBound: 2,
TierUpperBound: 5,
TierName: 'Tier 1',
TierDiscount: 0.15
},
{
TierLowerBound : 6,
TierUpperBound: 10,
TierName: 'Tier 2',
TierDiscount: 0.40
}
];
const returnObjectsWhereNumberFitsInBounds = n => {
return yourArrayOfObjects.filter(element => element.TierLowerBound < n && element.TierUpperBound > n)
}
const magicNumberToTest = 4;
console.log(returnObjectsWhereNumberFitsInBounds(magicNumberToTest));

Assuming that you have an array of objects with tier information, here is an approach:
const tiers = [{
TierLowerBound: 2,
TierUpperBound: 5,
TierName: 'Tier 1',
TierDiscount: 0.15
},
{
TierLowerBound: 6,
TierUpperBound: 10,
TierName: 'Tier 2',
TierDiscount: 0.40
}
]
function isBetween(numberGiven) {
let categoryFound = {}
tiers.forEach(function(arrayItem) {
if (numberGiven >= arrayItem.TierLowerBound && numberGiven <= arrayItem.TierUpperBound) {
console.log("The number requested, " + numberGiven + " is in " + arrayItem.TierName + " category!");
categoryFound = arrayItem;
}
});
return categoryFound;
}
isBetween(3);
isBetween(6);
isBetween(9);
// if you want to use the category in another place, call it like this:
const objInRange = isBetween(7);
console.log("objInRange: " + JSON.stringify(objInRange, null, 2));

Related

Javascript - calculations in a for loop

I'm not entirely sure if a for loop is the right thought the process to handle what my issue is. I am creating a line graph to display the % growth between two years for 10 years. I am able to get the data showing on the graph as expect but I unable to think of another way to make it more efficient. My current code works but I would like to handle the calculations for the entire array of data together rather than handling it separately.
my current logic:
const numerator is the index of the year I would to display a percentage growth I am subtracting it from const denominator
I am removing the commas in the first two lines since the value are higher numbers such as 1,323,349
Finally, the third line perChange is the calculation for the actual percentage. I am using 2 indexes at a time and using the larger number of the two to subtract the smaller one for example. index 1 - index 0; index 3 - index 2, index 5 - index 4 etc...
This is my current code:
const numeratorOne = Number(caArray[1].DataValue.replace(/,/g, ''))
const denominatorOne = Number(caArray[0].DataValue.replace(/,/g, ''))
const perChangeOne = 100 * Math.abs((denominatorOne - numeratorOne) / ((denominatorOne + numeratorOne ) /2 ) )
const numeratorTwo = Number(caArray[3].DataValue.replace(/,/g, ''))
const denominatorTwo = Number(caArray[2].DataValue.replace(/,/g, ''))
const perChangeTwo = 100 * Math.abs((denominatorTwo - numeratorTwo) / ((denominatorTwo + numeratorTwo) / 2))
const numeratorThree = Number(caArray[5].DataValue.replace(/,/g, ''))
const denominatorThree = Number(caArray[4].DataValue.replace(/,/g, ''))
const perChangeThree = 100 * Math.abs((denominatorThree - numeratorThree) / ((denominatorThree + numeratorThree) / 2))
const numeratorFour = Number(caArray[7].DataValue.replace(/,/g, ''))
const denominatorFour = Number(caArray[8].DataValue.replace(/,/g, ''))
const perChangeFour = 100 * Math.abs((denominatorFour - numeratorFour) / ((denominatorFour + numeratorFour) / 2))
const numeratorFive = Number(caArray[9].DataValue.replace(/,/g, ''))
const denominatorFive = Number(caArray[8].DataValue.replace(/,/g, ''))
const perChangeFive = 100 * Math.abs((denominatorFive - numeratorFive) / ((denominatorFive + numeratorFive) / 2))
const numeratorSix = Number(caArray[11].DataValue.replace(/,/g, ''))
const denominatorSix = Number(caArray[10].DataValue.replace(/,/g, ''))
const perChangeSix = 100 * Math.abs((denominatorSix - numeratorSix) / ((denominatorSix + numeratorSix) / 2))
const numeratorSeven = Number(caArray[13].DataValue.replace(/,/g, ''))
const denominatorSeven = Number(caArray[12].DataValue.replace(/,/g, ''))
const perChangeSeven = 100 * Math.abs((denominatorSeven - numeratorSeven) / ((denominatorSeven + numeratorSeven) / 2))
const numeratorEight = Number(caArray[15].DataValue.replace(/,/g, ''))
const denominatorEight = Number(caArray[16].DataValue.replace(/,/g, ''))
const perChangeEight = 100 * Math.abs((denominatorEight - numeratorEight) / ((denominatorEight + numeratorEight) / 2))
const numeratorNine = Number(caArray[17].DataValue.replace(/,/g, ''))
const denominatorNine = Number(caArray[16].DataValue.replace(/,/g, ''))
const perChangeNine = 100 * Math.abs((denominatorNine - numeratorNine) / ((denominatorNine + numeratorNine) / 2))
Highcharts.chart('container', {
chart: {
type: 'line'
},
title: {
text: 'Gross State Product in California'
},
xAxis: {
categories: ['1998', '2000', '2002', '2004', '2006', '2008', '2010', '2012', '2014', '2016', '2018'],
title: {
text: 'Year'
},
},
yAxis: {
categories: ['2', '4', '6', '8', '10'],
title: {
text: 'Percentage Change (%)'
}
},
plotOptions: {
line: {
dataLabels: {
enabled: false
},
enableMouseTracking: false
}
},
series: [{
name: 'California',
data: [perChangeOne, perChangeTwo, perChangeThree, perChangeFour, perChangeFive, perChangeSix, perChangeSeven, perChangeEight, perChangeNine, 8.3, 3.9],
color: '#002F65'
}, {
name: 'US',
color: '#0B7070',
data: [3.9, 4.2, 5.7, 8.5, 1.9, 5.2, 7.0, 6.6, 4.2, 5.3, 10]
}]
});
}
The desired outcome is a way to get the same results without having to create 10 different variables as I will need to continue to add more data to this graph in the future.
I have attempted this:
for (let i = 0; i < caArray.length; i++) {
let removeComma = Number(caArray.DataValue.replace(/,/g, ''))
}
for (let i = 0; i < removeComma.length; i += 2) {
// math logic here which I do not know how to work out
}
Iterate over the caArray first, pushing to either a numerators or denominators array, depending on the index of the item you're iterating over. Then map the numerators array to get the associated denominator and calculate the change, and you'll have the array you can put into the series.data property:
const numerators = [];
const denominators = [];
caArray.forEach(({ DataValue }, i) => {
const arrToPushTo = i % 2 === 0 ? denominators : numerators;
arrToPushTo.push(Number(DataValue.replace(/,/g, '')));
});
const changes = numerators.map((numerator, i) => {
const denominator = denominators[i];
return 100 * Math.abs((denominator - numerator) / ((denominator + numerator) / 2));
});
series: [{
name: 'California',
data: [...changes, 8.3, 3.9],
const caArray = [
{ DataValue: '10' },
{ DataValue: '20' },
{ DataValue: '30' },
{ DataValue: '40' },
];
const numerators = [];
const denominators = [];
caArray.forEach(({ DataValue }, i) => {
const arrToPushTo = i % 2 === 0 ? denominators : numerators;
arrToPushTo.push(Number(DataValue.replace(/,/g, '')));
});
const changes = numerators.map((numerator, i) => {
const denominator = denominators[i];
return 100 * Math.abs((denominator - numerator) / ((denominator + numerator) / 2));
});
console.log(changes);
If i understood correctly you just need one loop. You need to iterate by two each and then you have all the data in your current iteration. Something like this.
const changes = []
for(let i = 0; i < caArray.length; i+=2) {
if (!caArray[i + 1]) {
break
}
const denominator = Number(caArray[i].DataValue.replace(/,/g, ''));
const numerator = Number(caArray[i + 1].DataValue.replace(/,/g, ''));
const perChange = 100 * Math.abs((denominator - numerator) / ((denominator + numerator ) /2 ) )
changes.push(perChange);
}
This should work.

Look up tables and integer ranges - javascript

So I am looking to create look up tables. However I am running into a problem with integer ranges instead of just 1, 2, 3, etc. Here is what I have:
var ancient = 1;
var legendary = 19;
var epic = 251;
var rare = 1000;
var uncommon = 25000;
var common = 74629;
var poolTotal = ancient + legendary + epic + rare + uncommon + common;
var pool = general.rand(1, poolTotal);
var lootPool = {
1: function () {
return console.log("Ancient");
},
2-19: function () {
}
};
Of course I know 2-19 isn't going to work, but I've tried other things like [2-19] etc etc.
Okay, so more information:
When I call: lootPool[pool](); It will select a integer between 1 and poolTotal Depending on if it is 1 it will log it in the console as ancient. If it hits in the range of 2 through 19 it would be legendary. So on and so forth following my numbers.
EDIT: I am well aware I can easily do this with a switch, but I would like to try it this way.
Rather than making a huge lookup table (which is quite possible, but very inelegant), I'd suggest making a (small) object, choosing a random number, and then finding the first entry in the object whose value is greater than the random number:
// baseLootWeight: weights are proportional to each other
const baseLootWeight = {
ancient: 1,
legendary: 19,
epic: 251,
rare: 1000,
uncommon: 25000,
common: 74629,
};
let totalWeightSoFar = 0;
// lootWeight: weights are proportional to the total weight
const lootWeight = Object.entries(baseLootWeight).map(([rarity, weight]) => {
totalWeightSoFar += weight;
return { rarity, weight: totalWeightSoFar };
});
console.log(lootWeight);
const randomType = () => {
const rand = Math.floor(Math.random() * totalWeightSoFar);
return lootWeight
.find(({ rarity, weight }) => weight >= rand)
.rarity;
};
for (let i = 0; i < 10; i++) console.log(randomType());
Its not a lookup, but this might help you.
let loots = {
"Ancient": 1,
"Epic": 251,
"Legendary": 19
};
//We need loots sorted by value of lootType
function prepareSteps(loots) {
let steps = Object.entries(loots).map((val) => {return {"lootType": val[0], "lootVal": val[1]}});
steps.sort((a, b) => a.lootVal > b.lootVal);
return steps;
}
function getMyLoot(steps, val) {
let myLootRange;
for (var i = 0; i < steps.length; i++) {
if((i === 0 && val < steps[0].lootVal) || val === steps[i].lootVal) {
myLootRange = steps[i];
break;
}
else if( i + 1 < steps.length && val > steps[i].lootVal && val < steps[i + 1].lootVal) {
myLootRange = steps[i + 1];
break;
}
}
myLootRange && myLootRange['lootType'] ? console.log(myLootRange['lootType']) : console.log('Off Upper Limit!');
}
let steps = prepareSteps(loots);
let pool = 0;
getMyLoot(steps, pool);

Weighted Average with ElasticSearch

I'm new to ElasticSearch and i'm trying to make an weighted average in my index.
My data looks like this:
data = [{"id": 344,"q28": 1},{"id": 344,"q28": 1},{"id": 344,"q28": 2}, ...]
"q28": can be equal to 1,2,3 or 4
Example in JavaScript:
var data = [{"id": 344,"q28": 1},{"id": 344,"q28": 1},{"id": 344,"q28": 2}]
function calcWeightAverage(res) {
var score = 0
for (var i in res) {
if (res[i].q28 == 1)
score += 100
else if (res[i].q28 == 2)
score += 50
else if (res[i].q28 == 3)
score += 25
else if (res[i].q28 == 4)
score += 0
}
return score / res.length
}
console.log(calcWeightAverage(data)) // output 83.333...
Can you help me with to find a query that would calculate the weighted average of q28 directly in ElasticSearch ?
Thank you !
UPDATE 1
I'm close, see: "https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-scripting.html"
To test it, i created a file in config/scripts/my_script.groovy
1 + my_var
Then you have to restart ElasticSearch and make this query:
GET /_search
{
"script_fields": {
"my_field": {
"script": {
"file": "my_script",
"params": {
"my_var": 2
}
}
}
}
}
It's working, now i have to work on the script.
How can i loop in all my data to do something like my javascript function ?
Just to help you with a shorter Javascript part for the average.
function calcWeightAverage(res) {
return res.reduce(function (r, a) {
return r + ({ 1: 100, 2: 50, 3: 25, 4: 0 }[a.q28] || 0);
}, 0) / res.length;
}
var data = [{ id: 344, q28: 1 }, { id: 344, q28: 1 }, { id: 344, q28: 2 }];
console.log(calcWeightAverage(data));

How can I refactor this bunch of if statements?

Imagine that I have a variable called incomingValue and I'm getting a number from an API as it's value. The values are between 0 to 1 and I'm setting two other variables depending on this value using bunch of if statements like you see below.
var incomingValue; // Set by an API
var setValueName;
var setValueIcon;
if (incomingValue < 0.10 ) {
setValueName = 'something';
setValueIcon = 'something.png'
}
if (incomingValue > 0.09 && incomingValue < 0.20 ) {
setValueName = 'somethingElse';
setValueIcon = 'somethingElse.png';
}
In the actual implementation, I have around 10 if statements checking for specific intervals up until 1. e.g. do this if it's more than 0.10 but less than 0.16 and so on.
As a JavaScript beginner it feels like this is not the right way to do things even though it gets the job done. How would I refactor this code?
Update: As requested, I'm adding the full set of intervals that are used in the original code. I haven't included the full list before because the intervals don't follow a certain pattern.
0 to 0.09
0.09 to 0.20
0.20 to 0.38
0.38 to 0.48
0.48 to 0.52
0.52 to 0.62
0.61 to 0.80
0.80 to 1
Remember the single responsibility principle. Take that code out to a separate function like so:
function determineNameAndIcon(incomingValue) {
if (incomingValue < 0.10) {
return {
name: "something",
icon: "something.png"
};
}
if (incomingValue < 0.20) {
return {
name: "somethingElse",
icon: "somethingElse.png"
};
}
// etc
return {
name: "somethingDefault",
icon: "somethingDefault.png"
};
}
// ...
var incomingValue; // Set by an API
const {
name: setValueName,
icon: setValueIcon
} = determineNameAndIcon(incomingValue);
Notice that determineNameAndIcon is still a very long function with repeating parts. This can be further refactored to a data-driven version:
const nameAndIconList = [
{
maxValue: 0.10,
name: "something",
icon: "something.png"
},
{
maxValue: 0.20,
name: "somethingElse",
icon: "somethingElse.png"
},
// ...
];
const nameAndIconDefault = {
name: "somethingDefault",
icon: "somethingDefault.png"
};
function determineNameAndIcon(incomingValue) {
for (let item of nameAndIconList) {
if (incomingValue < item.maxValue) {
return item;
}
}
return nameAndIconDefault;
}
function findValue(incomingValue){
var itemValues = [
[.06, "valueName", "valueIcon"],
[.08, "valueName", "valueIcon"],
[.09, "valueName", "valueIcon"],
[.1, "valueName", "valueIcon"],
]
var foundValues = itemValues.
filter(v=>v[0] >= incomingValue)
.sort();
if(foundValues.length == 0){
throw "incoming value not found."
}
return foundValues[0];
}
let value = findValue(.079);
console.log( value );
This is assuming that you want the lowest portion of the range to be the one selected (just reverse the sort if you want it to be the highest).
A solution using an array where you set the ranges for your results:
var categories = [{something: [0, 0.1]},
{somethingElse: [0.1, 0.2]},
{somethingElse2: [0.2, 0.3]},
{anotherSomething: [0.3, 1]}];
function res(number){ return Object.keys(categories.filter(function(elem) {
var key = elem[Object.keys(elem)];
return number >= key[0] && number < key[1]
})[0])[0]};
var incomingValue = 0.12;
var setValueName = res(incomingValue);
var setValueIcon = res(incomingValue) + ".png";
console.log(setValueName, setValueIcon);
Mb I will refactor this code like this but it's not really standard patter :
var incomingValue=0.08; // Set by an API
var setValueName;
var setValueIcon;
switch(true) {
case incomingValue < 0.10 :
setValueName = "something";
setValueIcon ="something.png";
alert("Hello World !");
break;
case incomingValue > 0.09 && incomingValue < 0.20 :
setValueName = "somethingElse";
setValueIcon ="somethingElse.png";
alert("Hello !");
break;
default :
alert("Adele !");
break;
}
The mortal will use the if...else if... condition like this :
var incomingValue; // Set by an API
var setValueName;
var setValueIcon;
if (incomingValue < 0.10 ) {
setValueName = "something";
setValueIcon ="something.png"
} **else** if (incomingValue > 0.09 && incomingValue < 0.20 ) {
setValueName = "somethingElse";
setValueIcon ="somethingElse.png"
}
But I dont like this way...My opinion :)

Sorting in Javascript

I'd like to sort by time,day.
Here is my attempt:
var days = new Array();
var days['SU'] = 0;
var days['MO'] = 1;
var days['TU'] = 2;
var days['WE'] = 3;
var days['TH'] = 4;
var days['FR'] = 5;
var days['SA'] = 6;
events.sort(function(a, b)
{
if(a['day'] != b['day'])
{
return (days[a['day']] < days[b['day']]) ? 1 : -1;
}
else if(a['time'] != b['time'])
{
return (a['time'] < a['time']) ? 1 : -1;
}
else
return 0;
);
It's not tested, but am I doing it correct?
(Time asc, days asc) Mon 8am, Tues 8am, Mon 9pm is the order I'm looking for.
Cheers.
events[0]['day'] = 'MO';
events[0]['time'] = 8;
events[1]['day'] = 'MO';
events[1]['time'] = 21;
events[2]['day'] = 'TU';
events[2]['time'] = 8;
My solution which seems to work thanks to #T.J. Crowder
events = new Array();
events[0] = new Array();
events[0]['day'] = 'MO';
events[0]['time'] = 8;
events[1] = new Array();
events[1]['day'] = 'MO';
events[1]['time'] = 21;
events[2] = new Array();
events[2]['day'] = 'TU';
events[2]['time'] = 8;
var days = {
'SU': 0,
'MO': 1,
'TU': 2,
'WE': 3,
'TH': 4,
'FR': 5,
'SA': 6
};
events.sort(function(a, b)
{
if (a.time != b.time)
{
return a.time - b.time;
}
else if (a.day != b.day)
{
return days[a.day] - days[b.day];
}
else
{
return 0;
}
});
Condensed:
events.sort(function(a, b)
{
return a.time != b.time
? a.time - b.time
: days[a.day] - days[b.day];
});
Your fundamental approach is sound. A few notes:
You're not using days as an array, so I wouldn't make it an array. Instead:
var days = {
'SU': 0,
'MO': 1,
'TU': 2,
'WE': 3,
'TH': 4,
'FR': 5,
'SA': 6
};
Also, you don't need those quotes since none of those strings is a keyword, so:
var days = {
SU: 0,
MO: 1,
TU: 2,
WE: 3,
TH: 4,
FR: 5,
SA: 6
};
...but you may choose to keep them as a style thing, or to defend against adding ones that are keywords later.
You don't have to use the bracketed notation to look up a property (a['day']) unless the string you're using for the property name is dynamic or the property name is a reserved word. day is neither, so you can use the simpler dotted notation (a.day).
There is no elseif in JavaScript; use else if.
You can simplify this:
return (days[a['day']] < days[b['day']]) ? 1 : -1;
to
return days[a.day] - days[b.day];
..and you may be able to do something similar with your time values, but I don't know what they are, so... now that you've posted them, I do, and you can.
Strongly recommend always using braces, not just when you "need" them. (None of your three branches actually needs them, but you're only using them on two.)
You've compared a['time'] to a['time] rather than b['time'] when checking for equality.
You haven't ended your function (missing })
Since you can just subtract your time values, you don't need your final equality check.
So:
events.sort(function(a, b)
{
if (a.day != b.day)
{
return days[a.day] - days[b.day];
}
else
{
return a.time - b.time;
}
});
...or you can condense it further:
events.sort(function(a, b)
{
return (a.day != b.day
? days[a.day] - days[b.day]
: a.time - b.time);
});
Live example

Categories

Resources