如何将 ramda 食谱中的 flattenObj 函数转换为迭代函数
how to turn the flattenObj function from the ramda cookbook into an iterative function
我正在处理测试环境 nodejs/sequelize/mocha/chai。
我发现这个 flattenObj 非常有用
测试对象时,例如由sequelize生成。
它使这些结构易于 chai 消化,结果变得更加简洁
太糟糕了,它是以递归方式实现的:( .
特别是在 Javascript 中,这意味着厄运,因为总是有调用堆栈限制潜伏。
像在 setTimeout 中包装递归函数这样的技巧对我来说似乎不起作用,而且有点丑陋。
我目前正试图找出以迭代方式重写它的方法,但这是一个相当脑筋急转弯的问题,至少对我来说是这样。
在 ramda 函数中处理 while 循环感觉不对。
有没有一种方法可以在不破坏 ramda 约定的情况下以调用堆栈友好的方式执行此操作?
const go = obj_ => chain(([k, v]) => {
if (type(v) === 'Object' || type(v) === 'Array') {
return pipe(
tap(console.log),
map(([k_, v_]) => [`${k}.${k_}`, v_])
)(go(v))
} else {
return [[k, v]]
}
}, toPairs(obj_))
const flattenObj = obj => {
return fromPairs(go(obj))
}
flattenObj({a:1, b:{c:3}, d:{e:{f:6}, g:[{h:8, i:9}, 0]}})
{
"a": 1,
"b.c": 3,
"d.e.f": 6,
"d.g.0.h": 8,
"d.g.0.i": 9,
"d.g.1": 0
}
这按预期工作,但由于递归 go 函数,当对象变得太复杂时,它会崩溃导致调用堆栈超出错误。
如果它也适用于更复杂的结构,那将非常有用。
我不认为它以递归方式实现是件坏事。那是处理递归数据结构如JS对象的最好方法。
但是如果您想管理自己的堆栈,您始终可以将递归解决方案转换为迭代解决方案。这是一个相当丑陋的方法,但它似乎适用于那个简单的测试用例:
const flattenObj = (obj) => {
const results = [];
const steps = Object.entries(obj)
while (steps.length) {
const [key, val] = steps.splice(0, 1)[0]
if (typeof val == 'object') {
Array.prototype.push.apply(steps, Object.entries(val).map(
([k, v]) => [key + '.' + k, v]
))
} else {
results.push([key, val])
}
}
return results.reduce((a, [k, v]) => ({...a, [k]: v}), {})
}
const foo = {a:1, b:{c:3}, d:{e:{f:6}, g:[{h:8, i:9}, 0]}}
console.log(flattenObj(foo))
这不适用于循环结构,但 cookbook 版本可能两者都没有。
我最初是用一些 Ramda 函数写的(toPairs
代替 Object.entries
,is(Object, val)
代替 typeof val == 'object'
和 return fromPairs(results)
代替return results.reduce(...)
.) 但是随着所有的变化(splice
和 push
),感觉这是一个非常不符合 Ramda 风格的解决方案,我删除了它们。 (Ramda 的函数,你懂的,不想与变态的变体联系在一起!)
不知道这样能不能解决你的问题。我只用过 flattenObj
几次,尽管我可以在测试中看到它的实用性。但令我印象深刻的是,如果这导致了递归问题,那么循环数据结构比实际深度更有可能成为问题。但是我当然不知道你的数据,所以谁知道呢?
我正在处理测试环境 nodejs/sequelize/mocha/chai。
我发现这个 flattenObj 非常有用
测试对象时,例如由sequelize生成。
它使这些结构易于 chai 消化,结果变得更加简洁
太糟糕了,它是以递归方式实现的:( .
特别是在 Javascript 中,这意味着厄运,因为总是有调用堆栈限制潜伏。
像在 setTimeout 中包装递归函数这样的技巧对我来说似乎不起作用,而且有点丑陋。
我目前正试图找出以迭代方式重写它的方法,但这是一个相当脑筋急转弯的问题,至少对我来说是这样。
在 ramda 函数中处理 while 循环感觉不对。
有没有一种方法可以在不破坏 ramda 约定的情况下以调用堆栈友好的方式执行此操作?
const go = obj_ => chain(([k, v]) => {
if (type(v) === 'Object' || type(v) === 'Array') {
return pipe(
tap(console.log),
map(([k_, v_]) => [`${k}.${k_}`, v_])
)(go(v))
} else {
return [[k, v]]
}
}, toPairs(obj_))
const flattenObj = obj => {
return fromPairs(go(obj))
}
flattenObj({a:1, b:{c:3}, d:{e:{f:6}, g:[{h:8, i:9}, 0]}})
{
"a": 1,
"b.c": 3,
"d.e.f": 6,
"d.g.0.h": 8,
"d.g.0.i": 9,
"d.g.1": 0
}
这按预期工作,但由于递归 go 函数,当对象变得太复杂时,它会崩溃导致调用堆栈超出错误。
如果它也适用于更复杂的结构,那将非常有用。
我不认为它以递归方式实现是件坏事。那是处理递归数据结构如JS对象的最好方法。
但是如果您想管理自己的堆栈,您始终可以将递归解决方案转换为迭代解决方案。这是一个相当丑陋的方法,但它似乎适用于那个简单的测试用例:
const flattenObj = (obj) => {
const results = [];
const steps = Object.entries(obj)
while (steps.length) {
const [key, val] = steps.splice(0, 1)[0]
if (typeof val == 'object') {
Array.prototype.push.apply(steps, Object.entries(val).map(
([k, v]) => [key + '.' + k, v]
))
} else {
results.push([key, val])
}
}
return results.reduce((a, [k, v]) => ({...a, [k]: v}), {})
}
const foo = {a:1, b:{c:3}, d:{e:{f:6}, g:[{h:8, i:9}, 0]}}
console.log(flattenObj(foo))
这不适用于循环结构,但 cookbook 版本可能两者都没有。
我最初是用一些 Ramda 函数写的(toPairs
代替 Object.entries
,is(Object, val)
代替 typeof val == 'object'
和 return fromPairs(results)
代替return results.reduce(...)
.) 但是随着所有的变化(splice
和 push
),感觉这是一个非常不符合 Ramda 风格的解决方案,我删除了它们。 (Ramda 的函数,你懂的,不想与变态的变体联系在一起!)
不知道这样能不能解决你的问题。我只用过 flattenObj
几次,尽管我可以在测试中看到它的实用性。但令我印象深刻的是,如果这导致了递归问题,那么循环数据结构比实际深度更有可能成为问题。但是我当然不知道你的数据,所以谁知道呢?