I'm currently working with an array of nested arrays and attempting to pull out certain arrays if they meet a condition (length greater than 1). Here's the array -
const testArr = [
{
"type": 1,
"categories": [
{
"description": "Foo",
"subOptions": [
{
"subType": 1,
"subTypeTypeDescription": "Bar"
}
],
}
]
},
{
"type": 2,
"categories": [
{
"description": "Baz",
"subOptions": [
{
"subType": 2,
"subTypeTypeDescription": "Baf"
},
{
"subType": 3,
"subTypeTypeDescription": "Bee"
}
],
}
]
}
]
What I'm hoping to produce is an array of categories, but only if those categories have a subOptions array with a length > 1. Here is the hoped for result on the array above -
[
{
"description": "Baz",
"subOptions": [
{
"subType": 2,
"subTypeTypeDescription": "Baf"
},
{
"subType": 3,
"subTypeTypeDescription": "Bee"
}
],
}
]
I can get this to work by using normal foreach loops like so -
let categories: [];
testArr.forEach((service) => {
service.categories.forEach((cat) => {
if(cat.subOptions.length > 1) {
categories.push(cat)
}
})
})
but I'm trying to learn how to do these things with functional tools. I've tried to do it now several times with a mix of map, filter, and reduce and keep failing. If anyone can offer some direction on the most optimal way to do the same thing with functional methods, I'd greatly appreciate it.
Use a .filter that checks the length of the sub-array:
const testArr=[{type:1,categories:[{description:"Foo",subOptions:[{subType:1,subTypeTypeDescription:"Bar"}]}]},{type:2,categories:[{description:"Baz",subOptions:[{subType:2,subTypeTypeDescription:"Baf"},{subType:3,subTypeTypeDescription:"Bee"}]}]}];
const result = testArr
.flatMap(item => item.categories)
.filter(({ subOptions }) => subOptions.length >= 2);
console.log(result);
Try this
const categories = testArr.map(service => {
return service.categories.filter(category => category.subOptions.length > 1);
})
Or even this in one line:
const categories = testArr.map(service => service.categories.filter(category => category.subOptions.length > 1));
Related
For one of my e-commerce application requirement, I have a nested array of the form (Sample):
const data = [
{
"id": 1,
"group": "upper-wear",
"labels": [
{
"type": "shirts",
"quantity": "20",
},
],
popular: true
},
{
"id": 2,
"group": "bottom-wear",
"lables": [
{
"type": "trousers",
"quantity": "31",
},
],
popular: true
},
]
To this array, I need to insert new objects to the array 'labels' if the group value equals 'upper-wear'.
const newDataToInsert = [
{
"type": 'blazers',
"quantity": 19
},
]
This is what I tried so far, considering that for now I only need to insert to single label (i.e. 'upper-wear') (in future, there can be multiple labels category 'upper-wear', 'bottom-wear', to be inserted into):
const updatedArray = data.map((datum) => {
if (datum.group === 'upper-wear') {
return {
...datum,
labels: [...datum.labels, ...newDataToInsert]
};
}
});
console.log(updatedArray);
But there seems to be a silly issue that I am missing as the result returns like this:
[
{
id: 1,
group: 'upper-wear',
labels: [ [Object], [Object] ],
popular: true
},
undefined
]
I know there may be better approaches available, but this is what I can think of as the minimum solution for now.
any help to resolve the current or any better solution will be highly appreciated.
Try with this
updatedArray = data.map((d) => {
if (d.group && d.group === 'upper-wear') {
return { ...d, labels: d.labels.concat(newDataToInsert) }
} else {
return d;
}
})
const data = [
{
"id": 1,
"group": "upper-wear",
"labels": [
{
"type": "shirts",
"quantity": "20",
},
],
popular: true
},
{
"id": 2,
"group": "bottom-wear",
"lables": [
{
"type": "trousers",
"quantity": "31",
},
],
popular: true
},
];
const newDataToInsert = [
{
"type": 'blazers',
"quantity": 19
},
];
const updatedArray = data.map((d) => {
if (d.group && d.group === 'upper-wear') {
return { ...d, labels: d.labels.concat(newDataToInsert) }
} else {
return d;
}
});
console.log(updatedArray)
Explaination
Here while mapping the data, we check for the condition
IF
If it matches then we will first copy the whole object from the variable b return { ...b }
after that we take another variable with the same name lables return { ...d, labels: d.labels.concat(newDataToInsert) },As per the JSON default nature the new variable with the same name will hold the latest value
Here in labels we first take a copy of old data and then merge it with newDataToInsert array labels: d.labels.concat(newDataToInsert), It will merge 2 arrays and store them in JSON with the name labels
Else
In else we just return the current values else { return d; }
You don't actually need to iterate with map over the array. Just find an object in the array and change what you want.
const data=[{id:1,group:"upper-wear",labels:[{type:"shirts",quantity:"20"}],popular:true},{id:2,group:"bottom-wear",lables:[{type:"trousers",quantity:"31"}],popular:true}];
const newDataToInsert=[{type:"blazers",quantity:19}];
data.find(({ group }) => group === 'upper-wear')?.labels.push(...newDataToInsert);
console.log(data);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You're not returning all objects from your map. you're only returning a result when your criteria is met. This is resulting in your undefined objects...
const data = [
{ "id": 1, "group": "upper-wear", "labels": [ { "type": "shirts", "quantity": "20", }, ], popular: true },
{ "id": 2, "group": "bottom-wear", "lables": [ { "type": "trousers", "quantity": "31", }, ], popular: true },
]
const newDataToInsert = [ { "type": 'blazers',"quantity": 19 }, ]
const updatedArray = data.map(datum => {
if (datum.group === 'upper-wear') datum.labels = [...datum.labels, ...newDataToInsert]
return datum
});
console.log(updatedArray);
You can use Array#find to locate the desired group and then change labels for the group found. There are two options depending on how many items you would like to insert. Use Array#push to add the desired item; use forEach for more than one item:
const searchgroup = "upper-wear";
const target = data.find(({group}) => group === searchgroup);
target.labels.push(...newDataToInsert); //For one item to insert
//newDataToInsert.forEach(label => target.labels.push( label )); //For more than one item
const data = [{"id": 1, "group": "upper-wear", "labels": [{"type": "shirts", "quantity": "20"},],popular: true }, {"id": 2, "group": "bottom-wear", "lables": [{"type": "trousers", "quantity": "31", },],popular: true}];
const newDataToInsert = [{"type": 'blazers', "quantity": 19}];
//group to find
const searchgroup = "upper-wear";
//target element in data
const target = data.find(({group}) => group === searchgroup);
//check if group was found
if( target ) {
//if there's only one product in newDataToInsert us this:
//target.labels.push(...newDataToInsert);
//if you have more than one product to be inserted use this; also works for one
newDataToInsert.forEach(label => target.labels.push( label ));
} else {
console.log( `No such group found: ${searchgroup}!` );
}
console.log( data );
I have an array of objects that determine which ones should be showed first. An example of this array would be:
[
{
"id": "b94ae1a5-c6b2-4e45-87cd-a4036fdb7870",
"prerequisites_ids": [
"2a4fdd9c-45d0-49d9-a0eb-ba5a0464f2b1"
]
},
{
"id": "ef7d2415-808f-4efc-939e-2692f38a5ee7",
"prerequisites_ids": [
"74e41a2c-e74e-4016-bb2c-f2e84c04fe92"
]
},
{
"id": "74e41a2c-e74e-4016-bb2c-f2e84c04fe92",
"prerequisites_ids": []
},
{
"id": "2a4fdd9c-45d0-49d9-a0eb-ba5a0464f2b1",
"prerequisites_ids": [
"ef7d2415-808f-4efc-939e-2692f38a5ee7"
]
}
]
How could I sort it to get this?
[
{
"id": "74e41a2c-e74e-4016-bb2c-f2e84c04fe92",
"prerequisites_ids": []
},
{
"id": "ef7d2415-808f-4efc-939e-2692f38a5ee7",
"prerequisites_ids": [
"74e41a2c-e74e-4016-bb2c-f2e84c04fe92"
]
},
{
"id": "2a4fdd9c-45d0-49d9-a0eb-ba5a0464f2b1",
"prerequisites_ids": [
"ef7d2415-808f-4efc-939e-2692f38a5ee7"
]
},
{
"id": "b94ae1a5-c6b2-4e45-87cd-a4036fdb7870",
"prerequisites_ids": [
"2a4fdd9c-45d0-49d9-a0eb-ba5a0464f2b1"
]
}
]
I have tried creating a custom function:
export function comparePrerequisites(a, b) {
if (!a.prerequisites_ids) {
return -1
}
if (a.prerequisites_ids.includes(b.id)) {
return 1;
}
}
data.sort(comparePrerequisites)
but does not seem to work. Thanks in advance!
We have here the requirements for a topological sort. This is not a job for the sort method. Instead you can use recursion (a DFS traversal) to drill down to a dependency that is already collected, or to a leaf (no dependencies).
Here is an implementation:
function topologicalSort(tasks) {
const visited = new Set;
const taskMap = new Map(tasks.map(task => [task.id, task]));
function dfs(tasks) {
for (let task of tasks) {
if (!visited.has(task.id)) {
dfs(task.prerequisites_ids.map(id => taskMap.get(id)));
}
visited.add(task);
}
}
dfs(tasks);
return [...visited];
}
// Demo on your example:
let tasks = [{"id": "b94ae1a5-c6b2-4e45-87cd-a4036fdb7870","prerequisites_ids": ["2a4fdd9c-45d0-49d9-a0eb-ba5a0464f2b1"]},{"id": "ef7d2415-808f-4efc-939e-2692f38a5ee7","prerequisites_ids": ["74e41a2c-e74e-4016-bb2c-f2e84c04fe92"]},{"id": "74e41a2c-e74e-4016-bb2c-f2e84c04fe92","prerequisites_ids": []},{"id": "2a4fdd9c-45d0-49d9-a0eb-ba5a0464f2b1","prerequisites_ids": ["ef7d2415-808f-4efc-939e-2692f38a5ee7"]}];
console.log(topologicalSort(tasks));
I have a array as follows:
data = [
{
"id":1
"name":"london"
},
{
"id":2
"name":"paris"
},
{
"id":3
"name":"london"
},
{
"id":4
"name":"paris"
},
{
"id":5
"name":"australia"
},
{
"id":6
"name":"newzearland"
}
]
At runtime this array can have n number of elements. I want to group this array with respect to name attribute. All the elements with same name should be moved to a separate array. I don't know the what value can name have in advance. This is coming at runtime. For example, from above array I want final output as follows:
output:
newArray1 = [
{
"id":1
"name":"london"
},
{
"id":3
"name":"london"
}
]
newArray2 = [
{
"id":2
"name":"paris"
},
{
"id":4
"name":"paris"
}
]
newArray3 = [
{
"id":5
"name":"australia"
}
]
newArray4 = [
{
"id":6
"name":"newzearland"
}
]
How can I do that?
As Teemu has already pointed out in a comment, creating new variables to store the data is not ideal. You would have no way of knowing how many groups you've created and using variables that you can't be sure exist is not the best way to write code. Fortunately, JavaScript has objects, which can store data like this in a much cleaner way. Here's the code I've come up with:
function groupBy(arr, key) {
let res = {}
for (let element of arr) {
if (res.hasOwnProperty(element[key])) {
res[element[key]].push(element)
} else {
res[element[key]] = [element]
}
}
return res
}
This code is not the best, most efficient code ever, but it is written to be easier to understand for someone still learning. This code loops over every element in your data and checks whether our result already contains an array for elements with that name. If there's already an array for elements with that name, the current element is added to it. If there isn't one, a new one is created with the current element inside it. To do exactly what you want, you'd call this function with groupBy(data, "name") and assign it to a new variable like groupedData (THIS DOES NOT MODIFY THE DATA, IT RETURNS A NEW OBJECT OF GROUPED DATA) .
Start by getting all the unique .names, then map them to the original array filtered by each .name:
const data = [{
"id": 1, "name": "london"
},
{
"id": 2, "name": "paris"
},
{
"id": 3, "name": "london"
},
{
"id": 4, "name": "paris"
},
{
"id": 5, "name": "australia"
},
{
"id": 6, "name": "newzearland"
}
];
const newData = [...new Set(data
//Get all names in an array
.map(({name}) => name))]
//For each name filter original array by name
.map(n => data.filter(({name}) => n === name));
console.log( newData );
//OUTPUT: [newArray1, newArray2, .....]
You can get the expected result with grouping by key approach.
const data = [{"id":1,"name":"london"},{"id":2,"name":"paris"},{"id":3,"name":"london"},{"id":4,"name":"paris"},{"id":5,"name":"australia"},{"id":6,"name":"newzearland"}];
const result = Object.values(data.reduce((acc, obj) =>
({ ...acc, [obj.name]: [...(acc[obj.name] ?? []), obj] }), {}));
console.log(result);
const [newArray1, newArray2, newArray3, newArray4, ...rest] = result;
console.log('newArray1:', newArray1);
console.log('newArray2:', newArray2);
console.log('newArray3:', newArray3);
console.log('newArray4:', newArray4);
.as-console-wrapper{min-height: 100%!important; top: 0}
I am receiving the following structure from a system. I am attempting to bend it into the form needed for a particular graph utilizing chartjs. Given the JSON data structure … an array of objects in an object:
{
"chart": [
{
"date": "2018-10-29",
"done": 3,
"todo": 10
},
{
"date": "2018-10-30",
"done": 4,
"todo": 7
},
{
"date": "2018-10-31",
"done": 5,
"todo": 12
}
]
}
I need the desired JSON data structure ... an object of arrays (in one array, in one object)
{
"chart": [{
"date": [
"2018-10-29",
"2018-10-29",
"2018-10-31"
],
"done": [
3,
4,
5
],
"todo": [
10,
7,
12
]
}]
}
I have attempted to use the .map function but I don't seem to have the correct map-fu.
You could take an object and get all keys with ther values in single array.
var data = { chart: [{ date: "2018-10-29", done: 3, todo: 10 }, { date: "2018-10-30", done: 4, todo: 7 }, { date: "2018-10-31", done: 5, todo: 12 }] },
result = { chart: data.chart.reduce((r, o) => {
Object.entries(o).forEach(([k, v]) => (r[k] = r[k] || []).push(v));
return r;
}, {})
};
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
What about using reduce ?
const output = input.reduce((acc, curr) => ({
date: acc.date.concat(curr.date),
done: acc.done.concat(curr.done),
todo: acc.todo.concat(curr.todo),
}), { date: [], done: [], todo: [] });
const chartData = {
chart: [output],
};
Reference for reduce is here : https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Array/reduce
Here's a very explicit solution. There may be some slicker Javascript solutions; certainly you can do multiple .map calls, but that makes it less efficient.
// Variables
var dates = [];
var doneValues = [];
var todoValues = [];
// Loop through the original data once, collect the data.
originalJSON.forEach(function(data) {
dates.push(data["date"]);
doneValues .push(data["done"]);
todoValues .push(data["todo"]);
});
// Put it all together.
return {"chart": [{"date": dates, "done": doneValues , "todo": todoValues}]};
Modify it to suit your needs.
I'm trying to count the number of string in an array of objects using lowdb.
Here is a sample of my objects:
{
"tags": [
"test",
"test2"
]
},
{
"tags": [
"test",
"test3"
]
}
I'd like to get this:
{
test: 2,
test2: 1,
test3: 1
}
I have successfully get this doing like this:
_.each(selectAll().value(), (bookmark) => {
if (bookmark.tags.length > 0) {
_.each(bookmark.tags, (bookmarksTags) => {
if (!(bookmarksTags in tags)) {
tags[bookmarksTags] = 0
}
tags[bookmarksTags]++
})
}
})
It works but... it's ugly and I don't like it. Do you know a better and proper Lodash's way to do this?
You can use reduce() and forEach() with plain javascript.
var data = [{"tags":["test","test2"]},{"tags":["test","test3"]}]
var result = data.reduce(function(r, e) {
return (e.tags.forEach(e => r[e] = (r[e] || 0) + 1)), r
}, {})
console.log(result)
One way using Lodash:
_.countBy(_.flatMap(arr,'tags'))
Where arr is the source array
var o = [{ "tags": [ "test", "test2" ]},{ "tags": [ "test", "test3" ]}];
console.log(_.countBy(_.flatMap(o,'tags')));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>