Thinking About Recursion
solipsys.co.uk
solipsys.co.uk
[1] https://www.youtube.com/watch?list=PLZHQObOWTQDMRtm8h9bG9P06...
It seems like without that rule, the game's solution is 2n-1 steps (dump n-1 disks from the left to the right, move disk n to the center, then un-invert those n-1 disks on top of disk n), rather than 2^n-1 moves.
In the case where you can place larger disks on top of smaller disks, the sub-problems don't have the same solutions, i.e. In order to move N disks from A to B you first move N-1 disks from A to C, then one disk from A to B, then the N-1 disks back from C to B. But in order to solve the N-1 disks, you don't need to ensure that the largest disk ends up on the bottom -- you just move them all straight to the target.
I guess the rule is implicitly tied up in the fact that the sub-problems are solved in the same way as the larger problem.
It had better. If you're allowed to put larger disks on smaller disks, you can solve towers of Hanoi in linear time. Call the rods A, B, and C: the goal is to move the stack from A to C. First flip the stack upside down, one disk at a time, from A to B. Then do that again, from B to C. The stack is now upright on C, after 2n moves.
(In contrast, the real problem requires O(2^n) moves.)
Difference between "variables" in math vs. functional programming? None. The value of a variable can't change.
Difference between "function" in math vs. functional programming? None. It's just a map from one set to another.
f(x) = x^2 + x + 1
the value of x in the right-hand-side expression doesn't change in the same sense that variables in imperative languages change. But it allows the single expression "x^2 + x + 1" to have a value which varies by the value of x. For example, f(2) yields the value 7 while f(3) yields the value 13.Functional programming much better supports equational reasoning.
Imperative programming much better supports operational reasoning.
Or you could contrast "data flow" vs "control flow".
I'd have thought we won't need operational reasoning in 2017 but currently I conclude it may always be a part of computer programming..
Eventually, the guy in the second row asks the guy in the first row, who immediately says he's in the first row. Then each person adds 1 to the answer they get back and feed it back until eventually you get your answer.
Of course, I guess this just highlights how all the toy examples we see in CS courses often use trivial things like computing Fibonacci sequences or factorials, which are much easier to do iteratively. This often leaves students wondering why anyone would ever even use recursion, or at best, makes it seem like recursion vs. iteration is a mere stylistic choice.
Fantastic question. And a point I would respond with is that if you were to actually get up and walk to the front of the cinema counting rows as you go, you would realise that in doing so you would have walked forwards one row and then performed exactly the same action that the person in front of you would have done had they tried to count their row. So even this "iterative" procedure parallels recursion.
Perhaps a problem that seems harder to convert to iteration is that of evaluating arbitrary arithmetic expressions. In order to evaluate a binary expression, you evaluate its two parts (recursively) and then perform the binary operator on the two operands. In order to evaluate a numeric literal, you simply take its value. So (1 + (2 * 3)) involves evaluating 1 to itself, (2 * 3) to 6, and then finally (1 + 6) to 7.
Fine.
Now what is (27 * 96)? (2 * 6 * 10) + (7 * 9 * 10) + (2 * 9 * 10 * 10) + (7 * 6). etc. [In real life you'd do this in binary and the multiplies by 10 become shifts.]
This is not usually the fastest way to do scalar multiplication, but it illustrates that:
1. Ordinary scalar multiplication is polynomial multiplication.
2. Polynomial multiplication is convolution.
3. And since we can [sometimes] speed up convolution by moving it to the Fourier domain, we can multiply numbers by taking their FFTs first, doing an elementwise multiplication, and then taking an inverse FFT. This actually pays off on very large numbers.
Multiplication is fun stuff.
"No, no, no. Look ..."
"But to start you can only move the top-most disk?"
"Yes, but it won't work because ..."
"What? You have to move the top-most as the first move."
(Comment deliberately phrased provocatively to hear push-back and feedback.)
My first encounter with recursion was when I needed to traverse a directory tree. I couldn't figure how to go arbritrarily deep without writing tens of nested for-loops. Then recursion somehow hit me, or I found something interesting on Google, can't remember, it was over 10 years ago, pretty sure SO wasn't around.
...and then a while later I found out about os.walk (this was python).
import os
import os.path
def list_files_recursive(path):
filenames = os.listdir(path)
for filename in filenames:
fullname = os.path.join(path, filename)
if os.path.isdir(fullname):
yield from list_files_recursive(fullname) # recursively explore folders
else:
yield fullname
and here is the same program with an explicit stack import os
import os.path
def list_files_stack(path):
folders = [path] # create stack of unexplored folders
while folders: # repeat until stack is empty
path = folders.pop() # pick a folder from stack
filenames = os.listdir(path)
for filename in filenames:
fullname = os.path.join(path, filename)
if os.path.isdir(fullname):
folders.append(fullname) # push folder to stack
else:
yield fullnameThanks a lot!
Practical: If we right all of are programs and tail-recursive (returning a recursive call or constant at the end of the prgram) programs they are easy to transform into simple while loops with no stack. The idea is instead of calling another function just restart the current one with the updated arguments.
EX:
f(n,x)=f(n-1,x+1)
f(0,x)=x
goes to while(n > 0){
n--;
x++
}
Other techniques such as trampolining and continuation passing style also help translate recursive functions into iterative ones.Theorectical: Think about Brainfuck or Turing machines, these computing frameworks can compute any computable function using only what amounts to while loops and infinite memory.
Given then that we can produce arbitrary control flow using only while loops, we can reproduce how recursive calls are computed in computers with stack frames (provided by our stack).
You just won't get a stack overflow exception with the loop -- you'll run out of heap instead. It's still the stack filling up.
Yes. Maybe there could be some algebra in advance of -- or alongside -- early coding lessons, where this idea is central to both fields. One variable name, any numeric value.
It's the same with functions, the lifeblood of analysis -- one function name, any equation. So in a perfect world, people would be learning calculus and general analysis while writing their first program functions -- indeed, they might be learning higher math while coding their lesson materials at the same time.
I would really like to see a production language, where program-stack based recursion was a compile time error. Instead, only recursion as syntax sugar is allowed. i.e. Only recursion (or any cyclic function call graph) that the compiler can translate to "imperative code", should be allowed to compile.
For two reasons:
1. I honestly dream of a world where we can no longer think about infinitely propagating program-stacks.
2. It would force us to invent better optimization algorithms (like tail-call optimization) but for more general recursive patterns.
- 4 years of self learned programming
- 4 years of university for computer engineering
- 5 years in the industry, as a software engineer
[0] https://en.wikipedia.org/wiki/Continuation-passing_style
Previous discussion: https://news.ycombinator.com/item?id=13793404
https://jugad2.blogspot.in/2013/12/google-gets-recursion.htm...