在SML中实现算法时,我常常想知道是否有一种简单的方法可以使代码大量使用数组更具可读性。例如,如果我定义一个SML函数来交换数组中的2个元素,那么代码就是......
local open Array in
fun exch (a, i, j) =
let
val tmp = sub (a, i)
val _ = update (a, i, sub (a, j))
val _ = update (a, j, tmp)
in () end
end
我希望拥有的是一个更易读,更清晰的版本,就像这个Scala-snippet ...
def exch[T](a: Array[T], i: Int, j: Int) {
val tmp = a(i)
a(i) = a(j)
a(j) = tmp
}
对于像在数组中交换2个元素这样简单的东西,SML版本没问题。但是一旦算法变得越来越复杂,代码就变得越来越难以理解,并且会使基础算法模糊不清。
稍微复杂的例子就是这个堆栈(实现为可调整大小的数组)......
structure ArrayStack = struct
type 'a stack = ('a option array * (int ref)) ref
exception Empty
fun mkStack () = ref (Array.array (1, NONE), ref 0)
fun isEmpty (ref (_, ref 0)) = true
| isEmpty _ = false
fun resize (array as ref (xs, n), capacity) =
let
val length = Array.length xs
in
array := (Array.tabulate (
capacity,
fn i => if i < length then Array.sub (xs, i) else NONE
), n)
end
fun push (array as ref (xs, n : int ref), x) =
if Array.length xs = !n then (
resize (array, !n*2)
; push (array, x))
else (
Array.update (xs, !n, SOME x)
; n := !n+1)
fun pop (ref (xs, ref 0)) = raise Empty
| pop (array as ref (xs, n : int ref)) = let
val _ = (n := !n-1)
val x = Array.sub (xs, !n)
val _ = Array.update (xs, !n, NONE)
val q = (Array.length xs) div 4
val _ = if !n > 0 andalso !n = q then resize (array, q) else ()
in
valOf x
end
end
与http://algs4.cs.princeton.edu/13stacks/ResizingArrayStack.java.html的java实现相比,实现(尤其是push / pop)变得难以阅读。
如何让这些代码更具可读性?
答案 0 :(得分:2)
确实,在SML中使用数组相当笨拙。在某种程度上,这是故意的,以阻止它们的使用 - 因为大多数时候,它们不是数据结构的最佳选择。你的堆栈是一个很好的例子,因为它更好地作为列表实现:
structure ListStack =
struct
type 'a stack = 'a list ref
fun stack () = ref nil
fun isEmpty s = List.null (!s)
fun push (s, x) = s := x::(!s)
fun pop s =
case !s of
nil => raise Empty
| x::xs => (s := xs; x)
end
(事实上,你通常不会这样做,并且完全避免像这样的有状态数据结构,而是使用普通列表。)
如果您关注的是与列表有关的分配,那么请注意(a)它没有比数组版本更多的分配(一个::而不是每次推送一个SOME),并且(b)分配非常便宜像SML这样的语言。
但是既然你的问题是关于使用数组,那么这是一个稍微更加惯用的数组栈实现:
structure ArrayStack =
struct
open Array infix sub
datatype 'a stack = Stack of {data : 'a option array ref, size : int ref}
fun stack () = Stack {data = ref (array (1, NONE)), size = ref 0}
fun isEmpty (Stack {size, ...}) = !size = 0
fun resize (data, len') =
let val data' = array (len', NONE) in
copy {src = !data, dst = data', di = 0};
data := data'
end
fun push (Stack {data, size}, x) =
let val size' = !size + 1 in
if size' > length (!data) then resize (data, !size * 2) else ();
update (!data, !size, SOME x);
size := size'
end
fun pop (Stack {data, size}) =
if !size = 0 then raise Empty else
let
val _ = size := !size - 1
val x = !data sub (!size)
val q = length (!data) div 4
in
update (!data, !size, NONE);
if q > 0 andalso !size = q then resize (data, q) else ();
valOf x
end
end
特别是,我制作了sub
中缀,允许您编写arr sub i
。我这样做仅仅是为了演示,在这个例子中它并不值得,只有一个这样的用法。