【问题标题】:Recursive calls between two functions两个函数之间的递归调用
【发布时间】:2021-04-15 11:51:11
【问题描述】:

我需要递归检查两个对象和键需要排序,所以我创建了两个函数。

buildObj - 从对象中获取所有 uniq 键,对它们进行排序并在每个键上调用 buildObjKey

buildObjKey - 如果其中一个值是对象调用 buildObj 这些。其他返回是示例,实际代码更复杂。

所以问题:我在 buildObjKey 之前定义了 buildObj 并在尚未定义时调用 buildObjKey。这是不好的做法,但是如果我在 buildObjKey 之后移动 buildObj 的定义,我会在它被定义之前调用 buildObj... 这可能在两个函数之间进行递归调用没有这个麻烦吗?

const _ = require('lodash')

const example1 = {
  key3: 'foo',
  key1: {
    key4: {
      key6: 'boo'
    },
  },
}

const example2 = {
  key3: 'too',
  key1: {
    key4: {
      key6: 'hoo'
    },
  },
}

const buildObj = (obj1, obj2) => {
 return  _.uniq([...Object.keys(obj1), ...Object.keys(obj2)])
 .sort()
 .map(key => buildObjKey(key, obj1, obj2))
}

const buildObjKey = (key, obj1, obj2) => {
  const val1 = obj1[key];
  const val2 = obj2[key];
  if(val1 && val2){
    if(_.isObject(val1) && _.isObject(val2)){
      return buildObj(val1, val2)
    }
    if(_.isObject(val1)){
      return buildObj(val1, val1)
    }
    if(_.isObject(val2)){
      return buildObj(val2, val2)
    }
    return val1
  }
  return val1 || val2
}

buildObj(example1, example2)

执行示例 [ [ [ '嘘' ] ],'foo' ]

真正的代码是做两个对象的差异,因为复杂,我这里就不写了。这是结构的简单示例。

【问题讨论】:

  • 你考虑过使用柯里化吗?您是否考虑过将这些方法包装在静态类中?或者你有没有考虑过像吊装这样的事情?等
  • 这段代码是什么?预期行为的定义是什么?现在它返回[[["boo"]], "foo"],并且有很多方法可以像这样获得它。如果你解释逻辑也许我们可以帮助你。
  • 这非常令人困惑。代码本身与问题无关。问题可以总结为这段代码: `function aaa() { bbb() } function bbb () { aaa() } ` 我们确实想在定义之前使用bbb。所以你可以抑制 linter 警告。 eslint.org/docs/rules/no-use-before-define
  • 这应该不是错误。在这种情况下,ESLint 不够聪明。帮自己一个忙,改用 TypeScript 检查器。

标签: javascript function recursion functional-programming


【解决方案1】:

误解

我在buildObjKey 之前定义buildObj 并在尚未定义时调用buildObjKey...这是不好的做法,但如果我在buildObjKey 之后移动buildObj 的定义,我将在之前调用buildObj它被定义了......

这是不真实的。 buildObj 是一个函数,在你定义buildObjKey 之前它不会被调用。 buildObj 包含对 buildObjKey 的引用这一事实并不意味着它会尝试立即调用它。为了具体说明这一点,让我们看一个下面的简化示例。

注意isEvenisOdd 如何不产生输出直到其中一个函数被实际调用 -

function isEven (n)
{ console.log("isEven", n)
  if (n == 0)
    return true
  else
    return isOdd(n - 1)   // <- calls isOdd
}

function isOdd (n)
{ console.log("isOdd", n)
  if (n == 0)
    return false
  else
    return isEven(n - 1)  // <- calls isEven
}

console.log("first line of output")

console.log("result", isOdd(3))
first line of output
isOdd 3
isEven 2
isOdd 1
isEven 0
result true

可能且强大

这可以在两个函数之间进行递归调用而没有这个麻烦吗?

这是一种非常强大的技术,称为mutual recursion。相互递归是处理递归树(如程序中的深度嵌套对象)的绝佳方式。你有很好的直觉以这种方式处理它们。有关实际示例和说明,请参阅this Q&A


相关

巧合的是,我在this Q&A 中编写了一个通用的对象差异函数。这展示了泛型函数和可重用代码的优势。使用您问题中的test1test2 输入,我们可以计算出精确的差异,而无需对原始代码进行任何修改-

const test1 = {
  "common": {
    "setting1": "Value 1",
    "setting2": 200,
    "setting3": true,
    "setting6": {
      "key": "value",
      "doge": {
        "wow": ""
      }
    }
  },
  "group1": {
    "baz": "bas",
    "foo": "bar",
    "nest": {
      "key": "value"
    }
  },
  "group2": {
    "abc": 12345,
    "deep": {
      "id": 45
    }
  }
}
const test2 = {
  "common": {
    "follow": false,
    "setting1": "Value 1",
    "setting3": null,
    "setting4": "blah blah",
    "setting5": {
      "key5": "value5"
    },
    "setting6": {
      "key": "value",
      "ops": "vops",
      "doge": {
        "wow": "so much"
      }
    }
  },
  "group1": {
    "foo": "bar",
    "baz": "bars",
    "nest": "str"
  },
  "group3": {
    "fee": 100500,
    "deep": {
      "id": {
        "number": 45
      }
    }
  }
}
console.log(diff(test1, test2))

展开下面的sn-p,在自己的浏览器中验证diff的结果-

const isObject = x =>
  Object(x) === x

const isArray =
  Array.isArray

const mut = (o, [ k, v ]) =>
  (o[k] = v, o)

const diff1 = (left = {}, right = {}, rel = "left") =>
  Object
    .entries(left)
    .map
      ( ([ k, v ]) =>
          isObject(v) && isObject(right[k])
            ? [ k, diff1(v, right[k], rel) ]
        : right[k] !== v
            ? [ k, { [rel]: v } ]
        : [ k, {} ]
      )
    .filter
      ( ([ _, v ]) =>
          Object.keys(v).length !== 0
      )
    .reduce
      ( mut
      , isArray(left) && isArray(right) ? [] : {}
      )

const merge = (left = {}, right = {}) =>
  Object
    .entries(right)
    .map
      ( ([ k, v ]) =>
          isObject(v) && isObject(left [k])
            ? [ k, merge(left [k], v) ]
            : [ k, v ]
      )
    .reduce(mut, left)


const diff = (x = {}, y = {}, rx = "left", ry = "right") =>
  merge
    ( diff1(x, y, rx)
    , diff1(y, x, ry)
    )

const test1 = {
  "common": {
    "setting1": "Value 1",
    "setting2": 200,
    "setting3": true,
    "setting6": {
      "key": "value",
      "doge": {
        "wow": ""
      }
    }
  },
  "group1": {
    "baz": "bas",
    "foo": "bar",
    "nest": {
      "key": "value"
    }
  },
  "group2": {
    "abc": 12345,
    "deep": {
      "id": 45
    }
  }
}

const test2 = {
  "common": {
    "follow": false,
    "setting1": "Value 1",
    "setting3": null,
    "setting4": "blah blah",
    "setting5": {
      "key5": "value5"
    },
    "setting6": {
      "key": "value",
      "ops": "vops",
      "doge": {
        "wow": "so much"
      }
    }
  },
  "group1": {
    "foo": "bar",
    "baz": "bars",
    "nest": "str"
  },
  "group3": {
    "fee": 100500,
    "deep": {
      "id": {
        "number": 45
      }
    }
  }
}

console.log(diff(test1, test2))
{
  "common": {
    "setting2": {
      "left": 200
    },
    "setting3": {
      "left": true,
      "right": null
    },
    "setting6": {
      "doge": {
        "wow": {
          "left": "",
          "right": "so much"
        }
      },
      "ops": {
        "right": "vops"
      }
    },
    "follow": {
      "right": false
    },
    "setting4": {
      "right": "blah blah"
    },
    "setting5": {
      "right": {
        "key5": "value5"
      }
    }
  },
  "group1": {
    "baz": {
      "left": "bas",
      "right": "bars"
    },
    "nest": {
      "left": {
        "key": "value"
      },
      "right": "str"
    }
  },
  "group2": {
    "left": {
      "abc": 12345,
      "deep": {
        "id": 45
      }
    }
  },
  "group3": {
    "right": {
      "fee": 100500,
      "deep": {
        "id": {
          "number": 45
        }
      }
    }
  }
}

【讨论】:

    【解决方案2】:

    因此,您可以通过多种方式解决这个问题,老实说,这可能取决于您认为重要的是什么,即可能取决于您的编码风格和您的偏好。


    吊装

    一个足够简单的技术使用,请随意阅读更多关于MDN,但简而言之提升是您在文件开头声明一个变量的地方,然后在稍后一点。但是对于函数,您需要 使用这个概念只需将它们定义为传统函数。这个想法是,当您使用关键字function定义一个函数时,该函数基本上会在范围内的其他变量之前定义。

    这是一个小例子:

    test();
    
    function test() {
      console.log("Hello! :)")
    }

    柯里化

    就我个人而言,我是 currying 的忠实粉丝,我认为对你说实话真是太棒了,以至于我什至 written 谈到了 currying 如何与 React 配合得很好。如果你不确定什么是柯里化,本质上就是你有一个方法可以接受一个参数并返回一个结果,或者一个函数也接受一个参数等等。如果您对这个概念不熟悉,也许可以阅读this post 之类的东西,我不会继续讨论它,因为我知道其他人会比我更好地解释它!但除此之外,您可以基本上实现这样的目标:

    const fn1 = (arg) => (callback) => {
      if (callback === undefined || callback === null) {
        console.log(arg);
      } else if (typeof callback === "function") {
        callback(arg);
      } else {
        throw new Error("The callback argument must be defined, fn1(arg)(callback).");
      }
    }
    
    const fn2 = (arg) => {
      if (arg === "test") {
        fn1(arg)(() => {
          console.log(`This is a ${arg}`);
        });
      } else {
        fn1(arg)();
      }
    };
    
    fn2(5);
    fn2("test");

    我的意思是你可以甚至使用一个更久经考验的方法&如果你愿意的话,可以使用一个回调函数而不用currying,即你可以传入一个包含数组的对象的钥匙。然后,根据提供的参数中存在的键,您可以根据需要非常轻松地做到这一点。


    类语法

    这是另一种简单的方法,你的所有函数都定义在一个类中,如果你愿意,你可以基本上使类静态/无状态,我的意思是我认为有点特定于上下文的主题,更不用说你的编码风格和想法了。但是无论如何,您不必担心编写代码的顺序或类似的东西,您可以直接做一些简单且易于维护的事情,如下所示:

    class Test {
      run () {
        this.print();
      }
    
      print() {
        console.log("I ran!");
      }
    }
    
    new Test().run();

    我的意思是,如果您使用 TypeScript,这种方法会更有意义,因为您可以将 print 定义为私有方法,因此您只有能够调用 @987654330 @ 类之外的方法,但我相信你明白了吗?


    结论

    我认为这也取决于您的 ESLint 配置,但更重要的是您的编码风格以及您或您的团队最看重的东西,我认为有很多方法可以解决这个问题是合理的,只是决定什么最适合您的需求。

    如果有人要添加任何内容,请随时编辑我的答案或添加评论,但我认为这是没有一个正确答案的问题之一?随意不同意! P.S.因为你已经用functional-programming 标记了这个问题,我建议你使用currying,我喜欢它!

    【讨论】:

    • 您的帖子以前提开头“所以有很多方法可以解决这个问题”,但没有问题。 buildObj 在定义之前实际上并没有调用buildObjKey。 OP 编写了一对完全有效的 mutually recursive 函数。
    • @Thankyou 公平点,但我想我会给我的 2¢,我同意严格来说这不是问题,但我只是发现 也许 OP 想要探索本质上编写相同解决方案的潜在不同方式。 IE。可能是违反了他公司的编码风格准则? - 我不知道足够的信息来说明是否是这种情况,我对此表示怀疑,但我并不正式知道。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-11-24
    • 1970-01-01
    • 2017-11-18
    • 2021-04-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多