jubibanna
jubibanna

Reputation: 1185

F# assignment printf and for loops

I need help with a F# question I got at my first year of data science. Spent quite some hours on this and still can't figure it out.

So the assignment is about "printf" and loops and goes like this:

Make a function:
mulTable : n: int -> string (so the input is an integer and the output is a string)

which takes 1 argument and returns a string containing the first 1 <= n <= 10 lines so the table can be printed with a single printf "%s" statement.

For example a call to mulTable 3 should return:

enter image description here

I don't want the result, but maybe some kind of hint could be so helpful!

Upvotes: 0

Views: 580

Answers (2)

rmunn
rmunn

Reputation: 36668

Here are a few hints to help you get started:

Making strings instead of printing

Besides the printf function that prints to the screen, F# has a function sprintf that works just like printf, but it returns a string instead. E.g.,

let number = 2 + 3
printf "%i" number  // Prints "5", does not return a value
let text = sprintf "%i" number  // Assigns the string "5" to the variable "text"

Parentheses are sometimes needed

One part of F#'s syntax can catch newbies by surprise: the fact that function application has higher precedence than operators. What does this mean in practice? Well, look at this code:

let text = sprintf "Sum: %i" 2+3

This should return "Sum: 5", right? Nope: it will give you a compiler error telling you that you can't add strings and integers. That's because when F# sees a function followed by any number of parameters, it considers function application (applying the parameters to the function) to have higher precedence than other operators. In other words, just like 2+3*4 is treated as 2+(3*4) because multiplication has higher precedence than addition, the precedence of that example code works like this:

let text = sprintf "Sum: %i" 2+3
// is equivalent to
let text = (sprintf "Sum: %i" 2)+3
// but NOT equivalent to
let text = sprintf "Sum: %i" (2+3)

So F# processes this as let text = (sprintf "Sum: %i" 2)+3, which becomes let text = "2"+3. In some languages like Javascript this is legal... but would you expect that to produce the string "5"? Or the integer 5? Or the string "23"? It's ambiguous. So F# forbids adding two different types (like strings and ints) together, so that there will never be ambiguity about how any expression should be interpreted.

Now if you had written that sprintf line in my example, what you probably intended was the (2+3) variant. So if you're using sprintf (or any other function) and one of the things you want to pass it is a calculated value, then you need to put parentheses around the calculated value. This also applies to the results of function calls:

let double x = x * 2
let text = sprintf "Result: %i" double 5   // Compiler error
let text = sprintf "Result: %i" (double 5) // Returns "Result: 10"

Padding a string with spaces or zeroes

If you're using the printf or sprintf functions to print a number or convert a number to a string, you can specify a width in the %i format code, like so:

let shortString = sprintf "%i" 5      // Returns the string "5"
let longString = sprintf "%3i" 5      // Returns the string "  5"
let leftJustified = sprintf "%-3i" 5  // Returns the string "5  "
let zeroPadded = sprintf "%03i" 5     // Returns the string "005"
let tooLong = sprintf "%3i" 9999      // Returns the string "9999"

As you can see, a - character before the width will left-justify the number so that the space padding comes after the number instead of before, and a 0 character before the width will pad with zeroes instead of spaces. The number will never be truncated: if you specify a width that's too small for the number you used (like trying to fit 9999 into a width of 3 in my example), the full number will be printed or returned as a string, which can result in a string that's larger than you expected.

Making a list with for loops

F# also has a feature called list comprehensions. If you're familiar with Python, they work a little like Python's list comprehensions, but with a different syntax (naturally). Specifically, F#'s list comprehensions look kind of like regular F# code, but you use the yield keyword to put values into the list. E.g.,

// A list of all the numbers from 1 to 10
let numbers = [
    for i = 1 to 10 do
        yield i
]
// A list of all the even numbers from 2 to 20
let evenNumbers = [
    for i = 1 to 10 do
        yield i * 2
]
// You can use nested for loops as well
let nestedLoops = [
    for i = 1 to 10 do
        for j = 1 to i do
            yield i + j
]

Combining lists

Now you might already have figured out how you can combine these concepts to make a list of strings like [" 1"; " 2"; " 3"] and so on. Let's say you have two lists that each represent one line of text you want to return — and let's say that you want to combine them with a \n (a newline) in between. One way you could do it is like this:

let combined = list1 @ ["\n"] @ list2

Note that the @ operator means "append two lists", so the things on both sides of the @ must be a list. Doing list1 @ "\n" @ list2 would not have worked, because the string "\n" is not a list.

But if your class hasn't covered the list-append operator @ yet, then here's a way you could combine two lists with just for loops and the yield operator:

let combined = [
    for x in list1 do
        yield x
    yield "\n"
    for x in list2 do
        yield x
]

This isn't the best way to do it (there's a yield! operator that would be better), but the better ways to do it are more advanced subjects that your professor may be planning on covering later. If you want to know about them, leave a comment and I'll explain (or find a good explanation to link to).

Turning a list of strings into a single string

The last piece of the puzzle is how to turn a list of strings into a single string. For that, the String.concat function is best. It takes two parameters: the first one is a string that should be put between each item (and this is allowed to be the empty string). The second parameter is the list of items to combine. Example:

String.concat "+" ["a"; "b"]  // Returns "a+b"
String.concat "" ["  1"; "  2"; "  3"]  // Returns "  1  2  3"

You can take it from here

That should be all the pieces you need to put together to write the function. I've deliberately used examples that are similar to what you need, but not exactly what you need. It's now up to you to figure out how to put the puzzle pieces together and build your function. Good luck!

Upvotes: 3

jubibanna
jubibanna

Reputation: 1185

Figured out a way! Add a for loop and a nested for loop! where in the nested for loop your print (i*j) as if you had for

let function =
    for i = 1 to 10 do 
       for j = 1 to 10 do
          printf "%i" (i*j)

Upvotes: 2

Related Questions