Convert array of structured string into multi-level object in Javascript [duplicate] - javascript

This question already has answers here:
Create an object based on file path string
(3 answers)
Closed 1 year ago.
I have an array of structured strings with have connection | as a format which self-divided into levels. I want to convert it into a structured object with multiple levels.
Input:
[
"clothes|tshirt|tshirt-for-kids",
"clothes|coat|raincoat",
"clothes|coat|leather-coat",
"clothes|tshirt|tshirt-for-men",
"clothes|tshirt|tshirt-for-men|luxury-tshirt",
]
Expected output:
{
clothes: {
tshirt: {
tshirt-for-kids: {},
tshirt-for-men: {
luxury-tshirt: {}
}
},
coat: {
raincoat: {}
leather-coat: {}
}
}
}

Very simple task - just enumerate the array and create the relevant object keys:
var myArray = [
"clothes|tshirt|tshirt-for-kids",
"clothes|coat|raincoat",
"clothes|coat|leather-coat",
"clothes|tshirt|tshirt-for-men",
"clothes|tshirt|tshirt-for-men|luxury-tshirt",
]
var result = {}, levels, current, temp;
while(myArray.length > 0)
{
levels = myArray.pop().split('|');
temp = result;
while(levels.length > 0)
{
current = levels.shift();
if(!(current in temp)) temp[current] = {};
temp = temp[current];
}
}
console.log(result);

You could try this:
const input = [
"clothes|tshirt|tshirt-for-kids",
"clothes|coat|raincoat",
"clothes|coat|leather-coat",
"clothes|tshirt|tshirt-for-men",
"clothes|tshirt|tshirt-for-men|luxury-tshirt",
];
function convertStrToObject(str, sep, obj) {
const sepIndex = str.indexOf(sep);
if (sepIndex == -1) {
obj[str] = obj[str] || {};
} else {
const key = str.substring(0, sepIndex);
obj[key] = obj[key] || {};
convertStrToObject(str.substring(sepIndex + 1), sep, obj[key]);
}
}
const all = {};
for (let i = 0; i < input.length; ++i) {
convertStrToObject(input[i], "|", all);
}
console.log(all);

Assuming you intend to collect properties, all having an empty object as leaf node.
// input
const input = [
"clothes|tshirt|tshirt-for-kids",
"clothes|coat|raincoat",
"clothes|coat|leather-coat",
"clothes|tshirt|tshirt-for-men",
"clothes|tshirt|tshirt-for-men|luxury-tshirt",
];
// Here, we collect the properties
const out = {};
// map the input array, splitting each line at |
input.map(i => i.split('|'))
.filter(a => a.length > 0) // lets not entertain empty lines in input
.forEach(a => { // process each array of property names
// start at outermost level
let p = out;
// iterate properties
for(const v of a){
// create a property if it is not already there
if(!p.hasOwnProperty(v)){
p[v] = {};
}
// move to the nested level
p = p[v];
}
});
// lets see what we have created
console.log(out);

A number of solutions have been suggested already, but I'm surprised none involves reduce() - which would seem the more idiomatic solution to me.
var array = [
"clothes|tshirt|tshirt-for-kids",
"clothes|coat|raincoat",
"clothes|coat|leather-coat",
"clothes|tshirt|tshirt-for-men",
"clothes|tshirt|tshirt-for-men|luxury-tshirt",
]
var object = array.reduce(function (object, element) {
var keys = element.split("|")
keys.reduce(function (nextNestedObject, key) {
if (!nextNestedObject[key]) nextNestedObject[key] = {}
return nextNestedObject[key]
}, object)
return object
}, {})
console.log(object)

One Liner With eval
Used eval to evaluate strings like the following:
'o["clothes"]??={}'
'o["clothes"]["tshirt"]??={}'
'o["clothes"]["tshirt"]["tshirt-for-kids"]??={}'
const
data = ["clothes|tshirt|tshirt-for-kids", "clothes|coat|raincoat", "clothes|coat|leather-coat", "clothes|tshirt|tshirt-for-men", "clothes|tshirt|tshirt-for-men|luxury-tshirt"],
arr = data.map((d) => d.split("|")),
res = arr.reduce((r, a) => (a.forEach((k, i) => eval(`r["${a.slice(0, i + 1).join('"]["')}"]??={}`)), r), {});
console.log(res)
One Liner Without eval
const
data = ["clothes|tshirt|tshirt-for-kids", "clothes|coat|raincoat", "clothes|coat|leather-coat","clothes|tshirt|tshirt-for-men", "clothes|tshirt|tshirt-for-men|luxury-tshirt"],
res = data.reduce((r, d) => (d.split("|").reduce((o, k) => (o[k] ??= {}, o[k]), r), r), {});
console.log(res)

Related

how to get a cumulative total array from 3 two dimensional arrays

I have 3 two dimensional arrays as given below which are series data to plot lines on a graph with the key being the timestamp.
const arr1 = [[1641013200000,1881],[1643691600000,38993],[1646110800000,41337],[1648785600000,78856],[1651377600000,117738],[1654056000000,119869],[1656648000000,157799],[1659326400000,196752],[1662004800000,199061],[1664596800000,237034],[1667275200000,239153],[1669870800000,269967]]
const arr2 = [[1641013200000,1302],[1643691600000,3347],[1646110800000,4754],[1648785600000,6948],[1651377600000,9725],[1654056000000,11314],[1656648000000,13787],[1659326400000,16666],[1662004800000,18370],[1664596800000,20876],[1667275200000,22384],[1669870800000,23560]]
const arr3 = [[1643691600000,67350],[1648785600000,134700],[1651377600000,202148],[1654056000000,202270],[1656648000000,269843],[1659326400000,337346],[1662004800000,337470],[1664596800000,404861],[1667275200000,404889],[1669870800000,472239]]
I want to plot another series line which gives the cumulative total of all three arrays values
(Note: if a timestamp is not present in either of the arrays, add the previous index value)
const totalArray = [
[1641013200000,3183],[1643691600000, 109690],[1646110800000, 113441],[1648785600000, 220504],
[1651377600000, 329611],[1654056000000, 333453],[1656648000000, 441429],[1659326400000, 550764],
[1662004800000, 554901],[1664596800000, 662771],[1667275200000, 666426],[1669870800000, 765766]
]
I have tried this, but some values are incorrect due to the timestamp not being present in either one
Approach:
const arr1 = [
[1641013200000, 1881],
[1643691600000, 38993],
[1646110800000, 41337],
[1648785600000, 78856],
[1651377600000, 117738],
[1654056000000, 119869],
[1656648000000, 157799],
[1659326400000, 196752],
[1662004800000, 199061],
[1664596800000, 237034],
[1667275200000, 239153],
[1669870800000, 269967]
];
const arr2 = [
[1641013200000, 1302],
[1643691600000, 3347],
[1646110800000, 4754],
[1648785600000, 6948],
[1651377600000, 9725],
[1654056000000, 11314],
[1656648000000, 13787],
[1659326400000, 16666],
[1662004800000, 18370],
[1664596800000, 20876],
[1667275200000, 22384],
[1669870800000, 23560]
];
const arr3 = [
[1643691600000, 67350],
[1648785600000, 134700],
[1651377600000, 202148],
[1654056000000, 202270],
[1656648000000, 269843],
[1659326400000, 337346],
[1662004800000, 337470],
[1664596800000, 404861],
[1667275200000, 404889],
[1669870800000, 472239]
];
const calculateTotal = () => {
var ret;
for (let a3 of arr3) {
var index = arr1.map(function(el) {
return el[0];
}).indexOf(a3[0]);
console.log(index);
if (index === -1) {
ret = arr1[index][0];
console.log(ret);
}
}
let unsortedArr = arr1.concat(arr2, arr3);
var sortedArray = unsortedArr.sort((a, b) => a[0] - b[0]);
var added = addArray(sortedArray);
console.log("Curent Output: " + JSON.stringify(added));
}
const addArray = (tuples) => {
var hash = {},
keys = [];
tuples.forEach(function(tuple) {
var key = tuple[0],
value = tuple[1];
if (hash[key] === undefined) {
keys.push(key);
hash[key] = value;
} else {
hash[key] += value;
}
});
return keys.map(function(key) {
return ([key, hash[key]]);
});
}
calculateTotal();
Is it possible to achieve this?
In your code there is this:
if (index === -1) {
ret = arr1[index][0];
But that assignment will fail as arr1[-1] is not defined.
Then when you do:
let unsortedArr = arr1.concat(arr2, arr3);
...you end up with an array that does not have the knowledge to use default values (from a previous index) when any of the three arrays has a "missing" time stamp.
I would suggest this approach:
Collect all the unique timestamps (from all arrays) into a Map, and associate arrays to each of those keys: these will be empty initially.
Populate those arrays with the timestamps from the original arrays
Get the sorted list of entries from that map
Fill the "gaps" by carrying forward values to a next array when the corresponding slot is undefined. At the same time sum up these values for the final output.
Here is an implementation:
function mergeArrays(...arrays) {
const map = new Map(arrays.flatMap(arr => arr.map(([stamp]) => [stamp, []])));
arrays.forEach((arr, i) => {
for (const [timeStamp, value] of arr) {
map.get(timeStamp)[i] = value;
}
});
const state = Array(arrays.length).fill(0);
return Array.from(map).sort(([a], [b]) => a - b).map(([timeStamp, arr], i) =>
[timeStamp, state.reduce((sum, prev, j) => sum + (state[j] = arr[j] ?? prev), 0)]
);
}
// Example run
const arr1 = [[1641013200000,1881],[1643691600000,38993],[1646110800000,41337],[1648785600000,78856],[1651377600000,117738],[1654056000000,119869],[1656648000000,157799],[1659326400000,196752],[1662004800000,199061],[1664596800000,237034],[1667275200000,239153],[1669870800000,269967]];
const arr2 = [[1641013200000,1302],[1643691600000,3347],[1646110800000,4754],[1648785600000,6948],[1651377600000,9725],[1654056000000,11314],[1656648000000,13787],[1659326400000,16666],[1662004800000,18370],[1664596800000,20876],[1667275200000,22384],[1669870800000,23560]];
const arr3 = [[1643691600000,67350],[1648785600000,134700],[1651377600000,202148],[1654056000000,202270],[1656648000000,269843],[1659326400000,337346],[1662004800000,337470],[1664596800000,404861],[1667275200000,404889],[1669870800000,472239]];
const result = mergeArrays(arr1, arr2, arr3);
console.log(result);

Transform array into object with custom properties

I have this array
myarr = [
'=title1',
'longText0...',
'longtText1...',
'=title2',
'longTextA...',
'longtTextB...',
'longtTextC...'
];
symbol = indicates that is is a property, next to that is a list of items that belongs to that property
I want to transform that array into object
myObj = {
title1: [
'longText0...',
'longtText1...',
],
title2: [
'longTextA...',
'longtTextB...',
'longtTextC...'
]
}
I come up with this code so far:
const arrayToObject = (array) =>
array.reduce((obj, item) => {
if(item.startsWith('=')) {
const itemName = item.replace('=', '')
obj[itemName] = itemName;
} else {
//add the rest....
}
return obj
}, {})
console.log(arrayToObject(myarr))
My challenges so far is that I am not sure how to turn obj[itemName] so I can assign the items to it. Any ideas how to do that?
A reduce based approach which does not depend on outer scope references for keeping track of the currently to be built/aggregated property makes this information part of the reducer function's first parameter, the previousValue which serves as an accumulator/collector object.
Thus, as for the OP's task, this collector would feature two properties, the currentKey and the result, where the former holds the state of the currently processed property name and the latter being the programmatically built result.
// - reducer function which aggregates entries at time,
// either by creating a new property or by pushing a
// value into the currently processed property value.
// - keeps the state of the currently processed property
// by the accumulator`s/collector's `currentKey` property
// whereas the result programmatically gets build as
// the accumulator`s/collector's `result` property.
function aggregateEntry({ currentKey = null, result = {} }, item) {
const key = (item.startsWith('=') && item.slice(1));
if (
(key !== false) &&
(key !== currentKey)
) {
// keep track of the currently processed property name.
currentKey = key;
// create a new entry (key value pair).
result[currentKey] = [];
} else {
// push value into the currently processed property value.
result[currentKey].push(item);
}
return { currentKey, result };
}
console.log([
'=title1',
'longText0...',
'longtText1...',
'=title2',
'longTextA...',
'longtTextB...',
'longtTextC...',
].reduce(aggregateEntry, { result: {} }).result);
.as-console-wrapper { min-height: 100%!important; top: 0; }
I wouldn't do that with reduce but with a simple for loop, because you have to carry the itemname over multiple iterations
let o = {}, n = '';
for (let k of arr) {
if (k.startsWith('=')) {
n = k.substring(1);
o[n] = []
} else {
o[n].push(k);
}
}
You can of course also do it with reduce, but you have to put the declaration of itemname outside of the callback
let n = '';
let o = arr.reduce((a, c) => {
if (c.startsWith('=')) {
n = c.substring(1);
a[n] = [];
} else {
a[n].push(c);
}
return a;
}, {});
Please be aware, there is no error handling, ie the code assumes your array is well structured and the first element in the array must start with =
The following function will give you the desired results
function arrayToObject(arr)
{
let returnObj={};
for(let i =0; i <arr.length; i++)
{
if(arr[i].startsWith('='))
{
let itemName = arr[i].replace('=','');
returnObj[itemName]=[];
for(let j=i+1; j <arr.length;j++)
{
if(arr[j].startsWith('='))
{
break;
}
else
{
let value = arr[j];
returnObj[itemName].push(value) ;
}
}
}
}
return returnObj;
}
Here a version with reduce
const myarr = [
'=title1',
'longText0...',
'longtText1...',
'=title2',
'longTextA...',
'longtTextB...',
'longtTextC...'
];
const obj = myarr.reduce((res, el) => {
if(el.startsWith('=')){
const key = el.substring(1)
return {
data: {
...res.data,
[key]: [],
},
key
}
}
return {
...res,
data:{
...res.data,
[res.key]: [...res.data[res.key], el]
}
}
}, {
data: {},
key: ''
}).data
console.log(obj)
You don't need to keep the key somewhere separate for a reduce method:
const myarr = ['=title1', 'longText0...', 'longtText1...', '=title2', 'longTextA...', 'longtTextB...', 'longtTextC...'];
const res = Object.fromEntries(
myarr.reduce((acc, item) => {
if(item.startsWith('='))
acc.push([item.substring(1), []]);
else
acc[acc.length - 1]?.[1].push(item);
return acc;
}, [])
);
console.log(JSON.stringify( res ));

Script to group elements where every character is same to each other

For input:
["abc","def","okg","fed","bca"]
expected output should be:
["abc","bca"],["def","fed"],["okg"]
here "abc", "bca" and "def", "fed" contains same character and "okg" there is no element which contains these character
const arr = ["abc", "def", "okg", "fed", "bca"];
let find = (arr) => {
let res = [];
for (let i = 0; i < arr.length; i++) {
for (let j = 1; j < arr.length; j++) {
if (arr[i].search(arr[j])) {
res.push(arr[j]);
}
}
}
return res;
}
console.log(find(arr))
A reduce will do the trick - it seems the shortest code here (apart from the one using lodash)
const arr = ["abc", "def", "okg", "fed", "bca"],
res = Object.values(arr.reduce((acc, ele) => {
const key = ele.split("").sort();
(acc[key] = acc[key] || []).push(ele)
return acc
}, {}))
console.log(res)
.search returns a number indicating the index of where the match was found. Check that the result isn't -1 instead of checking that the result is truthy. But...
.search isn't the right tool here anyway, because it won't find different combinations of the same character. You need a different approach. One way would be to create an object whose keys are the characters found, and the values are the number of occurrences, then use a sorted representation of that object as a key. For example, have both abc and bca turn into something like:
a,1-b,1-c,1
Iterate through the input array, generating a key for each string, and putting the string on an object with that key. At the end, take the object's values.
const strToKey = (str) => {
const grouped = {};
for (const char of str) {
grouped[char] = (grouped[char] || 0) + 1;
}
return Object.entries(grouped)
.sort((a, b) => a[0].localeCompare(b[0]))
.join('-');
};
let find = (arr) => {
const grouped = {};
for (const str of arr) {
const key = strToKey(str);
grouped[key] ??= [];
grouped[key].push(str);
}
return Object.values(grouped);
}
console.log(find(["abc", "def", "okg", "fed", "bca"]));
Another option, when creating the keys, instead of sorting the object afterwards, you could sort the string first:
const strToKey = (str) => {
const grouped = {};
for (const char of [...str].sort()) {
grouped[char] = (grouped[char] || 0) + 1;
}
return Object.entries(grouped).join('-');
};
let find = (arr) => {
const grouped = {};
for (const str of arr) {
const key = strToKey(str);
grouped[key] ??= [];
grouped[key].push(str);
}
return Object.values(grouped);
}
console.log(find(["abc", "def", "okg", "fed", "bca"]));
const input = ["abc","def","okg","fed","bca"]
function getSortedString (str) {
return [...str].sort().join('');
};
function groupBy(input) {
const grouped = [];
while(input.length) {
const nextInput = [];
const first = input[0];
const matched = [first];
for (let i = 1; i < input.length; i++) {
if(getSortedString(first) === getSortedString(input[i])) {
matched.push(input[i])
} else {
nextInput.push(input[i])
}
}
input = nextInput;
grouped.push(matched);
}
console.log(grouped);
}
groupBy(input);
Using Object.values and groupBy (from lodash), you can get a straightforward solution:
You group your array elements by their "sorted" form and then use Object.values to get the output array.
const arr = ["abc", "def", "okg", "fed", "bca"];
const sortString = (str) => str.split("").sort().join("")
const result = Object.values(_.groupBy(arr, sortString));
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>

How to compare 2 array and add key if don't have

Supposed that I have two arrays to compare:
var arrOriginal={currency1: 1234, currency2: 2345};
var arrToCompare={currency1: 123};
I was trying to find the difference between the two.
Example result:
var result={currency1: 1111, currency2: 2345}
What I've tried:
Using for .. in and then substract.
for (var key in this.arrOriginal) {
this.result.push({
currency: key,
amount: format.formatCurrency(this.arrToCompare[key] - this.arrOriginal[key])
});
}
But it won't show the currency key which the second array don't have.
Any help is appreciated.
You could take a default value of zero for undefined properties of the object.
From inside to out:
Get entries from the object as an array of key/value pairs.
Map this entries and get an object with the key and a value of the difference of value and the corresponding value of the other object or zero.
Spread the object as parameters for Object.assign.
And get a new object with all objects from mapping.
var original = { currency1: 1234, currency2: 2345 },
deltas = { currency1: 123 },
result = Object.assign(
{},
...Object.entries(original).map(([k, v]) => ({ [k]: v - (deltas[k] || 0)}))
);
console.log(result);
A very simple approach is looping over the keys of arrOriginal, If that key is present in arrToCompare then substract that value else do not substract.
var arrOriginal={currency1: 1234, currency2: 2345};
var arrToCompare={currency1: 123};
var out = {}
for(var key in arrOriginal){
out[key] = arrOriginal[key] - (arrToCompare[key] || 0)
}
console.log(out)
You can use Object.entries and reduce
var arrOriginal={currency1: 1234, currency2: 2345};
var arrToCompare={currency1: 123};
let final = Object.entries(arrOriginal).reduce((op,[key,value])=>{
op[key] = value - (arrToCompare[key] || 0)
return op
},{})
console.log(final)
A very easy to understand approach would be to have an if-else statement with three branches that check if the key exists in both or either one of the original objects:
const original = { currency1: 1234, currency2: 2345 }
const toCompare = { currency1: 123 }
const result = {}
const allKeys = new Set(Object.keys(original).concat(Object.keys(toCompare)))
allKeys.forEach(k => {
if (k in original && k in toCompare) {
result[k] = original[k] - toCompare[k]
} else if (k in original) {
result[k] = original[k]
} else {
result[k] = toCompare[k]
}
})
console.log(result)

Convert set of object's into array item

I have several objects like this:
{'id[0]': 2}
{'url[0]': 11}
{'id[1]': 3}
{'url[1]': 14}
And I want to get something like this:
[{id:2, url:11}, {id:3, url:14}]
Also I have lodash in my project. Maybe lodash have some method for this?
You could use a regular expression for the keys and create a new object if necessary. Then assign the value to the key.
var data = [{ 'id[0]': 2 }, { 'url[0]': 11 }, { 'id[1]': 3 }, { 'url[1]': 14 }],
result = [];
data.forEach(function (a) {
Object.keys(a).forEach(function (k) {
var keys = k.match(/^([^\[]+)\[(\d+)\]$/);
if (keys.length === 3) {
result[keys[2]] = result[keys[2]] || {};
result[keys[2]][keys[1]] = a[k];
}
});
});
console.log(result);
This is an ES6 solution based on #NinaScholz solution.
I assume that the objects have only one property each, like the ones presented in the question.
Combine the array of objects to one large object using Object#assign, and convert to entries with Object.entries.
Iterate the array using Array#reduce.
Extract the original key an value from each entry using array
destructuring.
Extract the wanted key and index using a regex and array
destructuring.
Then create/update the new object at the index using object spread.
const data = [{ 'id[0]': 2 }, { 'url[0]': 11 }, { 'id[1]': 3 }, { 'url[1]': 14 }];
// combine to one object, and convert to entries
const result = Object.entries(Object.assign({}, ...data))
// extract the original key and value
.reduce((r, [k, value]) => {
// extract the key and index while ignoring the full match
const [, key, index] = k.match(/^([^\[]+)\[(\d+)\]$/);
// create/update the object at the index
r[index] = {...(r[index] || {}), [key]: value };
return r;
}, []);
console.log(result);
var arr = [{'id[0]': 2},
{'url[0]': 11},
{'id[1]': 3},
{'url[1]': 14}];
var result = [];
arr.forEach(function(e, i, a){
var index = +Object.keys(e)[0].split('[')[1].split(']')[0];//get the number inside []
result[index] = result[index] || {}; //if item is undefined make it empty object
result[index][Object.keys(e)[0].split('[')[0]] = e[Object.keys(e)[0]];//add item to object
})
console.log(result);
You can use for loop, .filter(), RegExp constructor with parameter "\["+i+"\]" where i is current index, Object.keys(), .reduce(), .replace() with RegExp /\[\d+\]/
var obj = [{
"id[0]": 2
}, {
"url[0]": 11
}, {
"id[1]": 3
}, {
"url[1]": 14
}];
var res = [];
for (var i = 0; i < obj.length / 2; i++) {
res[i] = obj.filter(function(o) {
return new RegExp("\[" + i + "\]").test(Object.keys(o))
})
.reduce(function(obj, o) {
var key = Object.keys(o).pop();
obj[key.replace(/\[\d+\]/, "")] = o[key];
return obj
}, {})
}
console.log(res);

Categories

Resources