3
\$\begingroup\$

I have this recursive function that I made which prints a table, but I really think my current solution is really ugly. Does anyone have an idea as to how I can improve my solution?

I'm trying to implement without any variables and with the use of sprintf.

let rec rec n =
   if n > 0 then
      rec(n-1) + sprintf "\n"+sprintf "%d" (n*1) + sprintf "\t%d" (n*1)+ sprintf "\t%d" (n*2) + sprintf "\t%d" (n*3) + sprintf "\t%d" (n*4)
+ sprintf "\t%d" (n*5) + sprintf "\t%d" (n*6) + sprintf "\t%d" (n*7) + sprintf "\t%d" (n*8) + sprintf "\t%d" (n*9) + sprintf "\t%d" (n*10)
   else
      sprintf "\t%d" 1 + sprintf "\t%d" 2 + sprintf "\t%d" 3 + sprintf "\t%d" 4 + sprintf "\t%d" 5 + sprintf "\t%d" 6 + sprintf "\t%d" 7 + sprintf "\t%d" 8 + sprintf "\t%d" 9 + sprintf "\t%d" 10
printfn "%s" (rec 6)
\$\endgroup\$
2
  • 2
    \$\begingroup\$ You know sprintf can take multiple arguments? \$\endgroup\$ Sep 26, 2016 at 22:41
  • 2
    \$\begingroup\$ Please don't do let rec rec. It looks confusing. Call it loop or something, but don't reuse a keyword. \$\endgroup\$
    – asibahi
    Sep 27, 2016 at 11:36

2 Answers 2

6
\$\begingroup\$

Firstly, sprintf doesn't only take one argument, that wouldn't be very useful. It takes as many arguments (and of corresponding types) as are mentioned in the format string.

So instead of this:

   sprintf "%d" (n*1) + sprintf "\t%d" (n*2)

you could do this:

   sprintf "%d\t%d" (n*1) (n*2)

Secondly, all those sprintf calls look awfully similar to me. And when there is a repetition, there is usually a way to shorten it by parametrizing. Your first line of sprintfs could look something like this:

   let columns = [1..10] |> List.map ( (*) n ) |> String.concat "\t"
   sprintf "\n%s" columns

And your second line of sprintfs doesn't need any sprintfs at all - it's all constant!

   "\t1\t2\t3\t4\t5\t6\t7\t8\t9\t10"

(although you could also use a list for clarity, especially if you want to make this useful for a varying number of columns)

Thirdly, this recursive function will never complete. Look at it closely: there is never a case where this function does not call itself. When n=0, it will call itself with n=6, and when n>0, it will call itself with n=n-1. So that, once you call it, it will just keep calling itself indefinitely. Well, not completely indefinitely, but until it runs out of stack.

From your explanation ("print a table"), I conclude that what you really wanted is to first print the header (the constant string), and then under it print 6 rows of 10 numbers each. Right? If so, the base case should just return header, and each non-base case should construct its row and prepend previous result to it:

let row n = 
   [1..10]
   |> List.map ( (*) n ) 
   |> String.concat "\t"
   |> sprintf "\n%s"

let rec table n =
   if n > 0 then table (n-1) + row n
   else "\t1\t2\t3\t4\t5\t6\t7\t8\t9\t10"

And fourthly, recursion is not really required for this task. True, you can encode any sequential process as a recursive function, but recursion is hard. It's tricky to get right (as this example illustrates), and even trickier to understand when reading the code. The usual approach to such problems is to encode the recursion once (in core library) as a parametrized function (for linear processes, such function is usually called fold), and then use this function, or its derivatives, in higher-level code. In your particular case, since you just want to produce a few strings and concatenate them, I would use map:

let row n = 
   [1..10]
   |> List.map ( (*) n ) 
   |> String.concat "\t"
   |> sprintf "\n%s"

let table n = 
   let header = "\t1\t2\t3\t4\t5\t6\t7\t8\t9\t10"
   let rows = [1..n] |> List.map row
   sprintf "%s\n%s" header (String.concat "\n" rows)
\$\endgroup\$
1
\$\begingroup\$

A recursive solution could be:

let print rows cols = 
  let listRow row =  ([1 .. cols] |> List.fold (fun str n -> sprintf "%s\t%d" str (n * row)) "")
  let head = listRow 1

  let rec doRow row = 
    if row <= rows then
      let curline = sprintf "%d%s" row  (listRow row)
      let nextlines = doRow (row + 1)
      sprintf "%s\n%s" curline nextlines
    else
      ""
  sprintf "%s\n%s" head (doRow 1)

[<EntryPoint>]
let main argv = 
  printfn "%s" (print 6 10)

  Console.ReadLine()|>ignore
  0

And a list based version:

let print rows cols = 
  let listRow row = 
    [1 .. cols] 
    |> List.map (fun n -> sprintf "%d" (n * row)) 
    |> String.concat "\t"

  let header = sprintf "\t%s" (listRow 1)

  let table = 
    [1 .. rows]
    |> List.map (fun n -> sprintf "%d\t%s" n (listRow n))
    |> String.concat "\n"

  sprintf "%s\n%s" header table
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.