在列表末尾追加一个元素
Append an element at the end of a list
如何在 ReasonML 中的列表末尾追加元素(相当于 JavaScript 中的 Array.concat
)?
您可以使用 List.append
或 @
运算符,即 shorthand 用于 List.append
。
let lstA = [ 1 ];
let lstB = lstA @ [ 2 ];
let lstC = List.append(lstB, [ 3 ]);
这是列表方法的文档:https://reasonml.github.io/api/List.html
虽然 Neil 的回答在技术上是正确的,但它掩盖了您在寻求 append
之前可能需要考虑的一些细节;具体来说,虽然将一个元素添加到列表的开头非常便宜,但将一个元素添加到列表的末尾非常昂贵。
要了解原因,让我们看一下列表是如何定义和构造的。列表的(概念)定义是:
// Reason
type list('a) = Cons('a, list('a)) | Nil;
(* OCaml *)
type 'a list = Cons of 'a* 'a list | Nil
其中 Nil
表示列表的末尾(并且它本身是一个空列表)并且 Cons
表示列表中的一个节点,包含一个 'a
类型的元素和一个指向列表其余部分的指针(list('a)
,OCaml:'a list
)。
如果我们去掉所有的语法糖和每个辅助函数,你将不得不构建一个这样的列表:
// Reason
let myList = Cons(1, Cons(2, Cons(3, Nil)));
(* OCaml *)
let myList = Cons (1, Cons (2, Cons (3, Nil)))
然后,为了向该列表的头部添加一个元素,我们构造一个包含新元素和指向旧列表的指针的节点:
// Reason
let myBiggerList = Cons(0, myList);
(* OCaml *)
let myBiggerList = Cons (0, myList)
这与 [0, ...myList]
(OCaml: 0 :: myList
) 完全相同。如果 myList
可以改变,我们当然不能这样做,但我们知道它不会,因为列表是不可变的。这使得它非常便宜,出于同样的原因,它也同样便宜,这就是为什么您通常会看到使用递归实现的列表处理函数,如下所示:
// Reason
let rec map = f =>
fun | [] => []
| [x, ...xs] => [f(x), ...map(f, xs)];
(* OCaml *)
let rec map f = function
| [] -> []
| x::xs -> (f x) :: (map f xs)
好吧,那么为什么在列表尾部添加一个元素如此昂贵?如果你回头看myList
,添加一个元素到结束意味着用 Cons(4, Nil)
替换最后的 Nil
。但随后我们需要替换 Cons(3, ...)
,因为它指向旧的 Nil
,而 Cons(2, ...)
因为它指向旧的 Cons(3, ...)
,依此类推整个列表。每次添加元素时都必须这样做。这很快就加起来了。
那么你应该怎么做呢?
如果您要添加到末尾并只是遍历它或总是从末尾删除元素,就像您在 JavaScript 中经常做的那样,您很可能只是颠倒了您的逻辑。不要在结尾添加和删除,而是在开头添加和删除。
如果您确实需要一个 FIFO 数据结构,其中元素在一端插入并在另一端取出,请考虑使用 Queue instead. In general, have a look at this comparison of the performance characteristics of the standard containers。
或者,如果这有点多,而您真的只是想像从 JavaScript 开始那样做,只需使用 array
而不是 [=35] =].您会在 Js.Array
module
中找到您熟悉的所有功能
如何在 ReasonML 中的列表末尾追加元素(相当于 JavaScript 中的 Array.concat
)?
您可以使用 List.append
或 @
运算符,即 shorthand 用于 List.append
。
let lstA = [ 1 ];
let lstB = lstA @ [ 2 ];
let lstC = List.append(lstB, [ 3 ]);
这是列表方法的文档:https://reasonml.github.io/api/List.html
虽然 Neil 的回答在技术上是正确的,但它掩盖了您在寻求 append
之前可能需要考虑的一些细节;具体来说,虽然将一个元素添加到列表的开头非常便宜,但将一个元素添加到列表的末尾非常昂贵。
要了解原因,让我们看一下列表是如何定义和构造的。列表的(概念)定义是:
// Reason
type list('a) = Cons('a, list('a)) | Nil;
(* OCaml *)
type 'a list = Cons of 'a* 'a list | Nil
其中 Nil
表示列表的末尾(并且它本身是一个空列表)并且 Cons
表示列表中的一个节点,包含一个 'a
类型的元素和一个指向列表其余部分的指针(list('a)
,OCaml:'a list
)。
如果我们去掉所有的语法糖和每个辅助函数,你将不得不构建一个这样的列表:
// Reason
let myList = Cons(1, Cons(2, Cons(3, Nil)));
(* OCaml *)
let myList = Cons (1, Cons (2, Cons (3, Nil)))
然后,为了向该列表的头部添加一个元素,我们构造一个包含新元素和指向旧列表的指针的节点:
// Reason
let myBiggerList = Cons(0, myList);
(* OCaml *)
let myBiggerList = Cons (0, myList)
这与 [0, ...myList]
(OCaml: 0 :: myList
) 完全相同。如果 myList
可以改变,我们当然不能这样做,但我们知道它不会,因为列表是不可变的。这使得它非常便宜,出于同样的原因,它也同样便宜,这就是为什么您通常会看到使用递归实现的列表处理函数,如下所示:
// Reason
let rec map = f =>
fun | [] => []
| [x, ...xs] => [f(x), ...map(f, xs)];
(* OCaml *)
let rec map f = function
| [] -> []
| x::xs -> (f x) :: (map f xs)
好吧,那么为什么在列表尾部添加一个元素如此昂贵?如果你回头看myList
,添加一个元素到结束意味着用 Cons(4, Nil)
替换最后的 Nil
。但随后我们需要替换 Cons(3, ...)
,因为它指向旧的 Nil
,而 Cons(2, ...)
因为它指向旧的 Cons(3, ...)
,依此类推整个列表。每次添加元素时都必须这样做。这很快就加起来了。
那么你应该怎么做呢?
如果您要添加到末尾并只是遍历它或总是从末尾删除元素,就像您在 JavaScript 中经常做的那样,您很可能只是颠倒了您的逻辑。不要在结尾添加和删除,而是在开头添加和删除。
如果您确实需要一个 FIFO 数据结构,其中元素在一端插入并在另一端取出,请考虑使用 Queue instead. In general, have a look at this comparison of the performance characteristics of the standard containers。
或者,如果这有点多,而您真的只是想像从 JavaScript 开始那样做,只需使用 array
而不是 [=35] =].您会在 Js.Array
module