I have an array of objects in the format
{type: number, sub_type: number}
I need to sort them into an array of objects formatted like this
{
type_id: (type from at least one object in array above),
sub_types: [
(all sub types from objects in array above that match this type)
]
}
This is what I came up but I think there is a more efficient way. rawTypes is an array of objects in need of formatting, types ends up being the array of formatted objects.
const typeIds = [...new Set(rawTypes.map(val => val.type))];
const types = typeIds.map(val => ({type_id: val, sub_types: [] }));
rawTypes.forEach(obj => {
let typeIndex = types.reduce((accum, val, i) => val.type_id === obj.type ? i : accum, 0);
types[typeIndex].sub_types.push(obj.sub_type);
});
I think a better solution would use recursion but I can't think of how to do it.
Look at this approach
var data = [{type: 5, sub_type: 10}, {type: 5, sub_type: 11}, {type: 6, sub_type: 12}];
var obj = data.reduce((a, c) => {
var current = a[`type_id_${c.type}`];
if (current) {
current.sub_types.push(c.sub_type);
} else {
var key = `type_id_${c.type}`;
a = { ...a, ...{ [key]: {sub_types: [c.sub_type], 'key': c.type} } };
}
return a;
}, {});
var array = Object.keys(obj).map((k) => ({ 'type': obj[k].key, 'subtypes': obj[k].sub_types }));
console.log(array)
.as-console-wrapper {
max-height: 100% !important
}
Related
So I have a list of objects, ex.
var data = [{a: 'data1', b: 'subdata1'}, {a: 'data2', b: 'subdata2'}, {a: 'data1', b: 'subdata3'}, {a: 'data1', b: 'subdata1'}]
(note the objects have other attributes too)
I'm looking to extract some condensed details of this list with a result:
[{type: 'data1', list: [{subtype: 'subdata1', count: 2}, {subtype: 'subdata3', count: 1}]}, {type: 'data2', list: [{subtype: 'data2', count: 1}]}]
I have been able to count the type (or subtype) with reduce:
data.reduce((lst, item) => { lst[item.type] = lst[item.type] + 1 || 1; return lst; }, {});
but, this isn't exactly the structure or complete detail I'm looking to achieve. I can obviously do the work manually with a for loop, but I'm hoping to understand map, reduce, etc. better for a cleaner/simpler implementation.
You could create a nested lookup table, (a -> b -> count), then you can iterate over that and build the result:
const table = {};
for(const { a, b } of data) {
if(!table[a]) table[a] = {};
if(!table[a][b]) table[a][b] = 0;
table[a][b]++;
}
const result = Object.entries(table)
.map(([type, entries]) => ({ type, list: Object.entries(entries).map(([ subtype, count ]) => ({ subtype, count })), }));
Yes, one could write that as a functional chain:
const result = Object.entries(
data.reduce(
((table, { a, b }) => (table[a] || (table[a] = {}))[b] = (table[a][b] || 0) + 1, table),
{}
)
).map(([type, entries]) => ({
type,
list: Object.entries(entries).map(([ subtype, count ]) => ({ subtype, count })),
}));
But IMO thats less readable.
I have an Object like this:
const val = {"abc":{"1":1, "2":6,"3":5},"def":{"1":3, "2":4,"3":8},"xyz":{"1":5, "2":6,"3":7}}
I want to transform the object data like below:
[{"abc":1,"def":3,"xyz":5},{"abc":6,"def":4,"xyz":6}, ...]
All the values are dynamic, any number of inner object may be there
I have tried like this:
const val = {"abc":{"1":1, "2":6,"3":5},"def":{"1":3, "2":4,"3":8},"xyz":{"1":5, "2":6,"3":7}}
let dataObj = {};
let secondArr = [];
let dataArr =[]
Object.entries(val).map(firstObj=>{
Object.entries(firstObj[1]).forEach(secondObj => {
dataObj={[firstObj[0]]:secondObj[1]};
secondArr.push(dataObj);
})
dataArr.push(secondArr)
})
console.log(dataArr)
Can anyone tell me a solution for this?
Thanks in advance
You could iterate the entries of the objects and take the inner keys as indices of the array with new objects with outer key and value.
var data = { abc: { 1: 1, 2: 6, 3: 5 }, def: { 1: 3, 2: 4, 3: 8 }, xyz: { 1: 5, 2: 6, 3: 7 } },
result = Object
.entries(data)
.reduce((r, [k, o]) => {
Object.entries(o).forEach(([i, v]) =>
Object.assign(r[i - 1] = r[i - 1] || {}, { [k]: v }));
return r;
}, []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Array of dictionaries should be converted simpler form.
data = [{A:1},{B:2},{C:3}]
data = {A: 1, B: 2}
data = ["0":{ A : 1, B : 2 , C : 3}]
Both are completely different datasets. I'm trying to map it also like below format.
The above should become like
data = [
{
name: "A",
y: 1
},
{
name: "B",
y: 2
},
{
name: "C",
y: 3
}
];
I tried this following approach but it's wrong
name = {}
data.forEach(function(k,x){
return name['name'] = k , name["y"] = x
})
Please suggest me a better approach.
map each object's entries to extract the key and the value, and return an object with name and y keys:
const data = [{A:1},{B:2},{C:3}]
const output = data.map(item => {
const [name, y] = Object.entries(item)[0];
return { name, y };
});
console.log(output);
If the keys (A, B, etc) are guaranteed to be unique throughout the array, then everything becomes simpler.
const data = [{A:1},{B:2},{C:3}];
const merged = Object.assign({}, ...data);
const newData = Object.entries(merged)
.map(([name, y]) => ({ name, y }));
console.log(newData);
However, if the keys aren't guaranteed unique, then refer to CertainPerformance's answer.
you can implement like this
var data = [{A:1},{B:2},{C:3}];
var reformattedArra = data.map(obj => {
let val = {};
val.name = Object.keys(obj)[0];
val.y = obj[Object.keys(obj)[0]];
return val;
})
console.log(JSON.stringify(reformattedArra));
I would say, use Object.keys() which is widly supported
let data = [{A:1},{B:2},{C:3}];
data = Object.assign({}, ...data);
data = Object.keys(data).map(key => ({ name: key, y: data[key] }));
console.log(data);
You yould could chekc the data format and if it is not an array, build one and reduce the array by taking the objetcs and create for each key/value a new object for the result set.
function simple(data) {
return (Array.isArray(data) ? data : [data]).reduce((r, o) => [...r, ...Object.entries(o).map(([name, y]) => ({ name, y }))], []);
}
console.log(simple([{ A: 1 }, { B: 2 }, { C: 3, D: 4 }]));
console.log(simple({ A: 1, B: 2 }));
.as-console-wrapper { max-height: 100% !important; top: 0; }
I have two arrays that I am concatinating.
However each of these arrays has same property name I want to leave by adding prefix to each.
Array A(aData) looks like
[
{
id: 1,
title: `title`
code: '34x'
},
...
]
Array B(bData) looke like:
[
{
id: 1
prop: 3,
otherporp: `prop`
code: 'hi67'
},
...
]
In order to combine the arrays I am doing concat and reduce to get only matching id's
const data: any = aData.concat(bData).reduce((acc, x) => {
acc[x.id] = Object.assign(acc[x.id] || {}, x);
return acc;
}, {});
return Object.values(data);
But the issue is that my bData code props getting lost.
Is there any way I can rename the code from aData to say aCode and the code from bData to bCode ?
You can create a new array from both of your array with updated key value aCode and bCode instead of code key. Then concat both of these arrays and merge them on the id key.
const arrA = [{ id: 1, title: `title`, code: '34x' }],
arrB = [{ id: 1, prop: 3, otherporp: `prop`, code: 'hi67'}];
const newArrA = arrA.map(({code, ...rest}) => ({...rest, aCode : code}));
const newArrB = arrB.map(({code, ...rest}) => ({...rest, bCode : code}))
const merged = Object.values([].concat(newArrA, newArrB).reduce((r,o) => {
r[o.id] = r[o.id] || Object.assign({},o);
Object.assign(r[o.id], o);
return r;
}, {}));
console.log(merged);
var arrA = [{
id: 1,
title: `title`,
code: '34x'
}],
arrB = [{
id: 1,
prop: 3,
otherporp: `prop`,
code: 'hi67'
}];
let newArrA = arrA.map(({
code,
...rest
}) => ({ ...rest,
aCode: code
}));
const newArrB = arrB.map(({
code,
...rest
}) => ({ ...rest,
bCode: code
}));
result = newArrA.map(function(v) {
var ret;
$.each(newArrB, function(k, v2) {
if (v2.id === v.id) {
ret = $.extend({}, v2, v);
return false;
}
});
return ret;
});
console.log(result);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
i am doing one of my front end project and i have a situation where i have to merge/add objects present in the array based on some conditions. Conditions would be
Only those Objects having same label should be merged.
For objects which has same label, if object 'a' has properties which are present in b object as well, the value has to be added, else simply copy the property.
So my input would be
[
{
label: 'label-1',
published: 1,
draft: 2,
id: 'some1'
},
{
label: 'label-1',
published: 2,
status: 0,
draft: 1,
id: 'some4'
},
{
label: 'label-2',
published: 1,
draft: 14,
id: 'some2'
},
{
label: 'label-2',
published: 12,
status: 0,
draft: 14,
id: 'some3'
}
]
and the expect
[
{
label: 'label-1',
published: 3,
draft: 4,
status: 0
},
{
label: 'label-2',
published: 13,
draft: 28,
status: 0
}
]
Currently i am using the following code for achieving the same , but find it not tidy . Is there any way this could be achieved easily.
function mapData(data) {
let groupData = _.groupBy(data, 'label');
let stackBarData = [];
Object.keys(groupData).forEach((key) => {
if (groupData[key] && groupData[key].length > 0) {
let temp = Array.from(groupData[key]).reduce((a, b) => {
for (let property in b) {
if (b.hasOwnProperty(property)) {
if (property !== 'label' && property !== 'id' && property !== 'Others') {
a[property] = (a[property] || 0) + b[property];
} else {
a[property] = b[property];
}
}
}
return a;
}, {});
stackBarData.push(temp);
}
});
return stackBarData;
}
Please help.
Here is a pure ES6 function that collects the object values that are numeric, adding them up (which is what you seem to do), per unique label:
function mapData(data) {
const grouped = new Map(data.map( ({label}) => [label, { label }] ));
for (let obj of data) {
let target = grouped.get(obj.label);
for (let [key, val] of Object.entries(obj)) {
if (typeof val === 'number') {
target[key] = (target[key] || 0) + val;
}
}
}
return [...grouped.values()];
}
// Sample data
const data = [{label: 'label-1',published: 1,draft: 2,id: 'some1'},{label: 'label-1',published: 2,status: 0,draft: 1,id: 'some4'},{label: 'label-2',published: 1,draft: 14,id: 'some2'},{label: 'label-2',published: 12,status: 0,draft: 14,id: 'some3'}];
console.log(mapData(data));
.as-console-wrapper { max-height: 100% !important; top: 0; }
If you have numeric properties that you wanted to exclude, then it might be better to have an explicit set of properties you are interested in:
const props = new Set(['status', 'published', 'draft']);
// ... etc
//
if (props.has(key)) {
target[key] = (target[key] || 0) + val;
}
// ...
Lodash
_.groupBy() by the label, _.map() the groups, and merge each group using _.mergeWith(), and _.omit() the id. When merging the groups, if the current value is a number, sum the current and new values, if not return undefined - If customizer returns undefined, merging is handled by the method instead.
const arr = [{"label":"label-1","published":1,"draft":2,"id":"some1"},{"label":"label-1","published":2,"status":0,"draft":1,"id":"some4"},{"label":"label-2","published":1,"draft":14,"id":"some2"},{"label":"label-2","published":12,"status":0,"draft":14,"id":"some3"}]
const result = _(arr)
.groupBy('label')
.map((g) => _.omit(_.mergeWith({}, ...g, (objValue, srcValue) => _.isNumber(objValue) ? objValue + srcValue : undefined), 'id'))
.value()
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
ES6
Iterate the array with Array.reduce(). On each iteration check if the accumulator (the Map) has the label, and if not add an empty object with the label as the key. Iterate the current object keys with Array.forEach(), ignore id, and sum the numeric values. To get an array spread the Map.values():
const arr = [{"label":"label-1","published":1,"draft":2,"id":"some1"},{"label":"label-1","published":2,"status":0,"draft":1,"id":"some4"},{"label":"label-2","published":1,"draft":14,"id":"some2"},{"label":"label-2","published":12,"status":0,"draft":14,"id":"some3"}]
const result = [...arr.reduce((m, o) => {
m.has(o.label) || m.set(o.label, {})
const obj = m.get(o.label)
Object.keys(o).forEach((k) => {
if(k === 'id') return
obj[k] = typeof o[k] === 'number' ? (obj[k] || 0) + o[k] : o[k]
})
return m
}, new Map()).values()]
console.log(result)