How to find highest key in JSON? - javascript

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, ''))

Related

Sort array by custom function in JavaScript

I am using only Javascript.
I have the following string :
?pn1=age&pn2=name&pv1=12&pv2=alice
What I need to do, is have the following outcome :
age:12|name:alice
I thought of a way to do this, it is the following :
var str = "?pn1=age&pn2=name&pv1=12&pv2=alice";
var strSplit = str.split("&");
for (var i = 0; i < strSplit.length; i++) {
console.log(strSplit[i]);
}
This returns the following result :
?pn1=age
pn2=name
pv1=12
pv2=alice
Since I want to join together pn1 and pv1 and pn2 and pv2, the number present in the end of the string is important.
?pn1=age
pn2=name
pv1=12
pv2=alice
So I thought a way to do this is to sort the array by this number. and then joining every 2 values together after sorting.
I tried the following code :
strSplit.sort(function() {
var pref = strSplit[i].split('=')[0];
return pref.charAt(pref.length-1);
});
It does not seem to work
Any help would be appreciated
You could split the parts, collect all items and return a joined string.
var string = '?pn1=age&pn2=name&pv1=12&pv2=alice',
result = string
.slice(1)
.split('&')
.reduce((r, p) => {
var [k, value] = p.split('='),
[key, index] = k.split(/(\d+)/);
index--;
r[index] = r[index] || {};
r[index][key] = value;
return r;
}, [])
.map(({ pn, pv }) => [pn, pv].join(':'))
.join('|');
console.log(result);
You can do that in following steps.
You can loop through half of the array and add corresponding keys and values to an array.
Consider i is the current index when we loop through half array.
The element at position i will be key.
Add the half of the length and add it to i to get corresponding value.
split() both key and value by = and get the second element.
var str = "?pn1=age&pn2=name&pv1=12&pv2=alice";
var arr = str.split("&");
let half = arr.length/2
let res = [];
for (var i = 0; i < half; i++) {
res.push(`${arr[i].split('=')[1]}:${arr[i + half].split('=')[1]}`);
}
console.log(res.join('|'))
You could use URLSearchParams to convert the query string to a collection of key-value pair.
Then loop through them to group the the pv and pn values based on the number.
Separate the string and and number values using the regex: (\D+)(\d+)
Loop through the obj.pn and get the corresponding pv value for the same number
Join the resulting array with |
This works with pn and pv values in any random order
const searchParams = new URLSearchParams("?pn1=age&pn2=name&pv1=12&pv2=alice")
const obj = { pn: {}, pv: {} }
for (let [key, value] of searchParams) {
const [, k, number] = key.match(/(\D+)(\d+)/)
obj[k][number] = value
}
const output = Object.entries(obj.pn)
.map(([n, key]) => `${key}:${obj.pv[n]}`)
.join("|")
console.log(output)
One idea is to first split values on & and add it to take digit as key and place on object and then later place the respective values in desired format
var str = "?pn1=age&pn2=name&pv1=12&pv2=alice".replace(/^\?/,'')
var strSplit = str.split("&");
let op = strSplit.reduce((op,inp) => {
let [key,value] = inp.split('=')
let digit = key.match(/\d+/)[0]
op[digit] = op[digit] || []
op[digit].push(value)
return op
},{})
let final = Object.values(op).reduce((op,inp) => {
let [key,value] = inp
op.push(`${key}:${value}`)
return op
} ,[]).join(' | ')
console.log(final)
You could convert that & split to a string and remove the ?xxx= then split it again by , to finally have an array with the stuff you're looking at.
var str = "?pn1=age&pn2=name&pv1=12&pv2=alice";
var split = str.split('&').toString().replace(/([?]?[pnv0-9]+[=])/g,'').split(',');
console.log(split[0] + ':' + split[2] + '|' + split[1] + ':' + split[3]);
EDIT : worth mentioning for those who are looking to the best performing solution, I tested all those provided here, click here for more infos.
The solution's list from the fastest to the slowest :
Maheer Ali (113,610 ops/s ±1.48% fastest)
tcj (112,324 ops/s ±1.01% 1.13% slower)
Nina Scholz (81,067 ops/s ±1.81% 28.64% slower)
Code Maniac (80,074 ops/s ±0.99% 29.52% slower)
adiga (33,065 ops/s ±0.92% 70.9% slower)

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 );
}

JavaScript to split data and calculate sums

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]));

Search string using RegEx for repeated letters and outputting into two-dimensional array in Javascript

I just started getting into RegEx. So far I have something like this but it looks like my pattern is off.
function hasRepeatedLetters(str) {
var patt = /[a-zA-Z]/g;
var result = patt.exec(str);
return result;
}
console.log(hasRepeatedLetters('aaBcDDbcca'));
I'd like to be able to input any argument with a combo of letters and the output to be a two-dimensional array with the first value being the repeated letter and the second value being the repeated letters length.
[["a",3],["b",1],["B",1],["c",3],["D",2]]
Here is a map / object based solution
var str = 'aaBcDDbcca';
var map = {}; // create a hash for keys and their counts
// loop over all characters and then add count
Array.from(str).forEach(function(character) {
map[character] = map[character] ? map[character] + 1 : 1;
});
// finally, convert map to a 2D array with 1st item as key and second item as count
var counts = Object.keys(map).map(function(key) {
return [key, map[key]];
})
console.log(counts);
You could use an ES6 Map for this, which has some nice features:
a map's set method returns the map again
the spread syntax applied on a map produces the desired array of pairs
function hasRepeatedLetters(str) {
return [...[...str].reduce( (acc, c) => acc.set(c, (acc.get(c) || 0) + 1), new Map )];
}
console.log(hasRepeatedLetters('aaBcDDbcca'));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Possible solution, using Array#forEach function.
var str = 'aaBcDDbcca'.split(''), obj = {};
str.forEach(v => obj[v] ? obj[v]++ : obj[v] = 1);
var res = Object.keys(obj).map(v => [v, obj[v]]);
console.log(JSON.stringify(res));
Personally, I would suggest you to keep it as an object.
var str = 'aaBcDDbcca'.split(''), obj = {};
str.forEach(v => obj[v] ? obj[v]++ : obj[v] = 1);
console.log(JSON.stringify(obj));

Javascript natural sort array/object and maintain index association

I have an array of items as follows in Javascript:
var users = Array();
users[562] = 'testuser3';
users[16] = 'testuser6';
users[834] = 'testuser1';
users[823] = 'testuser4';
users[23] = 'testuser2';
users[917] = 'testuser5';
I need to sort that array to get the following output:
users[834] = 'testuser1';
users[23] = 'testuser2';
users[562] = 'testuser3';
users[823] = 'testuser4';
users[917] = 'testuser5';
users[16] = 'testuser6';
Notice how it is sorted by the value of the array and the value-to-index association is maintained after the array is sorted (that is critical). I have looked for a solution to this, tried making it, but have hit a wall.
By the way, I am aware that this is technically not an array since that would mean the indices are always iterating 0 through n where n+1 is the counting number proceeding n. However you define it, the requirement for the project is still the same. Also, if it makes a difference, I am NOT using jquery.
The order of the elements of an array is defined by the index. So even if you specify the values in a different order, the values will always be stored in the order of their indices and undefined indices are undefined:
> var arr = [];
> arr[2] = 2;
> arr[0] = 0;
> arr
[0, undefined, 2]
Now if you want to store the pair of index and value, you will need a different data structure, maybe an array of array like this:
var arr = [
[562, 'testuser3'],
[16, 'testuser6'],
[834, 'testuser1'],
[823, 'testuser4'],
[23, 'testuser2'],
[917, 'testuser5']
];
This can be sorted with this comparison function:
function cmp(a, b) {
return a[1].localeCompare(b[1]);
}
arr.sort(cmp);
The result is this array:
[
[834, 'testuser1'],
[23, 'testuser2'],
[562, 'testuser3'],
[823, 'testuser4'],
[917, 'testuser5'],
[16, 'testuser6']
]
If I understand the question correctly, you're using arrays in a way they are not intended to be used. In fact, the initialization style
// Don't do this!
var array = new Array();
array[0] = 'value';
array[1] = 'value';
array[2] = 'value';
teaches wrong things about the nature and purpose of arrays. An array is an ordered list of items, indexed from zero up. The right way to create an array is with an array literal:
var array = [
'value',
'value',
'value'
]
The indexes are implied based on the order the items are specified. Creating an array and setting users[562] = 'testuser3' implies that there are at least 562 other users in the list, and that you have a reason for only knowing the 563rd at this time.
In your case, the index is data, and is does not represent the order of the items in the set. What you're looking for is a map or dictionary, represented in JavaScript by a plain object:
var users = {
562: 'testuser3',
16: 'testuser6',
834: 'testuser1',
823: 'testuser4',
23: 'testuser2',
917: 'testuser5'
}
Now your set does not have an order, but does have meaningful keys. From here, you can follow galambalazs's advice to create an array of the object's keys:
var userOrder;
if (typeof Object.keys === 'function') {
userOrder = Object.keys(users);
} else {
for (var key in users) {
userOrder.push(key);
}
}
…then sort it:
userOrder.sort(function(a, b){
return users[a].localeCompare(users[b]);
});
Here's a demo
You can't order arrays like this in Javascript. Your best bet is to make a map for order.
order = new Array();
order[0] = 562;
order[1] = 16;
order[2] = 834;
order[3] = 823;
order[4] = 23;
order[5] = 917;
In this way, you can have any order you want independently of the keys in the original array.
To sort your array use a custom sorting function.
order.sort( function(a, b) {
if ( users[a] < users[b] ) return -1;
else if ( users[a] > users[b] ) return 1;
else return 0;
});
for ( var i = 0; i < order.length; i++ ) {
// users[ order[i] ]
}
[Demo]
Using the ideas from the comments, I came up with the following solution. The naturalSort function is something I found on google and I modified it to sort a multidimensional array. Basically, I made the users array a multidimensional array with the first index being the user id and the second index being the user name. So:
users[0][0] = 72;
users[0][1] = 'testuser4';
users[1][0] = 91;
users[1][1] = 'testuser2';
users[2][0] = 12;
users[2][1] = 'testuser8';
users[3][0] = 3;
users[3][1] = 'testuser1';
users[4][0] = 18;
users[4][1] = 'testuser7';
users[5][0] = 47;
users[5][1] = 'testuser3';
users[6][0] = 16;
users[6][1] = 'testuser6';
users[7][0] = 20;
users[7][1] = 'testuser5';
I then sorted the array to get the following output:
users_sorted[0][0] = 3;
users_sorted[0][1] = 'testuser1';
users_sorted[1][0] = 91;
users_sorted[1][1] = 'testuser2';
users_sorted[2][0] = 47;
users_sorted[2][1] = 'testuser3';
users_sorted[3][0] = 72;
users_sorted[3][1] = 'testuser4';
users_sorted[4][0] = 20;
users_sorted[4][1] = 'testuser5';
users_sorted[5][0] = 16;
users_sorted[5][1] = 'testuser6';
users_sorted[6][0] = 18;
users_sorted[6][1] = 'testuser7';
users_sorted[7][0] = 12;
users_sorted[7][1] = 'testuser8';
The code to do this is below:
function naturalSort(a, b) // Function to natural-case insensitive sort multidimensional arrays by second index
{
// setup temp-scope variables for comparison evauluation
var re = /(-?[0-9\.]+)/g,
x = a[1].toString().toLowerCase() || '',
y = b[1].toString().toLowerCase() || '',
nC = String.fromCharCode(0),
xN = x.replace( re, nC + '$1' + nC ).split(nC),
yN = y.replace( re, nC + '$1' + nC ).split(nC),
xD = (new Date(x)).getTime(),
yD = xD ? (new Date(y)).getTime() : null;
// natural sorting of dates
if ( yD )
if ( xD < yD ) return -1;
else if ( xD > yD ) return 1;
// natural sorting through split numeric strings and default strings
for( var cLoc = 0, numS = Math.max(xN.length, yN.length); cLoc < numS; cLoc++ ) {
oFxNcL = parseFloat(xN[cLoc]) || xN[cLoc];
oFyNcL = parseFloat(yN[cLoc]) || yN[cLoc];
if (oFxNcL < oFyNcL) return -1;
else if (oFxNcL > oFyNcL) return 1;
}
return 0;
}
// Set values for index
var users = Array();
var temp = Array();
users.push(Array('72', 'testuser4'));
users.push(Array('91', 'testuser2'));
users.push(Array('12', 'testuser8'));
users.push(Array('3', 'testuser1'));
users.push(Array('18', 'testuser7'));
users.push(Array('47', 'testuser3'));
users.push(Array('16', 'testuser6'));
users.push(Array('20', 'testuser5'));
// Sort the array
var users_sorted = Array();
users_sorted = users.sort(naturalSort);
I'd use map once to make a new array of users,
then a second time to return the string you want from the new array.
var users= [];
users[562]= 'testuser3';
users[16]= 'testuser6';
users[834]= 'testuser1';
users[823]= 'testuser4';
users[23]= 'testuser2';
users[917]= 'testuser5';
var u2= [];
users.map(function(itm, i){
if(itm){
var n= parseInt(itm.substring(8), 10);
u2[n]= i;
}
});
u2.map(function(itm, i){
return 'users['+itm+']= testuser'+i;
}).join('\n');
/*returned value: (String)
users[834]= testuser1
users[23]= testuser2
users[562]= testuser3
users[823]= testuser4
users[917]= testuser5
users[16]= testuser6
*/
If you want to avoid any gaps. use a simple filter on the output-
u2.map(function(itm, i){
return 'users['+itm+']= testuser'+i;
}).filter(function(itm){return itm}).join('\n');
Sparse arrays usually spell trouble. You're better off saving key-value pairs in an array as objects (this technique is also valid JSON):
users = [{
"562": "testuser3"
},{
"16": "testuser6"
}, {
"834": "testuser1"
}, {
"823": "testuser4"
}, {
"23": "testuser2"
}, {
"917": "testuser5"
}];
As suggested, you can use a for loop to map the sorting function onto the array.
Array.prototype.sort() takes an optional custom comparison function -- so if you dump all of your users into an array in this manner [ [562, "testuser3"], [16, "testuser6"] ... etc.]
Then sort this array with the following function:
function(comparatorA, comparatorB) {
var userA = comparatorA[1], userB = comparatorB[1]
if (userA > userB) return 1;
if (userA < userB) return -1;
if (userA === userB) return 0;
}
Then rebuild your users object. (Which will loose you your sorting.) Or, keep the data in the newly sorted array of arrays, if that will work for your application.
A oneliner with array of array as a result:
For sorting by Key.
let usersMap = users.map((item, i) => [i, item]).sort((a, b) => a[0] - b[0]);
For sorting by Value. (works with primitive types)
let usersMap = users.map((item, i) => [i, item]).sort((a, b) => a[1] - b[1]);

Categories