Tail Recursive Recurrence¶

  • Let's revisit the affine recurrence problem we worked in an earlier topic of recursion

$$ x_0 = c, \qquad x_n = a x_{n-1} + b $$

  1. Write a function affine_recurrence_simple_tail (or affineRecurrenceSimpleTail) that takes:
  • 64-bit floating point numbers $a$, $b$, and $c$,
  • and an integer $n$ (you may assume $n \ge 0$), and returns $x_n$.
  • Hint: you need to introduce an auxiliary function that takes an extra parameter $m$ ($\le n$) and $x_m$

    • the auxiliary function returns $x_n$ given $m$ and $x_m$
    • that is,
      • if $m = n$ just return $x_m$, which is given
      • otherwise, compute $x_{m+1} = a x_m + b$ and obtain $x_n$ by recursive call
  • Note: affine_recurrence_simple_tail takes $O(n)$ time, but should not cause stack overflow even for large $n$

  1. Apply the same idea to get a tail recursive version of affine_recurrence_fast (or affineRecurrenceFast). Define it as affine_recurrence_fast_tail (affineRecurrenceFastTail)
  • Note: the tail recursive version of affine_recurrence_fast is not compelling and purely for practice, because it has practically no risk for stack overflow

  • Boilerplate source files {go,jl,ml,rs}/tail_recurrence.{go,jl,ml,rs} containing the test code is generated and shown below.

  • Edit the source files either by opening them in a text editor (e.g., vscode), or editing the cells below and executing them.

1. AI tutor¶

1-1. Prepare¶

  • Your personal AI tutor is provided for questions and feedback
  • Execute the following cell before you use it
In [ ]:
import heytutor

1-2. Examples¶

1-2-1. A general question¶

%%hey
How to write a function in Go?

1-2-2. A hint on this specific problem¶

%%hey
Give me a hint on this problem for Rust

1-2-3. NEW: A few builtin variables¶

  • {file:FILENAME} is the content of FILE
  • {bash[-1]} is the output of the last %%bash_ cell, {bash[-2]} that of the second last %%bash_ cell, etc.
  • {problem} is the content of the file you specified by %%hey problem_file=foo.md
  • {answer} is the content of the file you specified by %%hey answer_file=go/foo.go

1-2-4. Help when you struggle¶

%%hey answer_file=go/foo.go
I get this error when I compile it. What's wrong?"

My program:
{answer}

Error message:
{bash[-1]}

1-2-5. Ask feedback¶

  • You are encouraged to ask a feedback once you think you are done with the problem, to know if there is a better answer. You can do so by something like:
%%hey problem_file=foo.md answer_file=go/foo.md
Give me a feedback to my answer.

Problem:
{problem}

My Answer:
{answer}

2. Go¶

2-1. Baseline code¶

In [ ]:
import heytutor
In [ ]:
%%writefile_ go/tail_recurrence.go
package main
import "math"
import "fmt"

/** begin my answer */

func affineRecurrenceSimpleTailRec(a, b float64, m int64, xm float64, n int64) float64 {
	if m == n {
		return xm
	} else {
		xm_1 := a * xm + b
		return affineRecurrenceSimpleTailRec(a, b, m + 1, xm_1, n)
	}
}

func affineRecurrenceSimpleTail(a, b, c float64, n int64) float64 {
	return affineRecurrenceSimpleTailRec(a, b, 0, c, n)
}
	
func affineRecurrenceFastTailRec(a, b float64, m int64, xm float64, n int64) float64 {
	if m == n {
		return xm
	} else if (n - m) % 2 == 0 {
		return affineRecurrenceFastTailRec(a * a, a * b + b, m, xm, (m + n) / 2)
	} else {
		xm_1 := a * xm + b
		return affineRecurrenceFastTailRec(a, b, m + 1, xm_1, n)
	}
}

func affineRecurrenceFastTail(a, b, c float64, n int64) float64 {
	return affineRecurrenceFastTailRec(a, b, 0, c, n)
}
/** end my answer */

func main() {
	if !(math.Abs(affineRecurrenceFastTail(0.9999, 1.0, 2.0, 100000)  - 9999.5463184) < 1.0e-6) { panic("wrong") }
	if !(math.Abs(affineRecurrenceSimpleTail(0.9999, 1.0, 2.0, 100000)  - 9999.5463184) < 1.0e-6) { panic("wrong") }
	if !(math.Abs(affineRecurrenceFastTail(0.9999, 1.0, 2.0, 1000000) - 10000.0) < 1.0e-6) { panic("wrong") }
	if !(math.Abs(affineRecurrenceSimpleTail(0.9999, 1.0, 2.0, 1000000) - 10000.0) < 1.0e-6) { panic("wrong") }
	fmt.Println("OK")
}

2-2. Compile¶

In [ ]:
%%bash_
export PATH=${PATH}:~/.local/go/bin:~/go/bin
go build -o go/tail_recurrence go/tail_recurrence.go
  • Note: when you run go or other Go commands in a terminal (SSH or Jupyter terminal), you need to execute the first line (export PATH=${PATH}:~/go/bin)
  • You may consider adding that line in your ~/.bash_profile

2-3. Run¶

In [ ]:
%%bash_
go/tail_recurrence

2-4. Ask Questions or Get Feedback¶

In [ ]:
%%hey problem_file=tail_recurrence.md answer_file=go/tail_recurrence.go

Problem:
{problem}
My Answer (between /** begin my answer */ and /** end my answer */):
{answer}

Give me a feedback to my answer.

3. Julia¶

3-1. Baseline code¶

In [ ]:
import heytutor
In [ ]:
%%writefile_ jl/tail_recurrence.jl
### begin my answer

function affine_recurrence_simple_tail_rec(a, b, m, xm, n)
    if m == n
        xm
    else
        xm_1 = a * xm + b
        affine_recurrence_simple_tail_rec(a, b, m + 1, xm_1, n)
    end
end

affine_recurrence_simple_tail(a, b, c, n) = affine_recurrence_simple_tail_rec(a, b, 0, c, n)

function affine_recurrence_fast_tail_rec(a, b, m, xm, n)
    if m == n
        xm
    elseif (n - m) % 2 == 0
        affine_recurrence_fast_tail_rec(a * a, a * b + b, m, xm, div(m + n, 2))
    else
        xm_1 = a * xm + b
        affine_recurrence_fast_tail_rec(a, b, m + 1, xm_1, n)
    end
end

affine_recurrence_fast_tail(a, b, c, n) = affine_recurrence_fast_tail_rec(a, b, 0, c, n)
### end my answer

function main()
    @assert abs(affine_recurrence_fast_tail(0.9999,   1.0, 2.0, 100000)  - 9999.5463184) < 1.0e-6
    @assert abs(affine_recurrence_simple_tail(0.9999, 1.0, 2.0, 100000)  - 9999.5463184) < 1.0e-6
    @assert abs(affine_recurrence_fast_tail(0.9999,   1.0, 2.0, 1000000) - 10000.0) < 1.0e-6
    @assert abs(affine_recurrence_simple_tail(0.9999, 1.0, 2.0, 500000)  - 10000.0) < 1.0e-6
    println("OK")
end

main()

3-2. Compile¶

  • Julia code is compiled "just in time" (compiled upon executed), so does not need a specific action for compilation before you run

3-3. Run¶

In [ ]:
%%bash_
export PATH=${PATH}:~/.juliaup/bin
julia jl/tail_recurrence.jl
  • Note: when you run julia or other Julia commands in a terminal (SSH or Jupyter terminal), you need to execute the first line (export PATH=${PATH}:~/.juliaup/bin)
  • You may consider adding that line in your ~/.bash_profile

3-4. Interactive execution¶

  • julia command also serves is an interactive command for Julia programs

  • You can run a source code and continue interaction

$ julia -i jl/tail_recurrence.jl
  • For trial and error, you may also consider creating a Julia notebook

3-5. Ask Questions or Get Feedback¶

In [ ]:
%%hey problem_file=tail_recurrence.md answer_file=jl/tail_recurrence.jl

Problem:
{problem}

My Answer (between ### begin my answer and ### end my answer):
{answer}

Give me a feedback to my answer.

4. OCaml¶

4-1. Baseline code¶

In [ ]:
import heytutor
In [ ]:
%%writefile_ ml/tail_recurrence.ml
(** begin my answer *)

let rec affine_recurrence_simple_tail_rec a b m xm n =
  if m = n then
    xm
  else
    let xm_1 = a *. xm +. b in
    affine_recurrence_simple_tail_rec a b (m + 1) xm_1 n
;;
      
let affine_recurrence_simple_tail a b c n = affine_recurrence_simple_tail_rec a b 0 c n
;;
let rec affine_recurrence_fast_tail_rec a b m xm n =
  if m = n then
    xm
  else if (n - m) mod 2 = 0 then
    affine_recurrence_fast_tail_rec (a *. a) (a *. b +. b) m xm ((m + n) / 2)
  else
    let xm_1 = a *. xm +. b in
    affine_recurrence_fast_tail_rec a b (m + 1) xm_1 n
;;
      
let affine_recurrence_fast_tail a b c n = affine_recurrence_fast_tail_rec a b 0 c n
(** end my answer *)

let main () =
  assert (Float.abs (affine_recurrence_fast_tail   0.9999 1.0 2.0 100000  -. 9999.5463184) < 1.0e-6);
  assert (Float.abs (affine_recurrence_simple_tail 0.9999 1.0 2.0 100000  -. 9999.5463184) < 1.0e-6);
  assert (Float.abs (affine_recurrence_fast_tail   0.9999 1.0 2.0 1000000 -. 10000.0) < 1.0e-6);
  assert (Float.abs (affine_recurrence_simple_tail 0.9999 1.0 2.0 1000000 -. 10000.0) < 1.0e-6);
  Printf.printf "OK\n"
;;

main()

4-2. Compile¶

In [ ]:
%%bash_
eval $(opam env)
ocamlc ml/tail_recurrence.ml -o ml/tail_recurrence
  • Note: when you run ocamlc or other OCaml commands (see below) in a terminal (SSH or Jupyter terminal), you need to execute the first line (eval $(opam env))
  • You may consider adding that line in your ~/.bash_profile

4-3. Run¶

In [ ]:
%%bash_
ml/tail_recurrence

4-4. Interactive execution¶

  • ocaml command is an interactive command for OCaml programs

  • In terminal (Jupyter or SSH), you can directly run a source code

$ eval $(opam env)   # once in your session or put it in ~/.bash_profile
$ ocaml ml/tail_recurrence.ml
  • You can run a source code and continue interaction
$ eval $(opam env)   # once in your session or put it in ~/.bash_profile
$ ocaml -init ml/tail_recurrence.ml
  • For trial and error, you may also consider creating an OCaml notebook

4-5. Ask Questions or Get Feedback¶

In [ ]:
%%hey problem_file=tail_recurrence.md answer_file=ml/tail_recurrence.ml

Problem:
{problem}

My Answer (between (** begin my answer *) and (** end my answer *)):
{answer}

Give me a feedback to my answer.

5. Rust¶

5-1. Baseline code¶

In [ ]:
import heytutor
In [ ]:
%%writefile_ rs/tail_recurrence.rs
/** begin my answer */

fn affine_recurrence_simple_tail_rec(a: f64, b: f64, m: i64, xm: f64, n: i64) -> f64 {
    if m == n {
        xm
    } else {
	let xm_1 = a * xm + b;
	affine_recurrence_simple_tail_rec(a, b, m + 1, xm_1, n)
    }
}
fn affine_recurrence_simple_tail(a: f64, b: f64, c: f64, n: i64) -> f64 {
    affine_recurrence_simple_tail_rec(a, b, 0, c, n)
}

fn affine_recurrence_fast_tail_rec(a: f64, b: f64, m: i64, xm: f64, n: i64) -> f64 {
    if m == n {
        xm
    } else if (n - m) % 2 == 0 {
        affine_recurrence_fast_tail_rec(a * a, a * b + b, m, xm, (m + n) / 2)
    } else {
	let xm_1 = a * xm + b;
	affine_recurrence_fast_tail_rec(a, b, m + 1, xm_1, n)
    }
}

fn affine_recurrence_fast_tail(a: f64, b: f64, c: f64, n: i64) -> f64 {
    affine_recurrence_fast_tail_rec(a, b, 0, c, n)
}

/** end my answer */

fn main() {
    assert!((affine_recurrence_fast_tail(0.9999, 1.0, 2.0, 100000)  - 9999.5463184).abs() < 1.0e-6);
    assert!((affine_recurrence_simple_tail(0.9999, 1.0, 2.0, 100000)  - 9999.5463184).abs() < 1.0e-6);
    assert!((affine_recurrence_fast_tail(0.9999, 1.0, 2.0, 1000000) - 10000.0).abs() < 1.0e-6);
    assert!((affine_recurrence_simple_tail(0.9999, 1.0, 2.0, 1000000) - 10000.0).abs() < 1.0e-6);
    println!("OK");
}

5-2. Compile¶

In [ ]:
%%bash_
. ~/.cargo/env
rustc rs/tail_recurrence.rs -o rs/tail_recurrence
  • Note: when you run rustc or other Rust commands in a terminal (SSH or Jupyter terminal), you need to execute the first line (. ~/.cargo/env)
  • You may consider adding that line in your ~/.bash_profile

5-3. Run¶

In [ ]:
%%bash_
rs/tail_recurrence

5-4. Ask Questions or Get Feedback¶

In [ ]:
%%hey problem_file=tail_recurrence.md answer_file=rs/tail_recurrence.rs

Problem:
{problem}

My Answer (between /** begin my answer */ and /** end my answer */):
{answer}

Give me a feedback to my answer.