views:

278

answers:

4

A friend came across a quadratic Bézier curve function in his codebase that used a gigantic rats nest of a switch table to perform the computation. He challenged me to find a single, short expression that would allow him to replace the gigantic block of code.

In attempting to satisfy two different curiosities, I thought I'd try implementing the function in OCaml. I'm a very novice OCaml programmer and I'm also unfamiliar with the function and this specific implementation is hard to come by via Google.

Critiques on both the function's performance/correctness as well as its implementation are very much appreciated.

Implementation of Quadratic Bézier Curve:

let rec b2 n =
let p1 = -10. in
let p2 = 10. in
let q = n*.n in
let rec b2i n i hd =
  if i > n then
    List.rev hd
  else
    let t = i /. n in
  b2i n (i+.1.) ((((1.-.t)**2.)*.p1+.(2.*.t*.(1.-.t)*.q)+.(t**2.)*.p2) :: hd)
in b2i n 0. []
;;
let floatprint lst = List.iter (fun f -> Printf.printf "%f; " f) lst ;;
floatprint (b2 8.);;
A: 

Yes, this looks good.

+2  A: 

I have two suggestions:

You should call List.rev after b2i returns so ocaml can exploit it's tail-recursion optimizations. I am not sure how well ocaml will deal with the current implementation, List.rev is tail-recursive though. You'll notice that in this post it is done like that.

Also, you can make the resolution of the iteration be an optional argument like ?(epsilon=0.1).

As an ocaml programmer I don't see much wrong here aside from that --as long as P1 and P2 are in fact constants. Compile it down and see what the difference in assembly is between moving List.rev inside or out of the tail-recursion.

nlucaroni
the list.rev codepath will not affect b2i's tail recursion. Good idea on epsilon. Probably p1 and p2 should become arguments too.
Thelema
+3  A: 

b2 isn't recursive, so no need for [let rec b2 n =]. Since n never changes, no need to have it as argument to b2i, just use n from the enclosing scope. Your inner function should depend on p0, p1 and p2, but I see it depending on -10., n**2 and 10. The function also has the form of a map from [ 0.0; 1.0; 2.0; ...; n.0] to the final values. Could you write it:

let b i = 
  let t = i /. n in
  let tminus = (1.-.t) in
  (tminus *. tminus *. p0) +. (2. *. t *. tminus *. p1) +. (t *. t * p2)
in
List.map b ([generate list 1.0; 2.0; ... n.0])

A function to generate the list 1.0...n.0 could be: (for small n)

let rec count m n = if m > n then [] else m :: (count (m+.1.) n)
Thelema
+1  A: 

This may be picayune, but hd is not a good name for a list parameter. List.hd is a standard function that returns the first element of a list. Using hd as a name for a list will lead to confusion.

Chris Conway
what identifier do you use for this kind of argument/list?
Thelema
I use names like xs, ys, and zs for lists, and x, y, z for elements of those lists. Not saying that's the best way.
Chris Conway
since it's an accumulator, I like to use acc
nlucaroni