I have following data structure that I get from some api:
response = [{grade: 'A', frequency: 54, percentage: 24},
{grade: 'B', frequency: 50, percentage: 10},
{grade: 'C', frequency: 0, percentage: 0},
{grade: 'D', frequency: 50, percentage: 20},
...
];
Now some UI javascript library requires this data to be formatted and presented as follows:
label: ['A', 'B', 'C', 'D'];
data: [[54,50,0,50],[24,10,0,20]];
series: ['frequency', 'percentage'];
What would be the most efficient way to convert the response object to the above elements?
let label = response.map(data => data.grade);
let freqencyData = response.map(data => data.freqency);
let percentageData = response.map(data => data.percentage);
let data = [freqencyData, percentageData];
Would something like this this be efficient enough? Please not that this is an example and the response data in my case is too big and having the map done three times seems to be an overkill.
Thanks
You can use the function reduce to build the almost desired output in one loop.
Separate the label attribute with the rest of the attributes.
Group the values by the specific property-name (data, label, and series).
The Object Set allows you to keep unique strings for the series.
Object.from converts the object Set to Array.
Object.values extracts from this: {frequency: [1,2,3], percentage: [1,2,3]} to this: [[1,2,3], [1,2,3]]
let response = [{grade: 'A', frequency: 54, percentage: 24},{grade: 'B', frequency: 50, percentage: 10},{grade: 'C', frequency: 0, percentage: 0},{grade: 'D', frequency: 50, percentage: 20}];
let result = response.reduce((a, {grade: label, ...rest}) => {
Object.keys(rest).forEach(k => {
(a.series || (a.series = new Set())).add(k);
a.data = a.data || {};
(a.data[k] || (a.data[k] = [])).push(rest[k]);
});
(a.label || (a.label = [])).push(label);
return a;
}, Object.create(null));
result.series = Array.from(result.series);
result.data = Object.values(result.data);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You could just do an old fashioned loop:
let label = [];
let freqencyData = [];
let percentageData = [];
response.forEach(function(element) {
label.push(data.grade);
frequencyData.push(data.frequency);
percentageData.push(data.percentage);
});
let data = [freqencyData, percentageData];
Related
let arr = [{key: 1, value: 10},
{key: 5, value: 20}]
let reformatArr = arr.map(obj => {
let tmp = {};
tmp[obj.key] = obj.value
return tmp;
});
//reformatArr is [{1: 10}, {5: 20}]
How can I reverse this process to its original format?I'm asking because I'm reformatting to send to my backend and want to reformat back when the data is returned.
You can destructure the first element of Object.entries for each object.
let reformatted = [{1: 10}, {5: 20}];
let res = reformatted.map(obj => {
const [[key, value]] = Object.entries(obj);
return {key,value};
});
console.log(res);
I am receiving the following array from an API response:
[
{group: '1', , value: 'a'}
{group: '1', , value: 'b'}
{group: '2', , value: 'c'}
{group: '2', , value: 'd'}
]
I want to convert it to the following (want order of groups as received, values can be ordered in any way):
[
{group: '1', values: ['b', 'a'] },
{group: '2', values: ['c', 'd'] },
]
Which Javascript function will be the most efficient to convert it?
I am able to do this by:
let result = [];
data.reduce((groupNumber, input) => {
if (!groupNumber[input.group]) {
groupNumber[input.group] = {group: input.group, values: []};
result.push(groupNumber[input.group]);
}
groupNumber[input.group].values.push(input);
return groupNumber;
}, {});
Is reduce the correct function to be used here? Is there a more efficient approach?
Secondly, will reduce preserve the order in the result? If I am actually receiving the data with group 1 entries first, group 2 next, and so on, can I rely on result being ordered similarly?
Note that I only care about the order of the groups, not about the values inside the group.
Actually either by using reduce() or any other Array methods, the order will be always preserved as they will be executed from index 0 till the end of the array.
But reduce() is mainly used to accumulate the array elements into a single element, it's not the best method for doing such thing but it can be used as well.
Note that your actual code using reduce() isn't returning the right output.
In my opinion I think Array.map() method is better in this case, but it should be used in combination with Array.filter() to remove duplicate elements that will be kept with map():
var result = data.map(v => {
v.values = data.filter(e => e.group == v.group).map(x => x.value);
delete v.value;
return v;
}).filter(x => !x.values.some(e => !e));
Demo:
let data = [{
group: '1',
value: 'a'
}, {
group: '1',
value: 'b'
}, {
group: '2',
value: 'c'
}, {
group: '2',
value: 'd'
}];
var result = data.map(v => {
v.values = data.filter(e => e.group == v.group).map(x => x.value);
delete v.value;
return v;
}).filter(x => !x.values.some(e => !e));
console.log(result);
I would save my reply because we have a great explanation why your approach is great, because you have O(N) computational complexity (Thanks for the great comments to #CertainPerformance) and you iterate your array just one time.
Testing whether reduce method preserve order of object keys:
It looks like reduce method is not preserving order of keys. Here we see that keys are sorted in order how then iterated through source array myArray, however in Chrome browser this behavior is not reproducible(keys are sorted):
var myArray = [{letter: 'c'}, {letter:'e'}, {letter: 'e'}, {letter:'b'}, {letter: 'c'}, {letter:'a'}, {letter:'b'}, {letter:'a'}, {letter: 'd'}, {letter: 'd'}, {letter: 'd'}, {letter: 'd'}];
var myOrderedKeys = myArray.reduce((a, {letter}) => {
a[letter] = a[letter] || letter;
a[letter] = letter;
return a;
}, {})
console.log(myOrderedKeys);
Another example where we have more than one letter:
let notSimpleLetters = [{letter: 'ce'}, {letter:'ec'}, {letter: 'ec'}, {letter:'bw'}, {letter: 'ce'}, {letter:'aw'}, {letter:'ba'}, {letter:'aa'},
{letter: 'df'}, {letter: 'cb'}, {letter: 'dc'}, {letter: 'da'}];
let notSimpleKeys = notSimpleLetters.reduce((a, {letter}) => {
a[letter] = a[letter] || letter;
a[letter] = letter;
return a;
}, {})
console.log(notSimpleKeys);
Is there any shorthand method to convert array of string array with header as first array (Input as shown below) to Objects of array (as expected output shown below)
Using for loop we can achieve this, I am looking for any short hand and optimized solution to do this.
Let me know if is there any easy and optimized method to implement this.
Input
[
['fromAge', 'toAge', 'gender', 'institutionalRaf'],
[0, 10, 'F', '1.5'],
[11, 20, 'F', '2.5']
]
Expected Output :
[{
fromAge : 0,
toAge: 10,
gender: "F",
institutionalRaf : "1.5"
},
{
fromAge : 11,
toAge: 20,
gender: "F",
institutionalRaf : "2.5"
}
...
]
You can use map and reudce
Take the first element as header and rest of element as values
Loop through the values array for each element build a object with key from header and value from element
let data = [["fromAge","toAge","gender","institutionalRaf"],["1",'4','m','12'],["4",'12','f','22'],["10",'20','m','109']]
let [header,...values] = data
let final = values.map(v=> {
return v.reduce((op,inp,index)=>{
op[header[index]] = inp
return op
},{})
})
console.log(final)
You could separate the keys and the values and map the value as object with the keys.
var array = [['fromAge', 'toAge', 'gender', 'institutionalRaf'], [0, 10, 'F', '1.5'], [11, 20, 'F', '2.5']],
[keys, ...values] = array,
result = values.map(a => Object.assign(...keys.map((k, i) => ({ [k]: a[i] }))));
console.log(result);
I'd shift out the first array of keys, then .map to create entries and create the objects using Object.fromEntries:
const arr = [
['a', 'b', 'c'],
[1, 2, 3],
[4, 5, 6]
];
const keys = arr.shift();
const output = arr.map(values =>
Object.fromEntries(
values.map((value, i) => [keys[i], value])
)
);
console.log(output);
Object.fromEntries is a relatively new method. On older environments, either use a polyfill, or create the object with reduce instead:
const arr = [
['a', 'b', 'c'],
[1, 2, 3],
[4, 5, 6]
];
const keys = arr.shift();
const output = arr.map(values => (
values.reduce((a, value, i) => {
a[keys[i]] = value;
return a;
}, {})
));
console.log(output);
If keys are fixed we can use the simple approach like below
let arr=[
['fromAge', 'toAge', 'gender', 'institutionalRaf'],
[0, 10, 'F', '1.5'],
[11, 20, 'F', '2.5']
];
let arr1=arr.slice();
let x=arr1.shift();
let x1=arr1.map(a=>(
{
[x[0]]:a[0],
[x[1]]:a[1],
[x[2]]:a[2],
[x[3]]:a[3],
}
)
)
console.log(x1);
Use destructuring, map and reduce
const array = [
['fromAge', 'toAge', 'gender', 'institutionalRaf'],
[0, 10, 'F', '1.5'],
[11, 20, 'F', '2.5']
]
const [keys, ...values] = array
const result = values.map((value) => value.reduce((a, b, index) => ({...a, [keys[index]]: b}), {}), [])
console.log("result",result)
From an array like:
[{'name': 'a', 'val': 1}, {'name': 'b', 'val': 2}, {'name': 'a', 'val': 3}]
I want to get to:
[{ name: 'b', val: 2 }, { name: 'a', val: 3 }].
This can be achieved for example by:
arrOut = _.uniqBy(arrIn.reverse(), 'name').reverse();
but I am looking for a faster/cleaner method without the need for reversing the whole array twice.
Here is an idea: Iterate over the array twice. First time count the number of occurences of each name, the second time filter out elements if their respective count isn't 1.
Example:
const arrIn = [{'name': 'a', 'val': 1}, {'name': 'b', 'val': 2}, {'name': 'a', 'val': 3}];
const counter = Object.create(null);
for (const element of arrIn) {
counter[element.name] = (counter[element.name] || 0) + 1;
}
const arrOut = arrIn.filter(element => counter[element.name]-- === 1);
console.log(arrOut);
Another way would be to iterate over the array in reverse order, keeping track of the names you have seen and call .reverse on the result:
const arrIn = [{'name': 'a', 'val': 1}, {'name': 'b', 'val': 2}, {'name': 'a', 'val': 3}];
const arrOut = [];
const seen = new Set();
for (let i = arrIn.length; i--;) {
if (!seen.has(arrIn[i].name)) {
arrOut.push(arrIn[i]);
seen.add(arrIn[i].name);
}
}
console.log(arrOut.reverse());
Try with shift()
This method removes the first array element
If the original order should be preserved, one alternative approach is to use Array.filter() in conjunction with Array.some(). This is my attempt to preserve the original order of the elements:
let input = [
{'name': 'a', 'val': 1},
{'name': 'b', 'val': 2},
{'name': 'a', 'val': 3}
];
let res = input.filter(
({name, val}, idx, arr) => !arr.some((o, j) => o.name === name && j > idx)
);
console.log(res);
.as-console {background-color:black !important; color:lime;}
.as-console-wrapper {max-height:100% !important; top:0;}
Alternatively, a more efficient approach can be done using Array.reduceRight() and a Set to keep track of already visited names (as #Felix Kling show in one of his examples)
let input = [
{'name': 'a', 'val': 1},
{'name': 'b', 'val': 2},
{'name': 'a', 'val': 3},
{'name': 'b', 'val': 12},
{'name': 'c', 'val': 7},
{'name': 'b', 'val': 22}
];
let res = input.reduceRight((acc, obj) =>
{
if (acc.visited.has(obj.name))
return acc;
acc.visited.add(obj.name);
acc.res.unshift(obj);
return acc;
}, {res: [], visited: new Set()});
console.log(res.res);
.as-console {background-color:black !important; color:lime;}
.as-console-wrapper {max-height:100% !important; top:0;}
Example object array:
[{
id: 'a',
beforeId: null
}, {
id: 'b',
beforeId: 'c'
}, {
id: 'c',
beforeId: 'a'
}, {
id: 'd',
beforeId: 'b'
}]
Output order: d-b-c-a; each element sorted relative to each other element based on its beforeId property.
I could make a temporary array and sort the above array. Is sorting possible with array.sort?
You could build an object with the relations and generate the result by using the object with beforeId: null and unshift all objects for the result array.
The next object is the one with the actual val as key.
Complexity: O(2n).
function chain(array) {
var o = {}, pointer = null, result = [];
array.forEach(a => o[a.beforeId] = a);
while (o[pointer]) {
result.unshift(o[pointer]);
pointer = o[pointer].val;
}
return result;
}
var data = [{ val: 'a', beforeId: null }, { val: 'b', beforeId: 'c' }, { val: 'c', beforeId: 'a' }, { val: 'd', beforeId: 'b' }];
console.log(chain(data));
.as-console-wrapper { max-height: 100% !important; top: 0; }
This is a terribly inefficient and naïve algorithm, but it works:
const array = [
{id: 'a', beforeId: null},
{id: 'b', beforeId: 'c'},
{id: 'c', beforeId: 'a'},
{id: 'd', beforeId: 'b'}
];
// find the last element
const result = [array.find(i => i.beforeId === null)];
while (result.length < array.length) {
// find the element before the first element and prepend it
result.unshift(array.find(i => i.beforeId == result[0].id));
}
console.log(result);
Is sorting possible with array.sort?
sure, with a helper function:
graph = [
{id: 'a', beforeId: null},
{id: 'b', beforeId: 'c'},
{id: 'c', beforeId: 'a'},
{id: 'd', beforeId: 'b'}
];
let isBefore = (x, y) => {
for (let {id, beforeId} of graph) {
if (id === x)
return (beforeId === y) || isBefore(beforeId, y);
}
return false;
};
graph.sort((x, y) => x === y ? 0 : (isBefore(x.id, y.id) ? -1 : +1))
console.log(graph);
isBefore returns true if x is before y immediately or transitively.
For generic, non-linear topological sorting see https://en.wikipedia.org/wiki/Topological_sorting#Algorithms
UPD: As seen here, this turned out to be horribly inefficient, because sort involves many unnecessary comparisons. Here's the fastest (so far) version:
function sort(array) {
let o = {}, res = [], len = array.length;
for (let i = 0; i < len; i++)
o[array[i].beforeId] = array[i];
for (let i = len - 1, p = null; i >= 0; i--) {
res[i] = o[p];
p = o[p].id;
}
return res;
}
which is the #Nina's idea, optimized for speed.
You may try this approach :
// order(null, vals, []) = ["d", "b", "c", "a"]
function order(beforeId, vals, result){
var id = beforeId || null;
var before = vals.filter(function(val){
return val.beforeId === id
});
if (before.length === 0) return result;
return order(before[0].val,
vals,
[before[0].val].concat(result));
}