array of strings to tree data structure? - javascript

There is data returned from server containing an array of strings as hierarchy like this:
var array = [
"house.bedroom.bed",
"house.kitchen.spoon",
"house.kitchen.knife",
"house.bedroom.sofa",
"house.bedroom.tv",
"plants.trees",
"house.birds.parrot.grey"
]
i have successful made a tree data structure as object out of it to make Output the data in tree form below:
house
bedroom
bed
sofa
tv
kitchen
spoon
knife
birds
parrot
grey
plants
trees
and is there any way to pick a string? for example of asked "kitchen" i want to return all related to that string like this:
house.kitchen.knife
house.kitchen.spoon
Here the codes that i learned:
function find([key, values], string, temp = []) {
var result;
temp = temp.concat(key);
if (key === string) {
return temp.slice(1).join('.');
}
values.some(a => result = find(a, string, temp));
return result;
}
var result = array.reduce((r, s) => {
('root.' + s).split('.').reduce((a, item) => {
var array = a.find(([v]) => v === item);
if (!array) {
a.push(array = [item, []]);
}
return array[1];
}, r);
return r;
}, []).pop();
console.log(find(result, 'kitchen'));
console.log(result);
my output is:
house.kitchen

I propose to filter the original array
const data = ["house.bedroom.bed","house.kitchen.spoon", "house.kitchen.knife","house.bedroom.sofa","house.bedroom.tv",
"plants.trees","house.birds.parrot.grey"];
const result = data.filter((path) => path.split('.').includes('kitchen'));
console.log(result);
.as-console-wrapper{min-height: 100%!important; top: 0}

I believe I understand what you're asking. I would solve this problem with recursion.
function parse(items) {
return items.reduce((acc, item) => {
const k = item.slice(0, item.indexOf('.'))
const v = item.slice(item.indexOf('.') + 1).split('.')
const newItem = {
[k]: v.length > 1 ? parse(v) : v
}
return Object.assign(acc, newItem)
}, { })
}
This is not a complete solution but should get the general idea across. For each item in the array, split it off into a key and a value. The key will be the string before the first ., and the value with either be the single string after that ., or an object containing children.

Related

Looping data from json using Array

I'm trying to write a function but I doesn't make it. This function works like that
Input: changeSetting("a>b>c","hello")
After that "setting" named value change from {} to {"a":{"b":{"c":"hello"}}}
If input is changeSetting("a","hello") json become {} to {"a":"hello"}
My last code attempt:
function changeSetting(name,val) {
if (name.includes(">")) {
name = name.split('>')
let json = {}
name.map((el,i)=>{
let last = ""
name.filter(el=>!name.slice(i+1).includes(el)).map(el=> {
if(last!="") {
json[el] = {}
}})
})
}
}
How can we make this ? (Optimization not important but if is it good for me)
const changeSetting = (setting, target) => {
if (setting.length < 2) {
return {
[setting]: target
}
} else {
const keys = setting.split('>');
return keys.reduceRight((acc, curr, i) => {
console.log(acc);
if(i === keys.length - 1) {
return acc = {[curr] : target}
}
return acc = { [curr]: acc };
}, {})
}
}
console.log(changeSetting('a', 'hello'));
console.log(changeSetting('a>b>c', 'hello'));
function changeSetting(inputProperties, value) {
let result;
const properties = inputProperties.split(">");
result = `{${properties
.map((property) => `"${property}":`)
.join("{")}"${value}"${"}".repeat(properties.length)}`;
return result;
}
changeSetting("a>b>c", "hello");
changeSetting("a", "hello");
As you work with strings - you may try to use JSON like this:
function changeSetting(name, val) {
const keys = name.split(">");
return JSON.parse(
[
"{",
keys.map((key) => `"${key}"`).join(":{"),
":",
`"${val}"`,
"}".repeat(keys.length),
].join("")
);
}
There's multiple ways to do this, I've commented the snippet
const changeSetting = (name, val) => {
// Split and reverse the name letters
const nameSplit = name.split('>').reverse();
// Set up the inner most object
let newObj = {[nameSplit[0]]:val}
// Now remove the first letter and recurse through the rest
nameSplit.slice(1).forEach((el, idx) => newObj = {[el]: newObj});
console.log(newObj);
}
changeSetting("a>b>c", "hello")
changeSetting("a", "hello")
changeSetting("a>b>c>d>e>f>g", "hello")
You can create an array by splitting name on all > with String.prototype.split(), and then Array.prototype.reduceRight() the created array of elements with an object initial value {} and adding key value pairs but on the last element the value should be variable val.
Code:
const changeSetting = (name, val) => name
.split('>')
.reduceRight((a, c, i, arr) => ({
[c]: i === arr.length - 1 ? val : a
}), {})
console.log(changeSetting('a>b>c', 'hello'))
console.log(changeSetting('a', 'hello'))
console.log(changeSetting('a>b>c>d>e>f>g', 'hello'))

count number of occurrences of a variable in a child object in angular

I assigned a data that is returned from an API to a variable named todayData. There's a child object called meals which in it has a property name.
What I want to achieve is to count the number of occurrences in the name property of the meals object.
For example, the meal Rice can have multiple occurrences in the data.
DATA
[{"id":5,"referenceId":1189,"firstName":"Dan","lastName":"Daniels","orders":[{"id":109,"meals":[{"id":47,"name":"Fried Rice","description":"This is a very sweet meal","image":"","mealType":"LUNCH","unitPrice":-20,"status":"ENABLED"}],"serveDate":"2019-07-11 00:00:00"}]}]
JS
let occurences = this.todayData.reduce(function (r, row) {
r[row.orders.meals.name] = ++r[row.orders.meals.name] || 1;
return r;
}, {});
let result = Object.keys(occurences).map(function (key) {
return { meals: key, count: occurences[key] };
});
console.log(result);
SOLUTION:
r[row.orders[0].meals[0].name] = ++r[row.orders[0].meals[0].name] || 1;
Since some properties are of Array type, index should be set.
EDIT 1:
Solution for data with multiple orders along with multiple meals. (Thanks to Bill Cheng for making me to consider general approach.)
let mealOccureneceCount = {};
let occurences = this.todayData.forEach(user => {
user.orders.forEach(order => {
order.meals.forEach(meal => {
mealOccureneceCount[meal.name] = (mealOccureneceCount[meal.name] || 0) + 1;
});
});
});
console.log(mealOccureneceCount);
const occurences = this.todayData.reduce((r1, c1) => c1.orders.reduce((r2,c2) => c2.meals.reduce((r3,c3) => { r3[c3.name]= (r3[c3.name] || 0) + 1; return r3;}, r2), r1),{});
const result = Object.entries(occurences).map(([key, value]) => ({ meals: key, count: value }));
console.log(result);

Splitting string to object in JS

I get a response from a server like this:
3S1,https://lekcjaplus.vulcan.net.pl
TA1,https://uonetplus-komunikacja.umt.tarnow.pl
OP1,https://uonetplus-komunikacja.eszkola.opolskie.pl
RZ1,https://uonetplus-komunikacja.resman.pl
GD1,https://uonetplus-komunikacja.edu.gdansk.pl
P03,https://efeb-komunikacja-pro-efebmobile.pro.vulcan.pl
P01,http://efeb-komunikacja.pro-hudson.win.vulcan.pl
P02,http://efeb-komunikacja.pro-hudsonrc.win.vulcan.pl
P90,http://efeb-komunikacja-pro-mwujakowska.neo.win.vulcan.pl
I want to convert it to an object like this:
"3S1": "https://lekcjaplus.vulcan.net.pl",
"TA1": "https://uonetplus-komunikacja.umt.tarnow.pl",
"OP1": "https://uonetplus-komunikacja.eszkola.opolskie.pl",
"RZ1": "https://uonetplus-komunikacja.resman.pl",
"GD1": "https://uonetplus-komunikacja.edu.gdansk.pl",
"P03": "https://efeb-komunikacja-pro-efebmobile.pro.vulcan.pl",
"P01": "http://efeb-komunikacja.pro-hudson.win.vulcan.pl",
"P02": "http://efeb-komunikacja.pro-hudsonrc.win.vulcan.pl",
"P90": "http://efeb-komunikacja-pro-mwujakowska.neo.win.vulcan.pl"
What's the simplest way to achieve this?
You can split by new line and use reduce
let str = `3S1,https://lekcjaplus.vulcan.net.pl
TA1,https://uonetplus-komunikacja.umt.tarnow.pl
OP1,https://uonetplus-komunikacja.eszkola.opolskie.pl
RZ1,https://uonetplus-komunikacja.resman.pl
GD1,https://uonetplus-komunikacja.edu.gdansk.pl
P03,https://efeb-komunikacja-pro-efebmobile.pro.vulcan.pl
P01,http://efeb-komunikacja.pro-hudson.win.vulcan.pl
P02,http://efeb-komunikacja.pro-hudsonrc.win.vulcan.pl
P90,http://efeb-komunikacja-pro-mwujakowska.neo.win.vulcan.pl`;
let result = str.split(/\n/).reduce((c, v) => {
if( v.trim() !== '' ) {
let [k, o] = v.trim().split(',');
c[k] = o;
}
return c;
}, {});
console.log(result);
In case you have multiple , on each line, you can deconstruct the array and join(',')
let result = str.split(/\n/).reduce((c,v)=>{
if( v.trim() ) {
let [k,...o] = v.trim().split(',');
c[k] = o.join(',');
}
return c;
},{});
First of all, you need to split the string into lines
Then split every line into two parts, the first part will be a key and the second one will be the value of the same key.
let input = `3S1,https://lekcjaplus.vulcan.net.pl
TA1,https://uonetplus-komunikacja.umt.tarnow.pl
OP1,https://uonetplus-komunikacja.eszkola.opolskie.pl
RZ1,https://uonetplus-komunikacja.resman.pl
GD1,https://uonetplus-komunikacja.edu.gdansk.pl
P03,https://efeb-komunikacja-pro-efebmobile.pro.vulcan.pl
P01,http://efeb-komunikacja.pro-hudson.win.vulcan.pl
P02,http://efeb-komunikacja.pro-hudsonrc.win.vulcan.pl
P90,http://efeb-komunikacja-pro-mwujakowska.neo.win.vulcan.pl`;
output = input.split(/\n/g);
output = output.reduce((acc, item) => {
item = item.split(",");
acc[item[0]] = item[1];
return acc;
}, {})
console.log(output);

Cleaning the json object by removing duplicates and null and merging them into a single record

Cleaning the JSON object by removing duplicates and null and merging them into a single record
The json array looks like this:
var result =
[
{"id":"10035","occupation":null,"state":"FL"},
{"id":"10035","occupation":"doctor","state":null},
{"id":"10035","occupation":null,"state":null},
]
I want to merge records into one neglecting all the null fields and make it as a single record.Below is my expected output:
[
{"id":"10035","occupation":"doctor","state":"FL"}
]
You could do it with this ES6 script:
let data = [
{"id":"10035","occupation":null,"state":"FL"},
{"id":"10035","occupation":"doctor","state":null},
{"id":"10035","occupation":null,"state":null},
];
let result = Object.values(data.reduce ( (acc, {id, occupation, state}) => {
acc[id] = Object.assign({ id }, acc[id],
occupation && { occupation },
state && { state });
return acc;
}, {}));
console.log(result);
It will still produce multiple records if you have different id values in your input. When there are more than one non-null values for the other properties, but for the same id, then only the last one will survive.
When you're without support for Object.values
Use this definition of it:
Object.values = Object.values || (o => Object.keys(o).map(k => o[k]));
var final = {};
for (var i in result) {
for (var k in result[i]) {
if (result[i][k] && final[k] !== result[i][k]) {
final[k] = result[i][k];
}
}
}
console.log(final); // outputs: {id: "10035", state: "FL", occupation: "doctor"}
Here's a simple to understand example, which works for objects with any number of properties.
let data = [
{"id":"10035","occupation":null,"state":"FL"},
{"id":"10035","occupation":"doctor","state":null},
{"id":"10035","occupation":null,"state":null},
];
let result = data[0];
data.forEach(obj=> { // iterate through all objects in array
for(key in obj) // iterate through all properties of objects
if(obj[key]) result[key] = obj[key]; // if not null, assign to final result
});
console.log(result);
Here is a way to do it in O(n) time:
const mergeObjects = (data) => {
const dataWithoutDuplicates = {};
// first pass will get rid of dupes
let user;
for(let i = 0; i < data.length; data++) {
user = data[i];
if(!dataWithoutDuplicates[user.id]) {
dataWithoutDuplicates[user.id] = user
} else {
Object.keys(dataWithoutDuplicates[user.id]).forEach(key => {
if(dataWithoutDuplicates[user.id][key] === null && user[key]) {
dataWithoutDuplicates[user.id][key] = user[key]
}
})
}
return Object.values(dataWithoutDuplicates)
}

What is the most efficent way to filter an object with an array of arrays?

I'm trying to filter an Object by an array of arrays, getting back an array of objects.
Like this:
let obj =
{
"a.1":1,
"a.2":2,
"b.1":3,
"b.2":4,
"c.1":5,
"c.2":6
}
let array =
[
["a.1","b.1"],
["a"],
["b","c.1"]
]
let expectedResult =
[
{
"a.1":1,
"b.1":3,
},
{
"a.1":1,
"a.2":2,
},
{
"b.1":3,
"b.2":4,
"c.1":5
},
]
// this is what I came up with
const filterObjectByArray = (obj, arr) =>
Object.keys(obj)
.filter(ch => {
for (var index = 0; index < arr.length; index++)
if (ch.startsWith(arr[index]))
return true;
})
.reduce((ret, key) =>{
ret[key] = obj[key]
return ret
},{})
let result = array.map(arr => filterObjectByArray(obj, arr))
//kind of deepEqual
console.log(JSON.stringify(expectedResult) == JSON.stringify(result))
Is there a easier or more convenient way to do that? I need to do this operation quite often and my object will be up to couple hundreds entries big, so I see a potential bottleneck here.
I would create a one type mapping of the "base" (the letter) to the "real" keys, and then use it to translate the letter to the real keys when create the object.
const obj = {
"a.1": 1,
"a.2": 2,
"b.1": 3,
"b.2": 4,
"c.1": 5,
"c.2": 6
};
const array = [
["a.1", "b.1"],
["a"],
["b", "c.1"]
];
const getBaseKey = (key) => key.match(/^[a-z]+/)[0]; // get the base of the key - the letter. If it's only one letter, you can use key[0]
/** create a one time map of keys by their base **/
const oobjKeysMap = Object.keys(obj).reduce((map, key) => {
const baseKey = getBaseKey(key);
const curr = map.get(baseKey) || [];
curr.push(key);
return map.set(baseKey, curr);
}, new Map());
const result = array.map((sub) => // iterate the array
[].concat(...sub.map((k) => k in obj ? k : oobjKeysMap.get(getBaseKey(k)))) // create the least of "real" keys
.reduce((ret, key) => { // create the object
ret[key] = obj[key];
return ret;
}, {})
);
console.log(result);

Categories