【问题标题】:Calling and Passing Nested Functions in JavaScript在 JavaScript 中调用和传递嵌套函数
【发布时间】:2019-04-18 20:35:25
【问题描述】:

我有一个函数可以返回一系列数字的 LCM。它工作得很好,但这在函数内部有一个函数内部的函数。我的问题是为什么我不能通过从内部删除 scm() 来简化嵌套的 minimumCommon() ?为什么这个特殊的解决方案需要这个 if else 功能嵌套得这么深?

function smallestCommons(arr) {
  var max = Math.max(...arr);
  var min = Math.min(...arr);
  var candidate = max;

  var smallestCommon = function(low, high) {
  // inner function to use 'high' variable
    function scm(l, h) {
      if (h % l === 0) {
         return h;
      } else {
        return scm(l, h + high);
      }
    }
    return scm(low, high);
  };

  for (var i = min; i <= max; i += 1) {
    candidate = smallestCommon(i, candidate);
  }

  return candidate;
}

smallestCommons([5, 1]); // should return 60
smallestCommons([1, 13]); // should return 360360
smallestCommons([23, 18]); //should return 6056820

【问题讨论】:

  • LCM = 最小公倍数吗?如果是这样,不清楚为什么[5, 1]的LCM是60
  • (LCM = 最小公倍数) 5 和 1 的 LCM 是 5。1 和 13 的 LCM 是 13。23 和 18 的 LCM 是 414。我有点困惑是什么你在追求。无需嵌套这些函数的声明。如果您提取scm 函数并在smallestCommons 内部调用它,它们会运行得更快。 smallestCommon 函数完全无关紧要,因为它所做的只是定义 scm 并返回对 scm 的调用。

标签: javascript function callback nested-function


【解决方案1】:

拥有内部功能并不一定是坏事。有时您想减少一些本地重复,但又不想创建新的顶级函数。小心使用,它们可以清理代码。

但在您的特定情况下,没有必要将其嵌套。您可以将high 变量作为第三个参数传入:

function scm(l, h, high) {
  if (h % l === 0) {
     return h;
  } else {
    return scm(l, h + high, high);
  }
}

function smallestCommon (low, high) {
  return scm(low, high, high);
}

在处理递归时,这实际上是一种相当常见的模式:有一个递归函数和一个简化调用递归函数的辅助函数。但在递归很常见的函数式语言中,it's actually commonplace to have a local recursive function like you had originally (often called something like go).


很遗憾JS doesn't have a range functionsmallestCommons 基本上只是在[min,max] 范围内的缩减。在缺少 range 函数和 smallestCommon 的参数顺序错误之间,不幸的是,将您的代码转换为使用 reduce 有点笨重:

function smallestCommons(arr) {
  var max = Math.max(...arr);
  var min = Math.min(...arr);

  return Array.from(new Array(max - min), (x,i) => i + min)
              .reduce((acc, i) => smallestCommon(i, acc), max);
}

【讨论】:

  • 很好的解释,我完全同意你对这个用例的评估。
  • 不错的答案。我们以类似的方式处理问题:D
【解决方案2】:

我建议将其分解为更小的部分。您将拥有许多易于编写和调试的函数,而不是一个复杂且难以调试的函数。较小的函数也更容易在程序的其他部分进行测试和重用 -

const gcd = (m, n) =>
  n === 0
    ? m
    : gcd (n, m % n)

const lcm = (m, n) =>
  Math.abs (m * n) / gcd (m, n)
  
console.log
  ( lcm (1, 5)    // 5
  , lcm (3, 4)    // 12
  , lcm (23, 18)  // 414
  )

现在我们有minmax。此实现的独特之处在于它仅使用输入数组的 single 遍历来找到最小值和最大值 -

const None =
  Symbol ()

const list = (...values) =>
  values

const minmax = ([ x = None, ...rest ], then = list) =>
  x === None
    ? then (Infinity, -Infinity)
    : minmax
        ( rest
        , (min, max) =>
            then
              ( Math.min (min, x)
              , Math.max (max, x)
              )
        )

console.log
  ( minmax ([ 3, 4, 2, 5, 1 ])    // [ 1, 5 ]
  , minmax ([ 1, 5 ])             // [ 1, 5 ]
  , minmax ([ 5, 1 ])             // [ 1, 5 ]
  , minmax ([ 9 ])                // [ 9, 9 ]
  , minmax ([])                   // [ Infinity, -Infinity ]
  )

默认情况下minmax 返回最小值和最大值的list。我们可以将最小值和最大值直接插入到range 函数中,这可能对我们更有用,我们稍后会看到 -

const range = (m, n) =>
  m > n
    ? []
    : [ m, ... range (m + 1, n ) ]

console.log
  ( minmax ([ 3, 4, 2, 5, 1 ], range)    // [ 1, 2, 3, 4, 5 ]
  , minmax ([ 1, 5 ], range)             // [ 1, 2, 3, 4, 5 ]
  , minmax ([ 5, 1 ], range)             // [ 1, 2, 3, 4, 5 ]
  , minmax ([ 9 ], range)                // [ 9 ]
  , minmax ([], range)                   // []
  )

现在我们可以找到输入的最小值和最大值,在两者之间创建一个范围,剩下的就是计算范围内值的lcm。使用 .reduce 获取多个值并将它们减少为单个值 -

console.log
  ( minmax ([1, 5], range) .reduce (lcm, 1) // 60
  , minmax ([5, 1], range) .reduce (lcm, 1) // 60
  )

将其封装在一个函数中,我们就完成了 -

const smallestCommons = xs =>
  minmax (xs, range) .reduce (lcm, 1)

console.log
  ( smallestCommons ([ 5, 1 ])    // 60
  , smallestCommons ([ 1, 13 ])   // 360360
  , smallestCommons ([ 23, 18 ])  // 6056820
  )

在你自己的浏览器下面验证结果-

const gcd = (m, n) =>
  n === 0
    ? m
    : gcd (n, m % n)

const lcm = (m, n) =>
  Math.abs (m * n) / gcd (m, n)

const None =
  Symbol ()

const list = (...values) =>
  values

const minmax = ([ x = None, ...xs ], then = list) =>
  x === None
    ? then (Infinity, -Infinity)
    : minmax
        ( xs
        , (min, max) =>
            then
              ( Math.min (min, x)
              , Math.max (max, x)
              )
        )

const range = (m, n) =>
  m > n
    ? []
    : [ m, ... range (m + 1, n ) ]

const smallestCommons = xs =>
  minmax (xs, range) .reduce (lcm, 1)

console.log
  ( smallestCommons ([ 5, 1 ])    // 60
  , smallestCommons ([ 1, 13 ])   // 360360
  , smallestCommons ([ 23, 18 ])  // 6056820
  )

额外的

在上面,minmax 是使用延续传递样式定义的。我们通过传递range 作为指定的延续(then)来节省额外的计算。但是,我们可以调用minmax 而不指定延续并将(...)中间值传播到range。任何一个程序都可能对您更有意义。结果是一样的-

const smallestCommons = xs =>
  range (...minmax (xs)) .reduce (lcm, 1)

console.log
  ( smallestCommons ([ 5, 1 ])    // 60
  , smallestCommons ([ 1, 13 ])   // 360360
  , smallestCommons ([ 23, 18 ])  // 6056820
  )

同一头猪,不同的农场

smallestCommons 基本上只是在[min,max] - @Carcigenicate 范围内的缩减

希望通过多种方法看到相同的结果会有所帮助:D


表面

有些人会鄙视minmax 的上述实现,不管它的优雅和灵活性如何。现在我们可能更好地理解了减少,我们可以展示如何使用直接样式更好地实现 minmax -

const minmax = xs =>
  xs .reduce
    ( ([ min, max ], x) =>
        [ Math.min (min, x)
        , Math.max (max, x)
        ]
    , [ Infinity, -Infinity ]
    )

const smallestCommons = xs =>
  range (...minmax (xs)) .reduce (lcm, 1) // direct style now required here

【讨论】:

    【解决方案3】:

    如果您以这样的方式重写内部函数,则可以取消嵌套它,使其不引用外部范围内的变量。

    function scm(l, h, step) {
      if (h % l === 0) {
         return h;
      } else {
        return scm(l, h + h, step);
      }
    }
    
    function smallestCommons(arr) {
      var max = Math.max(...arr);
      var min = Math.min(...arr);
      return scm(min, max, max);
    }
    

    虽然它可能会破坏你的堆栈,但这是一个不同的问题。如果你得到一个RangeError,你必须重写scm,使其基于循环而不是递归。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-02-07
      • 2014-02-27
      • 2022-01-21
      • 2013-10-17
      • 2015-03-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多