I receive a response like
[
{id:1,name:"type1-something"},
{id:2,name:"something-type2"},
{id:3,name:"type3-something"},
{id:4,name:"something-type1"}
]
and I have an Enum that contains all names from the response
enum E{
E1 = 'type1-something',
E2 = 'something-type2',
E3 = 'type3-something',
E4 = 'something-type1',
}
I need to group response by their names.
For example from the response above I need to transform it in
{
"Type1" : [{id:1,name:"type1-something"},{id:4,name:"something-type1"}],
"Type2" : [{id:2,name:"something-type2"}],
"Type3" : [{id:3,name:"type3-something"}],
}
What approach can be taken? I think of a map and a for loop
if (object.name == E1 || object.name == E4)
MAP['Type1'].push(object)
But I have over 30 entries in the enum and this will approach will become very big and hard to understand. Obviously I can reduce the amount of code by adding some smaller enums
that will include only their type, but I wonder if there is a more obvious way that I do not see
You could get the type and group by this value.
const
getType = ({ name }) => name
.match(/type\d+/g)
?.map(s => s[0].toUpperCase() + s.slice(1))
.join('-') || '',
data = [{ id: 1, name: "type1-something" }, { id: 2, name: "something-type2" }, { id: 3, name: "type3-something" }, { id: 4, name: "something-type1" }, { id: 5, name: "type3-type1-something" }],
result = data.reduce((r, o) => {
(r[getType(o)] ??= []).push(o);
return r;
}, {});
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Related
I'm trying to split this object based on a common new property 'subject'.
There could be multiple subjects in a single object in the original data. But in the final data, it must have a single subject and the marks associated with it.
let x = {
'Name': 'Ajay',
'Maths': 0,
'English': 26,
}
and the expected output is
let y = [{
'Name' : 'Ajay',
'Marks' : 0,
'Subject' : 'Maths'
},{
'Name' : 'Ajay',
'Marks' : 26,
'Subject' : 'English'
}]
It's basically splitting each object into multiple objects.
Could anyone help me out with an approach to this?
I went with this approach in the end of iterating through the keys and skipping 'name' property.
for (let i of Object.keys(x)) {
let temp = {};
if (i === "Name") {
continue;
} else {
temp = {
Name: x["Name"],
Subject: i,
Marks: x[i],
};
}
You could destructure the common property and map the other entries.
const
convert = ({ Name, ...o }) => Object
.entries(o)
.map(([Subject, Marks]) => ({ Name, Subject, Marks })),
data = { Name: 'Ajay', Maths: 0, English: 26 },
result = convert(data);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can use object destructuring to extract name and then process remaining properties. Object.entries gives you key-value pairs:
let x = {
'Name': 'Ajay',
'Maths': 0,
'English': 26,
};
const { Name, ...subjects } = x;
const result = Object.entries(subjects).map(([Subject, Marks]) => ({Name, Subject, Marks}));
console.log(result);
I have an array of contact cards that hold names & addresses etc for users. What I want to do is create another array that removes any duplicate addresses (ie people in the same household) and creates a name that is a combination of the two. For example:
{flatNum: 1, Name: "ken"},{flatNum: 1, Name: "bob"}, {flatNum: 2, Name: "emma"}
would become:
{flatNum: 1, Name: "ken & bob"}, {flatNum: 2, Name: "emma"}
I know how I can achieve this with a long for loop type thing but was hoping to find a more concise method. I am assuming that reduce would be the key and have been playing around. Currently got this:
let contactCardsComb = contactCards.reduce(function(a,b){
if (a.flatNum == b.flatNum){
return a.Name = a.Name+b.Name;
}
});
Which is obviously horribly wrong but any pointers would be great
You could group by flatNum and get the values from the object.
const
data = [{ flatNum: 1, Name: "ken" }, { flatNum: 1, Name: "bob" }, { flatNum: 2, Name: "emma" }],
result = Object.values(data.reduce((r, { flatNum, Name }) => {
if (r[flatNum]) r[flatNum].Name += ' & ' + Name;
else r[flatNum] = { flatNum, Name };
return r;
}, {}));
console.log(result);
I take the OP's Q. as a chance for proofing that an abstract but (highly) configurable reduce task is in no time (less than a minute, here for the OP's problem) suitable for a variety of problems that at first sight do have nothing in common, due to the different environments and the naming of variables etc.
If one takes e.g. the following approach of this answer to another Q. ... "Segregate an array based on same name") ... some days ago, one ...
just needs to change the implementation (line 24) of how to merge the properties of two to be grouped items, and
just has to provide the correct key name (line 42) of the target value one wants to group around all the other list items.
... code ...
// reduce function that groups
// any data item generically by key.
function groupByKeyAndMergeProperties(collector, item) {
const { merge, key, index, list } = collector;
const groupKey = item[key];
let groupItem = index[groupKey];
if (!groupItem) {
// use `Object.assign` initially in order
// to not mutate the original (list's) reference.
groupItem = index[groupKey] = Object.assign({}, item);
list.push(groupItem);
merge(groupItem, null);
} else {
merge(groupItem, item);
}
return collector;
}
// task specific merge function,
// here, according to the OP's goal.
function mergeItemNames(targetItem, sourceItem) {
if (sourceItem !== null) {
targetItem.Name = `${ targetItem.Name } & ${ sourceItem.Name }`;
}
}
const sampleList = [
{ flatNum: 1, Name: "ken" },
{ flatNum: 1, Name: "bob" },
{ flatNum: 2, Name: "emma" },
{ flatNum: 2, Name: "april" },
{ flatNum: 2, Name: "june" },
{ flatNum: 3, Name: "john" }
];
console.log(
sampleList.reduce(groupByKeyAndMergeProperties, {
// task specific reduce configuration.
merge: mergeItemNames,
key: 'flatNum',
index: {},
list: []
}).list
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
this can be easily accomplished by using lodash library,
let contactCards = [
{flatNum: 1, Name: "ken"},
{flatNum: 1, Name: "bob"},
{flatNum: 2, Name: "emma"},
{flatNum: 2, Name: "april"},
{flatNum: 2, Name: "june"}
];
let tempGroup = _.groupBy(contactCards, contact => {
return contact.flatNum;
});
console.log('tempGroup :', tempGroup);
let contactCardsComb = Object
.values(tempGroup)
.map( e => e.reduce( (a,b) =>
({...a, Name: `${ a.Name } & ${ b.Name }` })
));
console.log('result : ', contactCardsComb);
.as-console-wrapper { min-height: 100%!important; top: 0; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash-compat/3.10.2/lodash.min.js"></script>
// a vanilla approach would be as follows
let flatNums = Array.from(new Set(contactCards.map( e => e.flatNum)))
let groupedContactCards = flatNums
.map( e => contactCards
.filter(contact => contact.flatNum === e))
let reducedContactCards = groupedContactCards
.map( e => e
.reduce((a,b) => ({...a,Name:`${a.Name} & ${b.Name}`})))
console.log(reducedContactCards)
or we can wrap this as below
let reducedContactCards = Array.from(new Set(contactCards.map( e => e.flatNum)))
.map( e => contactCards
.filter(contact => contact.flatNum === e))
.map( e => e.reduce((a,b) => ({...a,Name:`${a.Name} & ${b.Name}`})))
console.log(reducedContactCards)
// This is a large array of objects, e.g.:
let totalArray = [
{"id":"rec01dTDP9T4ZtHL4","fields":
{"user_id":170180717,"user_name":"abcdefg","event_id":516575,
}]
let uniqueArray = [];
let dupeArray = [];
let itemIndex = 0
totalArray.forEach(x => {
if(!uniqueArray.some(y => JSON.stringify(y) === JSON.stringify(x))){
uniqueArray.push(x)
} else(dupeArray.push(x))
})
node.warn(totalArray);
node.warn(uniqueArray);
node.warn(dupeArray);
return msg;
I need my code to identify duplicates in the array by a key value of user_id within the objects in the array. Right now, my code works to identify identical objects in the array, but I need it to identify dupes based on a key value inside the objects instead. How do I do this? I am struggling to figure out how to path the for each loop to identify the dupe based on the key value instead of the entire object.
Right now, my code works to identify identical objects in the array, but I need it to identify dupes based on a key value inside the objects instead. How do I do this?
Don’t compare the JSON representation of the whole objects then, but only their user_id property specifically.
totalArray.forEach(x => {
if(!uniqueArray.some(y => y.fields.user_id === x.fields.user_id)){
uniqueArray.push(x)
} else(dupeArray.push(x))
})
You could take a Set and push to either uniques or duplicates.
var array = [
{ id: 1, data: 0 },
{ id: 2, data: 1 },
{ id: 2, data: 2 },
{ id: 3, data: 3 },
{ id: 3, data: 4 },
{ id: 3, data: 5 },
],
uniques = [],
duplicates = [];
array.forEach(
(s => o => s.has(o.id) ? duplicates.push(o) : (s.add(o.id), uniques.push(o)))
(new Set)
);
console.log(uniques);
console.log(duplicates);
.as-console-wrapper { max-height: 100% !important; top: 0; }
One way is to keep a list of ids you found so far and act accordingly:
totalArray = [
{ id: 1, val: 10 },
{ id: 2, val: 20 },
{ id: 3, val: 30 },
{ id: 2, val: 15 },
{ id: 1, val: 50 }
]
const uniqueArray = []
const dupeArray = []
const ids = {}
totalArray.forEach( x => {
if (ids[x.id]) {
dupeArray.push(x)
} else {
uniqueArray.push(x)
ids[x.id] = true
}
})
for (const obj of uniqueArray) console.log("unique:",JSON.stringify(obj))
for (const obj of dupeArray) console.log("dupes: ",JSON.stringify(obj))
I have an array of objects as following :
[
{"id":1,"lib":"A","categoryID":10,"categoryTitle":"Cat10","moduleID":"2","moduleTitle":"Module 2"},
{"id":2,"lib":"B","categoryID":10,"categoryTitle":"Cat10","moduleID":"2","moduleTitle":"Module 2"},
...
{"id":110,"lib":"XXX","categoryID":90,"categoryTitle":"Cat90","moduleID":"4","moduleTitle":"Module 4"}
]
I want to group this array by (moduleID,moduleTitle) and then by (categoryID,categoryTitle).
This is what I tried :
function groupBy(data, id, text) {
return data.reduce(function (rv, x) {
var el = rv.find(function(r){
return r && r.id === x[id];
});
if (el) {
el.children.push(x);
} else {
rv.push({ id: x[id], text: x[text], children: [x] });
}
return rv;
}, []);
}
var result = groupBy(response, "moduleID", "moduleTitle");
result.forEach(function(el){
el.children = groupBy(el.children, "categoryID", "categoryTitle");
});
The above code is working as expected, but as you can see, after the first grouping I had to iterate again over the array which was grouped by the moduleId in order to group by the categoryId.
How can I modify this code so I can only call groupBy function once on the array ?
Edit:
Sorry this might be late, but I want this done by using ES5, no Shim and no Polyfill too.
Here's one possible (although may be a bit advanced) approach:
class DefaultMap extends Map {
constructor(factory, iter) {
super(iter || []);
this.factory = factory;
}
get(key) {
if (!this.has(key))
this.set(key, this.factory());
return super.get(key);
}
}
Basically, it's the a Map that invokes a factory function when a value is missing. Now, the funny part:
let grouper = new DefaultMap(() => new DefaultMap(Array));
for (let item of yourArray) {
let firstKey = item.whatever;
let secondKey = item.somethingElse;
grouper.get(firstKey).get(secondKey).push(item);
}
For each firstKey this creates a Map inside grouper, and the values of those maps are arrays grouped by the second key.
A more interesting part of your question is that you're using compound keys, which is quite tricky in JS, since it provides (almost) no immutable data structures. Consider:
items = [
{a: 'one', b: 1},
{a: 'one', b: 1},
{a: 'one', b: 2},
{a: 'two', b: 2},
]
let grouper = new DefaultMap(Array);
for (let x of items) {
let key = [x.a, x.b]; // wrong!
grouper.get(key).push(x);
}
So, we're naively grouping objects by a compound key and expecting to see two objects under ['one', 1] in our grouper (which is one level for the sake of the example). Of course, that won't work, because each key is a freshly created array and all of them are different for Map or any other keyed storage.
One possible solution is to create an immutable structure for each key. An obvious choice would be to use Symbol, e.g.
let tuple = (...args) => Symbol.for(JSON.stringify(args))
and then
for (let x of items) {
let key = tuple(x.a, x.b); // works
grouper.get(key).push(x);
}
You could extend your function by using an array for the grouping id/names.
function groupBy(data, groups) {
return data.reduce(function (rv, x) {
groups.reduce(function (level, key) {
var el;
level.some(function (r) {
if (r && r.id === x[key[0]]) {
el = r;
return true;
}
});
if (!el) {
el = { id: x[key[0]], text: x[key[1]], children: [] };
level.push(el);
}
return el.children;
}, rv).push({ id: x.id, text: x.lib });
return rv;
}, []);
}
var response = [{ id: 1, lib: "A", categoryID: 10, categoryTitle: "Cat10", moduleID: "2", moduleTitle: "Workflow" }, { id: 2, lib: "B", categoryID: 10, categoryTitle: "Cat10", moduleID: "2", moduleTitle: "Module 2" }, { id: 110, lib: "XXX", categoryID: 90, categoryTitle: "Cat90", moduleID: "4", moduleTitle: "Module 4" }],
result = groupBy(response, [["moduleID", "moduleTitle"], ["categoryID", "categoryTitle"]]);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Version with path as id.
function groupBy(data, groups) {
return data.reduce(function (rv, x) {
var path = [];
var last = groups.reduce(function (level, key, i) {
path.length = i;
path[i] = key[0].slice(0, -2).toUpperCase() + ':' + x[key[0]];
var id = path.join(';'),
el = level.find(function (r) {
return r && r.id === id;
});
if (!el) {
el = { id: path.join(';'), text: x[key[1]], children: [] };
level.push(el);
}
return el.children;
}, rv);
last.push({ id: path.concat('NODE:' + x.id).join(';') });
return rv;
}, []);
}
var response = [{ id: 1, lib: "A", categoryID: 10, categoryTitle: "Cat10", moduleID: "2", moduleTitle: "Workflow" }, { id: 2, lib: "B", categoryID: 10, categoryTitle: "Cat10", moduleID: "2", moduleTitle: "Module 2" }, { id: 110, lib: "XXX", categoryID: 90, categoryTitle: "Cat90", moduleID: "4", moduleTitle: "Module 4" }];
var result = groupBy(response, [["moduleID", "moduleTitle"], ["categoryID", "categoryTitle"]]);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You could do it like this:
const exit = Symbol("exit");
function groupBy(arr, ...props){
const root = {};
for(const el of arr){
const obj = props.map(key => el[key])
.reduce((obj, key) => obj[key] || (obj[key] = {}), root);
(obj[exit] || (obj[exit] = [])).push(el);
}
}
So you can access it like:
const grouped = groupBy(response, "moduleID", "moduleTitle");
console.log( grouped[2]["workflow"][exit] );
You might leave away that exit symbol, but it feels a bit wrong to mix a nested tree with arrays.
Not sure if title is formulated correct, but I have a JS object that looks like:
parent:{
children:[
{
id: "1"
user:[
{
id: 'aa',
email:'aa#google.com'
},
{
id: 'b',
email:'bbb#google.com'
},
]
},
{
id:"2",
user: [
{
id:'aa',
email:'aa#google.com'
},
{
id:'eee',
email:'eee#google.com'
}
]
}
]
}
The object is way bigger but follows the above structure.
How would I go to get a list of topics each user is on, filtered b ID?
E.g. 'aa' participates in children 1 and 2
'b' participates in child 1
etc.
I figure it out I have to map the object but not sure how to proceed after that
Assuming, you want an object with participant as key and all topic id in an object, then you could iterate the arrays an build a property with the associated id.
var data = { project: { topics: [{ id: "1", participants: [{ id: 'aa', email: 'aa#google.com' }, { id: 'b', email: 'bbb#google.com' }, ] }, { id: "2", participants: [{ id: 'aa', email: 'aa#google.com' }, { id: 'eee', email: 'eee#google.com' }] }] } },
result = Object.create(null);
data.project.topics.forEach(function (a) {
a.participants.forEach(function (b) {
result[b.id] = result[b.id] || [];
result[b.id].push(a.id);
});
});
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can write a function like this one:
function findTopicByID(id) {
let findedTopic = {};
let topics = obj.project.topics;
topics.map((topic) => {
if(parseInt(topic.id) === id) findedTopic = topic;
});
return findedTopic;
}
This function return the finded topic with the corresponding id or an empty object.
You can loop the topic array and build a new resulting array of users, if a user already exist then just update the users topic list, else create a new user with name, email, and a topic list.
let result = [];
project.topics.forEach((t) => {
t.participants.forEach((p) => {
let found = result.find((r) => r.name === p.id);
if (found) {
found.topics.push(t.id);
} else {
result.push({
name: p.id,
email: p.email,
topics: [t.id]
});
}
});
});
so now when you have the resulting array, you can just find a user and get which topics she participates in
let aa = result.find((r) => r.name === 'aa');
console.log(aa.topics);