在 sml 中创建堆栈

creating stack in sml

我正在尝试在 sml 中创建堆栈,我尝试过使用列表;但我无法将元素添加到列表中。我正在尝试从输入文件中读取行,假设该行显示:

push 5
push 9
add
quit

那么我希望输出文件是:

14

因为 5+9 是 14。 到目前为止,我已经能够创建布尔函数来识别该行是推送的还是具有数字的。

fun is_digit (c) = #"0" <= c andalso c <= #"9";
fun is_Push (c) = String.isSubstring "push" c;

fun stack(inFile : string, outFile : string) =
let
        val ins = TextIO.openIn inFile;
        val outs = TextIO.openOut outFile;
        val readLine = TextIO.inputLine ins;
        val it = []: string list;
        fun helper(readLine : string option) =
            case readLine of
                NONE => ( TextIO.closeIn ins; TextIO.closeOut outs)
                | SOME(c) => (
                    if is_Push c 
                    then
                    let
                        val number = String.sub(c,5);
                        val numbChar = Char.toString number;

                    in
                        val myList = nil :: numbChar;
                        TextIO.output(outs, Int.toString(length myList))
                    end

                    else 
                        TextIO.output(outs, "aaa\n");
                        helper(TextIO.inputLine ins))

in
        helper(readLine)
end

使用@ 添加到列表。例如。 it = it @ [number];

顺便说一下,我建议您将 c 重命名为 sline,因为 c 通常用于单个字符,而不是字符串。这会让你的程序的人 reader 感到困惑。

我建议将入栈和出栈放在列表的前面,实际入栈和出栈是通过模式匹配实现的,并将(修改后的)堆栈作为参数传递。

假设您有一个字符串列表,例如

["push 5", "push 9", "add", "quit"]

并且您想根据以下规则处理此字符串:

1) If the string is of the form "push d" (where d is a single digit) then
   push the integer value of d onto the stack

2) If the string is of the form "add" then pop the first two elements off
   the stack, add them, and push the sum back on

3) If the string is "quit" then return the top of the stack

在情况 3 中,您实际上 return 一个值,在其他情况下 - 在行列表的尾部调用处理函数并使用适当修改的堆栈。类似于:

fun process ([], stack) = 0
|   process ("quit"::lines, i::stack) = i
|   process ("add"::lines, i::j::stack) = process(lines,(i+j)::stack)
|   process (s::lines, stack) = 
        let 
            val d = String.sub(s,5)
            val i = Char.ord d - Char.ord(#"0")
        in
            process(lines,i::stack)
        end;

我在一个空的行列表中加入了 returning 0 的基本情况,但没有提供真正的错误检查。特别是 - 如果在堆栈少于 2 个元素时遇到 "add",它将因 运行 时间错误而崩溃,如果使用空堆栈调用 "quit" 将导致崩溃。

要使用它,请使用行列表和空堆栈调用它:

- process (["push 5", "push 9", "add", "quit"],[]);
val it = 14 : int

我会把问题分解成不同的关注点;特别是,将执行 I/O 的函数与纯函数分开。这使它们更易于测试,以后更容易编写。

  1. 定义堆栈命令的抽象表示并将您的文件转换为该抽象表示。定义一个异常类型来处理不正确的堆栈命令。这意味着您的数据表示已清理。

    datatype StackCommand = Push of int
                          | Pop
                          | Add
                          | Quit
    
    exception InvalidCommand of string * string list
    
  2. 创建可重用的辅助函数以从文件中读取行。

    fun isLinebreak c = c = #"\n"
    
    fun inputLines filename =
        let val fd = TextIO.openIn filename
            val content = TextIO.inputAll fd
            val _ = TextIO.closeIn fd
        in String.tokens isLinebreak content
        end
    
  3. 将解析单个命令的关注点分离到一个函数中,并使其需要正确命令的正确数量的参数。

    fun parseCommand line =
        case String.tokens Char.isSpace line of
             ["push", s] => (case Int.fromString s of
                                 SOME i => Push i
                               | NONE => raise InvalidCommand (push, s))
           | ["pop"] => Pop
           | ["add"] => Add
           | ["quit"] => Quit
           | (cmd::args) => raise InvalidCommand (cmd, args)
    
    val parseCommands = map parseCommand
    
    val inputCommands = parseCommands o inputLines
    
  4. 将堆栈定义为整数列表。给定一个堆栈,通过迭代此列表并根据命令更新堆栈来评估命令列表。

    type stack = int list
    exception StackError of stack * StackCommand
    
    fun evalCommand stack (Push i) = i::stack
      | evalCommand (_::stack) Pop = stack
      | evalCommand (i::j::stack) Add = i+j::stack
      | evalCommand stack Quit = stack
      | evalCommand stack cmd = raise StackError (stack, cmd)
    
    fun evalCommands stack [] = stack
      | evalCommands stack (Quit::_) = stack
      | evalCommands stack (cmd::cmds) =
        evalCommands (evalCommand stack cmd) cmds
    

然后您可以在 inputCommands 的 return 值上使用 evalCommands,或者您可以在从标准输入交互读取的 REPL 中使用 evalCommand