即使在附加标准 ML 后列表也是空的
Empty list even after appending Standard ML
我有一个函数,它接受一个列表,然后根据条件将元素放入两个不同的列表中。但是,当我检查返回的列表时,它们总是空的。为什么会这样?
fun divide_list(l1: type list): (type list, type list) =
let
val count = ref 0
and list1 = nil
and list2 = nil
in
while !count <> 8 do (
if !count % 2 = 0 then
list1 @ [List.nth(l1, !count)]
else
list2 @ [List.nth(l1, !count)];
count := !count + 1
);
(list1, list2)
end
我在运行之后得到的输出如下:
val it = ([],[]) : type list * type list
您不是在修改列表,而是在创建被丢弃的新列表。为了实现您想要的效果,您还需要将列表包装在引用中:and list1 = ref [] and list2 = ref []
。然后,在您打算修改它们的每个实例中,您使用 :=
(就像您修改 count
一样)。请注意,您将重写要返回的元组以获取每个引用所持有的值:(!list1, !list2)
.
作为旁注,很遗憾 Standard ML 没有告知您这一点。例如,在 OCaml 中,排序表达式 e ; e'
的输入规则确保 e
的计算结果为 () : unit
由于它的脱糖方式 (let () = e in e'
)。因此,与您的代码类似的 OCaml 甚至不会进行类型检查。
我会完全放弃基于 ref
的解决方案,并使用带有索引的折叠来做一些事情:
fun divide xs =
let
fun choose (x, ((l, r), i)) =
(if i mod 2 = 0 then (x :: l, r) else (l, x :: r), i + 1)
val (l, r) =
#1 (List.foldl choose (([], []), 0) xs)
in
(List.rev l, List.rev r)
end
我向后构建列表分区,然后在末尾反转以避免为每个元素追加到末尾的二次爆炸。如果你希望有 8
约束的长度,那么你可以将上述函数的用法与基础库中的 List.take
结合使用。
@contificate 给出了很好的解释。
如果您要实现列表分区功能。
没有理由不考虑将决定如何 对列表进行分区的函数。这个函数只需要接受一个值和return一个布尔值。
这很容易通过折叠实现。据我所知,没有必要跟踪索引。
fun partition f lst =
let
val (a, b) = List.foldl (fn (x, (a, b)) => if f x then (x::a, b) else (a, x::b)) ([], []) lst
in
(List.rev a, List.rev b)
end;
partition (fn x => x mod 2 = 0) [1, 2, 3, 4, 5];
产量:
([2, 4], [1, 3, 5])
如果您只想根据 index
进行拆分
如果我们的目标是根据索引将一个列表分成两个列表:
[4, 7, 1, 9, 8]
变为:
([4, 1, 8], [7, 9])
这完全可以通过简单的模式匹配和递归在功能上完成。
let rec split lst (acc1, acc2) =
match lst with
| [] -> (acc1, acc2)
...
这里我们传入要拆分的列表,以及一个包含两个列表的累加器。显然,如果列表为空,则结果只是累加器。如果列表中只有一个元素怎么办?
let rec split lst (acc1, acc2) =
match lst with
| [] -> (acc1, acc2)
| [x] -> (x::acc1, acc2)
...
好吧,它在第一个列表中。如果元素多于一个怎么办?
let rec split lst (acc1, acc2) =
match lst with
| [] -> (acc1, acc2)
| [x] -> (x::acc1, acc2)
| x::y::tail -> split tail (x::acc1, y::acc2)
嗯,那我们匹配前两个元素和尾部。我们更新累加器以将前两个元素放入各自的列表中,并使用尾部和更新后的累加器再次调用 split。
utop # split [4; 7; 1; 9; 8] ([], []);;
- : int list * int list = ([8; 1; 4], [9; 7])
糟糕。由于我们构建累加器的方式,它们是倒退的。我们可以使用 List.rev
来解决这个问题,但是因为我们不想做两次,所以当有一个元素时,我们将在一个空列表上调用 split
。
let rec split lst (acc1, acc2) =
match lst with
| [] -> (List.rev acc1, List.rev acc2)
| [x] -> split [] (x::acc1, acc2)
| x::y::tail -> split tail (x::acc1, y::acc2)
utop # split [4; 7; 1; 9; 8] ([], []);;
- : int list * int list = ([4; 1; 8], [7; 9])
最后,您可以隐藏 split
以消除显式传递空列表元组的需要。
let rec split lst (acc1, acc2) =
match lst with
| [] -> (List.rev acc1, List.rev acc2)
| [x] -> split [] (x::acc1, acc2)
| x::y::tail -> split tail (x::acc1, y::acc2)
let split lst = split lst ([], [])
我有一个函数,它接受一个列表,然后根据条件将元素放入两个不同的列表中。但是,当我检查返回的列表时,它们总是空的。为什么会这样?
fun divide_list(l1: type list): (type list, type list) =
let
val count = ref 0
and list1 = nil
and list2 = nil
in
while !count <> 8 do (
if !count % 2 = 0 then
list1 @ [List.nth(l1, !count)]
else
list2 @ [List.nth(l1, !count)];
count := !count + 1
);
(list1, list2)
end
我在运行之后得到的输出如下:
val it = ([],[]) : type list * type list
您不是在修改列表,而是在创建被丢弃的新列表。为了实现您想要的效果,您还需要将列表包装在引用中:and list1 = ref [] and list2 = ref []
。然后,在您打算修改它们的每个实例中,您使用 :=
(就像您修改 count
一样)。请注意,您将重写要返回的元组以获取每个引用所持有的值:(!list1, !list2)
.
作为旁注,很遗憾 Standard ML 没有告知您这一点。例如,在 OCaml 中,排序表达式 e ; e'
的输入规则确保 e
的计算结果为 () : unit
由于它的脱糖方式 (let () = e in e'
)。因此,与您的代码类似的 OCaml 甚至不会进行类型检查。
我会完全放弃基于 ref
的解决方案,并使用带有索引的折叠来做一些事情:
fun divide xs =
let
fun choose (x, ((l, r), i)) =
(if i mod 2 = 0 then (x :: l, r) else (l, x :: r), i + 1)
val (l, r) =
#1 (List.foldl choose (([], []), 0) xs)
in
(List.rev l, List.rev r)
end
我向后构建列表分区,然后在末尾反转以避免为每个元素追加到末尾的二次爆炸。如果你希望有 8
约束的长度,那么你可以将上述函数的用法与基础库中的 List.take
结合使用。
@contificate 给出了很好的解释。
如果您要实现列表分区功能。
没有理由不考虑将决定如何 对列表进行分区的函数。这个函数只需要接受一个值和return一个布尔值。
这很容易通过折叠实现。据我所知,没有必要跟踪索引。
fun partition f lst =
let
val (a, b) = List.foldl (fn (x, (a, b)) => if f x then (x::a, b) else (a, x::b)) ([], []) lst
in
(List.rev a, List.rev b)
end;
partition (fn x => x mod 2 = 0) [1, 2, 3, 4, 5];
产量:
([2, 4], [1, 3, 5])
如果您只想根据 index
进行拆分如果我们的目标是根据索引将一个列表分成两个列表:
[4, 7, 1, 9, 8]
变为:
([4, 1, 8], [7, 9])
这完全可以通过简单的模式匹配和递归在功能上完成。
let rec split lst (acc1, acc2) =
match lst with
| [] -> (acc1, acc2)
...
这里我们传入要拆分的列表,以及一个包含两个列表的累加器。显然,如果列表为空,则结果只是累加器。如果列表中只有一个元素怎么办?
let rec split lst (acc1, acc2) =
match lst with
| [] -> (acc1, acc2)
| [x] -> (x::acc1, acc2)
...
好吧,它在第一个列表中。如果元素多于一个怎么办?
let rec split lst (acc1, acc2) =
match lst with
| [] -> (acc1, acc2)
| [x] -> (x::acc1, acc2)
| x::y::tail -> split tail (x::acc1, y::acc2)
嗯,那我们匹配前两个元素和尾部。我们更新累加器以将前两个元素放入各自的列表中,并使用尾部和更新后的累加器再次调用 split。
utop # split [4; 7; 1; 9; 8] ([], []);;
- : int list * int list = ([8; 1; 4], [9; 7])
糟糕。由于我们构建累加器的方式,它们是倒退的。我们可以使用 List.rev
来解决这个问题,但是因为我们不想做两次,所以当有一个元素时,我们将在一个空列表上调用 split
。
let rec split lst (acc1, acc2) =
match lst with
| [] -> (List.rev acc1, List.rev acc2)
| [x] -> split [] (x::acc1, acc2)
| x::y::tail -> split tail (x::acc1, y::acc2)
utop # split [4; 7; 1; 9; 8] ([], []);;
- : int list * int list = ([4; 1; 8], [7; 9])
最后,您可以隐藏 split
以消除显式传递空列表元组的需要。
let rec split lst (acc1, acc2) =
match lst with
| [] -> (List.rev acc1, List.rev acc2)
| [x] -> split [] (x::acc1, acc2)
| x::y::tail -> split tail (x::acc1, y::acc2)
let split lst = split lst ([], [])