Ruby:递归混乱

时间:2013-04-01 03:22:55

标签: ruby recursion

我正在阅读使用ruby构建域爬虫方法的人的代码。我是递归概念的新手,无法理解如何阅读他们的代码。

代码:

def crawl_domain(url, page_limit = 100)
  return if @already_visited.size == page_limit                # [1]
  url_object = open_url(url)
  return if url_object == nil                                  # [2]
  parsed_url = parse_url(url_object)
  return if parsed_url == nil                                  # [3]
  @already_visited[url]=true if @already_visited[url] == nil
  page_urls = find_urls_on_page(parsed_url, url)
  page_urls.each do |page_url|
    if urls_on_same_domain?(url, page_url) and @already_visited[page_url] == nil
      crawl_domain(page_url)
    end
  end
end

问题:

  1. 连续return语句的组合是什么意思?
  2. 在第[1]行,如果@ already_visited的大小是 NOT ,那么与page_limit相同的程序会破坏crawl_domain方法并跳过剩下的代码吗?
  3. 如果@ already_visited的大小 与page_limit相同,是否会在设置url_object = open_url(url)
  4. 之后转到下一个return语句

    提前感谢您的帮助!

    来源: http://www.skorks.com/2009/07/how-to-write-a-web-crawler-in-ruby/

3 个答案:

答案 0 :(得分:3)

忘记网络抓取工具。考虑如何使用递归来添加构成数组的数字:

arr = [1, 2, 3, 4]
def sum(array)
  # ??
end
puts sum(arr) #=> 10, please

假装问题已经解决。 (所有递归都取决于假装。)然后sum([1, 2, 3, 4])1 + sum([2, 3, 4]),对于任何数组,通常sum是第一个元素加上sum的数组的其余部分。在ruby中,将数组拆分为第一个及其余数的方法是shift;调用shift返回数组的第一个元素将其从数组中删除。所以我们可以写:

arr = [1, 2, 3, 4]
def sum(array)
  return array.shift + sum(array)
end
puts sum(arr)

看,一个递归的解决方案!然而,有一个问题:我们永远递归(或者,至少,直到遇到某种错误)。在所有递归中,在递归之前在退化情况中放入某种“停止”是至关重要的。在我们的情况下,这种堕落的情况是什么?这是阵列空的时候! (最终将是这样,因为每个sum调用都会从数组中删除一个元素。)对于空数组,显然sum为零。所以:

arr = [1, 2, 3, 4]
def sum(array)
  return 0 unless array.length > 0
  return array.shift + sum(array)
end
puts sum(arr)

结束。

您的网络抓取工具类似。我们将递归以抓取当前页面的每个子级别,但首先我们有一些“停止”(return语句)以防止在各种退化情况下敲入错误情况。 (对于网络爬虫,没有必要检查当前页面是否实际上子级别,因为如果没有,each将无所作为,我们赢了“ t recurse。)

答案 1 :(得分:2)

递归困惑?阅读本书:

http://mitpress.mit.edu/sicp/

你将成为一个更好的程序员,你将永远追逐蓝色连胜。

答案 2 :(得分:1)

递归只是意味着该方法至少调用一次以完成它的任务。有时,算法可以比迭代地更加干净地表达。对于域抓取工具,您可以想象一旦您知道如何抓取顶级页面,就可以对每个内部链接使用相同的技术:

def crawl_domain(url)
  page_urls = find_urls_on_page(url)
  page_urls.each do |page_url|
    if urls_on_same_domain?(url, page_url)

      # We can assume the method that works for the top level will work
      # for all the urls on the page too.
      crawl_domain(page_url)
    end  
  end
end

这是该方法的核心,但它有一个主要问题:它永远不会结束。 (好吧,如果它所登陆的页面没有任何内部链接,它就会结束。)这就是为什么(回答问题#1)该方法有一些早期的返回调用。如果调用任何返回,则该方法不会调用自身。

要回答问题#2,return只会突破方法if @already_visited.size == page_limit。请注意,@already_visited会跟踪方法访问过的网址,以避免再次访问它们。哈希的大小还会告诉您访问过的网址数量。因此,如果网址数达到page_limit,则第一次返回会将您踢出该方法。 (page_limit默认为100,但奇怪的是递归调用不提供它提供的限制。我认为这可能是一个错误。)

对于你的第三个问题,你似乎误解了conditional modifiers。如果条件为真,则该语句仅执行 。因此,只有在尚未达到visited-url限制时,该方法才会继续。

我认为一个主要的混淆点是,在函数的 end 处看到一个返回调用更为常见。有些人甚至advocate a single exit point。但是最好使用自调用来构造递归方法:tail recursion。因此,使用递归方法,您会经常看到早期的回复呼叫提前纾困。由于在深度嵌套的条件中间很难发现那些return,因此它们倾向于使用这种基本格式:

return if some_reason_to_bail == true