JavaScript: Reduce an array to nested objects - javascript

So suppose my array looks like this:
let langArr = [
["python", "blue"]
,["python", "blue"]
,["c++", "red"]
,["java", "yellow"]
,["javascript", "lime"]
,["shell", "green"]
,["c++", "red"]
];
what I want is something like this:
{
python: {
count: 2
color: "blue"
}
c++: {
count: 2
color: "red"
}
java: {
count: 1
color: "yellow"
}
and so on...
}
I tried reduce method like this:
let langCount = langArr.reduce((lang, [name, color]) => {
lang[name] = (lang[name] || 0) + 1;
lang[color] = 'color';
return lang;
}, {});
console.log(langCount);
but I get this output:
{
python: 2
blue: "color"
c++: 2
red: "color"
java: 1
yellow: "color"
and so on...
}

You need an object for each language.
This approach takes an object as default value if lang[name] is falsy, like undefined.
The pattern
variable = variable || value;
works with a logical OR ||:
if variable has a truthy value, take this value,
if variable has a falsy value, take value instead.
let langArr = [["python", "blue"], ["python", "blue"], ["c++", "red"], ["java", "yellow"], ["javascript", "lime"], ["shell", "green"], ["c++", "red"]],
langCount = langArr.reduce((lang, [name, color]) => {
lang[name] = lang[name] || { count: 0, color };
lang[name].count++;
return lang;
}, {});
console.log(langCount);

You can use this:
array.reduce((acc, current) => {
if(!acc.hasOwnProperty(current[0])){
acc[current[0]] = {count: 0, color: current[1]};
}
acc[current[0]].count += 1;
return acc;
}, {});

Related

How to get element with key in list of dicts

I have a list of objects like this:
let colors = [
{
id: 0,
color: "green",
},
{
id: 1,
color: "blue",
},
{
id: 2,
color: "orange",
},
];
What is the best way (most optimized) to get an element with id=1... is there a way to do it without iterating through the list?
Small tip:
Use const instead of let when you're not reassigning values
Try doing the following:
// ✅ good pattern: destructuring. We only use what we need
const myColor = colors.find(({ id }) => id === 1)
You can iterate over the whole array once and create a lookup table if the array is static. Then, lookups can be done in constant time after O(n) preprocessing.
let colors = [
{
id: 0,
color: "green",
},
{
id: 1,
color: "blue",
},
{
id: 2,
color: "orange",
},
];
const lookup = colors.reduce((acc,curr)=>(acc[curr.id] = curr, acc), {});
console.log(lookup[1]);
console.log(lookup[2]);

Create grouped object out of array of Strings in JavaScript

I have an issue I've not been able to solve, basically I want to transform this:
{
"seamark:name": "Z-2",
"seamark:type": "buoy_lateral",
"seamark:light:range": "5",
"seamark:light:colour": "red",
"seamark:light:character": "Q",
"seamark:radar_reflector": "yes",
"seamark:buoy_lateral:shape": "can",
"seamark:buoy_lateral:colour": "red",
"seamark:buoy_lateral:system": "iala-a",
"seamark:buoy_lateral:category": "port"
}
into this:
{
seamark: {
name: "Z-2",
type: "buoy_lateral",
light: {
range: "5",
colour: "red",
reflector: "yes"
},
buoy_lateral: {
shape: "can",
colour: "red",
system: "iala-a",
category: "port
}
}
}
For now I've only achieved to get an array containing 10 objects with every time the path to the value (for example {seamark: {name: "Z-2"}}) using the code shown in the following link:
codepen
Would anyone have an idea on how to group the properties deeply once I have the result shown in the codepen? Or maybe even another idea? thanks in advance
You are trying to unflat an object.
You can use flat npm (https://www.npmjs.com/package/flat)
const { unflatten } = require('flat');
const unflat = unflatten({
"seamark:name": "Z-2",
"seamark:type": "buoy_lateral",
"seamark:light:range": "5",
"seamark:light:colour": "red",
"seamark:light:character": "Q",
"seamark:radar_reflector": "yes",
"seamark:buoy_lateral:shape": "can",
"seamark:buoy_lateral:colour": "red",
"seamark:buoy_lateral:system": "iala-a",
"seamark:buoy_lateral:category": "port"
}, { delimiter: ":" }); // notice delimiter : default is "."
console.log(unflat);
Output:
{
seamark: {
name: 'Z-2',
type: 'buoy_lateral',
light: { range: '5', colour: 'red', character: 'Q' },
radar_reflector: 'yes',
buoy_lateral:
{
shape: 'can',
colour: 'red',
system: 'iala-a',
category: 'port'
}
}
}
For setting a value, you could split the path and reduce the path by walking the given object. If no object exist, create a new property with the name. Later assign the value.
function setValue(object, path, value) {
var last = path.pop();
path.reduce((o, k) => o[k] = o[k] || {}, object)[last] = value;
}
var data = { "seamark:name": "Z-2", "seamark:type": "buoy_lateral", "seamark:light:range": "5", "seamark:light:colour": "red", "seamark:light:character": "Q", "seamark:radar_reflector": "yes", "seamark:buoy_lateral:shape": "can", "seamark:buoy_lateral:colour": "red", "seamark:buoy_lateral:system": "iala-a", "seamark:buoy_lateral:category": "port" };
Object
.keys(data)
.forEach(k => {
var keys = k.split(':');
if (keys.length === 1) return;
setValue(data, k.split(':'), data[k]);
delete data[k];
});
console.log(data);
You can also use "for..of" and "Array.reduce" like below
var obj = {
"seamark:name": "Z-2",
"seamark:type": "buoy_lateral",
"seamark:light:range": "5",
"seamark:light:colour": "red",
"seamark:light:character": "Q",
"seamark:radar_reflector": "yes",
"seamark:buoy_lateral:shape": "can",
"seamark:buoy_lateral:colour": "red",
"seamark:buoy_lateral:system": "iala-a",
"seamark:buoy_lateral:category": "port"
}
let newObj = {}
for(let [key, val] of Object.entries(obj)) {
let keys = key.split(':')
keys.reduce((o, d, i) => (
i == keys.length - 1
? (o[d] = val)
: (o[d] = o[d] || {})
, o[d])
, newObj)
}
console.log(newObj)

Compare two Objects and determine the parent node of differed properties

I have a scenario, were need to compare treeObject1 and treeObject2 to determine the exact difference at property level and find the parent of modified node.
In below provided objects, I need to get output as color blue. Since the difference is at otherObj2.
treeObject1 = {
color: "red",
value: 10,
otherObj: {
color: "blue",
otherObj2: {
otherColor: "blue",
otherValue: 20,
}
}
}
treeObject2 = {
color: "red",
value: 10,
otherObj: {
color: "blue",
otherObj2: {
otherColor: "Green",
otherValue: 20,
}
}
}
If you want the key "otherObj" as well let me know, that can easily be added. Otherwise here is a working version of what you were looking for.
This uses a combination of Object.keys and every
treeObject1 = {
color: "red",
value: 10,
otherObj: {
color: "blue",
otherObj2: {
otherColor: "blue",
otherValue: 20,
}
}
}
treeObject2 = {
color: "red",
value: 10,
otherObj: {
color: "blue",
otherObj2: {
otherColor: "Green",
otherValue: 20,
}
}
}
const findParentNode = (obj1, obj2, parent = null) => {
if(parent === null) parent = obj2;
//since the structures are the same we only get keys from the first object
const keys = Object.keys(obj1);
let result = null;
//iterate through every key
keys.every(key=>{
//if it's an object... then we recall findParentNode (recursive)
if(obj1[key] instanceof Object){
result = findParentNode(obj1[key], obj2[key], obj2);
//If result from findParentNode is not null then a difference was found.
//Return false to stop the every method.
if(result !== null) return false;
}else if(obj1[key] !== obj2[key]){
//If the objects are different we found a difference
//Set the parent as the difference
result = parent;
return false;
}
//return true to keep on looping
return true;
});
//return the result
return result;
}
console.log(findParentNode(treeObject1, treeObject2));
** note that the above snippet will return "null" if nothing was found. **
You could use a nested approach for objects and by checking the values.
function getDiffParents(object1, object2, parent = {}) {
return Object.assign(...Object.entries(object1).map(([k, v]) => v && typeof v === 'object'
? getDiffParents(v, object2[k], object1)
: v === object2[k]
? {}
: parent
));
}
var treeObject1 = { color: "red", value: 10, otherObj: { color: "blue", otherObj2: { otherColor: "blue", otherValue: 20 } } },
treeObject2 = { color: "red", value: 10, otherObj: { color: "blue", otherObj2: { otherColor: "Green", otherValue: 20 } } };
console.log(getDiffParents(treeObject1, treeObject2));
.as-console-wrapper { max-height: 100% !important; top: 0; }

Extract only properties that have value in object

I have an object that looks like this for example:
let data = {
color: "green",
color1: "red",
color2: null,
color3: 34,
color4: null,
color5: true,
color6: [],
}
I want to extract only the properties that have value and make a new object. So this new object would look like this:
let data = {
color: "green",
color1: "red",
color3: 34,
color5: true,
}
This would be dynamic, properties and values can change but at the end I always wanna make an object that has just values. What would be some practical way to do it?
You can create a new object by following the steps mentioned below :
You can iterate through the keys using Object.keys
Then, you can use Array.reduce to convert the array of keys into the transformed object.
In acc, you will get the accumulated object.
In curr, you will receive the current key
Create a function isEmptyObject where you can define rules to check if present or not. As per your example, I have checked for null and Array length. You can modify this function.
let data = {
color: "green",
color1: "red",
color2: null,
color3: 34,
color4: null,
color5: true,
color6: [],
}
var newObj = Object.keys(data).reduce((acc, curr) => {
if (isEmptyObject(data, curr)) return acc;
acc[curr] = data[curr];
return acc;
}, {});
function isEmptyObject(obj, key) {
return obj[key] === null || Array.isArray(obj[key]) && obj[key].length === 0;
}
console.log(newObj);

count values in each object of array

Sorry I've just changed this question :
I would like to know how many times I have THE WORD "orange" in my array. In this example "orange" > 4 times.
MyArray = [{
fruit1: "Orange is blabla",
fruit2: "Apple blabla",
fruit3: "blabla Banana",
color1: "ORANGE"
}, {
fruit4: "blabla Orange",
fruit5: "Apple",
fruit6: "Banana",
color2: "orange blabla"
}];
var newArr = MyArray.filter(function (item) {
return _.values(item.xxx === "orange";
});
You can do it in the following way
let MyArray = [{fruit1:"Orange", fruit2:"Apple", fruit3:"Banana", color1:"ORANGE"},{fruit4:"Orange", fruit5:"Apple", fruit6:"Banana", color2:"orange"}];
let result = MyArray.reduce((a, b) => {
for(let key of Object.keys(b)){
let re = /orange/gi;
if(b[key].match(re) != null){
a++;
}
}
return a;
}, 0);
console.log(result);
MyArray = [{
fruit1: "Orange is blabla",
fruit2: "Apple blabla",
fruit3: "blabla Banana",
color1: "ORANGE"
}, {
fruit4: "blabla Orange",
fruit5: "Apple",
fruit6: "Banana",
color2: "orange blabla"
}];
var totalOrange = 0;
MyArray.map(function(item) {
for (var key in item) {
if (item[key].toLowerCase().indexOf('orange') >= 0) {
totalOrange++;
}
}
});
console.log(totalOrange);
I guess something like this might work.
You could do it in one big loop to make it slightly faster, but this form in more readable imho.
const amountOfOranges = MyArray
// transform each object into an array of its values
.map( Object.values )
// remove all values not named 'orange'
.map( ary => ary.filter( value => value === 'orange' ) )
// replace all arrays by their length
.map( ary => ary.length )
// sum the results
.reduce( ( x, y ) => x + y, 0 );
var MyArray = [{fruit1:"Orange", fruit2:"Apple", fruit3:"Banana", color1:"ORANGE"},{fruit4:"Orange", fruit5:"Apple", fruit6:"Banana", color2:"orange"}];
var count = 0;
MyArray.map(function(item,index){
Object.keys(item).map(function(key){
if(item[key].toLowerCase().indexOf('orange') >= 0){
count++;
}
});
});
console.log(count);
With ES6 you can use spread syntax ... and Object.values to do this.
var MyArray = [{ fruit1: "Orange is blabla", fruit2: "Apple blabla", fruit3: "blabla Banana", color1: "ORANGE" }, { fruit4: "blabla Orange", fruit5: "Apple", fruit6: "Banana", color2: "orange blabla" }]
var count = [].concat(...MyArray.map(Object.values))
.filter(e => e.toLowerCase().includes('orange'))
.length;
console.log(count)
You can do something like this : -
const count = MyArray.reduce((count,item)=>{
Object.keys(item).forEach((val)=>{
if(item[val].toLowerCase().indexOf('orange') != -1){
count++;
}
})
return count
},0)

Categories