f#如何解析和计算字符串输入的计算器表达式



我必须在f#中创建一个计算器,我被一个任务卡住了。

我需要将一个sum作为字符串传入控制台,例如:"4 + 5"并解析计算

任何想法?

任何帮助都将不胜感激

open System
let rec calculator() =
    printf "Choose a sum type n1: Additionn2: Subtractionn3: Mulitiplicationn4: Divisionnnn"
    let input = Console.ReadLine();
    printf "now type in 2 numbersn"
    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()

我需要将一个sum作为字符串传入控制台,例如:"4 + 5"并解析计算

如果你确定你的字符串是一个由'+'和空格分隔的数字序列,你可以这样做:

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

它是做什么的?.Split '+'用字符+分隔字符串并创建字符串序列。在本例中,序列看起来像[|"4 "; " 5"|]。函数Seq.sumBy将给定函数应用于序列的每个元素,并对结果求和。我们使用(int)函数将字符串转换为数字。

请注意,如果字符串包含+以外的字符,空格和数字,或者没有数字的字符串被+(例如+ 7 + 87 ++ 8)分隔,则此解决方案将失败。

你可能想抓住System.FormatException。你最终会得到像

这样的东西
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

对于任何无效的公式,这只会输出0。避免该异常的另一个选项是丢弃所有不需要的字符和空字符串:

 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)

这段代码是做什么的?String.filter为每个字符询问我们的匿名函数是否应该考虑它。匿名函数检查字符是否在允许的字符列表中。结果是一个只包含数字和+的新字符串。我们用+分割这个字符串。

在将字符串列表传递给Seq.sumBy (int)之前,先过滤空字符串。这是用Seq.filter和一个函数组合来完成的:如果第一个参数小于第二个参数,(<)返回true。我们使用curry来获得(<) 0,它检查给定的整数是否大于0。我们将这个函数与String.length组合在一起,CC_22将一个字符串映射为一个整数,告诉它的长度。

Seq.filter运行此函数后,我们将结果列表传递给Seq.sumBy (int),如上所述。

然而,这可能会导致除和之外的任何结果非常令人惊讶。"4 * 5 + 7"会产生52

以健壮的方式解析像4 + 5这样的表达式通常需要依赖像FsLex/FsYaccFParsec这样的好工具。

喜欢从头开始做事情的经验丰富的开发人员(并不总是一件好事)可能会实现称为递归-体面-解析器的东西。

其他人发现他们所有的解析需要由RegEx来回答。然而,RegEx在您可以实现的目标方面是有限的。例如,如何定义一个正确解析以下表达式的RegEx:此外,RegEx代码往往晦涩难懂,难以解码,请考虑以下用于电子邮件验证的通用代码片段:

^[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])?)*$

除了复杂之外,它也是不完整的。可以在这里找到更完整的示例:使用正则表达式验证电子邮件地址。

此外,RegEx倾向于不产生任何错误消息来告诉用户表达式解析失败的原因。

另一个常见的(不完整&许多人使用的解析方法是使用String.Split在操作符上分割字符串。

例如:

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 (+)

考虑到它的逻辑结论,你最终可以得到一个解析器,看起来像这样

// 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

但是正如在评论中所说,我永远不希望这样的解析器进入生产代码。使用适当的工具,如FsLex/FsYaccFParsec

最新更新