Formatting the multiplication table f #
I am trying to create an N x M multiplication table program using f # where the values ββof n and m are given by the user and the table is calculated and stored in a 2D array and displayed on the console.
Any help would be very grateful for my array to display in a format like:
unlike the one in my program
My source code
open System
let func() =
let multiplication n m = Array2D.init n m (fun n m -> n * m)
printf "Enter N Value: "
let nValue = Console.ReadLine() |> System.Int32.Parse
printf "\nEnter M Value: "
let mValue = Console.ReadLine() |> System.Int32.Parse
let grid = multiplication nValue mValue
printfn "\n\n%A" grid
func()
Also I would like to know how I can get my values ββto start at 1 and not 0.
Any help would be much appreciated as I am new to F #.
source to share
All you have to do is add 1 to n and m before concatenating them, for example
let multiplication n m = Array2D.init n m (fun n m -> (n + 1) * (m + 1))
However, we have some parenthesis craziness, you can refactor it like this:
let myMultFunction n m = (n + 1) * (m + 1)
let multiplication n m = Array2D.init n m myMultFunction
The formatting is going to be a bit tricky, and using for loops is a bit of trickery, not very F #, but given that we are using 2d arrays, which are not functional in nature, I figured I could sneak it :;
printfn "A multiplication table:"
printf " "
for col in 0 .. mValue - 1 do
printf "%d\t" (col + 1)
printfn ""
for row in 0 .. nValue - 1 do
for col in 0 .. mValue - 1 do
if col = 0 then
printf "\n%d| " (row + 1)
printf "%d\t" grid.[row, col]
source to share
Formatting the output, like any user interface task, is usually surprisingly difficult. This case is no exception.
The idea would be like this:
- Specify how wide the "cells" of the table should be.
- Plot each row by concatenating the numbers converted to a string and appended to the cell width.
- Prepare the first line.
- Concatenate all lines separating them with a newline character.
First, let's see how we can determine the width of the "cell". What is the widest number in the table? Assuming both n
and are m
positive, the widest number will be n*m
obvious. So, we can calculate the cell width as follows:
let cellWidth = (n*m) |> string |> String.length
Likewise, the very first (left-most) column would be as wide as the largest number in it, this is n
:
let firstColWidth = n |> string |> String.length
Now let's make ourselves a function that will accept a number and a left panel with spaces to the required width:
let pad totalWidth (value: obj) =
let s = string value
if s.Length >= totalWidth then s
else (String.replicate (totalWidth-s.Length) " ") + s
This function is easy to follow: if the line is already exceeded, just return it, otherwise add spaces (totalWidth-s.Length)
.
With this function, we can format the row of our grid:
let formatRow rowIdx =
let cells = [for colIdx in 0..m-1 -> grid.[rowIdx,colIdx] |> pad cellWidth] // Each cell in this row padded to `cellWidth`
let firstCol = (rowIdx+1) |> pad firstColWidth // Leftmost column - just the row index itself padded to `firstColWidth`
let wholeRow = firstCol :: cells // Whole row consists of the leftmost column plus subsequent cells
String.concat " " wholeRow
Likewise, format the top-most line:
let firstRow =
let cols = [for col in 1..m -> col |> pad cellWidth]
let firstCol = " " |> pad firstColWidth
let wholeRow = firstCol :: cols
String.concat " " wholeRow
See how similar these functions are: the only difference is grid.[rowIdx,colIdx]
vs. col
... Why don't we generalize this?
let formatRowWith firstCell getCell =
let cells = [for colIdx in 0..m-1 -> getCell colIdx |> pad cellWidth]
let firstCol = firstCell |> pad firstColWidth
let wholeRow = firstCol :: cells
String.concat " " wholeRow
let formatRow rowIdx = formatRowWith (rowIdx+1) (fun c -> grid.[rowIdx,c])
let firstRow = formatRowWith " " (fun c -> c+1)
Finally, format each line, add the first and concatenate them all:
let rows = [0..n-1] |> List.map formatRow
let allRows = firstRow :: rows
String.concat "\n" allRows
Final code:
let formatGrid (grid:_[,]) =
let n, m = grid.GetLength 0, grid.GetLength 1
let cellWidth = (n*m) |> string |> String.length
let firstColWidth = n |> string |> String.length
let pad totalWidth (value: obj) =
let s = string value
if s.Length >= totalWidth then s
else (String.replicate (totalWidth-s.Length) " ") + s
let formatRowWith firstCell getCell =
let cells = [for colIdx in 0..m-1 -> getCell colIdx |> pad cellWidth]
let firstCol = firstCell |> pad firstColWidth
let wholeRow = firstCol :: cells
String.concat " " wholeRow
let formatRow rowIdx = formatRowWith (rowIdx+1) (fun c -> grid.[rowIdx,c])
let firstRow = formatRowWith " " id
let rows = [0..n-1] |> List.map formatRow
let allRows = firstRow :: rows
String.concat "\n" allRows
source to share
I shamelessly copied some parts of Fyodor Soikin's excellent answer, but changed the remainder to make the code more "functional" and shorter, albeit less understandable for those with an "imperative" bent.
let pad totalWidth (value: obj) =
let s = string value
if s.Length >= totalWidth then s
else (String.replicate (totalWidth - s.Length) " ") + s
let formatGrid (grid:_[,]) =
let n, m = grid.GetLength 0, grid.GetLength 1
let cellWidth = (n*m) |> string |> String.length
let firstColWidth = n |> string |> String.length
let frow str (rw: int []) = (Array.fold (fun s i -> s + (pad cellWidth i)) str rw) + "\n"
let firstRow = frow ("\n" + pad (firstColWidth+2) "") [|1..m|]
let folder str i = str + (frow ((pad firstColWidth (i+1)) + "| ") grid.[i, 0..])
List.fold folder firstRow [0..(n-1)]
let a2d = array2D [[1;2;3]; [4;5;6]]
formatGrid a2d
Output:
val it : string = "
123
1| 123
2| 456
"
source to share