How to group array objects into sub objects and count them - javascript

I receive an array from an api which looks like this
input = [
{ choices:[
{"food":"breakfast","preference":"tea"},
{"food":"lunch","preference":"burger"},
{"food":"supper","preference":"rice"}
]
},
{ choices:[
{"food":"breakfast","preference":"coffee"},
{"food":"lunch","preference":"burger"},
{"food":"supper","preference":"yam"}
]
},
{ choices:[
{"food":"breakfast","preference":"tea"},
{"food":"lunch","preference":"bread"},
{"food":"supper","preference":"yam"}
]
},
{ choices:[
{"food":"breakfast","preference":"coffee"},
{"food":"lunch","preference":"bread"},
{"food":"supper","preference":"rice"}
]
}
]
I tried Group by the array of objects based on the property and also the count in javascript
I need to group the individual preferences and count them
groupedChoices = [
[
{ "preference": "tea", "count": 3 },
{ "preference": "coffee", "count": 2 }
],
[
{ "preference": "burger", "count": 3 },
{ "preference": "bread", "count": 2 }
],
[
{ "preference": "rice", "count": 3 },
{ "preference": "yam", "count": 2 }
]
]

const input = [
{ choices:[
{"food":"breakfast","preference":"tea"},
{"food":"lunch","preference":"burger"},
{"food":"supper","preference":"rice"}
]
},
{ choices:[
{"food":"breakfast","preference":"coffee"},
{"food":"lunch","preference":"burger"},
{"food":"supper","preference":"yam"}
]
},
{choices: [
{"food":"breakfast","preference":"tea"},
{"food":"lunch","preference":"bread"},
{"food":"supper","preference":"yam"}
]
},
{ choices:[
{"food":"breakfast","preference":"coffee"},
{"food":"lunch","preference":"bread"},
{"food":"supper","preference":"rice"}
]
}
];
const result = input.reduce((acc, { choices }) => {
choices.forEach(({ food, preference }) => {
if (!acc[food]) acc[food] = {}
if (!acc[food][preference]) acc[food][preference] = 1
else ++acc[food][preference]
})
return acc
}, {})
console.log(result)

Related

How to filtering out the multiple nested object in Javascript object

Javascript
I have a nested array of objects, I'm trying to filter the given array of objects using a property from the third level of its array property value. For example, from the below array I like to filter the entire array using the property ListId: 10
Example
let test = {
"test":true,
"group":[
{
"name":"header",
"value":[
{
"id":"0",
"list":[
{
"ListId":10,
"name":"string1",
"state":"BY",
"techId":0
},
{
"ListId":11,
"name":"string2",
"state":"BY"
},
{
"ListId":12,
"name":"string3",
"state":"BY"
}
]
}
]
},
{
"name":"header2",
"value":[
{
"id":"01",
"list":[
{
"ListId":100,
"name":"string1",
"state":"BY",
"techId":0
},
{
"ListId":111,
"name":"string2",
"state":"BY"
},
{
"ListId":121,
"name":"string3",
"state":"BY"
}
]
}
]
}
]
}
Filtervalue with ListId = 10
Expected output :
{
"test":true,
"group":[
{
"name":"header",
"value":[
{
"id":"0",
"list":[
{
"ListId":10,
"name":"string1",
"state":"BY",
"techId":0
}
]
}
]
}
]
}
How can I use the filter method using javascript to get this expected result?
You can two it in two times :
First, filter the list arrays,
Secondly filter the groups array using the some method
let test= {
"test": true,
"group": [
{
"name": "header",
"value": [
{
"id": "0",
"list": [
{
"ListId": 10,
"name": "string1",
"state": "BY",
"techId": 0
},
{
"ListId": 11,
"name": "string2",
"state": "BY"
},
{
"ListId": 12,
"name": "string3",
"state": "BY"
}
]
}
]
},
{
"name": "header2",
"value": [
{
"id": "01",
"list": [
{
"ListId": 100,
"name": "string1",
"state": "BY",
"techId": 0
},
{
"ListId": 111,
"name": "string2",
"state": "BY"
},
{
"ListId": 121,
"name": "string3",
"state": "BY"
}
]
}
]
}
]
}
test.group.forEach(group => {
group.value.forEach(value => {
value.list = value.list.filter(list => list.ListId === 10)
})
})
test.group = test.group.filter(group => group.value.some(value => value.list.length > 0))
console.log(test)
Note : You should use plural names for you arrays, it helps understanding the data. For example lists not list for the array.
let z ={"group1": [
{
"name": "header",
"value": [
{
"id": 0,
"list": [
{
"ListId": 10,
"Name": "string1"
},
{
"ListId": 11,
"Name": "string2"
}
]
}
]
}
]}
// This function was written from understading that 'group1' is not a fixed property, but part of a dynamic list due to the number '1'
const getItemByListId = (list, listId) => {
const listKeys = Object.keys(list);
const selectedListKey = listKeys.find(key => {
const groupItems = list[key];
const selectedItem = groupItems.find(({ value: nestedItems }) => {
const selectedNestedItem = nestedItems.find(({ list }) => {
const selectedList = list.find(({ ListId }) => ListId === listId)
return selectedList;
});
return selectedNestedItem;
});
return selectedItem;
});
if (!selectedListKey) {
return null;
}
return list[selectedListKey];
};
console.log(getItemByListId(z, 10));

Create a nested object with children from array of arrays

I have the following array of arrays
let arr = [
[ "Female" , "Male" ],
[ "Dinner" , "Lunch" ],
[ "No" , "Yes" ],
]
I'd like to achieve this structure
let foo = [
{
value: "Female",
children: [
{
value: "Dinner",
children: [
{
value: "No"
},
{
value: "Yes"
},
]
},
{
value: "Lunch",
children: [
{
value: "No"
},
{
value: "Yes"
},
]
},
]
},
{
value: "Male",
children: [
{
value: "Dinner",
children: [
{
value: "No"
},
{
value: "Yes"
},
]
},
{
value: "Lunch",
children: [
{
value: "No"
},
{
value: "Yes"
},
]
},
]
},
]
I simply can't wrap my head around the problem to achieve this, thus, I don't have a starting code to post, so please if you can help, it would be great.
recursion
Recursion is a functional heritage and so using it with functional style yields the best results. This means avoiding things like mutation, variable reassignments, and other side effects.
We can write make(t) using inductive inductive reasoning -
If the input t is empty, return the empty result []
(inductive) t has at least one element. For all value in the first element t[0], return a new object {value, children} where children is the result of the recursive sub-problem make(t.slice(1))
const make = t =>
t.length == 0
? [] // 1
: t[0].map(value => ({ value, children: make(t.slice(1)) })) // 2
const myinput = [
[ "Female" , "Male" ],
[ "Dinner" , "Lunch" ],
[ "No" , "Yes" ]
]
console.log(make(myinput))
Above we write make as a single pure functional expression using ?:. This is equivalent to an imperative style if..else -
function make(t) {
if (t.length == 0)
return []
else
return t[0].map(value => ({ value, children: make(t.slice(1)) }))
}
const myinput = [
[ "Female" , "Male" ],
[ "Dinner" , "Lunch" ],
[ "No" , "Yes" ]
]
console.log(make(myinput))
visualize
It helps for us to visualize how these work
make([[ "Female" , "Male" ], [ "Dinner" , "Lunch" ], [ "No" , "Yes" ]])
= [
{value: "Female", children: make([[ "Dinner" , "Lunch" ], [ "No" , "Yes" ]]) },
{value: "Male", children: make([[ "Dinner" , "Lunch" ], [ "No" , "Yes" ]]) }
]
make([[ "Dinner" , "Lunch" ], [ "No" , "Yes" ]])
= [
{value: "Dinner", children: make([[ "No" , "Yes" ]]) },
{value: "Lunch", children: make([[ "No" , "Yes" ]]) }
}
make([[ "No" , "Yes" ]])
= [
{value: "No", children: make([]) },
{value: "Yes", children: make([]) }
}
make([])
= []
remove empty children
Now that we see how it works, we prevent making empty children: [] properties by adding one more conditional. When t has just one element, simply create a {value} for all value in the element -
function make(t) {
switch (t.length) {
case 0:
return []
case 1:
return t[0].map(value => ({ value }))
default:
return t[0].map(value => ({ value, children: make(t.slice(1)) }))
}
}
const myinput = [
[ "Female" , "Male" ],
[ "Dinner" , "Lunch" ],
[ "No" , "Yes" ]
]
console.log(make(myinput))
Which produces the output you are looking for -
[
{
"value": "Female",
"children": [
{
"value": "Dinner",
"children": [
{
"value": "No"
},
{
"value": "Yes"
}
]
},
{
"value": "Lunch",
"children": [
{
"value": "No"
},
{
"value": "Yes"
}
]
}
]
},
{
"value": "Male",
"children": [
{
"value": "Dinner",
"children": [
{
"value": "No"
},
{
"value": "Yes"
}
]
},
{
"value": "Lunch",
"children": [
{
"value": "No"
},
{
"value": "Yes"
}
]
}
]
}
]
You can also do it without recursion with 2 for
let arr = [
[ "Female" , "Male" ],
[ "Dinner" , "Lunch" ],
[ "No" , "Yes" ],
];
var lastChild = -1;
for(var i = arr.length-1; i >= 0; i--) {
var item = arr[i];
var lastChildTemp = [];
for(var j = 0; j < item.length; j++) {
var newChild = {value: item[j]};
if(lastChild != -1) {
newChild.children = lastChild;
}
lastChildTemp.push(newChild);
}
lastChild = lastChildTemp;
}
console.log(JSON.stringify(lastChildTemp,null,2));
Output:
[
{
"value": "Female",
"children": [
{
"value": "Dinner",
"children": [
{
"value": "No"
},
{
"value": "Yes"
}
]
},
{
"value": "Lunch",
"children": [
{
"value": "No"
},
{
"value": "Yes"
}
]
}
]
},
{
"value": "Male",
"children": [
{
"value": "Dinner",
"children": [
{
"value": "No"
},
{
"value": "Yes"
}
]
},
{
"value": "Lunch",
"children": [
{
"value": "No"
},
{
"value": "Yes"
}
]
}
]
}
]
The key here is to use backward for (starting from high index to low index), then create a lastChild object. Then put it in .children attribute of each next objects.
Rearrange your Array using the below code, then iterate as your wish and this is dynamic. you can have more rows in arr variable.
let arr = [
[ "Female" , "Male" ],
[ "Dinner" , "Lunch" ],
[ "No" , "Yes" ],
]
for(let i=arr.length-2; i>-1; i--){
for(let j=0; j< arr[i].length; j++) {
item = {}
item[arr[i][j]] = arr[i+1];
arr[i][j] = [];
arr[i][j] = item;
}
arr.pop();
}
console.log(arr);
/*output*/
[
[{
'Female': [{
'Dinner': ['No', 'Yes']
}, {
'Lunch': ['No', 'Yes']
}]
}, {
'Male': [{
'Dinner': ['No', 'Yes']
}, {
'Lunch': ['No', 'Yes']
}]
}]
]
https://jsfiddle.net/Frangly/ywsL0pbt/149/
You can try this:
let arr = [
['Female', 'Male'],
['Dinner', 'Lunch'],
['No', 'Yes']
]
function makeTree(a, ch = [], currIndex = 0) {
for (const item of a[currIndex]) {
if (a[currIndex + 1]) {
// If there is an array after this one then
// include the 'children' array
const obj = { value: item, children: [] }
ch.push(obj)
// Run the function again to fill the `children`
// array with the values of the next array
makeTree(a, obj.children, currIndex + 1)
} else {
// If this is the last array then
// just include the value
ch.push({ value: item })
}
}
return ch
}
const result = makeTree(arr)
console.log(JSON.stringify(result, null, 2))
.as-console-wrapper { min-height: 100% }
Checkout this code snippet. It outputs as per your need.
let arr = [
[ "Female" , "Male" ],
[ "Dinner" , "Lunch" ],
[ "No" , "Yes" ],
]
let foo = [];
let arr2 = [];
arr[2].forEach(yn => {
arr2.push({ "value": yn});
});
let arr1 = [];
arr[1].forEach(dl => {
arr1.push({
"value": dl,
"children": arr2
});
});
arr[0].forEach(fm => {
foo.push({
"value": fm,
"children": arr1
});
});
console.log(JSON.stringify(foo, null, 2))

Return name and id property value of all arrays inside object

How to return name and id property value of all arrays? The idea is to make a single map of all these arrays and return the id and name?
Something like this
filters.[key].map((option, index) => (
<ItemFilter key={index}>{option}</ItemFilter>
))
I have this array object
filters: {
"services": [
{
"id": "1b975589-7111-46a4-b433-d0e3c0d7c08c",
"name": "Bank"
},
{
"id": "91d4637e-a17f-4b31-8675-c041fe06e2ad",
"name": "Income"
}
],
"accountTypes": [
{
"id": "1f34205b-2e5a-430e-982c-5673cbdb3a68",
"name": "Digital Account"
}
],
"channels": [
{
"id": "875f8350-073e-4a20-be20-38482a86892b",
"name": "Chat"
}
]
}
You can use flatMap or flat to achieve the desired result.
Object.values(obj.filters).flatMap(v => v)
or
Object.values(obj.filters).flat()
const obj = {
filters: {
services: [
{
id: "1b975589-7111-46a4-b433-d0e3c0d7c08c",
name: "Bank",
},
{
id: "91d4637e-a17f-4b31-8675-c041fe06e2ad",
name: "Income",
},
],
accountTypes: [
{
id: "1f34205b-2e5a-430e-982c-5673cbdb3a68",
name: "Digital Account",
},
],
channels: [
{
id: "875f8350-073e-4a20-be20-38482a86892b",
name: "Chat",
},
],
},
};
const result = Object.values(obj.filters).flatMap(v => v);
console.log(result);
If option is referring to name in your example code it could look something like this:
Object.values(
{
filters: {
services: [
{
id: "1b975589-7111-46a4-b433-d0e3c0d7c08c",
name: "Bank",
},
{
id: "91d4637e-a17f-4b31-8675-c041fe06e2ad",
name: "Income",
},
],
accountTypes: [
{
id: "1f34205b-2e5a-430e-982c-5673cbdb3a68",
name: "Digital Account",
},
],
channels: [
{
id: "875f8350-073e-4a20-be20-38482a86892b",
name: "Chat",
},
],
},
}.filters
)
.flat()
.map(({ name, index }) => <ItemFilter key={index}>{name}</ItemFilter>);

sort json nested array based on index not working as expected

I have a json array and I want to sort the array based on its index number
[
{
"name":"abc",
"index":2,
"values":[
{
"work":"three3",
"index":3
},
{
"work":"one1",
"index":1
},
{
"work":"two2",
"index":2
}
]
},
{
"name":"pqr",
"index":1,
"values":[
{
"work":"three",
"index":3
},
{
"work":"two",
"index":2
},
{
"work":"one",
"index":1
}
]
}
]
What I expect from this array is:
[
{
"filename":"pqr",
"children":[
{
"work":"one",
"index":1
},
{
"work":"two",
"index":2
},
{
"work":"three",
"index":3
}
]
},
{
"filename":"abc",
"children":[
{
"work":"one1",
"index":1
},
{
"work":"two2",
"index":2
},
{
"work":"three3",
"index":3
}
]
}
]
Tried something like below.
const filterBy = (arr, childname, filterText) =>
{
return arr.map(({filename, children}) =>
{
return {filename, children: children.map(({filename, children}) =>
{
if (filename === childname)
return {filename, children: children.filter(
x => x.filename.match(filterText)
)};
else
return {filename, children};
})};
});
}
It is a json array and what we can not be sure it will be in order so I want an array or object should be in sorted order
But how can I include that index in inner level and outer level and sort using it accordingly?
You can first sort the array based on index. This will return a sorted array now use map. Inside the callback function get the values array and again sort it.Inside the array map function return the object with required key and value
let data = [{
"name": "abc",
"index": 2,
"values": [{
"work": "three3",
"index": 3
},
{
"work": "one1",
"index": 1
},
{
"work": "two2",
"index": 2
}
]
},
{
"name": "pqr",
"index": 1,
"values": [{
"work": "three",
"index": 3
},
{
"work": "two",
"index": 2
},
{
"work": "one",
"index": 1
}
]
}
]
let newdt = data.sort(function(a, b) {
return a.index - b.index
}).map(function(item) {
let val = item.values.sort(function(a, b) {
return a.index - b.index;
})
return {
name: item.name,
children: val
}
})
console.log(newdt)
First sort the outer array, use same function to sort inner array
function compare(a, b) {
if (a.index < b.index)
return -1;
if (a.index > b.index)
return 1;
return 0;
}
var objs = [{
"name": "abc",
"index": 2,
"values": [{
"work": "three3",
"index": 3
},
{
"work": "one1",
"index": 1
},
{
"work": "two2",
"index": 2
}
]
},
{
"name": "pqr",
"index": 1,
"values": [{
"work": "three",
"index": 3
},
{
"work": "two",
"index": 2
},
{
"work": "one",
"index": 1
}
]
}
]
var result = objs.sort(compare).map(function(item) {
var children = item.values.sort(compare);
return {
filename: item.name,
children: children
}
});
console.log(result);

Query an object array to check if it contains an array of strings in mongoose

say i have the following inside my db:
knights
{
name: 'Knightley',
skills: [
{ name: 'sword', level: 2 },
{ name: 'shield', level: 1 }
]
},
{
name: 'Cowardly',
skills: [
{ name: 'sword', level: 1 },
{ name: 'shield', level: 5 }
]
}
and i want to return all knights with skills of sword and shield.
something like this (pseudo):
Knight.find({ skills.name: which contains ['sword', 'shield'] })
how do i do this kind of query?
thanks!
You need to use $elemMatch to find inside the array with $in operator
db.collection.find({
skills: {
$elemMatch: {
name: { $in: ["sword", "shield"] }
}
}
})
Output
[
{
"name": "Knightley",
"skills": [
{
"level": 2,
"name": "sword"
},
{
"level": 1,
"name": "shield"
}
]
},
{
"name": "Cowardly",
"skills": [
{
"level": 1,
"name": "sword"
},
{
"level": 5,
"name": "shield"
}
]
}
]
Check it here

Categories