将 char 列表转换为 int 列表时的 ML 列表键入

ML list typing when converting char list to int list

我在将字符列表转换为 int 列表时遇到了问题。我的目标基本上是采用诸如 325 之类的数字,并使其 return 成为 [3,2,5] 的列表。到目前为止,我所做的是获取数字,然后将其转换为字符串,然后将其分解为 char 数组。然后我想将每个字符转换为相应的 int。当我将我的字符列表映射到 fn c => Char.ord(c) 时,字符列表变成了一个 ?.int 列表,这阻止了我对其进行操作(+,-)。我是 ML 的新手,对它的类型系统不是很了解,但对我来说似乎很奇怪。

代码如下:

open IntInf;

fun fact_helper (0, r : int) = r
  | fact_helper (n : int, r : int) = fact_helper (n-1, n*r);

fun factorial n:int = fact_helper (n, 1);

fun num_to_digits n =  
    let val digits_as_chars = explode (IntInf.toString n);
    in map (fn c => (Char.ord c)) digits_as_chars
    end;

理想情况下,我希望能够在我的映射函数中执行 fn c => (Char.ord c) - 48 以获得真正的数字值。我以前做过类似的事情并且它当时有效但现在没有而且我不确定为什么我得到 ?.int list 类型。原始问题可以找到 Project Euler problem 20.

如果你想把一个数字变成一个数字列表,你可以使用这个递归公式(@ 是列表追加运算符)

list(digits(num)) = list(digits(num/10)) @ list(n % 10)

SMLNJ 中的解决方案是这样的:

fun num_to_array 0 = []
|   num_to_array n = num_to_array(n div 10) @ [n mod 10];

问题是你做了 open IntInf,所以类型 int 和运算符 + 和朋友现在引用 IntInf 模块。普通的 int 类型被 IntInf.int 覆盖,因此被打印为 ?.int (SML/NJ 使用伪语法 ?.x 来引用不可访问范围中的名称)。 Char.ord returns普通int类型。

所以您的代码不一定有错,但 open 可能会产生混淆效果。通常应避免在顶层范围内使用 open

如果您真的希望 num_to_digits 函数使用无限整数进行计算,那么您必须包装对 IntInf.fromInt 的调用(或只是 fromInt,因为 IntInf被打开)在Char.ord c.

左右

首先对您的代码进行一些反馈:

  • (fn c => (Char.ord c)) 中的内括号不是必需的。
  • 因为 Char.ord 等同于 fn c => Char.ord c,你可以写成 map ord chars.
  • fun factorial n:int = ...并不是你想的那个意思。这里的 :int 部分是指 factorial 的 return 类型,顺便说一句,它与 n 的类型相同。您可能想说但由于类型推断而不必说的是:

    fun factorial (n : int) : int = ...
    
  • 一般不需要类型注解。代码非常简单易读:

    fun fact_helper (0, r) = r
      | fact_helper (n, r) = fact_helper (n-1, n*r);
    
    fun factorial n = fact_helper (n, 1);
    

接下来,根据 Andreas'es 和 galfisher 的建议,您可能希望同时使用 IntInf 和数字运算符。此外,IntInf 中有一个非常简洁的函数,称为 divMod,它可以同时提供除法和余数:

open IntInf

fun digits n =
    let fun aux n res =
            case divMod (n, 10) of
                 (0, d) => d::res
               | (n', d) => aux n' (d::res)
    in aux n [] end

但是您什么时候真正需要数字列表?您很可能希望递归该列表并构建其他内容,例如数字总和,或其他。这种递归模式——连续访问列表中的每个元素——也可以直接应用于数字并概括为折叠:

(* f is the operator that we fold with
 * e is the initial accumulated value (temporary result)
 * n is the number on which we fold across
 *)
fun folddigits f e n =
    case divMod (n, 10) of
         (0, d) => f (d, e)
       | (n', d) => folddigits f (f (d, e)) n'

有了这个,你可以很容易地用 :: 运算符将数字折叠成一个列表:

fun digits n = folddigits (fn (d, res) => d::res) [] n

或者如果您意识到语法糖 op::fn (d, res) => d::res) 完全相同,并且还对参数 n 执行 eta conversion:

val digits = folddigits op:: []

或数字总和(递归应用直到剩下一位数字):

val sum_of_digits = folddigits
    (fn (d, res) => let val res = d + res in
                      if res < 10 then res else 1 + (res mod 10)
                    end) 0