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 );
}
Related
Is there an array method (or any other way really) to group objects within an array by a property and sum by another. Then return the max?
let array = [
{ticker:'AAPL',val:400},
{ticker:'IBM',val:200},
{ticker:'AAPL',val:500},
{ticker:'SBUX',val:800},
]
let obj = {}
array.forEach( h => {
obj[h.ticker] = obj[h.ticker] || 0;
obj[h.ticker] += h.val
})
let temp = Object.keys( obj ).map(function ( key ) { return obj[key] });
console.log(Math.max(...temp)) // returns 900
If you just want to modify it to use reduce instead of forEach, you can do:
let obj = array.reduce((a,b) => ({...a, [b.ticker]: (a[b.ticker] || 0) + b.val}), {});
I have a string:
var rrule = "DTSTART=20190514T111500Z;FREQ=DAILY;INTERVAL=1";
I want to convert this string to key-> value pairs in an array.
[
dtstart: 20190514T111500Z,
freq: daily,
interval: 1
]
I know I can take the string and split it based on the semicolon:
var array = rrule.split(";");
... but this leaves me with an array like this:
[
"DTSTART=20190514T111500Z",
"FREQ=DAILY",
"INTERVAL=1"
]
I guess I need another step to map out the keys/values, but I get lost at this point.
Ideally, for the string I want to be able to easily access what dtstarts equals, what interval equals, what other variables equal and so on.
let str = "DTSTART=20190514T111500Z;FREQ=DAILY;INTERVAL=1";
let obj = {};
for (let entry of str.split(";")) {
let pair = entry.split("=");
obj[pair[0]] = pair[1];
}
console.log(obj);
You already know how to split on the ; to get an array, from there you can just aggregate (using reduce) to get an object:
var rrule = "DTSTART=20190514T111500Z;FREQ=DAILY;INTERVAL=1";
var result = rrule.split(";").reduce( (obj,item) => {
let [key,value] = item.split("=");
obj[key] = value;
return obj;
},{});
console.log(result["DTSTART"])
console.log(result["FREQ"])
console.log(result["INTERVAL"])
You were correct to start with split first, this would then return you an array of strings.
To easily convert them, just use map, to return the split the single strings once more, and then return an object based on the property name you would like to give it and it's value
function createKeyValuePairFromString( str ) {
return str.split(';').map( item => {
const splitted = item.split('=');
return { [splitted[0]]: splitted[1] };
});
}
console.log( createKeyValuePairFromString("DTSTART=20190514T111500Z;FREQ=DAILY;INTERVAL=1") );
Use array created and split it again with =
function convertToObject(cookieString) {
const cookieObj = {};
if (!cookieString && typeof cookieString !== 'string') return cookieObj;
const arr = cookieString.split(';');
arr.forEach(record => {
if (record.includes('=')) {
const [key, value] = record.split('=');
cookieObj[key.trim()] = value;
}
});
return cookieObj;
}
You can use it like the code below:
var rrule = "DTSTART=20190514T111500Z;FREQ=DAILY;INTERVAL=1";
let finalObj = {};
rrule.split(';').forEach(i => finalObj[i.split('=')[0]] = i.split('=')[1]);
console.log('finalObj',finalObj);
Here I'm first splitting with ';' so consider the first item to be DTSTART=20190514T111500Z Then on splitting with = I get finalObject['DTSTART'] = 20190514T111500Z
Using forEach()
let str = "DTSTART=20190514T111500Z;FREQ=DAILY;INTERVAL=1";
let obj = {};
let strArr = str.split(';')
strArr.forEach((str) => {
let [key, value] = str.split('=')
obj[key] = value;
});
console.log(obj);
Here's a fairly simple version, returning an object, not an array:
const toObj = str => str
.split (';')
.map ( s => s .split ('=') )
.reduce ( (a, [k, v]) => ({...a, [k]: v}), {} )
let str = "DTSTART=20190514T111500Z;FREQ=DAILY;INTERVAL=1";
console.log (
toObj(str)
)
One of the reasons I like the library is that we can write this sort of logic more simply. In Ramda (disclaimer: I'm one of the authors), it might look like this:
const toObj = pipe ( split (';'), map (split ('=') ), fromPairs)
let str = "DTSTART=20190514T111500Z;FREQ=DAILY;INTERVAL=1";
console.log (
toObj(str)
)
<script src="https://bundle.run/ramda#0.26.1"></script><script>
const {pipe, split, map, fromPairs} = ramda; </script>
var str = "DTSTART=20190514T111500Z;FREQ=DAILY;INTERVAL=1";
// string splitting rule
const rule = (string, delimiter) => string.split(delimiter);
const result = rule(str, ';').reduce((acc, s) => {
const [key, value] = rule(s, '=');
acc[key] = value;
return acc;
}, {});
console.log(result);
const arr = [
{"datetime":"2018/8/5","value":85,"type":"A"},
{"datetime":"2018/8/10","value":7,"type":"B"},
{"datetime":"2018/8/10","value":73,"type":"A"}
];
I have an array as you can see in the snippet. My issue is I need to check something per day:
For each day when A value > 60 or when B value > 6 then do something.
Else when A value <= 60 and when B value <= 6 then do something else.
And I don't know how to do this check with the current array structure as each step in the loop is a different day. I want to compare all values for one day at the same time.
Is it possible to transform the array to look like this? Then I will be able to compare day per day...
const arr = [
{"datetime":"2018/8/5","valueA":85,"valueB":undefined},
{"datetime":"2018/8/10","valueB":7,"valueA":73}
];
Thank you!
You can make a the date groups by reducing into an object. Then just set the appropriate value in that object. In the end your array will be in the Object.keys() of the grouped object.
[As you might surmise from the comments, the order of the final array is not guaranteed because object keys and values are not guaranteed. If your original data is ordered by date, you should say so in the question because there will be more efficient ways to do this if the order is guaranteed].
const arr = [{"datetime":"2018/8/5","value":85,"type":"A"},{"datetime":"2018/8/10","value":7,"type":"B"},{"datetime":"2018/8/10","value":73,"type":"A"}];
let groups = arr.reduce((obj, {datetime, value, type}) => {
if (!obj[datetime]) obj[datetime] = {datetime, valueA:undefined, valueB:undefined}
let currentKey = type == 'A' ? "valueA" : "valueB"
obj[datetime][currentKey] = value
return obj
},{})
let newArr = Object.values(groups)
console.log(newArr)
This will transform the array as OP asked for, and will respect the order.
const arr = [{"datetime":"2018/8/5","value":85,"type":"A"},
{"datetime":"2018/8/10","value":7,"type":"B"},
{"datetime":"2018/8/10","value":73,"type":"A"}];
var daysArr = []
arr.map(function(day){
var keyName = 'value'+day.type
var found = false
var dayObj = {}
for (var i=0; i < daysArr.length; i++) {
if (daysArr[i].datetime === day.datetime) {
daysArr[i][keyName] = day.value;
found = true
break
}
}
if (!found) {
dayObj = {"datetime":day.datetime,valueA:undefined,valueB:undefined}
dayObj[keyName] = day.value
daysArr.push(dayObj)
}
})
console.log(daysArr);
One solution could be using reduce(). Note that if a key is not defined will return undefined (this is exemplified on the second log to the console), so I consider redundant to define, for example "value-B": undefined, unless you want to assign to it another default value.
Warning: As discussed on the comments, you should note that the order of the final result, may not be preserved.
const arr = [
{"datetime":"2018/8/5","value":85,"type":"A"},
{"datetime":"2018/8/10","value":7,"type":"B"},
{"datetime":"2018/8/10","value":73,"type":"A"}
];
let res = arr.reduce((acc, {datetime, value, type: type}) =>
{
acc[datetime] = acc[datetime] || {};
Object.assign(acc[datetime], {datetime, [`value-${type}`]: value});
return acc;
}, {});
console.log(Object.values(res));
console.log(Object.values(res)[0]["value-B"]);
You could do this:
<html>
<head>
<meta charset="UTF-8"></meta>
<script type="text/javascript">
const arr = [{"datetime":"2018/8/5","value":85,"type":"A"},
{"datetime":"2018/8/10","value":7,"type":"B"},
{"datetime":"2018/8/10","value":73,"type":"A"}];
var new_arr = group_items(arr)
console.log(new_arr)
function group_items(arr)
{
var ret_arr = []
for(var x=0;x<arr.length;x++)
{
var cur_date = arr[x].datetime
var pos = lookup_date(cur_date, ret_arr)
var obj = {}
obj.datetime = cur_date
if(pos != false)
{
//add to existing item
if(arr[x].type == 'A')
{
ret_arr[pos].valueA = arr[x].value
}
else if(arr[x].type == 'B')
{
ret_arr[pos].valueB = arr[x].value
}
}
else{
if(arr[x].type == 'A')
{
obj.valueA = arr[x].value
}
else if(arr[x].type == 'B')
{
obj.valueB = arr[x].value
}
ret_arr.push(obj)
}
}
return ret_arr
}
function lookup_date(date, arr)
{
/*
returns the position in arr of date
*/
var retval = false
for(var x=0;x<arr.length;x++)
{
if(arr[x].datetime == date)
{
retval = x
break
}
}
return retval
}
</script>
</head>
<body>
</body>
If you don't need the final array to include the datetimes in the same order as the original, then you can just make an object that maps datetimes to the corresponding values and then use Object.values to get the final array. This approach does not guarantee order, since objects are unordered data structures:
const arr = [
{"datetime":"2018/8/5","value":85,"type":"A"},
{"datetime":"2018/8/10","value":7,"type":"B"},
{"datetime":"2018/8/10","value":73,"type":"A"}
];
const values_by_date = { };
arr.forEach( ({ datetime, type, value }) =>
values_by_date[ datetime ] = {
datetime/*, valueA: undefined, valueB: undefined*/,
...values_by_date[ datetime ], [`value${type}`]: value
}
);
const result = Object.values( values_by_date );
console.log( result );
If you need the final array to include the datetimes in the same order as the original array and the original array is already sorted by datetime, you can do it in a single pass like this:
const arr = [
{"datetime":"2018/8/5","value":85,"type":"A"},
{"datetime":"2018/8/10","value":7,"type":"B"},
{"datetime":"2018/8/10","value":73,"type":"A"}
];
const result = arr.reduce( ({ result, datetime: prev }, { datetime, type, value }) => {
if ( datetime !== prev )
result.push( { datetime/*, valueA: undefined, valueB: undefined*/ } );
Object.assign( result[ result.length - 1 ], { [`value${type}`]: value } );
return { result, datetime };
}, { result: [] } ).result;
console.log( result );
Note: In either snippet you can uncomment /*, valueA: undefined, valueB: undefined*/ if you want the resulting objects to include properties for the missing values.
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, ''))
I believe what I need are two JavaScript functions. I am receiving a comma separated string that holds two types of data: 1) device name followed by 2) numeric value. These two values are separated by a comma, and each set is also separated by a comma. Example string below:
Device_A,5,Device_C,2,Device_A,10,Device_B,8,Device_B,2,Device_C,7
What I want to do is create two separate functions. The first function finds the unique device names and returns just the names in a comma separated string. The second function would calculate the sum of the numeric values for each device. The expected results from the example string above would return:
Function 1 (Device List):
Device_A, Device_B, Device_C
Function 2 (Sums per Device List):
15,10,9
The lists do not need to return in any particular order as long at they both match up. All I have successfully done at this point is return a list of unique values (including numeric values)... I'm stuck on separating the list, but still referring to device name to sum up all of the values.
Thanks in advance. Let me know if you have any questions!
Matt
You could use an object for collecting the names and count.
This edit contains a shared function and two function for the result in equal order.
function getGrouped(data) {
var array = data.split(','),
temp = Object.create(null),
i = 0;
while (i < array.length) {
temp[array[i]] = (temp[array[i]] || 0) + +array[i + 1] || 0;
i += 2;
}
return temp;
}
function getDevices(data) {
var temp = getGrouped(data);
return Object.keys(temp).sort().join();
}
function getCounts(data) {
var temp = getGrouped(data);
return Object.keys(temp).sort().map(function (k) { return temp[k]; }).join();
}
var data = "Device_A,5,Device_C,2,Device_A,10,Device_B,8,Device_B,2,Device_C,7";
console.log(getDevices(data));
console.log(getCounts(data));
When starting out on a problem like this, I think it's wise to not worry about doing it in a single loop or in a fancy one-liner at first.
A) Start out by defining what data structures you need and how to go from one format to another:
Convert my string of data to a list of keys and values
Somehow group these keys and values based on the key
Sum the values for each group
Return a list of all unique keys
Return a list of all summed values
B) Then, try to see if any of the code you've written has the potential be re-used by other parts of your application and refactor accordingly.
C) Finally, assess if there are performance bottle necks and only if there are, optimize for performance.
A. A function for each step:
// 1. From string to array of keys and values
// You already figured this one out. Split by ","!
const namesAndValuesFromString =
str => str.split(",");
// 2. Grouping by key
// Let's first make pairs:
const deviceValuePairs = devicesAndValues => {
let pair = [];
const pairs = [];
devicesAndValues.forEach(x => {
pair.push(x);
if (pair.length === 2) {
pairs.push(pair);
pair = [];
}
});
return pairs;
};
// Key value pairs are a nice starting point for constructing a grouped object:
const kvpsToDeviceValuesObj = kvps => {
const valuesByDevice = {};
kvps.forEach(([key, value]) => {
value = Number(value);
if (!valuesByDevice[key]) {
valuesByDevice[key] = [];
}
valuesByDevice[key].push(value);
});
return valuesByDevice;
};
// 3. Now, we can get to summing the values arrays
const sumValueArrays = valuesByDevice => {
const summedValuesByDevice = {};
// Loop over the objects entries
Object.entries(valuesByDevice).forEach(
([key, values]) => {
summedValuesByDevice[key] = values
.reduce((a, b) => a + b);
}
);
return summedValuesByDevice;
};
// 4. + 5. Now that we have an object with device ids as keys, and summed values inside, we can retrieve the two lists
const getDevices = Object.keys;
const getSums = Object.values;
// Running the code:
const namesAndValues =
namesAndValuesFromString("A,5,C,2,A,10,B,8,B,2,C,7");
console.log(namesAndValues);
const kvps = deviceValuePairs(namesAndValues);
console.log(kvps);
const valuesByDevice = kvpsToDeviceValuesObj(kvps);
console.log(valuesByDevice);
const sumValues = sumValueArrays(valuesByDevice);
console.log(sumValues);
const devices = getDevices(sumValues);
console.log(devices);
const sums = getSums(sumValues);
console.log(sums);
B. Refactoring!
Once you understand each of those steps, you'll start to see things that can be generalized or combined. That's where the fun starts :)
// UTILITIES
const split = del => arr => arr.split(del);
const toPairs = arr => {
let pair = [];
return arr.reduce(
(pairs, x) => {
pair.push(x);
if (pair.length === 2) {
pairs.push(pair);
pair = [];
}
return pairs;
}, []);
};
const sum = (x, y = 0) => +x + y;
const kvpsToGroups = grouper => kvps =>
kvps.reduce(
(groups, [key, value]) => Object.assign(groups, {
[key]: grouper(value, groups[key])
}), {});
// YOUR APP
const sumGrouper = kvpsToGroups(sum);
const dataSplitter = split(",");
const parseData = str => sumGrouper(toPairs(dataSplitter(str)));
// MAIN
const result = parseData("A,5,C,2,A,10,B,8,B,2,C,7");
console.log("devices:", Object.keys(result));
console.log("sums:", Object.values(result));
another way by regexs
let str = "Device_A,5,Device_C,2,Device_A,10,Device_B,8,Device_B,2,Device_C,7", obj = {}
str.match(/(\w+,[0-9]+)/g).forEach((s) => {
s = s.split(',')
obj[s[0]] = (obj[s[0]] || 0) + (Number(s[1]) || 0)
})
console.log(obj)
Something like this should do it:
var input = "Device_A,5,Device_C,2,Device_A,10,Device_B,8,Device_B,2,Device_C,7";
var output = input.split(',').reduce((accumulator, currentValue, currentIndex, array) => {
accumulator[currentValue] = (accumulator[currentValue] || 0)
+ parseInt(array[currentIndex + 1]);
array.splice(0,1);
return accumulator;
}, {});
console.log(Object.keys(output));
console.log(Object.keys(output).map(k => output[k]));