无论深度如何,如何删除具有负值的对象属性?
How to remove object properties with negative values, regardless of depth?
我正在寻找一种方法来删除具有负 值 的对象属性。尽管提供了现有解决方案 here,但它仅适用于无深度对象。我正在寻找一种解决方案来消除 any 深度的负面对象属性。
这需要一个递归的解决方案,我做了一个让我进步的尝试,但仍然不完全。
考虑以下 stocksMarkets
数据。结构故意乱七八糟,以表明我希望无论深度如何都删除负面属性。
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,
};
我想要一个函数,让我们称它为 removeNegatives()
它将 return 以下输出:
// 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,
// },
// };
这是我的尝试:
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),
};
}, {});
};
但它并没有真正让我得到我想要的东西:-/它只在第二个深度级别上工作(见berlin
),即便如此,它return是一个只有值而不是完整的 属性(即包括密钥)。
// { tokyo: [], nyc: [], berlin: [ 2 ], paris: {} }
不必那么复杂。只需遍历对象。如果当前 属性 是一个对象,则用该对象再次调用该函数,否则检查 属性 值是否小于 0,如果是,则将其删除。最后 return 更新的对象。
const stocksMarkets={tokyo:{today:{toyota:-1.56,sony:-.89,nippon:-.94,mitsubishi:.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};
function remove(obj) {
for (const prop in obj) {
if (typeof obj[prop] === 'object') {
remove(obj[prop]);
continue;
}
if (obj[prop] < 0) delete obj[prop];
}
return obj;
}
console.log(remove(stocksMarkets));
注意:这会改变原始对象。如果您想制作该对象的深层副本并使用它,那么最快的方法可能是字符串化,然后解析它。
const copy = JSON.parse(JSON.stringify(stocksMarkets));
或者:在某些 浏览器中有一个全新的功能叫做structuredClone
(检查兼容性table)。
const clone = structureClone(stocksMarkets);
使用这个:
const removeNegatives = (obj) => {
return Object.entries(obj).reduce((t, [key, value]) => {
const v =
value && typeof value === "object"
? removeNegatives(value)
: value >= 0
? value
: null;
if (v!==null) {
t[key] = v;
}
return t;
}, {});
};
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 positives = removeNegatives(stocksMarkets);
console.log({ positives, stocksMarkets });
.as-console-wrapper { min-height: 100%!important; top: 0; }
您可以获取条目并使用正值或嵌套对象收集它们。
const
filterBy = fn => {
const f = object => Object.fromEntries(Object
.entries(object)
.reduce((r, [k, v]) => {
if (v && typeof v === 'object') r.push([k, f(v)]);
else if (fn(v)) r.push([k, v]);
return r;
}, [])
);
return f;
},
fn = v => v >= 0,
filter = filterBy(fn),
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 },
result = filter(stocksMarkets);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
注意: 这会改变对象。如果不可变性是您的用例所关心的问题,您应该首先克隆该对象。
对于递归对象节点操作,最好创建一个允许递归“访问”对象节点的辅助函数,使用提供的访问者函数可以根据业务逻辑自由操作对象节点。一个基本的实现看起来像这样(以打字稿显示,以便清楚发生了什么):
function visit_object(obj: any, visitor: (o: any, k: string )=>any|void ) {
for (let key in obj) {
if (typeof obj[key] === 'object') {
visit_object(obj[key],visitor);
} else {
visitor(obj,key);//visit the node object, allowing manipulation.
}
}
// not necessary to return obj; js objects are pass by reference value.
}
关于您的具体用例,下面演示如何删除值为负的节点。
visit_object( yourObject, (o,k)=>{
if(o[k]<0){
delete o[k];
}
});
编辑:curried 版本以提高性能,包括可选的 deepClone
const deepClone = (inObject) => {
let outObject, value, key;
if (typeof inObject !== "object" || inObject === null) {
return inObject; // Return the value if inObject is not an object
}
// Create an array or object to hold the values
outObject = Array.isArray(inObject) ? [] : {};
for (key in inObject) {
value = inObject[key];
// Recursively (deep) copy for nested objects, including arrays
outObject[key] = deepClone(value);
}
return outObject;
},
visit_object = visitor => ( obj ) => {
for (let key in obj) {
if (typeof obj[key] === 'object') {
visit_object( obj[key], visitor);
} else {
visitor(obj,key);//visit the node object, allowing manipulation.
}
}
// not necessary to return obj; js objects are pass by reference value.
},
filter=(o,k)=>{
if(o[k]<0){
delete o[k];
}
};
Array.prototype.flatMap
、Object.entries
和 Object.fromEntries
的组合以及一定量的递归可以使这样的问题变得相当简单:
const removeNegatives = (obj) => Object (obj) === obj
? Object .fromEntries (Object .entries (obj) .flatMap (
([k, v]) => v < 0 ? [] : [[k, removeNegatives (v)]]
))
: obj
const stockMarkets = {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}
console .log (removeNegatives (stockMarkets))
.as-console-wrapper {max-height: 100% !important; top: 0}
如果我们的输入不是一个对象,我们只是 return 它原封不动。如果是,我们将它分成 key-value 对,然后对于每一对,如果值为负数,我们跳过它;否则我们会重复该值。然后我们将这些结果 key-value 对拼接回一个对象。
您可能想在 v < 0
之前在 v
上做一个 type-check。由你决定。
不过,这是在乞求多一层抽象。我可能更愿意这样写:
const filterObj = (pred) => (obj) => Object (obj) === obj
? Object .fromEntries (Object .entries (obj) .flatMap (
([k, v]) => pred (v) ? [[k, filterObj (pred) (v)]] : []
))
: obj
const removeNegatives = filterObj ((v) => typeof v !== 'number' || v > 0)
更新:简单叶过滤
OP 要求一种允许对叶子进行更简单过滤的方法。我知道最简单的方法是通过这样的中间阶段:
[
[["tokyo", "today", "toyota"], -1.56],
[["tokyo", "today", "sony"], -0.89],
[["tokyo", "today", "nippon"], -0.94],
[["tokyo", "today", "mitsubishi"], 0.65],
[["tokyo", "yearToDate", "toyota"], -75.95],
[["tokyo", "yearToDate", "softbank"], -49.83],
[["tokyo", "yearToDate", "canon"], 22.9],
[["nyc", "sp500", "ea"], 8.5],
[["nyc", "sp500", "tesla"], -66],
[["nyc", "dowJones", "visa"], 3.14],
[["nyc", "dowJones", "chevron"], 2.38],
[["nyc", "dowJones", "intel"], -1.18],
[["nyc", "dowJones", "salesforce"], -5.88],
[["berlin", "foo"], 2],
[["paris"], -3]
]
然后 运行 我们对这些条目进行简单过滤以获取:
[
[["tokyo", "today", "mitsubishi"], 0.65],
[["tokyo", "yearToDate", "canon"], 22.9],
[["nyc", "sp500", "ea"], 8.5],
[["nyc", "dowJones", "visa"], 3.14],
[["nyc", "dowJones", "chevron"], 2.38],
[["berlin", "foo"], 2],
]
并将其重新组合成一个对象。我有 lying around 功能可以进行提取和再水化,因此只需将它们捆绑在一起即可:
// utility functions
const pathEntries = (obj) =>
Object (obj) === obj
? Object .entries (obj) .flatMap (
([k, x]) => pathEntries (x) .map (([p, v]) => [[Array.isArray(obj) ? Number(k) : k, ... p], v])
)
: [[[], obj]]
const setPath = ([p, ...ps]) => (v) => (o) =>
p == undefined ? v : Object .assign (
Array .isArray (o) || Number.isInteger (p) ? [] : {},
{...o, [p]: setPath (ps) (v) ((o || {}) [p])}
)
const hydrate = (xs) =>
xs .reduce ((a, [p, v]) => setPath (p) (v) (a), {})
const filterLeaves = (fn) => (obj) =>
hydrate (pathEntries (obj) .filter (([k, v]) => fn (v)))
// main function
const removeNegatives = filterLeaves ((v) => v >= 0)
// sample data
const stockMarkets = {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}
// demo
console .log (removeNegatives (stockMarkets))
.as-console-wrapper {max-height: 100% !important; top: 0}
您可以在 various other answers 中查看 pathEntries
、setPath
和 hydrate
的详细信息。这里的重要函数是 filterLeaves
,它只接受叶值的谓词 运行s pathEntries
,使用该谓词过滤结果并在结果上调用 hydrate
。这使得我们的主要功能变得微不足道,filterLeaves ((v) => v > 0)
.
但是我们可以用这个分解来做各种各样的事情。我们可以根据键和值进行过滤。我们可以在水合之前过滤并映射它们。我们甚至可以映射到多个新的 key-value 结果。这些可能性中的任何一种都像 filterLeaves
.
一样简单
基于Object.entries
, Array.prototype.reduce
和一些基本类型检查的精益非变异递归(树行走)实现...
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; }
由于一直在讨论性能问题...许多开发人员仍然低估了 JIT 编译器对函数的性能提升 statements/declarations。
我觉得可以免费使用 r3wt 的 性能测试参考,我可以说的是,两个带有 corecursion 的函数语句在 chrome/mac 环境中表现最好...
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 ... 重构上述基于 corecursion 的实现,以涵盖最新 2 条评论提到的 2 个开放点...
"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; }
我正在寻找一种方法来删除具有负 值 的对象属性。尽管提供了现有解决方案 here,但它仅适用于无深度对象。我正在寻找一种解决方案来消除 any 深度的负面对象属性。
这需要一个递归的解决方案,我做了一个让我进步的尝试,但仍然不完全。
考虑以下 stocksMarkets
数据。结构故意乱七八糟,以表明我希望无论深度如何都删除负面属性。
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,
};
我想要一个函数,让我们称它为 removeNegatives()
它将 return 以下输出:
// 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,
// },
// };
这是我的尝试:
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),
};
}, {});
};
但它并没有真正让我得到我想要的东西:-/它只在第二个深度级别上工作(见berlin
),即便如此,它return是一个只有值而不是完整的 属性(即包括密钥)。
// { tokyo: [], nyc: [], berlin: [ 2 ], paris: {} }
不必那么复杂。只需遍历对象。如果当前 属性 是一个对象,则用该对象再次调用该函数,否则检查 属性 值是否小于 0,如果是,则将其删除。最后 return 更新的对象。
const stocksMarkets={tokyo:{today:{toyota:-1.56,sony:-.89,nippon:-.94,mitsubishi:.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};
function remove(obj) {
for (const prop in obj) {
if (typeof obj[prop] === 'object') {
remove(obj[prop]);
continue;
}
if (obj[prop] < 0) delete obj[prop];
}
return obj;
}
console.log(remove(stocksMarkets));
注意:这会改变原始对象。如果您想制作该对象的深层副本并使用它,那么最快的方法可能是字符串化,然后解析它。
const copy = JSON.parse(JSON.stringify(stocksMarkets));
或者:在某些 浏览器中有一个全新的功能叫做structuredClone
(检查兼容性table)。
const clone = structureClone(stocksMarkets);
使用这个:
const removeNegatives = (obj) => {
return Object.entries(obj).reduce((t, [key, value]) => {
const v =
value && typeof value === "object"
? removeNegatives(value)
: value >= 0
? value
: null;
if (v!==null) {
t[key] = v;
}
return t;
}, {});
};
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 positives = removeNegatives(stocksMarkets);
console.log({ positives, stocksMarkets });
.as-console-wrapper { min-height: 100%!important; top: 0; }
您可以获取条目并使用正值或嵌套对象收集它们。
const
filterBy = fn => {
const f = object => Object.fromEntries(Object
.entries(object)
.reduce((r, [k, v]) => {
if (v && typeof v === 'object') r.push([k, f(v)]);
else if (fn(v)) r.push([k, v]);
return r;
}, [])
);
return f;
},
fn = v => v >= 0,
filter = filterBy(fn),
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 },
result = filter(stocksMarkets);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
注意: 这会改变对象。如果不可变性是您的用例所关心的问题,您应该首先克隆该对象。
对于递归对象节点操作,最好创建一个允许递归“访问”对象节点的辅助函数,使用提供的访问者函数可以根据业务逻辑自由操作对象节点。一个基本的实现看起来像这样(以打字稿显示,以便清楚发生了什么):
function visit_object(obj: any, visitor: (o: any, k: string )=>any|void ) {
for (let key in obj) {
if (typeof obj[key] === 'object') {
visit_object(obj[key],visitor);
} else {
visitor(obj,key);//visit the node object, allowing manipulation.
}
}
// not necessary to return obj; js objects are pass by reference value.
}
关于您的具体用例,下面演示如何删除值为负的节点。
visit_object( yourObject, (o,k)=>{
if(o[k]<0){
delete o[k];
}
});
编辑:curried 版本以提高性能,包括可选的 deepClone
const deepClone = (inObject) => {
let outObject, value, key;
if (typeof inObject !== "object" || inObject === null) {
return inObject; // Return the value if inObject is not an object
}
// Create an array or object to hold the values
outObject = Array.isArray(inObject) ? [] : {};
for (key in inObject) {
value = inObject[key];
// Recursively (deep) copy for nested objects, including arrays
outObject[key] = deepClone(value);
}
return outObject;
},
visit_object = visitor => ( obj ) => {
for (let key in obj) {
if (typeof obj[key] === 'object') {
visit_object( obj[key], visitor);
} else {
visitor(obj,key);//visit the node object, allowing manipulation.
}
}
// not necessary to return obj; js objects are pass by reference value.
},
filter=(o,k)=>{
if(o[k]<0){
delete o[k];
}
};
Array.prototype.flatMap
、Object.entries
和 Object.fromEntries
的组合以及一定量的递归可以使这样的问题变得相当简单:
const removeNegatives = (obj) => Object (obj) === obj
? Object .fromEntries (Object .entries (obj) .flatMap (
([k, v]) => v < 0 ? [] : [[k, removeNegatives (v)]]
))
: obj
const stockMarkets = {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}
console .log (removeNegatives (stockMarkets))
.as-console-wrapper {max-height: 100% !important; top: 0}
如果我们的输入不是一个对象,我们只是 return 它原封不动。如果是,我们将它分成 key-value 对,然后对于每一对,如果值为负数,我们跳过它;否则我们会重复该值。然后我们将这些结果 key-value 对拼接回一个对象。
您可能想在 v < 0
之前在 v
上做一个 type-check。由你决定。
不过,这是在乞求多一层抽象。我可能更愿意这样写:
const filterObj = (pred) => (obj) => Object (obj) === obj
? Object .fromEntries (Object .entries (obj) .flatMap (
([k, v]) => pred (v) ? [[k, filterObj (pred) (v)]] : []
))
: obj
const removeNegatives = filterObj ((v) => typeof v !== 'number' || v > 0)
更新:简单叶过滤
OP 要求一种允许对叶子进行更简单过滤的方法。我知道最简单的方法是通过这样的中间阶段:
[
[["tokyo", "today", "toyota"], -1.56],
[["tokyo", "today", "sony"], -0.89],
[["tokyo", "today", "nippon"], -0.94],
[["tokyo", "today", "mitsubishi"], 0.65],
[["tokyo", "yearToDate", "toyota"], -75.95],
[["tokyo", "yearToDate", "softbank"], -49.83],
[["tokyo", "yearToDate", "canon"], 22.9],
[["nyc", "sp500", "ea"], 8.5],
[["nyc", "sp500", "tesla"], -66],
[["nyc", "dowJones", "visa"], 3.14],
[["nyc", "dowJones", "chevron"], 2.38],
[["nyc", "dowJones", "intel"], -1.18],
[["nyc", "dowJones", "salesforce"], -5.88],
[["berlin", "foo"], 2],
[["paris"], -3]
]
然后 运行 我们对这些条目进行简单过滤以获取:
[
[["tokyo", "today", "mitsubishi"], 0.65],
[["tokyo", "yearToDate", "canon"], 22.9],
[["nyc", "sp500", "ea"], 8.5],
[["nyc", "dowJones", "visa"], 3.14],
[["nyc", "dowJones", "chevron"], 2.38],
[["berlin", "foo"], 2],
]
并将其重新组合成一个对象。我有 lying around 功能可以进行提取和再水化,因此只需将它们捆绑在一起即可:
// utility functions
const pathEntries = (obj) =>
Object (obj) === obj
? Object .entries (obj) .flatMap (
([k, x]) => pathEntries (x) .map (([p, v]) => [[Array.isArray(obj) ? Number(k) : k, ... p], v])
)
: [[[], obj]]
const setPath = ([p, ...ps]) => (v) => (o) =>
p == undefined ? v : Object .assign (
Array .isArray (o) || Number.isInteger (p) ? [] : {},
{...o, [p]: setPath (ps) (v) ((o || {}) [p])}
)
const hydrate = (xs) =>
xs .reduce ((a, [p, v]) => setPath (p) (v) (a), {})
const filterLeaves = (fn) => (obj) =>
hydrate (pathEntries (obj) .filter (([k, v]) => fn (v)))
// main function
const removeNegatives = filterLeaves ((v) => v >= 0)
// sample data
const stockMarkets = {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}
// demo
console .log (removeNegatives (stockMarkets))
.as-console-wrapper {max-height: 100% !important; top: 0}
您可以在 various other answers 中查看 pathEntries
、setPath
和 hydrate
的详细信息。这里的重要函数是 filterLeaves
,它只接受叶值的谓词 运行s pathEntries
,使用该谓词过滤结果并在结果上调用 hydrate
。这使得我们的主要功能变得微不足道,filterLeaves ((v) => v > 0)
.
但是我们可以用这个分解来做各种各样的事情。我们可以根据键和值进行过滤。我们可以在水合之前过滤并映射它们。我们甚至可以映射到多个新的 key-value 结果。这些可能性中的任何一种都像 filterLeaves
.
基于Object.entries
, Array.prototype.reduce
和一些基本类型检查的精益非变异递归(树行走)实现...
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; }
由于一直在讨论性能问题...许多开发人员仍然低估了 JIT 编译器对函数的性能提升 statements/declarations。
我觉得可以免费使用 r3wt 的 性能测试参考,我可以说的是,两个带有 corecursion 的函数语句在 chrome/mac 环境中表现最好...
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 ... 重构上述基于 corecursion 的实现,以涵盖最新 2 条评论提到的 2 个开放点...
"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; }