2

I have to create a calculator in F#, and im stuck on a task.

I need to pass a sum in as a string into the console e.g. "4 + 5" and parse and calculate it.

any ideas?

any help would be greatly appreciated

open System

let rec calculator() =
    printf "Choose a sum type \n1: Addition\n2: Subtraction\n3: Mulitiplication\n4: Division\n\n\n"

    let input = Console.ReadLine();

    printf "now type in 2 numbers\n"

    let num1 = Console.ReadLine();
    let num2 = Console.ReadLine();

    let a : int = int32 num1
    let b : int = int32 num2

    let addition x y = x + y
    let subtraction x y = x - y
    let multiply x y = x * y
    let divide x y = x / y 

    match input with

    | "1" -> printfn("The result is: %d")(addition a b)
    | "2" -> printfn("The result is: %d")(subtraction a b)
    | "3" -> printfn("The result is: %d")(multiply a b)
    | "4" -> printfn("The result is: %d")(divide a b)

    ignore(Console.ReadKey())
    ignore(calculator())

calculator()
Guy Coder
  • 24,501
  • 8
  • 71
  • 136
  • See: [Int32.Parse Method (String)](https://msdn.microsoft.com/en-us/library/b3h1hf19(v=vs.110).aspx) – Guy Coder Jul 01 '16 at 13:44
  • 1
    Of interest: [Formula Calculator](http://www.fssnip.net/4Y) – Guy Coder Jul 01 '16 at 13:44
  • I had a look at the Int32.Parse method, but struggling with how to read in the operator for the sum – Kane Punter Jul 01 '16 at 13:46
  • 2
    The Formula Calculator example has more detail than you need. I posted it because most people who ask similar questions will need the added detail as they progress. If you take the time to understand it then you will have your answer and a better understanding of parsing and evaluating expressions. – Guy Coder Jul 01 '16 at 13:50
  • I don't plan to provide an answer as these questions typically turn into a discussion which I want to avoid. – Guy Coder Jul 01 '16 at 13:51
  • 1
    Of interest: [Calculator Walkthrough](https://fsharpforfunandprofit.com/posts/calculator-design/) – Guy Coder Jul 01 '16 at 13:53
  • Possible duplicate: [How can evaluate an expression stored as a string in F#](http://stackoverflow.com/questions/372043/how-can-evaluate-an-expression-stored-as-a-string-in-f) – Guy Coder Jul 01 '16 at 13:54
  • i wouldn't just want an asnwer. I'd much prefer is someone could help me breakdown the problem and help me understand it better because im struggling learning this by myself – Kane Punter Jul 01 '16 at 13:55
  • 1
    FParsec has a pretty decent and succinct example: https://bitbucket.org/fparsec/main/src/5778ffc05aeb0d3a43fc0142f365784532cbabe5/Samples/Calculator/calculator.fs?at=default&fileviewer=file-view-default – Just another metaprogrammer Jul 01 '16 at 13:56
  • i dont think that i am allowed to use external libraries for my assignment – Kane Punter Jul 01 '16 at 13:58
  • @FuleSnabel I agree that FParsec is a valid solution but at this point I think it would overwhelm the OP as formal parsing seems to be beyond their current knowledge base. – Guy Coder Jul 01 '16 at 13:58
  • And now the other shoe falls, so this is an assignment. Care to share the course and problems so that we can help you but not do all of the work for you. Also assignments that get answered here tend to get noticed by the instructor and then the class is banned from using StackOverflow. If you only get hints the instructor tends to allow it. – Guy Coder Jul 01 '16 at 14:00
  • See the comments for this question: [Fparsec recursive grammatics throw StackOverflowException](http://stackoverflow.com/q/37077358/1243762) – Guy Coder Jul 01 '16 at 14:03
  • im not too sure how to share the course and problems without sharing the assignment brief, which i think is risky. I could show you the current code i have? – Kane Punter Jul 01 '16 at 14:11
  • As a general rule, showing "This is what I've done so far, but now I have a problem that I don't know how to solve" will get FAR better answers than "How do I do this task?" The latter kinds of questions tend to get closed. As for your main question, you want to take a two-step approach: first, split that string into tokens via some rule. Like, "a series of digits is a number token. A + or - is an operator token." Then write another function that looks at each token via some `match` expression and decides what to do with it. – rmunn Jul 01 '16 at 14:15
  • Are the assignments pointed online? If so just put the link in the comments. For the most part most I think you might get an answer, but it will leave you asking more questions than helping. Also there are many ways to solve this problem and different people will give you different ways. That will become a major problem for you because with lack of knowledge you will not be able to sort out what helps with your version and what it means resulting in geting more lost. My best advise is to find one example that can makes sense to you and then use only that one for learning. – Guy Coder Jul 01 '16 at 14:16
  • this is what ive done so far : https://kane_punter@bitbucket.org/kane_punter/calculator-f.git – Kane Punter Jul 01 '16 at 14:17
  • i posted a link. it wont let me post the code itself as its too long – Kane Punter Jul 01 '16 at 14:21
  • You are further away from a solution than I expected. How much programming experience do you have and what is the title of the course; I am trying to understand if it is a parsing course or F# course. I suspect it is an F# course, in which case discussing formal parsing should be avoided for this answer. – Guy Coder Jul 01 '16 at 14:29
  • it's was a course on task based S/E. had a couple assignments in c# which ive done. just stuck on this f# one – Kane Punter Jul 01 '16 at 14:34
  • What does S/E mean? – Guy Coder Jul 01 '16 at 14:37
  • Software Engineering – Kane Punter Jul 01 '16 at 14:38
  • If the input is always "4 + 5" or `variable op variable` then just use simple string splitting with `Int32.Parse` and be done with it. You really need to be more specific with what the assignment says. – Guy Coder Jul 01 '16 at 14:40
  • okay. i'll look more into that way of doing it – Kane Punter Jul 01 '16 at 14:42
  • @GuyCoder im also going to look into Regex – Kane Punter Jul 01 '16 at 15:35

2 Answers2

4

Parsing expressions like 4 + 5 in a robust way usually involves relying on good tools like FsLex/FsYacc or FParsec.

Seasoned developers that like do things from scratch (not always a good thing) might implement something called a Recursive-Decent-Parser.

Others found all their parsing needs to be answered by RegEx. RegEx is however limited in what you can achieve with it. For instance how do you define a RegEx that parses the following expression correctly: 3 + 3*(x+3)/2. In addition RegEx code tend to be obscure and hard to decode, consider this common snippet for email verification:

^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$

In addition to being complicated it's also incomplete. More complete ones can be found here: Using a regular expression to validate an email address.

In addition RegEx tend not to produce any error messages to tell the user why an expression failed to parse.

Another common (incomplete & inefficient) parsing approach used by many is using String.Split to split the string on the operators.

For instance:

let r = 
  "1+2+3".Split '+'             // Produces an array: [|"1", "2", "3"|]
  |> Seq.map int                // Maps to seq of ints
  |> Seq.reduce (+)             // Reduces the seq using (+)

Taken to it's logical conclusion you can end up with a parser that looks like this

// Very inefficient, can't handle sub expressions, no error reporting...
//  Please use this as an illustration only, not production code
let stupidParse =
  let split (c: char) (s: string) = s.Split c
  let trim (s: string) = s.Trim ()
  let op c r f  = split c >> Seq.map (trim >> f) >> Seq.reduce r
  int |> op '/' (/) |> op '*' (*) |> op '-' (-) |> op '+' (+)

[<EntryPoint>]
let main argv = 
  let examples = [| "1"; "1-3"; "1*3"; "1 + 2*3 - 2" |]
  for example in examples do
    printfn "%s -> %d" example <| stupidParse example
  0

But as said in the comment I would never want a parser like this to enter production code. Use the proper tools like FsLex/FsYacc or FParsec.

Community
  • 1
  • 1
4

I need to pass a sum in as a string into the console e.g. "4 + 5" and parse and calculate it.

If you are sure that your string is a sequence of numbers seperated by '+' and possibly white spaces, you can do something like this:

"4 + 5".Split '+' |> Seq.sumBy (int)

What does it do? .Split '+' separates the string by the character + and creates sequence of strings. In this example, the sequence would look like [|"4 "; " 5"|]. The function Seq.sumBy applies a given function to a each element of a sequence and sums up the result. We use the function (int) to convert strings to numbers.

Be aware that this solution fails miserably if the string either contains a character other than +, whitespace and digits or if a string without a digit is separated by + (e.g. + 7 + 8 or 7 ++ 8).

You might want to catch System.FormatException. You'd end up with something like

let sumString (input:string) : int = 
    try
        input.Split '+' |> Seq.sumBy (int)
    with
    | :? System.FormatException ->
        print "This does not look like a sum. Let's just assume the result is zero."
        0

This would just output 0 for any invalid formula. Another option to avoid the exception is throwing away all unwanted characters and empty strings:

 let sumString (input:System.String) : int = 
    (input |> String.filter (fun c -> ['0'; '1'; '2'; '3'; '4'; '5'; '6'; '7'; '8'; '9'; '+'] |> List.contains c)).Split '+'
    |> Seq.filter (((<) 0) << String.length)
    |> Seq.sumBy (int)

What does this code do? String.filter asks our anonymous function for every character whether it shall be considered. Our anonymous function checks whether the character is in a list of allowed characters. The result is a new string containing only digits and +. We split this string by +.

Before we pass our list of strings to Seq.sumBy (int), we filter our empty strings. This is done with Seq.filter and a composition of functions: (<) returns true if the first parameter is less than the second one. We use currying to obtain (<) 0, which checks whether the given integer is bigger than 0. We compose this function with String.length which maps a string to an integer telling its length.

After letting Seq.filter play with this function, we pass the resulting list to Seq.sumBy (int) as above.

This, however, might lead to quite surprising results for anything else than sums. "4 * 5 + 7" would yield 52.

fsharpnoob
  • 103
  • 4