How to create nested arrays based on arrays of strings in javascript? - javascript

I am trying to create nested arrays with array of strings.
Each string object on the array is delimited by a '|' and that char its uses to create a nested array over an already existing array.
edit fix IE: current array
var arr = [
{ val : 'root|leaf|lead2|boo|foo|lee'},
{ val : 'root|leaf|lead3|boo|foo|lee'},
{ val : 'root|leaf2|boo'},
{ val : 'root|leaf2|foo'},
{ val : 'root|leaf2|leaf3|more'},
{ val : 'root|leaf2|leaf3|things'},
{ val : 'root|leaf2|leaf3|here'},
{ val : 'sibling|leaf|leaf2|boo'},
{ val : 'sibling|leaf|leaf2|foo'},
{ val : 'sibling|leaf|leaf2|lee'},
{ val : 'sibling|boo'},
{ val : 'sibling|foo'},
{ val : 'sibling|boo|leaf3'},
{ val : 'sibling|boo|leaf3|more'},
{ val : 'sibling|boo|leaf3|things'},
{ val : 'sibling|boo|leaf3|here'},
{ val : 'sibling|ops'},
];
var nested = [
root = [
leaf = [
leaf2 = [
'boo', 'foo', 'lee'
],
leaf3 = [
'boo', 'foo', 'lee'
]
],
leaf2 = [
'boo', 'foo', leaf3 = [
'more', 'things', 'here'
]
]
],
sibling = [
leaf = [
leaf = [
leaf2 = [
'boo', 'foo', 'lee'
]
]
],
'ops',
'boo', 'foo', leaf3 = [
'more', 'things', 'here'
]
]
];

You can find here a functional approach, by using .map() and .reduce() methods. The idea is to parse the path by splitting over the | character, and then build the object on the fly.
const arr = [
{cat : 'one|two|thre|boo'},
{cat : 'one|two|boo|boo|ouch'},
{cat : 'one|two|thre|boo|lee'},
{cat : 'one|hey|something|other'},
{cat : 'one|hey|keys|other'},
{cat : 'this|blaj|something|other'},
];
function create(array) {
const parse = elm => elm.cat.split('|');
const build = (keys, obj, acc) => {
keys.reduce((a, b) => {
if (!a[b]) a[b] = {};
return a[b];
}, obj);
Object.assign(acc, obj);
return acc;
};
const obj = {};
return array
.map(a => parse(a))
.reduce((acc, keys) => build(keys, obj, {}), {});
}
console.log(create(arr))
You can find the Working plunkr

Related

How to loop inside a map() loop?

The below script outputs
================================================
Log entry ID: 1
UUID: 2
Timestamp: 3
--------------------
Log entry ID: 4
UUID: 5
Timestamp: 6
which is what I want.
Right now description is hard coded, where I would like it to be built using arr instead.
My current thinking were to somehow generate the inner array in the map() function:
[
`Log entry ID: ${element['_id']}`,
`UUID: ${element['_source'].uuid}`,
`Timestamp: ${element['_source']['#timestamp']}\n`,
]
but because of the templating, this is not even a valid array with 3 elements, when looking at it on its own. So I am all out of ideas.
Question
Somehow I have to loop over the elements in arr before it is given to map(), I suppose.
Does anyone know how that could be done?
const dedent = require('dedent');
const arr = [
[ 'Log entry ID', '_id' ],
[ 'UUID', 'uuid' ],
[ 'Timestamp', '#timestamp' ],
]
;
const docs = [
{'_id': 1,'_source': {'uuid': 2,'#timestamp': 3}},
{'_id': 4,'_source': {'uuid': 5,'#timestamp': 6}},
];
const description = dedent`
================================================
${
docs.map((element) => [
`Log entry ID: ${element['_id']}`,
`UUID: ${element['_source'].uuid}`,
`Timestamp: ${element['_source']['#timestamp']}\n`,
].join('\n')).join('--------------------\n')
}
`;
console.log(description);
Update
I control arr so changing it to eg. is possible, or something else
const arr = [
[ 'Log entry ID', '_id' ],
[ 'UUID', {'_source': 'uuid'} ],
[ 'Timestamp', {'_source': '#timestamp'} ],
]
;
Since arr is in your control perhaps you can specify the path of the key itself.
const arr = [
['Log entry ID', '_id'],
['UUID', '_source.uuid'],
['Timestamp', '_source.#timestamp'],
['Description', '_source._desc']
];
const docs = [{
'_id': 1,
'_source': {
'uuid': 2,
'#timestamp': 3,
'_desc': 'test 1'
}
},
{
'_id': 4,
'_source': {
'uuid': 5,
'#timestamp': 6,
'_desc': 'test 2'
}
},
];
const getValue = (object, keys) => keys.split('.').reduce((o, k) => (o || {})[k], object);
console.log(docs.map((element) => arr.map((label) => {
return `${label[0]}: ${getValue(element, label[1])}`
}).join('\n')).join('\n--------------------\n'))
Assuming that the keys of a docs array element are all unique, one could traverse the object looking for a matching key.
function findVal(object, key) {
var value;
Object.keys(object).some(function(k) {
if (k === key) {
value = object[k];
return true;
}
if (object[k] && typeof object[k] === 'object') {
value = findVal(object[k], key);
return value !== undefined;
}
});
return value;
}
docs.map((element) =>
arr.map(([item, key]) =>
`${item}: ${findVal(element, key)}`)
)
The FindVal is taken from here.
you could create a function to get data from arr and put in description
/* Simple Hello World in Node.js */
const arr = [
[ 'Log entry ID', '_id' ],
[ 'UUID', 'uuid' ],
[ 'Timestamp', '#timestamp' ],
]
;
const docs = [
{'_id': 1,'_source': {'uuid': 2,'#timestamp': 3}},
{'_id': 4,'_source': {'uuid': 5,'#timestamp': 6}},
];
const findInArr=function(value){
var t = ""
arr.forEach(item=>{
if (item.includes(value)) {t = item[0]}
})
return t
}
const description = dedent`
================================================
${
docs.map((element) => [
`${findInArr('_id')}: ${element['_id']}`,
`${findInArr('UUID')}: ${element['_source'].uuid}`,
`${findInArr('Timestamp')}: ${element['_source']['#timestamp']}\n`,
].join('\n')).join('--------------------\n')
}`;
console.log(description);

filter array of objects based on list of values

I have an array as following
array = [
{
name: 'A'
instructors: [
{
name:'InsA'
}
]
businessUnit: {name:'myBusiness'}
},
{
name: 'B'
instructors: [
{
name:'InsB'
}
]
businessUnit: {name:'myBusinessB'}
}
]
I want to filter this array with the values i have which are also in an array as following
nameArr = [A,C,D]
instructorArr = [InsA,InsC,InsZ]
businessName = [myBusinessB,myBusinessX,myBusinessD]
If can filter this array if i have to check with just one value as following
const filtered = _.filter(groupActivityList, (obj) => {
return (
obj.name === (groupClassFilter !== defaultFilter ? groupClassFilter : obj.name) &&
obj.instructors.length > 0 &&
obj.instructors[0]?.name ===
(groupInstructorFilter !== defaultFilter
? groupInstructorFilter
: obj.instructors.length > 0 && obj.instructors[0]?.name) &&
obj.businessUnit.name ===
(groupFacilityFilter !== defaultFilter ? groupFacilityFilter : obj.businessUnit.name)
);
});
How do i filter when i have a set of values to filter with Ex: nameArr = [A,C,D]
An example for filtering the array using Array.prototype.some and Array.prototype.includes:
const array = [{
name: 'A',
instructors: [{
name: 'InsA'
}],
businessUnit: {
name: 'myBusinessA'
}
},
{
name: 'B',
instructors: [{
name: 'InsB'
}],
businessUnit: {
name: 'myBusinessB'
}
},
{
name: 'Z',
instructors: [{
name: 'InsZZ'
}],
businessUnit: {
name: 'myBusinessZZ'
}
}
];
const nameArr = ['A', 'C', 'D']
const instructorArr = ['InsA', 'InsC', 'InsZ']
const businessName = ['myBusinessB', 'myBusinessX', 'myBusinessD', 'myBusinessA']
const filtered = array.filter(el => nameArr.includes(el.name) && el.instructors.some(ins => instructorArr.some(iA => ins.name === iA)) &&
businessName.includes(el.businessUnit.name)
);
console.log(filtered);
You can use the Array#some property, if I understand the question correctly.
Instead of checking obj.name === filterObj.name, you can check with fiterObj.names.some(name => name === obj.name) which will return true if any of the elements inside the namesArr matches the object's name

Convert an array to json object by javascript

I am stuck to solve this problem.
Convert an array below
var input = [
'animal/mammal/dog',
'animal/mammal/cat/tiger',
'animal/mammal/cat/lion',
'animal/mammal/elephant',
'animal/reptile',
'plant/sunflower'
]
to json Object
var expectedResult = {
"animal": {
"mammal": {
"dog": true,
"cat": {
"tiger": true,
"lion": true
},
"elephant": true
},
"reptile": true
},
"plant": {
"sunflower": true
}
}
Which data structure and algorithm can I apply for it?
Thanks
You need to first split each element to convert to array
using reverse reduce method you can convert them to object.
And your last step is merge this objects.
Lodash.js merge method is an one way to merge them.
var input = ['animal/mammal/dog','animal/mammal/cat/tiger','animal/mammal/cat/lion', 'animal/mammal/elephant','animal/reptile', 'plant/sunflower']
var finalbyLodash={}
input.forEach(x=>{
const keys = x.split("/");
const result = keys.reverse().reduce((res, key) => ({[key]: res}), true);
finalbyLodash = _.merge({}, finalbyLodash, result);
});
console.log(finalbyLodash);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.js"></script>
To make the process more understandable, break the problem down into pieces.
The first step is convert each string into something we can use, converting this:
"animal/mammal/dog"
into this:
[ "animal", "mammal", "dog" ]
That's an array of property names needed to build the final object.
Two functions will accomplish this for you, String.prototype.split() to split the string into an array, and Array.prototype.map() to transform each of the array elements:
let splitIntoNames = input.map(str => str.split('/'));
The intermediate result is this:
[
[ "animal", "mammal", "dog" ],
[ "animal", "mammal", "cat", "tiger" ],
[ "animal", "mammal", "cat", "lion" ],
[ "animal", "mammal", "elephant" ],
[ "animal", "reptile" ],
[ "plant", "sunflower" ]
]
Next step is to iterate over each array, using Array.prototype.forEach() to add properties to the object. You could add properties to the object with a for loop, but let's do that with a recursive function addName():
function addName(element, list, index) {
if (index >= list.length) {
return;
}
let name = list[index];
let isEndOfList = index === list.length - 1;
element[name] = element[name] || (isEndOfList ? true : {});
addName(element[name], list, index + 1);
}
let result = {};
splitIntoNames.forEach((list) => {
addName(result, list, 0);
});
The result:
result: {
"animal": {
"mammal": {
"dog": true,
"cat": {
"tiger": true,
"lion": true
},
"elephant": true
},
"reptile": true
},
"plant": {
"sunflower": true
}
}
const input = [
"animal/mammal/dog",
"animal/mammal/cat/tiger",
"animal/mammal/cat/lion",
"animal/mammal/elephant",
"animal/reptile",
"plant/sunflower",
];
let splitIntoNames = input.map((str) => str.split("/"));
console.log("splitIntoNames:", JSON.stringify(splitIntoNames, null, 2));
function addName(element, list, index) {
if (index >= list.length) {
return;
}
let name = list[index];
let isEndOfList = index === list.length - 1;
element[name] = element[name] || (isEndOfList ? true : {});
addName(element[name], list, index + 1);
}
let result = {};
splitIntoNames.forEach((list) => {
addName(result, list, 0);
});
console.log("result:", JSON.stringify(result, null, 2));
You can create a function that will slice every element from the array by "/" than you put the results into a variable and than just mount the Json. I mean something like that below:
window.onload = function() {
var expectedResult;
var input = [
'animal/mammal/dog',
'animal/mammal/cat/tiger',
'animal/mammal/cat/lion',
'animal/mammal/elephant',
'animal/reptile',
'plant/sunflower'
]
input.forEach(element => {
var data = element.split('/');
var dog = data[2] === 'dog' ? true : false
var tiger = data[2] === 'cat' && data[3] === 'tiger' ? true : false
var lion = data[2] === 'cat' && data[3] === 'lion' ? true : false
expectedResult = {
data[0]: {
data[1]: {
"dog": dog,
"cat": {
"tiger": tiger,
"lion": lion
}
}
}
}
})
}
Late to the party, here is my try. I'm implmenting recursive approach:
var input = ['animal/mammal/dog', 'animal/mammal/cat/tiger', 'animal/mammal/cat/lion', 'animal/mammal/elephant', 'animal/reptile', 'plant/sunflower'];
result = (buildObj = (array, Obj = {}) => {
array.forEach((val) => {
keys = val.split('/');
(nestedFn = (object) => {
outKey = keys.shift();
object[outKey] = object[outKey] || {};
if (keys.length == 0) object[outKey] = true;
if (keys.length > 0) nestedFn(object[outKey]);
})(Obj)
})
return Obj;
})(input);
console.log(result);
I try with array reduce, hope it help
let input = [
"animal/mammal/dog",
"animal/mammal/cat/tiger",
"animal/mammal/cat/lion",
"animal/elephant",
"animal/reptile",
"plant/sunflower",
];
let convertInput = (i = []) =>
i.reduce((prev, currItem = "") => {
let pointer = prev;
currItem.split("/").reduce((prevPre, currPre, preIdx, arrPre) => {
if (!pointer[currPre]) {
pointer[currPre] = preIdx === arrPre.length - 1 ? true : {};
}
pointer = pointer[currPre];
}, {});
return prev;
}, {});
console.log(JSON.stringify(convertInput(input), null, 4));

Whats the most elegant way to transform this JSON structure? [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 3 years ago.
Improve this question
How to transform the following data with few lines of code?
From:
[
{filter:true, type:"one", name:"left"},
{filter:true, type:"two", name:"up"},
{filter:true, type:"one", name:"right"},
{filter:false, type:"three", name:"down"}
]
Into:
[
["left","right"],
["up"]
]
The transformation do the following:
keep items where filter is true
group items by type
map items on their name value
Performance does not matter, only cognitive complexity does.
Today, I use lodash filter/group then a double loop to map items in the sub-arrays.
I am curious to see it exists a way to do the same job without a double loop.
.filter(), destructure for filter and check for True
.reduce(), to create our Object; using type for the Key, and build an Array of name as the Value
Object.values() to return only the Values of that Object
const data = [
{filter:true, type:"one", name:"left"},
{filter:true, type:"two", name:"up"},
{filter:true, type:"one", name:"right"},
{filter:false, type:"three", name:"down"}
];
const newArray = Object.values(data
.filter(({filter}) => filter)
.reduce((acc, {type, name}) => {
acc[type] = acc[type] || [];
acc[type].push(name);
return acc;
}, {})
);
console.log(newArray);
You can use reduce to iterate the array, and group the names in an object by their type, and then convert the object back to an array with Object.values():
const arr = [{"filter":true,"type":"one","name":"left"},{"filter":true,"type":"two","name":"up"},{"filter":true,"type":"one","name":"right"},{"filter":false,"type":"three","name":"down"}]
const result = Object.values(
arr.reduce((r, o) => {
if(!o.filter) return r // ignore item if filter is false
if(!r[o.type]) r[o.type] = [] // init a property in the accumulator by the type, if none exists
r[o.type].push(o.name) // push the current name to the type property in the accumulator
return r
}, {})
)
console.log(result)
Another approach would be to filter out items with filter: false, and then reduce to a Map. Afterwards you can convert the Map back to an object by using Array.from() on the Map's .values() iterator:
const arr = [{"filter":true,"type":"one","name":"left"},{"filter":true,"type":"two","name":"up"},{"filter":true,"type":"one","name":"right"},{"filter":false,"type":"three","name":"down"}]
const result = Array.from(arr
.filter(o => o.filter) // remove items with filter: false
.reduce((r, o) =>
r.set(o.type, [...(r.get(o.type) || []), o.name]) // reduce to a Map
, new Map()
).values()) // get an array from the Map's values
console.log(result)
Sometimes simple is cleaner. Create an object where type is the key and then grab the values:
var arr = [{filter:true, type:"one", name:"left"}, {filter:true, type:"two", name:"up"}, {filter:true, type:"one", name:"right"}, {filter:false, type:"three", name:"down"}];
var obj = {};
arr.forEach((item) => {
if(item.filter){
obj[item.type] = obj[item.type] || [];
obj[item.type].push(item.name);
}
});
console.log(Object.values(obj));
Or, if you're willing to sacrifice readability for shortness:
var arr = [{filter:true, type:"one", name:"left"}, {filter:true, type:"two", name:"up"}, {filter:true, type:"one", name:"right"}, {filter:false, type:"three", name:"down"}];
var obj = {};
arr.forEach((item) => {if(item.filter) obj[item.type] ? obj[item.type].push(item.name) : obj[item.type] = [item.name];});
console.log(Object.values(obj));
You can do something like this
const arr = [
{ filter: true, type: "one", name: "left" },
{ filter: true, type: "two", name: "up" },
{ filter: true, type: "one", name: "right" },
{ filter: false, type: "three", name: "down" }
];
const groupItems = arr => {
const dict = {};
arr.forEach(item => {
const {type, name, filter} = item;
if (filter) {
dict[type] ? dict[type].push(name) : dict[type] = [name];
}
});
return Object.values(dict);
}
console.log(groupItems(arr));
An approach with a Map.
var data = [{ filter: true, type: "one", name: "left" }, { filter: true, type: "two", name: "up" }, { filter: true, type: "one", name: "right" }, { filter: false, type: "three", name: "down" }],
result = Array.from(data
.reduce(
(m, { filter, type, name }) => filter
? m.set(type, [...(m.get(type) || []), name])
: m,
new Map
)
.values()
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
const arr = [
{filter:true, type:"one", name:"left"},
{filter:true, type:"two", name:"up"},
{filter:true, type:"one", name:"right"},
{filter:false, type:"three", name:"down"}
]
const output = arr
.filter(item => item.filter)
.reduce((acc, curr) => {
const existing_item = acc.find(item => item.type === curr.type);
if (!existing_item) {
acc.push({ ...curr, names: [curr.name] });
} else {
existing_item.names.push(curr.name);
}
return acc;
}, [])
.map(obj => obj.names);
console.log({output});
// Alternate way
const output2 = Object.values(
arr
.filter(item => item.filter)
.reduce(
(acc, curr) => ({
...acc,
[curr.type]:
curr.type in acc ? [...acc[curr.type], curr.name] : [curr.name]
}),
{}
)
);
console.log({ output2 });

How to merge two objects based on id and group same key value pairs

var routePlan = [
{
"id" : 1,
"farmerName" : "Farmer1",
"farmerId" : 1
},
{
"id" : 2,
"farmerName" : "Farmer2",
"farmerId" : 2
},
{
"id" : 1,
"farmerName" : "Farmer3",
"farmerId" : 3
}
];
I want to merge objects having same id and create a new combined object using javascript or angularjs
var routePlan = [
{
"id" : 1,
"farmers" : [
{
"farmerName" : "Farmer1",
"farmerId" : 1
},
{
"farmerName" : "Farmer3",
"farmerId" : 3
}
]
},
{
"id" : 2,
"farmerName" : "Farmer3",
"farmerId" : 2
}
];
Please help me, I have seen lot of examples on the net but nothing seems to match my requirement
It can surely be improved, but this is a working solution:
let temp = routePlan.reduce(function(acc, item){
acc[item.id] = acc[item.id] || [];
acc[item.id].push({ "farmerName": item.farmerName, "farmerId" : item.farmerId });
return acc;
}, {});
let newRoute = Object.keys(temp).map(function(key){
let newObj = {}
if (temp[key].length > 1){
newObj.id = parseInt(key);
newObj.farmers = temp[key];
}
else
{
newObj = Object.assign({}, routePlan.find(elem => elem.id == key));
}
return newObj;
});
Note the reduce function used to group your objects by id.
You could take a hash table as reference to the groups.
var data = [{ id: 1, farmerName: "Farmer1", farmerId: 1 }, { id: 2, farmerName: "Farmer2", farmerId: 2 }, { id: 1, farmerName: "Farmer3", farmerId: 3 }],
hash = Object.create(null),
grouped = data.reduce(function (r, a) {
function getP(o) {
return ['farmerName', 'farmerId'].reduce(function (r, k) {
r[k] = o[k];
return r;
}, {});
}
if (!(a.id in hash)) {
hash[a.id] = r.push(a) - 1;
return r;
}
if (!r[hash[a.id]].farmers) {
r[hash[a.id]] = { id: a.id, farmers: [getP(r[hash[a.id]])] };
}
r[hash[a.id]].farmers.push(getP(a));
return r
}, []);
console.log(grouped);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Categories

Resources