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));
Related
My problem is that I want to get the total sum of SplitValue that has the SplitType of RATIO and use it inside the FOR loop to calculate the ratio of split. i.e. 3 + 2 = 5.
The comments I made in each line of my code below explain my troubles, I'm getting a loop of 3 and 5 instead of just 5. Thanks
var details = {
"Amount": 1396.8000000000002,
"SplitInfo": [{
"SplitType": "RATIO",
"SplitValue": 3
},
{
"SplitType": "RATIO",
"SplitValue": 2
}
]
};
var conRatioSumArr = 0;
var balance = details.Amount;
for (var i = 0; i < details.SplitInfo.length; i++) {
// I want to Get the total sum of SplitValue that has the SplitType of RATIO
// 3 + 2 = 5
// Here is what I've tried
splitTypeArr = details.SplitInfo[i].SplitType;
splitValueArr = details.SplitInfo[i].SplitValue;
if (splitTypeArr === "RATIO") {
conRatioSumArr += splitValueArr;
console.log(conRatioSumArr); // This gives me a loop of 3 & 5. I only need the total value which is 5 instead of both 3 and 5. Note that if I put this outside the for loop, I get only the total which is 5, but I want to use the sum inside of this for loop not outside the for loop to enable me calculate the ratio below.
splitAmount = (balance * (splitValueArr / 5)); // The total above is expected to replace the 5 here, but I'm getting a loop of 3 & 5 instead.
// Like this
// splitAmount = (balance * (splitValueArr / conRatioSumArr));
// splitAmount is expected to give: 838.08 and 558.7200000000001 split respectively
console.log(splitAmount);
}
}
I think you are making it too complicated.
All you need to do is get the total amount of parts in the ratio, and then do the (simple) math.
var details = {
amount: 1396.8000000000002,
splitInfo: [
{
type: "RATIO",
value: 3,
},
{
type: "RATIO",
value: 2,
},
],
};
let totalParts = 0;
for (const split of details.splitInfo) {
if (split.type == "RATIO") totalParts += split.value;
}
for (const split of details.splitInfo) {
if (split.type == "RATIO") {
let amountForSplit = details.amount * (split.value / totalParts);
console.log(amountForSplit);
}
}
This code is simple to understand and modify I hope to your liking. Using the array method reduce.
var details = {
"Amount": 1396.8000000000002,
"SplitInfo": [{
"SplitType": "RATIO",
"SplitValue": 3
},
{
"SplitType": "RATIO",
"SplitValue": 2
}
]
};
var result = details.SplitInfo.reduce(function(agg, item) {
if (item.SplitType == "RATIO") {
agg.sum = (agg.sum || 0) + item.SplitValue
agg.count = (agg.count || 0) + 1
}
return agg;
}, {})
console.log(result)
console.log("average is " + result.sum / result.count)
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));
I'm trying to create a function in javascript/jquery to work out what the minimum number of coins it would take to add up to a total inputed amount.
So I have an object array of the coins:
var coins = [
{
pennies: 200,
print: '£2'
},
{
pennies: 100,
print: '£1'
},
{
pennies: 50,
print: '50p'
},
{
pennies: 20,
print: '20p'},
{
pennies: 10,
print: '10p'
},
{
pennies: 5,
print: '5p'},
{
pennies: 2,
print: '2p'
},
{
pennies: 1,
print: '1p'
}
];
var $input = $('input');
$input.keypress(function(e) {
if (e.which == 13) {
// do something
}
});
And an input field in my HTML.
So if someone enters 123p I would like the form to return 1 x £1, 1 x 20p, 1 x 2p and 1 x 1p.
I'm struggling to figure out where to start. If anyone could help it would be much appreciated.
Thanks
It sounds like you just need to iterate over the coins array, starting from the highest denomination:
const coins=[{pennies:200,print:'£2'},{pennies:100,print:'£1'},{pennies:50,print:'50p'},{pennies:20,print:'20p'},{pennies:10,print:'10p'},{pennies:5,print:'5p'},{pennies:2,print:'2p'},{pennies:1,print:'1p'}];
const getCoins = penniesToGo => coins.reduce((coinCountStr, { pennies, print }) => {
const numCoins = Math.floor(penniesToGo / pennies);
if (numCoins < 1) return coinCountStr;
penniesToGo -= numCoins * pennies;
const thisCoinStr = `${numCoins}x ${print}`;
return coinCountStr ? coinCountStr + ', ' + thisCoinStr : thisCoinStr;
}, '');
console.log(getCoins(201));
console.log(getCoins(333));
console.log(getCoins(6));
I have the following code:
function display_message() {
var low = data.result[0].max; //returns 30
var medium = data.result[1].max; // returns 60
var high = data.result[2].max; // returns 100
// mypoints are 67 for example
if(mypoints > low) {
if(mypoints > medium) {
alert('You got a high score');
} else {
alert('You got a medium score');
}
} else {
alert('You got a low score');
}
}
This code works fine. I compare my average score to the standard low / medium / high score.
Low score: 0-30 points
Medium score: 31-60 points
High score: 61-100 points
My question though is how to make my code a bit prettier? I am not sure if the code is considered as clear and efficient.
Any opinions would be much appreciated, thank you
There is no need for the if else with low, just check from smallest to highest.
if (mypoints <= low) {
//low msg
} else if (mypoints <= medium) {
//medium msg
} else {
//high msg
}
or you can go the opposite direction and check for the highest first with greater than
You could use a condition without nested conditions.
if (mypoints > medium) {
alert('You got a high score');
} else if (mypoints > low) {
alert('You got a medium score');
} else {
alert('You got a low score');
}
Here, we iterate over the various values that make up the score range. The loop will iterate over each score range in turn, meaning you need to have the lowest score first and highest score last. We then save score name against myscore to be alerted out at a later point.
This approach allows for expandability - you can add as many score ranges in the middle without having to add any more if/else blocks.
let data = {result: [{max: 30}, {max: 60}, {max: 100}]},
mypoints = 67;
function display_message() {
let score_range = {
low: data.result[0].max, //returns 30
medium: data.result[1].max, // returns 60
high: data.result[2].max // returns 100
},
myscore = 'fail';
for (var score in score_range) {
if (score_range.hasOwnProperty(score)) {
if (mypoints > score_range[score]) {
myscore = score;
}
}
}
alert('You got a ' + myscore + ' score!');
}
display_message();
You could store the messages in an array, and find the correct index like so:
function display_message() {
var low = 30,
medium = 60,
rank;
mypoints = 67; // for example
rank = ['low', 'medium', 'high'][+(mypoints > low) + +(mypoints > medium)];
console.log('You got a ' + rank + ' score');
}
display_message();
The magic is in the unary plus operator which converts booleans returned by comparisons to 0 or 1 accordingly. It's also easy to include more rankings if needed.
mypoints < low ? alert("you get low score") : (mypoints < medium ? alert("you get medium score") : alert("you get high score"))
You can use the switch statement
function display_message() {
var low = data.result[0].max; //returns 30
var medium = data.result[1].max; // returns 60
var high = data.result[2].max; // returns 100
switch (true) {
case mypoints > medium:
alert('You got a high score');
break;
case mypoints > low:
alert('You got a medium score');
break;
default:
alert('You got a low score');
}
}
You can create a function which takes the score and an array as an argument with the different levels and their names {"score": 30, "text": "You got a low score"} and then just loop that and output what is closest to what you sent in and return the matching text.
Example:
var myScore = 50,
scoreIntervals = [{
"score": 30,
"text": "Low score"
},{
"score": 60,
"text": "Average score"
},{
"score": 100,
"text": "High score"
}];
function evaluateScore(score, scoreIntervals) {
var output = scoreIntervals[scoreIntervals.length - 1].text;
$.each(scoreIntervals, function(key, val) {
if(score <= val.score) {
output = val.text;
return false;
}
});
return output;
}
console.log(evaluateScore(myScore, scoreIntervals));
I have the following array with two objects:
var myArr = [{
id: 3,
licences: 100
new_value_pr_licence: 40
}, {
id: 4,
licences: 200
new_value_pr_licence: 25
}]
A user wish to buy 150 licences. This means that they fall into the category 100 because they are above 100 licences but below 200 which means they pay $40 per licence.
Note that the array object values varies.
Order your plans by the price per licence:
myArr.sort(function (a, b) {
return a.new_value_pr_licence - b.new_value_pr_licence;
})
then starting from the start of the array, take as many of that plan as you can without going over the number the user wants to buy:
var numUserWants = 150;
var purchases = {};
var cheapestAvailableProduct = myArr.shift();
while (numUserWants > 0 && cheapestAvailableProduct) {
if (numUserWants <= cheapestAvailableProduct.licences) {
purchases[cheapestAvailableProduct.id] = Math.floor(cheapestAvailableProduct.licences / numUserWants);
numUserWants = cheapestAvailableProduct.licences % numUserWants;
}
cheapestAvailableProduct = myArr.shift();
}
At this point, purchases will now be a map of plan id to number:
purchases => {
3: 3
4: 1
}
This doesn't handle the case where over-purchasing is the cheapest option (eg: it's cheaper to buy 160 at 4x40, instead of 150 at 3x40 + 1x25 + 1x5), but it's probably a good start for you to tweaking.
Just a simple forEach here. Take the number requested, begin calculating/mutating total based on option limits, and once the number requested is less than the option limit you have your final total, which wont be mutated any longer and returned from the function.
function calculateDiscountedTotal(numberRequested, myArr){
var total;
// loop, compare, calculate
myArr.forEach(function(option) {
if(numberRequested >= option.licenses){
total = numberRequested * option.new_value_pr_licence
}
}
if(total != undefined){
return total;
} else {
// user never had enough for initial discount
return "no discount price";
}
}
Sort the array first in terms of number of licenses and then get the object in which number of licenses is less than number of licenses to be bought (just less than the next item in the array which is greater than number of licenses to be bought)
var myArr = [
{
id: 3,
licences: 100
new_value_pr_licence: 40,
},
{
id: 4,
licences: 200,
new_value_pr_licence: 25
},
];
var numOfLic = 150;
myArr.sort( function(a,b){ return a.licences - b.licences } );
var selectedObj = myArr.reduce( function(prev,current){
if ( current.licences > numOfLic )
{
return prev;
}
});
console.log ( "pricing should be " + ( selectedObj.new_value_pr_licence * numOfLic ) );