嵌套的Reduce函数/递归/函数编程/树遍历

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)

Tha*_*you 3

功能风格

\n\n

您的答案是正确的,但是根据用户提供的过程的长度重复出现是一个失误。相反,可变长度路径应作为单个可变长度值 \xe2\x80\x93 数组传递

\n\n
const 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    )\n
Run Code Online (Sandbox Code Playgroud)\n\n

上面的自由值被定义为使用前缀表示法,这在函数式风格 \xe2\x80\x93 中更常见

\n\n
const 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 ])\n
Run Code Online (Sandbox Code Playgroud)\n\n

现在我们有一个通用的reduceTree函数 \xe2\x80\x93

\n\n
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// ]\n
Run Code Online (Sandbox Code Playgroud)\n\n

我们可以调整结果的输出,但是我们喜欢 \xe2\x80\x93

\n\n
const 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// }\n
Run Code Online (Sandbox Code Playgroud)\n\n

我们的测试input应该证明reduceTree于各种级别的嵌套 \xe2\x80\x93

\n\n
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})\n
Run Code Online (Sandbox Code Playgroud)\n\n

最后,验证该程序在浏览器中 \xe2\x80\x93 下是否正常运行

\n\n

\r\n
\r\n
const 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    )\n
Run Code Online (Sandbox Code Playgroud)\r\n
\r\n
\r\n

\n\n


\n\n

...在一些朋友的帮助下

\n\n

命令式生成器可以轻松完成此类任务,同时提供直观的语言来描述预期的过程。下面我们添加traverse[ path, value ]为嵌套生成对tree(对象)\xe2\x80\x93生成对

\n\n
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}\n
Run Code Online (Sandbox Code Playgroud)\n\n

使用Array.from我们可以将生成器直接插入到我们现有的函数中reducereduceTree现在只是一个专业化 \xe2\x80\x93

\n\n
const reduceTree = (proc, state, tree) =>\n  reduce\n    ( (acc, [ path, value ]) =>\n        proc (acc, value, path)\n    , state\n    , Array.from (traverse (tree))\n    )\n
Run Code Online (Sandbox Code Playgroud)\n\n

调用站点相同\xe2\x80\x93

\n\n
const 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 }\n
Run Code Online (Sandbox Code Playgroud)\n\n

在浏览器中验证 \xe2\x80\x93 下的结果

\n\n

\r\n
\r\n
const 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 ])\n
Run Code Online (Sandbox Code Playgroud)\r\n
\r\n
\r\n

\n