Ric*_*ter 6 javascript recursion reduce functional-programming traversal
我一直reduce遇到这样的情况,我最终嵌套很多函数来深入到对象中.拉出逻辑很难,因为在底部我需要访问沿途遍历的各种键.基本上,我正在寻找一种更好的方法来实现以下目标:
import { curry } from 'lodash/fp'
import { fromJS } from 'immutable'
const reduce = curry((fn, acc, it) => it.reduce(fn, acc))
describe('reduceNested', () => {
const input = fromJS({
a1: {
b1: {
c1: {
d1: {
e1: 'one',
e2: 'two',
e3: 'three'
},
d2: {
e1: 'one',
e2: 'two',
e3: 'three'
}
},
c2: {
d1: {
e1: 'one',
e2: 'two'
}
}
}
},
a2: {
b1: {
c1: {
d1: {
e1: 'one'
},
d2: {
e1: 'one'
}
}
},
b2: {
c1: {
d1: {
e1: 'one'
},
d2: {
e1: 'one'
}
}
}
},
a3: {
b1: {
c1: {}
}
}
})
const expected = fromJS({
one: [
'a1.b1.c1.d1.e1',
'a1.b1.c1.d2.e1',
'a1.b1.c2.d1.e1',
'a2.b1.c1.d1.e1',
'a2.b1.c1.d2.e1',
'a2.b2.c1.d1.e1',
'a2.b2.c1.d2.e1'
],
two: ['a1.b1.c1.d1.e2', 'a1.b1.c1.d2.e2', 'a1.b1.c2.d1.e2'],
three: ['a1.b1.c1.d1.e3', 'a1.b1.c1.d2.e3']
})
const init = fromJS({ one: [], two: [], three: [] })
test('madness', () => {
const result = reduce(
(acc2, val, key) =>
reduce(
(acc3, val2, key2) =>
reduce(
(acc4, val3, key3) =>
reduce(
(acc5, val4, key4) =>
reduce(
(acc6, val5, key5) =>
acc6.update(val5, i =>
i.push(`${key}.${key2}.${key3}.${key4}.${key5}`)
),
acc5,
val4
),
acc4,
val3
),
acc3,
val2
),
acc2,
val
),
init,
input
)
expect(result).toEqual(expected)
})
test('better', () => {
const result = reduceNested(
(acc, curr, a, b, c, d, e) =>
acc.update(curr, i => i.push(`${a}.${b}.${c}.${d}.${e}`)),
init,
input
)
expect(result).toEqual(expected)
})
})
Run Code Online (Sandbox Code Playgroud)
我想编写一个函数reduceNested来实现相同的结果但没有所有嵌套的reduce函数.我没有看到lodash/fp地址或类似地址,所以我的想法是创建一个新函数reduceNested并将变量添加到树中每个键的回调中.我已经尝试实现了实际的逻辑,但不幸的是暂时停留了.我知道reduceNested需要用来fn.length确定钻进源的距离,但除此之外我只是卡住了.
const reduceNested = curry((fn, acc, iter) => {
// TODO --> use (fn.length - 2)
})
Run Code Online (Sandbox Code Playgroud)
功能风格
\n\n您的答案是正确的,但是根据用户提供的过程的长度重复出现是一个失误。相反,可变长度路径应作为单个可变长度值 \xe2\x80\x93 数组传递
\n\nconst reduceTree = (proc, state, tree, path = []) =>\n reduce // call reduce with:\n ( (acc, [ key, value ]) => // reducer\n isObject (value) // value is an object (another tree):\n ? reduceTree // recur with:\n ( proc // the proc\n , acc // the acc\n , value // this value (the tree)\n , append (path, key) // add this key to the path\n ) // value is NOT an object (non-tree):\n : proc // call the proc with:\n ( acc // the acc\n , value // this value (non-tree, plain value)\n , append (path, key) // add this key to the path\n )\n , state // initial input state \n , Object.entries (tree) // [ key, value ] pairs of input tree\n )\nRun Code Online (Sandbox Code Playgroud)\n\n上面的自由值被定义为使用前缀表示法,这在函数式风格 \xe2\x80\x93 中更常见
\n\nconst isObject = x =>\n Object (x) === x\n\nconst reduce = (proc, state, arr) =>\n arr .reduce (proc, state)\n\nconst append = (xs, x) =>\n xs .concat ([ x ])\nRun Code Online (Sandbox Code Playgroud)\n\n现在我们有一个通用的reduceTree函数 \xe2\x80\x93
const result =\n reduceTree\n ( (acc, value, path) => // reducer\n [ ...acc, { path, value } ] \n , [] // initial state\n , input // input tree\n )\n\nconsole.log (result)\n// [ { path: [ \'a1\', \'b1\', \'c1\', \'d1\', \'e1\' ], value: \'one\' }\n// , { path: [ \'a1\', \'b1\', \'c1\', \'d1\', \'e2\' ], value: \'two\' }\n// , { path: [ \'a1\', \'b1\', \'c1\', \'d1\', \'e3\' ], value: \'three\' }\n// , { path: [ \'a1\', \'b1\', \'c1\', \'d2\', \'e1\' ], value: \'one\' }\n// , { path: [ \'a1\', \'b1\', \'c1\', \'d2\', \'e2\' ], value: \'two\' }\n// , { path: [ \'a1\', \'b1\', \'c1\', \'d2\', \'e3\' ], value: \'three\' }\n// , { path: [ \'a1\', \'b1\', \'c2\', \'d1\', \'e1\' ], value: \'one\' }\n// , { path: [ \'a1\', \'b1\', \'c2\', \'d1\', \'e2\' ], value: \'two\' }\n// , { path: [ \'a2\', \'b1\', \'c1\', \'d1\', \'e1\' ], value: \'one\' }\n// , { path: [ \'a2\', \'b1\', \'c1\', \'d2\', \'e1\' ], value: \'one\' }\n// , { path: [ \'a2\', \'b2\', \'c1\', \'d1\', \'e1\' ], value: \'one\' }\n// , { path: [ \'a2\', \'b2\', \'c1\', \'d2\', \'e1\' ], value: \'one\' } \n// ]\nRun Code Online (Sandbox Code Playgroud)\n\n我们可以调整结果的输出,但是我们喜欢 \xe2\x80\x93
\n\nconst result =\n reduceTree\n ( (acc, value, path) => // reducer\n ({ ...acc, [ path .join (\'.\') ]: value })\n , {} // initial state\n , input // input tree\n )\n\nconsole.log (result)\n// { \'a1.b1.c1.d1.e1\': \'one\'\n// , \'a1.b1.c1.d1.e2\': \'two\'\n// , \'a1.b1.c1.d1.e3\': \'three\'\n// , \'a1.b1.c1.d2.e1\': \'one\'\n// , \'a1.b1.c1.d2.e2\': \'two\'\n// , \'a1.b1.c1.d2.e3\': \'three\'\n// , \'a1.b1.c2.d1.e1\': \'one\'\n// , \'a1.b1.c2.d1.e2\': \'two\'\n// , \'a2.b1.c1.d1.e1\': \'one\'\n// , \'a2.b1.c1.d2.e1\': \'one\'\n// , \'a2.b2.c1.d1.e1\': \'one\'\n// , \'a2.b2.c1.d2.e1\': \'one\'\n// }\nRun Code Online (Sandbox Code Playgroud)\n\n我们的测试input应该证明reduceTree于各种级别的嵌套 \xe2\x80\x93
test (\'better\', () => {\n const input =\n { a: { b: { c: 1, d: 2 } }, e: 3 }\n\n const expected =\n { \'a.b.c\': 1, \'a.b.d\': 2, e: 3 }\n\n const result =\n reduceTree\n ( (acc, value, path) =>\n ({ ...acc, [ path .join (\'.\') ]: value })\n , {}\n , input \n )\n\n expect(result).toEqual(expected)\n})\nRun Code Online (Sandbox Code Playgroud)\n\n最后,验证该程序在浏览器中 \xe2\x80\x93 下是否正常运行
\n\nconst reduceTree = (proc, state, tree, path = []) =>\n reduce // call reduce with:\n ( (acc, [ key, value ]) => // reducer\n isObject (value) // value is an object (another tree):\n ? reduceTree // recur with:\n ( proc // the proc\n , acc // the acc\n , value // this value (the tree)\n , append (path, key) // add this key to the path\n ) // value is NOT an object (non-tree):\n : proc // call the proc with:\n ( acc // the acc\n , value // this value (non-tree, plain value)\n , append (path, key) // add this key to the path\n )\n , state // initial input state \n , Object.entries (tree) // [ key, value ] pairs of input tree\n )\nRun Code Online (Sandbox Code Playgroud)\r\n...在一些朋友的帮助下
\n\n命令式生成器可以轻松完成此类任务,同时提供直观的语言来描述预期的过程。下面我们添加traverse它[ path, value ]为嵌套生成对tree(对象)\xe2\x80\x93生成对
const traverse = function* (tree = {}, path = [])\n{ for (const [ key, value ] of Object.entries (tree))\n if (isObject (value))\n yield* traverse (value, append (path, key))\n else\n yield [ append (path, key), value ]\n}\nRun Code Online (Sandbox Code Playgroud)\n\n使用Array.from我们可以将生成器直接插入到我们现有的函数中reduce;reduceTree现在只是一个专业化 \xe2\x80\x93
const reduceTree = (proc, state, tree) =>\n reduce\n ( (acc, [ path, value ]) =>\n proc (acc, value, path)\n , state\n , Array.from (traverse (tree))\n )\nRun Code Online (Sandbox Code Playgroud)\n\n调用站点相同\xe2\x80\x93
\n\nconst input =\n { a: { b: { c: 1, d: 2 } }, e: 3 }\n\nconst result =\n reduceTree\n ( (acc, value, path) =>\n ({ ...acc, [ path .join (\'.\') ]: value })\n , {}\n , input\n )\n\nconsole.log (result)\n// { \'a.b.c\': 1, \'a.b.d\': 2, e: 3 }\nRun Code Online (Sandbox Code Playgroud)\n\n在浏览器中验证 \xe2\x80\x93 下的结果
\n\nconst isObject = x =>\n Object (x) === x\n\nconst reduce = (proc, state, arr) =>\n arr .reduce (proc, state)\n\nconst append = (xs, x) =>\n xs .concat ([ x ])\nRun Code Online (Sandbox Code Playgroud)\r\n