Using Sequence to delete a part of an expression
What happens to the list after we assign an element to Sequence[]
The answers given so far already contain most of the pieces needed to explain this behavior, but I decided to still add this answer.
This topic has been discussed many times in the past, although not on this site. Here is one such discussion, where I also contributed an answer. I will take my answer from there, and adopt it to this case.
Consider your example:
xx = Range[5]; xx[[1]] = Sequence[]; xx
Now, let us see what is in xx
:
?xx
Global`xx xx={Sequence[],2,3,4,5}
We see that the first element is still there.
Some reasoning behind this behavior
We don't really change the size of a list (as stored in OwnValues
etc) by assigning Sequence[]
to some of its elements - Sequence[]
is just as good an element as anything else.
The important part to realize is that all the Sequence[]
magic happens at
evaluation stage - the size of an evaluated list becomes effectively
smaller as a result of evaluation (during which Sequence
-s inside heads
without SequenceHold
or HoldAllComplete
attribute are spliced).
This seems entirely consistent, given that lists are internally
implemented as arrays and then to really change the size of a list as it is
stored, we would generally need O(n) operations where n is the size of the
list. OTOH, operations like Part
and assignments should not be concerned with anything else than just assigning parts of expressions, and such list
rearrangements should be beyond their scope IMO.
Back to the example
Returning to your example, all subsequent assignments simply re-assign
Sequence[]
to the first element. Since the list is not evaluated during the part assignment, Sequence
splicing does not happen.
Here is one way to get the elements removed, which would further clarify things.
xx = Range[5];
xx[[1]] = Sequence[];
xx = xx
xx[[1]] = Sequence[];
xx = xx
xx[[1]] = Sequence[];
xx = xx
(*
{2, 3, 4, 5}
{3, 4, 5}
{4, 5}
*)
In this case, the code does what we need - removes the first element each time. The key part here is the assignment xx = xx
. In this case it is non-trivial, because we do evaluate xx
on the r.h.s. during the assignment, the Sequence
splicing proceeds as it should, and then we assigned the result (which no longer contains the original first element, and is one element smaller now), to the same variable.
Note however, that xx = xx
is an O(n) operation if xx
stores a list of length n
, which contains one or more Sequence[]
inside it. So, this method isn't really efficient. There is no magic here either, because there is no way you can remove an element of an array in O(1) time - you'd need a different data structure if you need fast element removals (such as a linked list, or actually an Association
). That the element removal is hidden in this method by a high-level construct like Sequence
does not change that fundamental fact.
Summary
What you have observed is a result of an interplay between evaluation and the way part assignments are performed. Here are some things to keep in mind regarding this:
- Part assignments fundamentally can not remove elements from an expression stored in a variable.
- Assigning
Sequence[]
(orNothing
, for that matter) to a part of an expression will not remove that part, but rather put thereSequence[]
orNothing
. Indeed, this seems to be the only sensible behavior. - Shrinking of expressions containing
Sequence[]
is an evaluation-time effect, while expressions stored in variables are kept unevaluated, also during part assignments.
These points together explain your observation. In fact, this is just one reason to avoid using Sequence
as an element-removing device, when possible. There are other options available for that. If you really do want to change your expression (stored in a symbol) via Sequence
part assignment, one way to do that is to reassign the expression to the variable, using var = var
idiom, allowing it to evaluate.
Note however, that var = var
is an O(n) operation, so that won't be very efficient if you only remove one or a few elements in a large list. Which is another argument against using Sequence
, because it hides the underlying algorithmic complexity and may therefore result in performance surprises for the uninitiated.
Noted the documentation of the Sequence's Details:
Sequence objects will automatically be flattened out in all functions except those with attribute SequenceHold or HoldAllComplete.
xx= Range@5;
Do[xx[[1]] = Nothing, 3];xx
{2, 3, 4, 5}
It'll return a list
just without first element.You should activate to remove the Sequence[]
use xx=xx
xx = Range@5;
Do[xx[[1]] = Nothing; xx = xx, 3]; xx
{4, 5}
As your case,you should add a xx=xx
in the expression
xx = Range[5]; xx[[1]] = Sequence[]; xx = xx; xx[[1]] = Sequence[]; xx
{3, 4, 5}