I'm looking for a way to remove object properties with negative values. Although an existing solution is provided here, it works only with no-depth objects. I'm looking for a solution to remove negative object properties of any depth.
This calls for a recursive solution, and I made an attempt that advanced me, but still not quite it.
Consider the following stocksMarkets data. The structure is purposely messy to demonstrate my desire to remove negative properties regardless of depth.
const stocksMarkets = {
tokyo: {
today: {
toyota: -1.56,
sony: -0.89,
nippon: -0.94,
mitsubishi: 0.65,
},
yearToDate: {
toyota: -75.95,
softbank: -49.83,
canon: 22.9,
},
},
nyc: {
sp500: {
ea: 8.5,
tesla: -66,
},
dowJones: {
visa: 3.14,
chevron: 2.38,
intel: -1.18,
salesforce: -5.88,
},
},
berlin: {
foo: 2,
},
paris: -3,
};
I want a function, let's call it removeNegatives() that would return the following output:
// pseudo-code
removeNegatives(stocksMarkets)
// {
// tokyo: {
// today: {
// mitsubishi: 0.65,
// },
// yearToDate: {
// canon: 22.9,
// },
// },
// nyc: {
// sp500: {
// ea: 8.5,
// },
// dowJones: {
// visa: 3.14,
// chevron: 2.38,
// },
// },
// berlin: {
// foo: 2,
// },
// };
Here's my attempt:
const removeNegatives = (obj) => {
return Object.entries(obj).reduce((t, [key, value]) => {
return {
...t,
[key]:
typeof value !== 'object'
? removeNegatives(value)
: Object.values(value).filter((v) => v >= 0),
};
}, {});
};
But it doesn't really gets me what I want :-/ It works on 2nd depth level only (see berlin), and even then, it returns an array with just the value rather than the full property (i.e., including the key).
// { tokyo: [], nyc: [], berlin: [ 2 ], paris: {} }
A lean non mutating recursive (tree walking) implementation based on Object.entries, Array.prototype.reduce and some basic type checking ...
function cloneStructureButKeepNumberTypeEntriesOfJustPositiveValues(root) {
return Object
.entries(root)
.reduce((node, [key, value]) => {
// simple but reliable object type test.
if (value && ('object' === typeof value)) {
node[key] =
cloneStructureButKeepNumberTypeEntriesOfJustPositiveValues(value);
} else if (
// either not a number type
('number' !== typeof value) ||
// OR (if number type then)
// a positive number value.
(Math.abs(value) === value)
) {
node[key] = value;
}
return node;
}, {});
}
const stocksMarkets = {
tokyo: {
today: {
toyota: -1.56,
sony: -0.89,
nippon: -0.94,
mitsubishi: 0.65,
},
yearToDate: {
toyota: -75.95,
softbank: -49.83,
canon: 22.9,
},
},
nyc: {
sp500: {
ea: 8.5,
tesla: -66,
},
dowJones: {
visa: 3.14,
chevron: 2.38,
intel: -1.18,
salesforce: -5.88,
},
},
berlin: {
foo: 2,
},
paris: -3,
};
const rosyOutlooks =
cloneStructureButKeepNumberTypeEntriesOfJustPositiveValues(stocksMarkets);
console.log({ rosyOutlooks, stocksMarkets });
.as-console-wrapper { min-height: 100%!important; top: 0; }
Since there was discussion going on about performance ... A lot of developers still underestimate the performance boost the JIT compiler gives to function statements/declarations.
I felt free tying r3wt's performance test reference, and what I can say is that two function statements with corecursion perform best in a chrome/mac environment ...
function corecursivelyAggregateEntryByTypeAndValue(node, [key, value]) {
// simple but reliable object type test.
if (value && ('object' === typeof value)) {
node[key] =
cloneStructureButKeepNumberTypeEntriesOfJustPositiveValues(value);
} else if (
// either not a number type
('number' !== typeof value) ||
// OR (if number type then)
// a positive number value.
(Math.abs(value) === value)
) {
node[key] = value;
}
return node;
}
function cloneStructureButKeepNumberTypeEntriesOfJustPositiveValues(root) {
return Object
.entries(root)
.reduce(corecursivelyAggregateEntryByTypeAndValue, {});
}
const stocksMarkets = {
tokyo: {
today: {
toyota: -1.56,
sony: -0.89,
nippon: -0.94,
mitsubishi: 0.65,
},
yearToDate: {
toyota: -75.95,
softbank: -49.83,
canon: 22.9,
},
},
nyc: {
sp500: {
ea: 8.5,
tesla: -66,
},
dowJones: {
visa: 3.14,
chevron: 2.38,
intel: -1.18,
salesforce: -5.88,
},
},
berlin: {
foo: 2,
},
paris: -3,
};
const rosyOutlooks =
cloneStructureButKeepNumberTypeEntriesOfJustPositiveValues(stocksMarkets);
console.log({ rosyOutlooks, stocksMarkets });
.as-console-wrapper { min-height: 100%!important; top: 0; }
Edit ... refactoring of the above corecursion based implementation in order to cover the 2 open points mentioned by the latest 2 comments ...
"OP requested in the comments the ability to filter by a predicate function, which your answer doesn't do, but nonetheless i added it to the bench [...]" – r3wt
"Nice (although you know I personally prefer terse names!) I wonder, why did you choose Math.abs(value) === value over value >= 0?" – Scott Sauyet
// The implementation of a generic approach gets covered
// by two corecursively working function statements.
function corecursivelyAggregateEntryByCustomCondition(
{ condition, node }, [key, value],
) {
if (value && ('object' === typeof value)) {
node[key] =
copyStructureWithConditionFulfillingEntriesOnly(value, condition);
} else if (condition(value)) {
node[key] = value;
}
return { condition, node };
}
function copyStructureWithConditionFulfillingEntriesOnly(
root, condition,
) {
return Object
.entries(root)
.reduce(
corecursivelyAggregateEntryByCustomCondition,
{ condition, node: {} },
)
.node;
}
// the condition ... a custom predicate function.
function isNeitherNumberTypeNorNegativeValue(value) {
return (
'number' !== typeof value ||
0 <= value
);
}
// the data to work upon.
const stocksMarkets = {
tokyo: {
today: {
toyota: -1.56,
sony: -0.89,
nippon: -0.94,
mitsubishi: 0.65,
},
yearToDate: {
toyota: -75.95,
softbank: -49.83,
canon: 22.9,
},
},
nyc: {
sp500: {
ea: 8.5,
tesla: -66,
},
dowJones: {
visa: 3.14,
chevron: 2.38,
intel: -1.18,
salesforce: -5.88,
},
},
berlin: {
foo: 2,
},
paris: -3,
};
// object creation.
const rosyOutlooks =
copyStructureWithConditionFulfillingEntriesOnly(
stocksMarkets,
isNeitherNumberTypeNorNegativeValue,
);
console.log({ rosyOutlooks, stocksMarkets });
.as-console-wrapper { min-height: 100%!important; top: 0; }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With