如何在SML/NJ中优雅打印含换行的Array2二维字符串数组?
这个问题我之前也碰到过!核心原因是没注意Array2.fold的折叠函数参数顺序和字符串拼接的结合方向不匹配,导致每行内容被反转了。让我拆解一下问题并给出优雅的解决方案:
问题根源
你用的Array2.fold Array2.RowMajor (op ^) "" grid,这里的op ^是标准的字符串拼接操作,它的参数顺序是(string * string) -> string,也就是第一个参数拼在第二个参数的前面。而Array2.fold的折叠逻辑是:遍历元素时,把当前元素作为第一个参数,累加器作为第二个参数传给折叠函数。
举个例子,假设某一行的元素是["h","e","l","l","o","\n"],折叠过程会是这样:
- 初始累加器:
"" - 处理
"h":"h" ^ "" = "h" - 处理
"e":"e" ^ "h" = "eh" - 处理
"l":"l" ^ "eh" = "leh" - ...以此类推,最终得到
"\nolleh",自然每行内容就反转了,连换行符都跑到前面去了。
优雅的解决方案
方案1:调整折叠函数的参数顺序
只需要把拼接逻辑改成累加器在前,当前元素在后,用匿名函数替换op ^即可:
print (Array2.fold Array2.RowMajor (fn (elem, acc) => acc ^ elem) "" grid)
这样遍历行内元素从左到右时,每次都是把新元素拼在累加器的末尾,完全符合正常的打印顺序。比如刚才的例子会得到"hello\n",完美达到预期。
方案2:结合列表的foldr(如果你习惯列表操作)
如果更熟悉列表的foldr逻辑(它天生适合正序拼接字符串),可以先把二维数组转成行的列表,再逐行用foldr处理后拼接:
fun printGrid grid = let val rowList = Array2.rowList grid (* 将Array2转为行的列表 *) val fullContent = String.concat (map (fn row => foldr op ^ "" row) rowList) in print fullContent end
这个方案更贴近你提到的“列表用foldl/foldr轻松处理”的思路,不过相比方案1多了一步转列表的操作,适合习惯列表式编程的场景。
补充说明
列表的foldl和foldr之所以能轻松处理,是因为我们可以灵活选择参数顺序:用foldl时用fn (x, acc) => acc ^ x正序拼接,用foldr时直接用op ^就能正序拼接(因为foldr从右到左遍历,每次把元素拼在后面)。而Array2的fold只有左折叠的实现,所以必须注意函数的参数顺序是否符合需求。
内容的提问来源于stack exchange,提问作者bolzano

