Sort Objects That Meet Threshold - javascript

I have an array of objects returned by the database that I am attempting to run a sort() on.
[{
PER_ID: 5511,
DEAL_TIME_TO_PRESENT_MINS_BIZ_AVG: '353',
DEAL_COUNT_PRESENTED: 118,
},
{
PER_ID: 5016,
DEAL_TIME_TO_PRESENT_MINS_BIZ_AVG: '372',
DEAL_COUNT_PRESENTED: 109,
},{
PER_ID: 4181,
DEAL_TIME_TO_PRESENT_MINS_BIZ_AVG: '359',
DEAL_COUNT_PRESENTED: 73,
},
{
PER_ID: 5016,
DEAL_TIME_TO_PRESENT_MINS_BIZ_AVG: '372',
DEAL_COUNT_PRESENTED: 109,
},{
PER_ID: 158,
DEAL_TIME_TO_PRESENT_MINS_BIZ_AVG: '370',
DEAL_COUNT_PRESENTED: 1112,
}]
I'd like to get the value of PER_ID for the object with the lowest value for DEAL_TIME_TO_PRESENT_MINS_BIZ_AVG where also DEAL_COUNT_PRESENTED >= 10.
The code I have is:
per_recs = per_recs.sort(function (prior, curr) {
return ((curr.DEAL_COUNT_PRESENTED >= 10 && (prior.DEAL_TIME_TO_PRESENT_MINS_BIZ_AVG - curr.DEAL_TIME_TO_PRESENT_MINS_BIZ_AVG > 0) ? 1 : 0));
});
let per_id_with_lowest_deal_time_to_present_mins_biz_avg = per_recs[0].PER_ID;

Simplify your problem and filter out the elements that do not interest you first, like that :
const items = [{
PER_ID: 5511,
DEAL_TIME_TO_PRESENT_MINS_BIZ_AVG: '353',
DEAL_COUNT_PRESENTED: 118,
},
{
PER_ID: 5016,
DEAL_TIME_TO_PRESENT_MINS_BIZ_AVG: '372',
DEAL_COUNT_PRESENTED: 109,
},{
PER_ID: 4181,
DEAL_TIME_TO_PRESENT_MINS_BIZ_AVG: '359',
DEAL_COUNT_PRESENTED: 9,
},
{
PER_ID: 5016,
DEAL_TIME_TO_PRESENT_MINS_BIZ_AVG: '372',
DEAL_COUNT_PRESENTED: 109,
},{
PER_ID: 158,
DEAL_TIME_TO_PRESENT_MINS_BIZ_AVG: '370',
DEAL_COUNT_PRESENTED: 1112,
}]
const sortedItems = items
.filter(item => item.DEAL_COUNT_PRESENTED >= 10)
.sort((a, b) => {
return +a.DEAL_TIME_TO_PRESENT_MINS_BIZ_AVG - +b.DEAL_TIME_TO_PRESENT_MINS_BIZ_AVG
})
let foundItem = sortedItems[0].PER_ID;
Here you go !

To get the a small number m (1) from n entries a top-m heap sort is usually more efficient. For 1 it is even shorter as a simple reduce can be used (the accumulator represents the heap):
const lowest = records.reduce((current, entry) =>
entry.count >= 10 && (!current || entry.avg < current.avg) ? entry : current, null);

Related

javascript array of object add rank but don't change order of the main array

I want to add a rank key on each element based on totalpremium value(but I don't want to change the order of objects inside array), My current code works fine but It changes the order of the main array of object which I don't want to do.
Array of objects:
[
{
totalcount: 2834682,
totalpremium: 652553820
},
{
totalcount: 1380674,
totalpremium: 430713235
},
{
totalcount: 862119,
totalpremium: 165983898
}
]
Working Function
// add rank to each object
function rank_by_key(array, key_name) {
if (key_name) {
array.sort(function (a, b) {
return b[key_name] - a[key_name];
});
var rank = 1;
for (var i = 0; i < array.length; i++) {
if (i > 0 && array[i][key_name] < array[i - 1][key_name]) {
rank++;
}
array[i].rank = rank;
}
}
return array;
}
ranked_result = rank_by_key(array, 'totalpremium');
Expected Output:
[
{
"totalcount": 2834682,
"totalpremium": 110,
"rank": 2
},
{
"totalcount": 1380674,
"totalpremium": 121,
"rank":1
},
{
"totalcount": 862119,
"totalpremium": 100,
"rank": 3
}
]
Current Output:
[
{
"totalcount": 1380674,
"totalpremium": 121,
"rank":1
},
{
"totalcount": 2834682,
"totalpremium": 110,
"rank": 2
},
{
"totalcount": 862119,
"totalpremium": 100,
"rank": 3
}
]
This creates a ranks array which we can then reference in creating the new property
let data = [
{
totalcount: 2834682,
totalpremium: 652553820
},
{
totalcount: 1380674,
totalpremium: 43071324534435
},
{
totalcount: 862119,
totalpremium: 165983898
}
]
let ranks = data.map(e => e.totalpremium).sort((a,b) => b-a)
let ranked = data.map( e=> ({...e, rank: (ranks.indexOf(e.totalpremium)+1)}));
console.log(ranked)
All you need is just a sort by field, nothing else. Following array indicies are your expected ranks.
You can do it as follows (if you need to have rank in your elements explicitly):
sort your array copy (sort does mutate original array, that's why you need to create copy),
then use map (map also does not mutate orginal array).
Code example:
const data = [{totalcount:2834682,totalpremium:652553820},
{totalcount:862119,totalpremium:165983898},{totalcount:1380674,totalpremium:430713235}];
const res = [...data].sort((a, b) => b.totalpremium - a.totalpremium)
.map((e, i) => ({rank: i + 1, ...e}))
console.log("Result: ", res)
console.log("Original data: ", data)
.as-console-wrapper { max-height: 100% !important; top: 0; } /* ignore this */
Judging from the code you provided, you seem to want the rank to take equality into account, e.g. [10,20,30,20] would be ranked [3,2,1,2] instead of [4,2,1,3].
The performance bottleneck will be the sorting of your keys, but there's little we can do about it.
Here's what I got, using good old functions and intermediate variables.
Some of the ES6 syntactic sugar comes at a cost, while sets seem to work like a charm. I reverted to old-fashioned methods when they seemed to offer a good readability/performance trade-off, but that's highly debatable, of course.
function rank_according_to (data, key_name)
{
// collect unique key values
let unique_keys = new Set();
for (let i = 0 ; i != data.length ; i++) unique_keys.add(data[i][key_name]);
// sort unique key values
let sorted_keys = [...unique_keys].sort( (a,b) => b-a );
// associate a rank to each value
let rank_of_key = [];
for (let i = 0 ; i != sorted_keys.length ; i++) rank_of_key[sorted_keys[i]] = i+1;
// Option 1: Put the ranks into the original array
//for (let i = 0 ; i != data.length ; i++) data[i].rank = rank_of_key[data[i][key_name]];
// Option 2: put the ranks into a deep copy
return data.map (record => ({ ...record, rank: (rank_of_key[record[key_name]]) }) );
}
I changed your dataset to have more readable numbers and a case of identical key values:
console.log (sample)
Array(4) [ {…}, {…}, {…}, {…} ]
​0: Object { key_one: 123, key_two: 121 }
​1: Object { key_one: 135, key_two: 110 }
​2: Object { key_one: 123, key_two: 151 }
​3: Object { key_one: 100, key_two: 100 }
console.log (rank_according_to (sample, "key_one"));
0: Object { key_one: 123, key_two: 121, rank: 2 }
1: Object { key_one: 135, key_two: 110, rank: 1 }
2: Object { key_one: 123, key_two: 151, rank: 2 }
3: Object { key_one: 100, key_two: 100, rank: 3 }
console.log (rank_according_to (sample, "key_two"));
​0: Object { key_one: 123, key_two: 121, rank: 2 }
​1: Object { key_one: 135, key_two: 110, rank: 3 }
​2: Object { key_one: 123, key_two: 151, rank: 1 }
​3: Object { key_one: 100, key_two: 100, rank: 4 }
Now if you ask me, assuming efficiency is not an issue, you could spare yourself some of the hassle by saving the original indexes in an "original_order" property, so that you could sort the array back into place once you're done generating rankings.
At any rate, I'm not too happy with a design that relies on side effects of shallow copies. It's confusing, much harder to maintain, no more efficient than a lot of other possible solutions, and likely to waste far more time in debugging than the extra cost of designing a sturdier solution from the get-go.
You can first sort the object, loop through the result, get the corresponding object and assign the index to the rank property:
Object.entries(arr[0]).sort(([, a], [, b]) => {
return b.totalpremium - a.totalpremium
}).forEach((e, i) => arr[0][(e[0])].rank = i + 1);
Result:
const arr = [{
"A": {
"totalcount": 2834682,
"totalpremium": 110
},
"B": {
"totalcount": 1380674,
"totalpremium": 121
},
"C": {
"totalcount": 862119,
"totalpremium": 100
}
}]
Object.entries(arr[0]).sort(([,a],[,b])=>{return b.totalpremium-a.totalpremium}).forEach((e,i) => arr[0][(e[0])].rank = i+1);
console.log(arr);

How can I compare values from multiple objects?

I want to compare values from objects that I keep in an array.
I know that I can create new arrays with values from each object but I'm trying to find some way to do it without creating them.
Consider we have such situation:
soldiers[first, second, third]
first{name: John, shooting: 95, combat: 50, tactic: 88}
second{name: Arnold, shooting: 97, combat: 72, tactic: 68}
third{name: William, shooting: 87, combat: 86, tactic: 97}
I'd like to select the best soldier from the provided above - but I can't create one rating (i.e. average).
There will be some conditions that soldier must fill - for example: at least 60 points in combat (no matter if every other property is 100).
So I'm trying to find way to compare multiple properties and return name of just one soldier.
I'll appreciate every tip. Thanks!
I have made you an exmaple with comments. Let me know if this pushes you in the right direction or if you need any other help.
const soldiers = [{
name: "John",
shooting: 95,
combat: 50,
tactic: 88
},
{
name: "Arnold",
shooting: 97,
combat: 72,
tactic: 68
},
{
name: "William",
shooting: 87,
combat: 86,
tactic: 97
}
];
const filteredSoldiers = soldiers
.filter(soldier => soldier.combat > 60) // Find every soldier where combat is higher than 60
.map(soldier => {
return {
name: soldier.name,
average: (soldier.combat + soldier.tactic + soldier.shooting) / 3
};
// map will return an array with the filtered soldiers, and we put their average and their name in there
})
.sort((a, b) => b.average - a.average);
// Lastly we sort them high to low by their average score
console.log(
filteredSoldiers.length > 0 ? filteredSoldiers[0].name : 'No soldiers with combat score higher thn 60'
);
jsfiddle
In the filter condition you can of course add more checks.
You need to got through all items and select best value;
Note that some soldiers can have similar values, that's why values.name is array
let a = {
name: "John", shooting: 95, combat: 50, tactic: 88
};
let b = {
name: "Arnold", shooting: 97, combat: 72, tactic: 68
};
let c = {
name: "William", shooting: 87, combat: 86, tactic: 97
};
let soldiers = [a, b, c];
let values = {
shooting: {
name: [],
score: 0
},
combat: {
name: [],
score: 0
},
tactic: {
name: [],
score: 0
}
};
soldiers.map((item) => {
['shooting', 'combat', 'tactic'].forEach(name => {
if (item[name] > values[name].score) {
values[name].name = [item.name];
values[name].score = item[name];
} else if (item[name] === values[name].score) {
values[name].name.push(item.name);
}
});
});
console.log(values);

if/else statement in map function?

I am here want to use map function in javascript to loop a type data array,but i get error for these syntax below :
function porti(scores) {
const test = scores.map(pass, fail) => {
if (scores < 75){
test.fail
} else {
test.pass
}
return {pass, fail}
}
}
output must be, if scores < 75 : fail, else : pass
console.log(porti([80, 45, 90, 65, 74, 100, 85, 30]));
// { pass: [ 80, 90, 100, 85 ], fail: [ 45, 65, 74, 30 ] }
console.log(porti([]));
// { pass: [], fail: [] }
I think reduce would be better for this situation. This will allow us to reduce the array to an object of two item arrays.
let items = [80, 45, 90, 65, 74, 100, 85, 30]
let result = items.reduce((obj, item) => {
item < 75 ? obj.fail.push(item) : obj.pass.push(item)
return obj
}, {pass:[], fail:[]})
console.log(result)
If you wanted to use filter you could...
let items = [80, 45, 90, 65, 74, 100, 85, 30]
let result = {
pass: items.filter(i => i >= 75),
fail: items.filter(i => i < 75)
}
console.log(result)
And here is how we can do it with forEach...
let items = [80, 45, 90, 65, 74, 100, 85, 30]
let result = {pass:[], fail:[]}
items.forEach(itm => itm < 75 ? result.fail.push(itm) : result.pass.push(itm))
console.log(result)
You could integrate the check as ternary for getting the key for pushing.
function porti(scores) {
var result = { pass: [], fail: [] },
score;
for (score of scores) {
result[score < 75 ? 'fail': 'pass'].push(score);
}
return result
}
console.log(porti([80, 45, 90, 65, 74, 100, 85, 30]));
console.log(porti([]));
.as-console-wrapper { max-height: 100% !important; top: 0; }
As mentioned above .map() should best be saved for when you are looking to return an array by manipulating a previous array. If you don't wish to use a vanilla for loop. You could try this
const testScores = [...someArray of numbers]
function porti(tesScores) {
const result = {
pass: [],
fail: []
}
for (let score of testScores) {
if (score < 75) {
result.fail.push(score)
} else {
result.pass.push(score)
}
return result
}}

calculate average result from multi-dimensionally sorted array (JavaScript)

Below is the layout of my JSON File.
{
"questions": ["Question1", "Question2"],
"orgs": ["Org1", "Org2", "Org3"],
"dates": ["Q1", "Q2", "Q3"],
"values": [
[
[5, 88, 18],
[50, 83, 10],
[29, 78, 80]
],
[
[46, 51, 61],
[95, 21, 15],
[49, 86, 43]
]
]
}
I'm trying to retrieve a single array of values by looping through each question, indexed by an "orgs" value and then adding each value retrieved and dividing it by data.dates.length.
Here is my code;
d3.json("data.json", function(error, data) {
var array = new Array()
var orgS = "Org2"
var org = data.orgs.indexOf(orgS);
for (var question = 0; question < data.questions.length; question++) {
array.push(
data.values[question][org]
)
console.log(array)
}
// add array together
array.reduce(function(a, b) {
return a + b;
})
// calculate average
var avg = array / data.dates.length;
})
Here is a plnk;
http://plnkr.co/edit/wMv8GmkD1ynjo9WZVlMb?p=preview
I think the issue here is how I'm retrieving the values in the first place? as at the moment, although I am retrieving the correct values in the console log, I'm getting the array twice, and both times inside nested arrays. I'm not so sure how to remedy the problem?
For reference;
[question1][org1] corresponds to the values [5, 88, 18].
Hope someone can offer some advice here?
Thanks!
Since you clarified your question to indicate you want to calculate separate averages for each question, I've rewritten my answer. You should do all the calculations in the for loop, since the loop is looping through the questions. Then store your averages in an array.
d3.json("data.json", function(error, data) {
var averages = new Array()
var orgS = "Org2"
var org = data.orgs.indexOf(orgS);
var values, sum;
for (var question = 0; question < data.questions.length; question++) {
// get the values for the question/org
values = data.values[question][org];
// calculate the sum
sum = values.reduce(function(a, b) {
return a + b;
});
// calculate the average
averages.push(sum / values.length);
}
console.log(averages);
});
Perform the .reduce() in the for loop and push that result into array. That will give you the an array of the results you expected.
array.push(data.values[question][org].reduce(function(a, b) {
return a + b
}, 0) / data.dates.length)
[
47.666666666666664,
43.666666666666664
]
Currently, you're attempting to perform addition on the arrays themselves in the .reduce() callback instead of reducing the members of each individual array to their sum, and then average.
Demo: (Click the text below to show the whole function)
var data = {
"questions": ["Question1", "Question2"],
"orgs": ["Org1", "Org2", "Org3"],
"dates": ["Q1", "Q2", "Q3"],
"values": [
[
[5, 88, 18],
[50, 83, 10],
[29, 78, 80]
],
[
[46, 51, 61],
[95, 21, 15],
[49, 86, 43]
]
]
}
x(data)
// Your callback function.
function x(data) {
var array = new Array()
var orgS = "Org2"
var org = data.orgs.indexOf(orgS);
for (var question = 0; question < data.questions.length; question++) {
array.push(data.values[question][org].reduce(function(a, b) {
return a + b
}, 0) / data.dates.length)
}
console.log(array)
}
Instead of a for loop, you could also use .map().
var array = data.questions.map(function(_, question) {
return data.values[question][org].reduce(function(a, b) {
return a + b
}, 0) / data.dates.length
})
Demo: (Click the text below to show the whole function)
var data = {
"questions": ["Question1", "Question2"],
"orgs": ["Org1", "Org2", "Org3"],
"dates": ["Q1", "Q2", "Q3"],
"values": [
[
[5, 88, 18],
[50, 83, 10],
[29, 78, 80]
],
[
[46, 51, 61],
[95, 21, 15],
[49, 86, 43]
]
]
}
x(data)
// Your callback function.
function x(data) {
var orgS = "Org2"
var org = data.orgs.indexOf(orgS);
var array = data.questions.map(function(_, question) {
return data.values[question][org].reduce(function(a, b) {
return a + b
}, 0) / data.dates.length
})
console.log(array)
}
You need to store the sum, the result of reduce.
// add array together
// store in sum
var sum = array.reduce(function(a, b) {
return a + b;
}, 0); // use 0 as start value
For the average, you do not need the length of data.dates but from array, because you collecting the values and this length is important.
// calculate average
var avg = sum / array.length;
Together for all values, you might get this
var data = { "questions": ["Question1", "Question2"], "orgs": ["Org1", "Org2", "Org3"], "dates": ["Q1", "Q2", "Q3"], "values": [[[5, 88, 18], [50, 83, 10], [29, 78, 80]], [[46, 51, 61], [95, 21, 15], [49, 86, 43]]] },
sum = [];
data.values.forEach(function (a, i) {
sum[i] = sum[i] || [];
a.forEach(function (b) {
b.forEach(function (c, j) {
sum[i][j] = sum[i][j] || 0;
sum[i][j] += c;
});
});
});
data.avg = sum.map(function (a, i) {
return a.map(function (b) {
return b / data.values[i].length;
});
});
console.log(sum);
console.log(data);

Remove duplicate objects from JSON file in JavaScript

This is the current JSON file:
[{
"name": "Peter",
"age": 30,
"hair color": "brown"
}, {
"name": "Steve",
"age": 55,
"hair color": "blonde"
}, {
"name": "Steve",
"age": 55,
"hair color": "blonde"
}]
I want to remove the duplicate Steve individual from the list. How can I make a new JSON that checks if the object's name matches and remove any duplicates in JavaScript?
You must load the JSON data in to the program and parse that with JSON.parse, like this
var array = JSON.parse(content.toString())
To filter out the repeated names from the array of Objects, we use Array.prototype.filter function. You can store the names in an object, and next time when the same name appears we simply filter it out from the result.
var seenNames = {};
array = array.filter(function(currentObject) {
if (currentObject.name in seenNames) {
return false;
} else {
seenNames[currentObject.name] = true;
return true;
}
});
console.log(array);
# [ { name: 'Peter', age: 30, 'hair color': 'brown' },
# { name: 'Steve', age: 55, 'hair color': 'blonde' } ]
var data = [{
"name": "Peter",
"age": 30,
"hair color": "brown"
}, {
"name": "Steve",
"age": 55,
"hair color": "blonde"
}, {
"name": "Steve",
"age": 55,
"hair color": "blonde"
}]
data = this.data.filter((obj, pos, arr) => {
return arr.map(mapObj =>
mapObj.name).indexOf(obj.name) == pos;
});
console.log(data);
Using Underscore.js and the uniq function:
_.uniq(array, false, function (item) { return item.name; })
Loop, check, splice, repeat:
var distinctValues = {};
for (var i = 0; i < data.length; i++) {
if (distinctValues.hasOwnProperty(data[i].name]) {
//already has it
data.splice(i, 1);
i--;
} else {
distinctValues[data[i].name] = true;
}
}
This was the best solution I could find that makes you able to filter on multiple values in you json object. Solution without _ (module)
array.filter((thing, index, self) =>
index === self.findIndex((t) => (
t.place === thing.place && t.name === thing.name // you can add more arguments here to filter more
))
)
//Other example
array.filter((thing, index, self) =>
index === self.findIndex((t) => (
t.place === thing.place && t.name === thing.name && t.time === thing.time
))
)
Python one-liner from CLI:
cat file_with_duplicates.json | python2 -c 'import sys; import json; sys.stdout.write(json.dumps(reduce(lambda x, y: x + [y] if y not in x else x, json.loads(sys.stdin.read()), [])))' > unique_items.txt

Categories

Resources