问题
How to append an element at the end of a list in ReasonML (the equivalent of Array.concat
in JavaScript)?
回答1:
While Neil's answer is technically correct, it glosses over some details that you might want to consider before reaching for append
; specifically that while adding an element to the beginning of a list is very cheap, adding an element to the end is very expensive.
To understand why, let's look at how a list is defined and constructed. The (conceptual) definition of a list is:
// Reason
type list('a) = Cons('a, list('a)) | Nil;
(* OCaml *)
type 'a list = Cons of 'a* 'a list | Nil
where Nil
represents the end of a list (and by itself an empty list) and Cons
represents a node in the list, containing an element of type 'a
and a pointer to the rest of the list (list('a)
, OCaml: 'a list
).
If we took away all the syntax sugar and every helper function, you would have to construct a list like this:
// Reason
let myList = Cons(1, Cons(2, Cons(3, Nil)));
(* OCaml *)
let myList = Cons (1, Cons (2, Cons (3, Nil)))
To add an element to the head of this list then, we construct a node containing our new element and a pointer to the old list:
// Reason
let myBiggerList = Cons(0, myList);
(* OCaml *)
let myBiggerList = Cons (0, myList)
This is exactly the same as doing [0, ...myList]
(OCaml: 0 :: myList
). If myList
could change we wouldn't be able to do this, of course, but we know it doesn't since lists are immutable. That makes this very cheap, and for the same reason it's just as cheap to pop the head off, which is why you'll usually see list processing functions implemented using recursion, like this:
// 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)
Ok, so then why is it so expensive to add an element to the tail of the list? If you look back at myList
, adding an element to the end means replacing the final Nil
with, say, Cons(4, Nil)
. But then we need to replace Cons(3, ...)
since that points to the old Nil
, and Cons(2, ...)
because it point to the old Cons(3, ...)
, and so on through the entire list. And you have to do that every time you add an element. That quickly adds up.
So what should you do instead?
If you're adding to the end and either just iterating through it or always taking elements off the end, like you often would in JavaScript, you cam most likely just reverse your logic. Instead of adding to and taking off the end, add to and take off the beginning.
If you actually need a FIFO data structure, where elements are inserted at one end and taken off at the other, consider using a Queue instead. In general, have a look at this comparison of the performance characteristics of the standard containers.
Or if this is all a bit much and you'd really just like to do it like you're used to from JavaScript, just use an array
instead of a list
. You'll find all the functions you're familiar with in the Js.Array module
回答2:
You can use List.append
or the @
operator which is shorthand for List.append
.
let lstA = [ 1 ];
let lstB = lstA @ [ 2 ];
let lstC = List.append(lstB, [ 3 ]);
Here is the documentation for List methods: https://reasonml.github.io/api/List.html
See a playground link here: https://reasonml.github.io/en/try.html?reason=DYUwLgBMDOYIIQLwQNoQIwQLoG4BQokMYAQklLAgAKoQBM2+hFYAwuQDICWsAdAIYAHQSAB2AEwAUxEgBpaAZmwBKfEA
来源:https://stackoverflow.com/questions/49051006/append-an-element-at-the-end-of-a-list