递归函数的递归出口_Ruby:使用函数,递归,范围和块的技巧

递归函数的递归出口

背景 (Background)

I’ve been studying for technical interviews: doing a bunch of problems on Leetcode, and anytime I needed to use recursion for DFS or any other problems, I got really confused and ran into a lot of errors.

我一直在研究技术面试:在Leetcode上做很多问题,每当我需要对DFS使用递归或其他任何问题时,我都感到非常困惑,并遇到很多错误。

More specifically, when I needed to pass in variables into recursive functions and update some sort of counter variable.

更具体地说,当我需要将变量传递给递归函数并更新某种counter变量时。

There’s one problem I’m sure you guys have seen if you’ve been practicing Leetcode: the number of islands problem. Say on top of keeping track of the number of islands, I wanted to keep track of exactly how much land mass there was. So each time I encountered a ‘1’, I need to increment a counter as I’m recursing.

如果您一直在练习Leetcode,我敢肯定,有一个问题是: 孤岛数量问题 。 说说跟踪岛屿的数量,我想跟踪一下到底有多少土地。 因此,每次遇到“ 1”时,我都需要在递归时增加一个计数器。

Whenever I tried to pass in a ‘counter’ variable and update the counter variable in my recursion, it wouldn’t get updated as I wanted it to. After modifying it inside my function, the counter stayed the same variable I initially set it to.

每当我尝试传递“计数器”变量并在递归中更新计数器变量时,都不会像我想要的那样对其进行更新。 在函数中对其进行修改后,计数器将保持与最初设置其相同的变量。

So yeah, in this article I want to go over just what I found out after struggling with variables, scopes, and recursion in RUBY, and any other general tips for Recursion and search algos.

是的,在这篇文章中,我想回顾一下在RUBY中变量,作用域和递归问题以及其他有关递归和搜索算法的一般技巧后所发现的问题。

价值传递 (Pass-by-value)

First off, the above example doesn’t work because Ruby is a pass by value language.

首先,上面的示例不起作用,因为Ruby是按值传递语言。

When we puts x after calling our method, we still get 0 because the b inside the function definition is not referring to the variable x outside the function. b is actually just an entirely different variable pointing to the same value.

当我们在调用方法之后puts x ,由于函数定义内的b并未引用函数外的变量x ,所以我们仍然得到0。 b实际上只是指向相同值的完全不同的变量。

What happens when we call the function with x is:

当我们使用x调用函数时,将发生以下情况:

  1. The function creates a local variable named b and sets that equal to the value that was passed in. (‘Point b to the memory location of int 0’).

    该函数创建一个名为b的局部变量,并将其设置为等于传入的值。(“将b指向int 0的内存位置”)。

  2. Then it sets the local variable b to 2. (‘Point b to a new memory location for int 2’).

    然后将局部变量b为2。(“将b指向int 2的新存储位置”)。

Therefore, the location x is pointing to after the function hasn’t changed and is still pointing to 0. You can check out the object that a variable is pointing to by using object_id. I suggest you experiment with object_ids to see how variable assignments work.

因此,函数未更改后x所指向的位置仍然指向0。您可以使用object_id来检出变量所指向的对象 我建议您尝试使用object_ids来查看变量分配如何工作。

修改函数中的数组 (Modifying arrays in functions)

One way we can modify outside variables inside our functions is by passing in an array (and also a hash). Performing array modifications like push, pop, and shift changes the array in the object’s memory location. Whereas, ASSIGNING a new value is pointing the variable to a new object in memory.

我们可以在函数内部修改外部变量的一种方法是通过传入数组(以及哈希)。 执行诸如pushpopshift类的数组修改会更改对象内存位置中的数组 。 而分配新值则将变量指向内存中的新对象。

So if for whatever reason you needed to keep track of some list of answers while recursing, you can just pass in an array and modify the array.

因此,如果出于任何原因需要在递归时跟踪一些答案列表,则可以传入数组并修改数组。

我们可以在不传递变量的情况下仅在函数中修改外部变量吗? (Can we just modify an outside variable in our function w/o passing in the variable?)

No, we can’t. (I should say ‘no we can’t with classic Ruby function definitions’ we’ll get to this later.)

不,我们不能。 (我应该说“不,我们不能使用经典的Ruby函数定义 ”,我们稍后再讨论。)

Ruby function definitions create their own different scope. They are called ‘scope gates’ for this reason. They can’t access variables declared outside the function.

Ruby函数定义创建它们自己的不同范围。 因此,它们被称为“ 范围门 ”。 他们无法访问在函数外部声明的变量。

那么,我们如何在Ruby函数内部不断修改计数器变量? (Then, how do we keep modify a counter variable inside of Ruby functions?)

The first way is to use global variables (global variables created using the key $) but using global variables is generally frowned upon, so we’ll go over other methods.

第一种方法是使用全局变量(使用键$创建的全局变量),但是通常不赞成使用全局变量,因此我们将介绍其他方法。

积木 (Blocks)

We can use blocks. Blocks are chunks of code that we can call later (they’re essentially functions but with slightly different properties). They’re commonly passed into functions like in array.map.

我们可以使用块。 块是我们稍后可以调用的代码块(它们本质上是函数,但属性稍有不同)。 它们通常被传递给array.map类的array.map

[1, 2, 3].map {|x| x+1}

[1, 2, 3].map {|x| x+1}

You can read more about how blocks work and details of blocks, but the important thing is blocks have closure, which means that when blocks are called later on they can reference variables and methods defined in the same scope as the block definition.

您可以阅读有关块的工作原理和块详细信息的更多信息,但重要的是块具有闭包 ,这意味着稍后调用块时,它们可以引用与块定义在相同范围内定义的变量和方法。

In simpler terms, blocks can use variables and methods defined outside the block.

简单来说,块可以使用在块外部定义的变量和方法。

We can define blocks in two different ways:

我们可以用两种不同的方式定义块:

  1. Lambdas and procs

    Lambdaproc

When I first started out using recursion, lambdas and procs were what I used because I ran into so many problems when using normal Ruby functions.

刚开始使用递归时,我使用的是lambda和proc,因为使用常规Ruby函数时遇到了很多问题。

Lambdas and procs are just Ruby blocks that we can store in variables. We can define them using keywords lambda or proc. Later we can call a lambda or a proc using lambda_name.call(any_args) or proc_name.call(any_args) .

Lambda和proc只是我们可以存储在变量中的Ruby块。 我们可以使用关键字lambdaproc定义它们。 稍后,我们可以使用lambda_name.call(any_args)proc_name.call(any_args)调用lambdaproc

You can read more about lambdas and procs here but the important thing is that lambdas and procs are blocks, and therefore, have closure.

您可以在此处阅读有关lambda和proc的更多信息,但重要的是lambda和proc是block,因此具有closures

If you’re going to use lambdas and procs, you should know how procs and lambdas handle return statements.

如果要使用lambda和proc,则应了解proc和lambda如何处理return语句。

Proc ‘returns’ will return from the method that the proc was called in. While lambdas only return from the lambdas itself.

Proc“返回”将从调用proc的方法中返回。而lambda仅从lambda本身返回。

One use case of procs is if you’re recursing through a structure and you want to return a value from the outer function when you find the target. If you want your block to return like a normal function just use lambdas.

procs的一种使用情况是,如果要遍历结构,并且想在找到目标时从外部函数返回值。 如果您希望您的代码块像普通函数一样返回,请使用lambdas。

define_method (define_method)

2. You can also create a method using a block by using define_method. This function will have closure.

2.您也可以通过使用define_method使用块创建方法 此功能将关闭。

# Define a method using define_method and passing in a block
x = 0
define_method(:func) do
  x = 2
end
func()


puts x # => 2

So now, we know that we can use blocks to increment an outside variable inside a method or a chunk of code.

现在,我们知道可以使用块来增加方法或代码块中的外部变量。

其他增加计数器的方法: (Other ways to increment a counter:)

仅在需要DFS时使用堆栈 (Just use a stack when you need to DFS)

Instead of using recursive blocks or methods, we can just use a stack and not worry about variable scope.

除了使用递归块或方法,我们可以只使用堆栈,而不必担心变量范围。

It’s a little bit more work because you have to explicitly work with a stack instead of just using an implicit one with recursion, but honestly, it saves you from having to deal with variable references in recursion.

这需要做更多的工作,因为您必须显式地使用堆栈,而不是仅使用带递归的隐式堆栈,但是老实说,它使您不必在递归中处理变量引用。

Also, stacks are very common in algorithms so why not just familiarize yourself with stacks?

另外,堆栈在算法中非常常见,所以为什么不仅仅熟悉一下堆栈呢?

And on top of that, it’s very easy to switch a DFS using a stack to BFS using a queue in case you ever need to implement a BFS.

最重要的是,很容易将使用堆栈的DFS切换为使用队列的BFS,以防您需要实现BFS。

This is more of a side note because this applies to queues, but Ruby arrays can work as deques. Other languages need to use structures called ‘deques’ for their queues, otherwise inserting and removing a value at and from the beginning of an array would take O(N) time, but Ruby arrays work perfectly fine as deques. Push, pop, shift, and unshift for ruby arrays all have amortized O(1) time. Check out this stack overflow question.

这更多是一个旁注,因为它适用于队列,但是Ruby数组可以用作 端队列 其他语言需要在队列中使用称为“双端队列”的结构,否则在数组的开头或从数组的开头插入和删除值将花费O(N)时间,但Ruby数组作为双端队列的工作效果很好。 Ruby阵列的推入,弹出,移位和不移位均已摊销O(1)时间。 查看此 堆栈溢出问题。

# DFS with stack
stack = []
stack << start # add start
counter = 0


while !stack.empty?
  curr = stack.pop
  counter += 1
  # Any search logic you need
  # Add any of curr's valid children into the stack
end

我们如何使用堆栈来跟踪DFS中的深度? (How do we keep track of depth in DFS using a stack?)

The last thing is yes, it’s easy to keep track of a counter in a DFS using a stack, but how do we keep track of depth? Depth is different from a counter because a counter just counts each iteration, while depth keeps track of how many layers of nodes you searched through to get to a point.

最后一件事是,使用堆栈跟踪DFS中的计数器很容易,但是如何跟踪深度呢? 深度与计数器不同,因为计数器仅对每次迭代进行计数,而深度则跟踪您搜索到点的节点的多少层。

This might be important because some algorithm questions ask to return the depth or the ‘length’ of a path. For example, when you’re DFSing a nested array and you need to return the depth of a target value.

这可能很重要,因为某些算法问题要求返回路径的深度或“长度”。 例如,当您使用DFS嵌套数组时,您需要返回目标值的深度

Store each node you encounter in an array with the depth:

将遇到的每个节点存储在具有深度的数组中:

# node
[value, depth]


# Your stack
[[value1, depth1], [value2, depth1], [value3, depth2] ... ]


while !stack.empty?
  val, dep = stack.pop # use this syntax to assign vars quickly to val and depth
  
  # other stuff
  
  # adding a node
  stack << [new_val, dep+1] # when adding a new node add with current depth + 1
end

Each node you remove from the stack will know the current depth or ‘length’ of the path to that node. Any of its children should be inserted into the stack with curr_depth + 1.

从堆栈中删除的每个节点都将知道到该节点的路径的当前深度或“长度”。 它的任何子级都应使用curr_depth + 1插入堆栈。

从本文中获得什么...。 (What to take away from this article….)

  • Ruby function definitions create their own scope. You can’t reference or modify outside variables when using classic Ruby function definitions i.e. (def function_name … end). You can, however, pass in an array, and MODIFY that array inside a function, because array modifications change the array in-place in memory.

    Ruby函数定义创建自己的范围。 使用经典Ruby函数定义(即( def function_name … end )时,您不能引用或修改外部变量。 但是,您可以传入数组,并在函数内部修改该数组,因为数组修改会在内存中就地更改数组。

  • Next, if you can, use stacks for DFSing. It’s easier working with variables. It’ll make you more familiar with stacks, which are common in algorithms. You can also switch from DFS with a stack to BFS with a queue easily.

    接下来,如果可以,请使用堆栈进行DFSing。 使用变量更容易。 它会让您更加熟悉算法中常见的堆栈。 您也可以轻松地从带有堆栈的DFS切换到带有队列的BFS。
  • And lastly, if you still need to use recursion, use lambdas, procs, or define_method. They have closure and can reference outside variables.

    最后,如果您仍然需要使用递归,请使用lambda,proc或define_method。 它们具有闭包并且可以引用外部变量。

Alright, I hope you found this helpful. I’m not really an expert, I’m just sharing some things I’ve picked up on while solving Leetcode problems. Good luck with your interviewing.

好吧,希望您发现这对您有所帮助。 我不是真正的专家,我只是分享一些我在解决Leetcode问题时学到的东西。 祝您面试顺利。

翻译自: https://medium.com/dev-genius/ruby-tips-on-working-with-functions-recursions-scope-and-blocks-dda3a6e86967

递归函数的递归出口

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值