【问题标题】:Need explanation for recursive replace function递归替换函数需要说明
【发布时间】:2020-04-12 21:41:56
【问题描述】:

您好,我对 javascript 编程非常陌生。我正在学习 FCC 和 Grasshopper。在 Grasshopper 上,我被困在关于递归替换功能的课程上。我不明白。如果有人解释,这将是一个很大的帮助。 所以根据教训,它是这样的:

var wrongDocument = "This document  ahs a typo in it. The other one ahs typo too.";
function changeSpelling(string, oldPart, newPart) {
  if (string.includes(oldPart) === false) {
    return string;
  }
  string = string.replace(oldPart, newPart);
  return changeSpelling(string, oldPart, newPart);
};
console.log(wrongDocument);
console.log(changeSpelling(wrongDocument, 'ahs', 'has'));

我面临的问题是我不明白 return 语句为什么以及如何调用它所在的函数?

第二个为什么不能用 if...else 语句来解释? 第三,我在理解参数和参数时遇到问题。 所以我写了以下代码。这绝对不是替换所有拼写,但我无法进一步理解。

var chat = "This document ahs a typo in it. The other one ahs too.";
var fix = (a, b) => {
    if (chat.includes(a) === false) {
        return chat;
    }
    else {
        return chat.replace(a, b)
    }
}

console.log(chat);
console.log(fix('ahs', 'has'));

请解释递归函数的工作原理以及我的代码有什么问题。

【问题讨论】:

  • 不确定这是解释递归的地方。此外,您还有几个问题很难用 stackoverflow 的格式来解释
  • 只是一些让您入门的东西:递归类似于编写 while 循环,而不是 if / elsewhile (string.includes(oldPart)) { string = string.replace(oldPart, newPart); } return string; 相当于递归实现。
  • @Mulli 知道。但我不能问别的地方。我在各自的论坛上问过,但要等一周才会有人回复。在那之前我不能拖延我的进步。如果可以在 cmets 中解释,那也很好。
  • @PatrickRoberts 谢谢。让我检查一下。还没遇到过。
  • @ScottSauyet 我知道,但我认为一个好的教程也会提到还有其他(更好的)方法可以做同样的事情......我想它没有,这就是我这样做的原因那...

标签: javascript function recursion


【解决方案1】:

我面临的问题是我不明白 return 语句为什么以及如何调用它所在的函数?

你是对的。这将是一个大问题,递归将永远追逐它的尾巴,除了这一点:

  if (string.includes(oldPart) === false) {
    return string;
  }

所以递归调用 (return changeSpelling(string, oldPart, newPart)) 并不是从函数返回的唯一方法。还有至少一个(在这种情况下只有一个)基本情况在没有递归调用的情况下返回。此外,每次递归调用都会朝着基本情况前进,因为每次出现特定错字的实例都会减少一次。如果这两个都为真,那么递归调用是定义良好的。

您的替代解决方案仅替换错字的第一个实例。这就是它的设计目的。有几种方法可以解决这个问题。也许最明显的方法是将您的代码封装在 while 循环中,如下所示:

const fix = (chat, a, b) => {
  if (chat .includes (a) === false) {
    return chat;
  }
  else {
    return chat.replace(a, b)
  }
}

const fixAll = (chat, a, b) => {
  let test = chat
  while (test .includes (a)) {
    test = fix (test, a, b)
  }
  return test
}

const chat = "This document ahs a typo in it. The other one ahs too.";

console .log (chat)
console .log (fixAll (chat, 'ahs', 'has'))

(请注意,我现在将字符串传递给函数。这使得代码更易于理解和测试,仅使用其参数,除了返回值之外什么都不做。这就是纯函数的定义。你会发现纯函数有很多优点。)

但其中有一些真正的冗余。我们没有充分的理由检查两次是否包含该字符串。这可以通过将相关工作从fix 内联到fixAll 中来清理:

const fixAll = (chat, a, b) => {
  let test = chat
  while (test .includes (a)) {
    test = test .replace (a, b)
  }
  return test
}

const chat = "This document ahs a typo in it. The other one ahs too.";

console .log (chat)
console .log (fixAll (chat, 'ahs', 'has'))

这是解决此问题的完全合理的方法。大概你的课程是关于教你一种替代技术,一种使用递归来解决问题的技术。所以让我们重新开始吧。

问:我们想做什么?

A:用正确的字符串替换所有出现的错字。

问:我们应该怎么做?

A:什么意思?

问:那么,我们首先应该做什么?

A:我猜是替换第一个。

问:我们如何做到这一点?

答:string .replace (oldPart, newPart)

问:接下来呢?

A:好吧,那我们替换第二个。

问:如何?

答:同理,string .replace (oldPart, newPart)

问:然后呢?

A:第三个,同理。

问:然后呢?

A:嗯,我们一直这样做,直到不再有拼写错误。

问:我们怎么知道没有?

A:我们与string .includes (oldPart)核实

问:我们什么时候做这个测试?

A:更换前。

问:每次?

答:是的。

问:好的,那么。你能把它写成一组明确的说明吗?

A:来了

1. Check if the string has any misspellings
2. If not, return it as is
3. If so, replace it with the good string
4. Go back to step 1.

问:看起来不错。但是当你回到第 1 步时,你用什么字符串来测试呢?

A:我们刚刚用正确的单词替换了拼写错误的单词。

问:那么我们如何把它写成一个函数呢?

A:这个怎么样?

const changeSpelling = (string, oldPart, newPart) => {
  if (!string .includes (oldPart)) {
    return string
  }
  string.replace (oldPart, newPart)
  // hmm, do it again here.  But how?
}

问:是的。现在你想再做一次。您已经有一个功能可以做到这一点;这就是你在写的。我们不能直接调用它吗?

A:但它并不完整。

问:这一步就完成了。在这里再次调用该函数会是什么样子?

A:我想是这样的:

const changeSpelling = (string, oldPart, newPart) => {
  if (!string .includes (oldPart)) {
    return string
  }
  string .replace (oldPart, newPart)
  changeSpelling (string, oldPart, newPart)
}

问:越来越近了,但最后我们会返回什么?

A:对了,应该是return changeSpelling (string, oldPart, newPart)

问:正确,但我们应该传递什么字符串?

A:我们刚刚改的那个。

问:是的,但是.replace 不会改变你的字符串,它会创建一个包含替换的新字符串。

A:哦,所以我们需要保存更改后的字符串。怎么样?

const changeSpelling = (string, oldPart, newPart) => {
  if (string .includes (oldPart) === false) {
    return string
  }
  const newString = string .replace (oldPart, newPart)
  return changeSpelling (newString, oldPart, newPart)
}

问:看起来不错。它是否遵循我们提到的递归规则?

A:嗯,它至少有一个基本情况。另一条规则是什么?

问:递归案例必须朝着您的基本案例之一取得进展。

A:是的,由于我们已经替换了其中一个错别字,我们正朝着没有任何错别字的情况前进。所以是的。

问:我认为它会起作用。你满意吗?

A:我想是的。你会这样做吗?

问:嗯,我非常喜欢使用表达式而不是控制语句,并且不分配不必要的临时变量,所以我的看起来有点不同。但基本的想法是一样的。

A:你能告诉我你会怎么做吗?

问:当然,我的看起来更像这样:

const changeSpelling = (oldPart, newPart) => (string) =>
  string .includes (oldPart)
    ? changeSpelling (oldPart, newPart) (string .replace (oldPart, newPart))
    : string

A:看起来很不一样。为什么说一样呢?

问:我认为这是另一天的教训。

【讨论】:

  • 喜欢Q/A 风格!我会在某个时候使用它。顺便说一句,答案很好,你让我不必写答案:)
  • @Thankyou:有什么可以帮忙的!
  • 这是您可以用a && b || c 替换a ? b : c 的有趣案例之一。并不是说这是一种改进,只是很吸引人......
  • @Thankyou:是的,但我已经完全停止使用它了。我发现显式条件运算符更容易理解,即使它只是如何布局标点符号的问题。
  • @谢谢。我确实在两个 Ramda 函数 whenotherwise 中使用了类似的东西,它们都是 ifElse :: (a -> Bool) -> (a -> b) -> (a -> b) -> (a -> b) 的注释,它们用 identity 替换了最后两个参数中的任何一个。它很有用,但 && - || 语法感觉太晦涩了。
猜你喜欢
  • 2015-02-17
  • 2014-04-06
  • 2016-05-17
  • 1970-01-01
  • 2018-08-08
  • 2021-12-16
  • 2016-01-16
  • 1970-01-01
  • 2014-10-08
相关资源
最近更新 更多