我必须在F#中创建一个计算器,并且我坚持完成任务。
我需要将一个总和作为字符串传递到控制台,例如“4 + 5”并解析并计算它。
任何想法?
任何帮助将不胜感激
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()
答案 0 :(得分:3)
以强大的方式解析4 + 5
等表达式通常需要依赖FsLex/FsYacc
或FParsec
等优秀工具。
喜欢从头开始做事的经验丰富的开发人员(并不总是一件好事)可能会实现一个名为Recursive-Decent-Parser的东西。
其他人发现他们所有的解析都需要RegEx
来回答。然而,RegEx
对于您可以实现的目标是有限的。例如,如何定义正确解析以下表达式的RegEx
:3 + 3*(x+3)/2
。此外,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])?)*$
除了复杂之外,它还不完整。更完整的内容可以在这里找到:Using a regular expression to validate an email address。
此外,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/FsYacc
或FParsec
等正确的工具。
答案 1 :(得分:3)
我需要将一个总和作为字符串传递到控制台,例如&#34; 4 + 5&#34;并解析并计算它。
如果你确定你的字符串是由'+'
和可能是空格分隔的数字序列,你可以这样做:
"4 + 5".Split '+' |> Seq.sumBy (int)
它做什么? .Split '+'
用字符+
分隔字符串并创建字符串序列。在此示例中,序列看起来像[|"4 "; " 5"|]
。函数Seq.sumBy
将给定函数应用于序列的每个元素并总结结果。我们使用函数(int)
将字符串转换为数字。
请注意,如果字符串包含+
以外的字符,空格和数字,或者没有数字的字符串被+
分隔,则此解决方案会失败(例如+ 7 + 8
或7 ++ 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
。我们使用currying来获取(<) 0
,它检查给定的整数是否大于0
。我们用String.length
组成这个函数,它将一个字符串映射到一个告诉它长度的整数。
让Seq.filter
玩这个功能后,我们将结果列表传递给Seq.sumBy (int)
,如上所述。
"4 * 5 + 7"
会产生52
。