Between query result from json array using lodash - javascript

I have json array look like this
[
{"hoteldetail":{"hotelid":"00007111","hotelname":"hjghghg","minrate":"500"},
{"hoteldetail"{"hotelid":"00007111","hotelname":"hjghghg","minrate":"1200"},
{"hoteldetail":{"hotelid":"00007111","hotelname":"hjghghg","minrate":"7000"},
{"hoteldetail":{"hotelid":"00007111","hotelname":"hjghghg","minrate":"8000"}
]
From this i want to fetch the items with in a range of minrate
Eg: if the minrate range is (500,7500) it will returns the items from the json array that contains only the minrate with in the above range
So how can i get the output using lodash?

The straightforward implementation is
_.filter(arr, item => _.inRange(item.hoteldetail.minrate, 500, 7501))
which works exactly as it reads: filter the array for item whose property hoteldetail.minrate is within a specified range. However, newer lodash has a very nice function called _.iteratee which allows you to provide a filter shorthand, for example:
_.filter(arr, 'minrate in range 500, 7500')
Here is the implementation
_.iteratee = _.wrap(_.iteratee, function(callback, func) {
var p = /minrate in range (\d+), (\d+)/g.exec(func);
return !p ? callback(func) : function(object) {
return object.hoteldetail.minrate >= p[1] && object.hoteldetail.minrate <= p[2];
};
});
You can use this iteratee just like writing English _.filter(arr, 'minrate in range 100, 200'), _.filter(arr, 'minrate in range 5000, 6000'), etc. You can even go so far as to generalize minrate in the following manner:
_.iteratee = _.wrap(_.iteratee, function(callback, func) {
var p = /(minrate|number\ of\ rooms) in range (\d+), (\d+)/g.exec(func);
return !p ? callback(func) : function(object) {
var prop = object.hoteldetail[p[1]];
return prop >= p[2] && prop <= p[3];
};
});
And use it as _.filter(arr, 'number of rooms in range 1, 5')
Doc: https://lodash.com/docs#iteratee

var _ = require('lodash'); //lodash 4.5
var hotels = [
{"hoteldetail":{"hotelid":"00007111","hotelname":"hjghghg","minrate":"500"}},
{"hoteldetail":{"hotelid":"00007111","hotelname":"hjghghg","minrate":"1200"}},
{"hoteldetail":{"hotelid":"00007111","hotelname":"hjghghg","minrate":"7000"}},
{"hoteldetail":{"hotelid":"00007111","hotelname":"hjghghg","minrate":"8000"}}
]
var filteredItems = _.filter(hotels, function(hotel){
return _.inRange(hotel.hoteldetail.mitrate, 500, 7500);
});

Related

How to find highest key in JSON?

An API I'm working with is returning poorly structured data like this:
{
"scsi0": "vm-101-disk-1.qcow2,size=32G",
"scsi1": "vm-101-disk-2.qcow2,size=32G",
"scsi2": "vm-101-disk-3.qcow2,size=32G"
}
As you can see, instead of having a scsi object with then contains the 0, 1, 2 key/value pairs, I just have the keys named like that.
In JavaScript, how can I search for the highest scsi value which in that case would be 2?
Object.keys() is a good place to start for jobs like this. How about something like this?
var data = {
"scsi0": "vm-101-disk-1.qcow2,size=32G",
"scsi1": "vm-101-disk-2.qcow2,size=32G",
"scsi2": "vm-101-disk-3.qcow2,size=32G"
};
// get keys of data
var keys = Object.keys(data);
// get array of number values
var numbers = keys.map(function(key) {
// strip text out of keys, parse as integers
return parseInt(key.replace(/\D/g, '') || 0);
})
// get the largest number in the array
var highest = Math.max.apply(null, numbers);
// build the data key with this number
var key = "scsi" + highest;
// get the data pertaining to the key
var final = data[key];
// log the result
console.log(final);
You could use a collator to get a compare function that takes such embedded numbers into account:
const data = {
"scsi0": "vm-101-disk-1.qcow2,size=32G",
"scsi11": "vm-101-disk-2.qcow2,size=32G",
"scsi2": "vm-101-disk-3.qcow2,size=32G"
};
const cmp = (new Intl.Collator(undefined, {numeric: true})).compare;
const max = Object.keys(data).reduce((a, b) => cmp(a, b) > 0 ? a : b);
console.log(max)
Use reduce on keys of your object:
var o = {
"scsi0": "vm-101-disk-1.qcow2,size=32G",
"scsi11": "vm-101-disk-2.qcow2,size=32G",
"scsi2": "vm-101-disk-3.qcow2,size=32G"
};
var max = Object.keys(o).reduce((m, k) => +m.replace(/\D/g, '') > +k.replace(/\D/g, '') ? m : m = k);
console.log(max, +max.replace(/\D/g, ''))

Use Object keys as dates to get average of values

I have an object like so. They key is a timestamp and the value is my number
var history = {
'1505845390000': 295426,
'1505757979000': 4115911,
'1505677767000': 4033384,
'1505675472000': 4033384,
'1505591090000': 3943956,
'1505502071000': 3848963,
'1505499910000': 3848963,
'1505499894000': 3848963
}
What I want to do is:
1) For the latest 5 dates (keys), get an average of the values
2) For a date range, get an average of the values
you can do the following for the first case
var obj = {
'1505845390000': 295426,
'1505757979000': 4115911,
'1505677767000': 4033384,
'1505675472000': 4033384,
'1505591090000': 3943956,
'1505502071000': 3848963,
'1505499910000': 3848963,
'1505499894000': 3848963
}
let ans = Object.keys(obj).sort();
ans = ans.slice(ans.length-5).reduce((a, b) => a+obj[b], 0);
console.log(ans/5);
For the 2nd case you can do
var obj = {
'1505845390000': 295426,
'1505757979000': 4115911,
'1505677767000': 4033384,
'1505675472000': 4033384,
'1505591090000': 3943956,
'1505502071000': 3848963,
'1505499910000': 3848963,
'1505499894000': 3848963
}
let start = '1505591090000', end = '1505845390000'
let ans = Object.keys(obj).filter(e => e>=start && e<=end);
let result = ans.reduce((a,b) => a+obj[b],0)/ans.length
console.log(result);
This answer has a good explanation of how to filter an object by its keys:
https://stackoverflow.com/a/38750895/5009210
Basically use Object.keys() to get an array of keys, then Array.filter() to select the ones you want, then Array.reduce() to reconstruct an object with the relevant values for the filtered keys. Like this:
Object.keys( history )
.filter( someFilterFunction )
.reduce( (obj, key) => {
obj[key] = history[key];
}, {});
Once you've done this, you can extract the remaining values using Object.values() and then pass them to a simple average function (I assume you want the mean average):
function meanAverage( valuesToAverage ) {
//avoid divide by zero
if ( valuesToAverage.length ) {
const valueSum = sum( valuesToAverage );
return valueSum / valuesToAverage.length;
}
return 0;
}
function sum( valuesToSum ) {
return valuesToSum.reduce( (a, b) => a + b );
}

Group and aggregate an array of javascript objects

I am having some trouble rationalising and aggregating an array of objects in javascript.
Given the array
[{"description":"Bright","size":"2XL","price":10.99},{"description":"Bright","size":"XL","price":10.99},{"description":"Bright","size":"L","price":9.99},{"group":"Foos","description":"Dull","size":"XL","price":9.99},{"description":"Dull","size":"L","price":8.99},{"description":"Dull","size":"2XL","price":9.99},{"description":"Shiny","size":"XL","price":9.99},{"description":"Shiny","size":"S","price":8.99},{"description":"Shiny","size":"3XL","price":10.3},{"description":"Shiny","size":"2XL","price":9.99}]
I am trying to convert it to an array in the format (actual values may be wrong here).
[{"descriptions":"Shiny, Bright, Dull","sizeRange":"S - L","price":8.99},{"descriptions":"Shiny, Bright, Dull","sizes":"XL - 2XL","price":9.99},{"descriptions":"Dark","sizes":"S - 2XL","price":10.99}]
That is - I wish to group each set of items by price, showing the descriptions and size ranges for them.
So far this is what I have, and it seems to be working, but it just seems very cumbersome. Really I'd be more than happy to use something like lodash or underscore if it would help to rationalise the code a bit rather than using native JS.
function groupBy (array, key) {
return array.reduce(function(value, property) {
(value[property[key]] = value[property[key]] || []).push(property);
return value;
}, {});
};
function unique(array) {
return Array.from(new Set(array));
};
function getRanges(data)
{
var result = [];
// simple map of sizes from smallest to largest to use for sorting
var sizeSort = {'S':1, 'M':2, 'L':3, 'XL':4, '2XL':5, '3XL':6, '4XL':7, '5XL':8};
// group the remaining variants by price
var group = groupBy(data, 'price');
// for each variant price group
for(var price in group) {
var item = {};
item.price = price;
// get the range of sizes sorted smallest to largest
var sizes = unique(group[price].map(function(i) {
return i.size;
})).sort(function(a, b) {
return sizeSort[a] - sizeSort[b];
});
// Add single size, or first and last size.
item.sizes = (sizes.length === 1) ?
sizes.shift() :
sizes.shift() + ' - ' + sizes.pop();
// Add the descriptions as alphabetically sorted CSV
item.description = unique(group[price].map(function(i) {
return i.description;
})).sort().join(", ");
result.push(item);
}
return result;
}
Here is a version using lodash..
I think it looks more rational..
function calc(data) {
var sizeSort = {'S':1, 'M':2, 'L':3, 'XL':4, '2XL':5,
'3XL':6, '4XL':7, '5XL':8};
return _.chain(data).
groupBy('price').
map(function(f){
var sizes = _.chain(f).map('size').uniq().
sortBy(function (a) { return sizeSort[a] }).value();
return {
price: _.head(f).price,
description: _.chain(f).map('description').uniq().join(',').value(),
size: sizes.length === 1 ? _.first(sizes) : _.join([_.first(sizes),_.last(sizes)], ' - ')
}
}).
sortBy(['price']).
value();
}
//put data at end, so not having to scroll down to see code
var data = [{"description":"Bright","size":"2XL","price":10.99},{"description":"Bright","size":"XL","price":10.99},{"description":"Bright","size":"L","price":9.99},{"group":"Foos","description":"Dull","size":"XL","price":9.99},{"description":"Dull","size":"L","price":8.99},{"description":"Dull","size":"2XL","price":9.99},{"description":"Shiny","size":"XL","price":9.99},{"description":"Shiny","size":"S","price":8.99},{"description":"Shiny","size":"3XL","price":10.3},{"description":"Shiny","size":"2XL","price":9.99}];
console.log(calc(data));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.16.3/lodash.js"></script>
A vanilla JavaScript solution (with ES6 template strings)
/*
Some boilerplate functions. Listed underscore/lodash functions that
could replace them above
*/
// _.mapObject(object, reducer)
function reduceValues(object, reducer) {
let newObject = {}
for (var property in object) {
if (object.hasOwnProperty(property)) {
newObject[property] = reducer(object[property])
}
}
return newObject
}
// _.groupBy
function groupBy(arr, key) {
let reducer = (grouped, item) => {
let group_value = item[key]
if (!grouped[group_value]) {
grouped[group_value] = []
}
grouped[group_value].push(item)
return grouped
}
return arr.reduce(reducer, {})
}
// _.values
function objectValues(object) {
let values = []
for (var property in object) {
if (object.hasOwnProperty(property)) {
values.push(object[property])
}
}
return values
}
/*
Shirt specific functions and data
*/
// Mapping of shirts to their order.
let sizesToNumbers = {'S':1, 'M':2, 'L':3, 'XL':4, '2XL':5, '3XL':6, '4XL':7, '5XL':8};
// Create an intermediate summary with data instead of strings.
// This makes processing easier to write and reason about
function reduceShirtsToSummary(shirts) {
let reducer = (summary, shirt) => {
summary['descriptions'].add(shirt['description'])
let shirtSize = shirt['size']
if (!summary['smallestSize'] || sizesToNumbers[shirtSize] < sizesToNumbers[summary['smallestSize']]) {
summary['smallestSize'] = shirtSize
}
if (!summary['largestSize'] || sizesToNumbers[shirtSize] > sizesToNumbers[summary['largestSize']]) {
summary['largestSize'] = shirtSize
}
summary['prices'].push(shirt['price'])
return summary
}
return shirts.reduce(reducer, {'descriptions': new Set(), 'prices': []})
}
// Convert the shirt summary data into the "labelized" version with strings in the example
function labelizeShirtSummary(shirtSummary) {
let labelizedShirtSummary = {}
labelizedShirtSummary['descriptions'] = Array.from(shirtSummary['descriptions']).join(', ')
labelizedShirtSummary['price'] = shirtSummary['prices'][0]
labelizedShirtSummary['sizes'] = `${shirtSummary['smallestSize']} - ${shirtSummary['largestSize']}`
return labelizedShirtSummary
}
let grouped = groupBy(shirts, 'price')
let groupedAndSummarized = reduceValues(grouped, reduceShirtsToSummary)
let labelizedSummaries = objectValues(groupedAndSummarized).map(labelizeShirtSummary)
// Gives desired output
console.log(labelizedSummaries)

Group (by year) and sum in javascript collection

I want to sum the values of the same year in a single value for each year, in a node javascript application. I would like to avoid a for(;;) iteration, discovering the year, sum the values.... Maybe there is something in the Underscore library (like _.groupBy but I cannot figure how use it in this case).
The json data is this (i.e quarterly income data):
{ "data":
["2015-07-22",125677000000.0],
["2015-04-28",129006000000.0],
["2015-01-28",123328000000.0],
["2014-10-27",111547000000.0],
["2014-07-23",120940000000.0],
["2014-04-24",120178999999.99998],
["2014-01-28",129684000000.0],
["2013-10-30",123549000000.0],
["2013-07-24",123354000000.00002],
["2013-04-24",135490000000.0],
["2013-01-24",127346000000.0],
["2012-10-31",118210000000.0],
[etc...]}
the result should be:
{ "data":
["2015",sum of all 2015 data],
["2014",sum of all 2014 data],
["2013",sum of all 2013 data],
[etc...]}
Not a problem at all, no loops, no underscore needed.
a = { "data":[
["2015-07-22",125677000000.0],
["2015-04-28",129006000000.0],
["2015-01-28",123328000000.0],
["2014-10-27",111547000000.0],
["2014-07-23",120940000000.0],
["2014-04-24",120178999999.99998],
["2014-01-28",129684000000.0],
["2013-10-30",123549000000.0],
["2013-07-24",123354000000.00002],
["2013-04-24",135490000000.0],
["2013-01-24",127346000000.0],
["2012-10-31",118210000000.0]]}
var years = a.data.map(function(d) {
return [new Date(d[0]).getFullYear(), d[1]];
});
var sums = years.reduce(function(prev, curr, idx, arr) {
var sum = prev[curr[0]];
prev[curr[0]] = sum ? sum + curr[1] : curr[1];
return prev;
}, {});
> sums
Object {2012: 118210000000, 2013: 509739000000, 2014: 482350000000, 2015: 378011000000}
Note that I had to fix your JSON data. The one you posted wasn't valid JSON, so I just added array brackets around the list of lists.
You can even do it in 1 line if you want.
a.data.map(function(d) { return [new Date(d[0]).getFullYear(), d[1]]; }).reduce(function(prev, curr, idx, arr) { var sum = prev[curr[0]]; prev[curr[0]] = sum ? sum + curr[1] : curr[1]; return prev; }, {});
Just kidding, please don't do that :)
var data = [
["2015-07-22",125677000000.0],
["2015-04-28",129006000000.0],
["2015-01-28",123328000000.0],
["2014-10-27",111547000000.0],
["2014-07-23",120940000000.0],
["2014-04-24",120178999999.99998],
["2014-01-28",129684000000.0],
["2013-10-30",123549000000.0],
["2013-07-24",123354000000.00002],
["2013-04-24",135490000000.0],
["2013-01-24",127346000000.0],
["2012-10-31",118210000000.0]
];
Create the sums in a map (years -> sums) - then turn the map into an array with _.pairs:
var result = _.pairs(data.reduce(function(prev, curr) {
var year = curr[0].substr(0, 4);
prev[year] = prev[year] ? prev[year] + curr[1] : curr[1];
return prev;
}, {}));
Then order by first element, descending:
result.sort(function(a, b) { return a[0] < a[1]; });
Now that you mention underscore, iterating once and calling the indexes do it
var json = { "data": [["2015-07-22",125677000000.0], ["2015-04-28",129006000000.0], ["2015-01-28",123328000000.0], ["2014-10-27",111547000000.0], ["2014-07-23",120940000000.0], ["2014-04-24",120178999999.99998], ["2014-01-28",129684000000.0], ["2013-10-30",123549000000.0], ["2013-07-24",123354000000.00002], ["2013-04-24",135490000000.0], ["2013-01-24",127346000000.0], ["2012-10-31",118210000000.0]]
};
var sums = {};
_.each(json.data, function(arr) {
var index = arr[0].split('-')[0];
if (typeof(sums[index]) !== 'undefined') {
sums[index] += arr[1];
} else {
sums[index] = arr[1];
} });
And yes, as #bbill points out, I fix your JSON as well :)
Here is a JSFiddle for it

Iterating through object to add up all matching properties

I have a caml query which is going to return something like this in xml.
ID Title Percentage
7;#7 2 1.00000000000000
7;#7 3 0.220000000000000
7;#7 sub 1.1 0
7;#7 4 0.140000000000000
12;#12 7 0.670000000000000
13;#13 6 0.700000000000000
I'll likely create an aray of objects for each item. Something like this:
var result = [{id:7,title:"2",percent:1.0},...,{id:13,title:"6",percent:0.7}]
How could I iterate through the result and add up all the percentages with the same ID so I end up with something like:
var total = [{id:7,percent:1.36,count:4},{id:12,percent:0.67,count:1},{id:13,percent:0.7,count:1}]
Or even if I could just get
percent/count = totalPercentage so I end up with an object with just {id:7,totalPercentage:0.325}
Try this:
var percentages = {};
result.forEach(function (it) {
var obj = percentages[it.id] = percentages[it.id] || {
percent: 0,
count: 0,
id: it.id
};
obj.percent += Number(it.percent); // Casting to Number, in case percent comes as string
obj.count++;
});
This creates an object, with ids as keys. Should you wish to convert it to an array:
total = Object.keys(percentages).map(function (it) {
return percentages[it]
});
To get the average of the percentages, you can do this:
total = total.map(function(it) {
return {
id: it.id,
percent: it.percent / it.count
};
});
Just make the new object and iterate though the old one. You can see a demo here: http://jsfiddle.net/TSyE5/2/
var totalPercent = [],
total = {},
result = [{id:8,title:"3",percent:1.0},{id:7,title:"2",percent:1.0},{id:7,title:"2",percent:3.0},{id:13,title:"6",percent:0.7},{id:13,title:"6",percent:0.7},{id:13,title:"6",percent:0.7}];
$.each(result, function(){
!(this.id in total) && (total[this.id] = {id:this.id, title:this.title, percent:0, count:0});
total[this.id].percent += this.percent;
total[this.id].count++;
});
$.each(total, function(i){
total[i].percent = total[i].percent/total[i].count;
totalPercent.push({id:total[i].id, percent:total[i].percent});
});

Categories