1

我一直在尝试计算整数 3 元组列表中的元素,这等于使用 SML 的给定整数,但它不起作用。谁能帮我弄清楚下面的代码有什么问题或为我整理一下?

 fun number_in_month(x : int*int*int list, m: int) =
    if null x then 0

         else
           let fun inc x = x + 1;
         in
             val counter = 0;
             if m = #2 (hd x) andalso m > 0 then inc counter
            number_in_month((tl x), m)
           `  else
             number_in_month((tl x), m)
        end

这个函数应该返回 m 等于列表中每个元组的第二个元素的次数。

4

2 回答 2

5

显然,你很难放弃你的命令式思维。

让我尝试解决您的一些问题

  • 您应该使用模式匹配而不是使用null x,hd xtl x. 这也适用于分解元组和记录。例如

    fun number_in_month ((x1, x2, x3) :: xs, m) = ...
    

    或者,因为我们从不使用 x1 和 x3

    fun number_in_month ((_, x2, _) :: xs, m) = ...
    

    这样可以清楚地看到第一个参数是一个三元组列表,不需要类型注释

    此外,当您省略显式类型注释时,这是拥有一个可以为您推断它们的类型系统的整个想法(见下一点),那么这段代码

    fun foo42 xs = map (fn x => #2 x) xs
    

    会给你一些关于“未解决的弹性记录”的令人讨厌的错误(这个错误信息来自 SML/NJ)

    /tmp/sml20620PlF:105.5-105.44 Error: unresolved flex record
       (can't tell what fields there are besides #2)
    

    这很容易通过分解三元组来解决

    fun foo42 xs = map (fn (_, x2, _) => x2) xs
    
  • 说到类型注释。它们(几乎总是)不需要,并且会混淆代码的可读性。更不用说它们不必要地限制了您可以使用函数的类型。

    根据您真正不想要的,您给出的类型注释也是错误的。您应该在int * int * int. 目前它被解释为两个 int 和一个 int list 的三元组int * int * (int list)

    如果你真的坚持输入注释你的函数,那么你可以这样做

    val number_in_month : (int * int * int) list * int -> int = 
        fn ([]            , m) => 0
         | ((_,x2,_) :: xs, m) => 42
    

    这“几乎”像 Haskell 一样,类型在函数声明之前给出。

  • 尝试在缩进代码的方式上更加一致。这会给你更好的清晰度。在这里,我特别考虑了您缩进else部分结束in ... end 部分的方式。下面的部分显然在很多方面仍然是错误的,我无法开始想象,但它给出了如何做到这一点的想法

    fun number_in_month(x : int*int*int list, m: int) =
        if null x then 0
        else
          let fun inc x = x + 1;
          in
            val counter = 0;
            if m = #2 (hd x) andalso m > 0 then
               inc counter
               number_in_month((tl x), m)
            else
               number_in_month((tl x), m)
          end
    
  • 您不能在let 表达式的部分val counter = 0内声明变量。in ... endlet 表达式的语义是

    let
      dec
    in
      exp_1; ...; exp_n
    end
    

    因此所有声明(函数和值绑定等)都必须放在该let ... in部分中。

  • 地球上不需要增量功能,它只会使可读性变得混乱。请记住,SML 使用单一赋值,因此变量在声明后是不可变的。

  • 嵌套 if 表达式中的序列事物

    inc counter
    number_in_month((tl x), m)
    

    完全没有意义。您可以在部件内拥有多个表达式 then ... else(实际上是任何需要单个表达式的地方)的唯一方法是使用序列 (exp_1; ...; exp_n)。然而,这仅在除最后一个表达式之外的所有表达式都有副作用时才可用,因为它们的结果被忽略/丢弃

    - (print "Foo\n"; print "Bar\n"; 42);
    Foo
    Bar
    val it = 42 : int
    

如果您在 SO 上搜索一下,您会看到最近提出回答了一个非常相似的问题。尽管最后一个参数的类型有所不同,但您仍然可能会得到一些有用的指针。

总而言之,解决方案可能看起来像

fun number_in_month ([], _) = 0
  | number_in_month ((_,x2,_) :: xs, m) = 
    if x2 = m then
      1 + number_in_month(xs, m)
    else
      number_in_month(xs, m)

但是,由于您的问题比前面提到的问题更简单,您可以轻松地使用基础库中 list 模块中的一些高阶函数

fun number_in_month (xs, m) = length (List.filter (fn (_, x2, _) => x2 = m) xs)

甚至(可以说)更简单,通过折叠列表并在每次匹配时沿途增加一个变量

fun number_in_month (xs, m) = foldl (fn ((_, x2, _), b) => if x2 = m then b+1 else b) 0 xs
于 2013-01-20T22:41:21.070 回答
3
fun number_in_month (L : (int*int*int) list, m : int) =
if L = nil
then 0
else
    (if #2 (hd L) = m then 1 else 0) + number_in_month (tl L,m);

测试:

number_in_month ([] , 2);
number_in_month ([(1,2,3)] , 2);
number_in_month ([(1,2,3),(2,2,2)] , 2);
number_in_month ([(1,2,3),(2,2,2),(19,11,29)] , 2);
number_in_month ([(1,2,3),(2,2,2),(19,11,29),(10,28,19)] , 2);
number_in_month ([(1,2,3),(2,2,2),(19,11,29),(10,2,19)] , 2);
number_in_month ([(1,2,3),(2,2,2),(19,11,29),(10,28,19)] , 2);
number_in_month ([(1,2,3),(2,2,2),(19,11,29),(10,28,19)] , 2);
number_in_month ([(1,2,3),(2,2,2),(19,11,29),(10,28,19),(16,2,7)] , 2);

参考:

http://www.cs.sunysb.edu/~leo/CSE215/smllistexamples.txt

http://www.standardml.org/Basis/list.html

于 2013-11-24T17:30:46.770 回答