Perl Part I · Module 5 · Arrays and Lists
splice: Insert and Remove in the Middle
The four functions in the last chapter all work at an array's ends, which covers most of what scripts need. When you have to act in the middle — drop the third row, insert two entries after the header, swap a span for a shorter one — splice is the single function that does all three. It takes an array, a starting position, how many elements to affect, and optionally what to put in their place, and the combinations of those arguments give you removal, insertion and replacement without rebuilding the array by hand. Two of its details are worth measuring rather than guessing: a negative length does not mean what most people assume, and the function returns something different depending on whether you assign it to an array or to a scalar.
Intermediate13 min readPerlPart IArraysData Structures
Part I · Module 5 · Chapter 5.5 · splice: Insert and Remove in the Middle
1. Four Arguments, Three Jobs
splice(ARRAY, OFFSET, LENGTH, REPLACEMENT_LIST)| You supply | You get |
|---|---|
| array, offset, length | remove that span and return it |
| array, offset | remove from offset to the end |
array, offset, 0, list | insert the list at offset, removing nothing |
| array, offset, length, list | replace the span with the list |
One function, and which job it does is decided entirely by which arguments you give it. That is why it is worth learning as a unit rather than as four separate things.
2. Remove a Span
use strict;
use warnings;
my @a = ("a", "b", "c", "d", "e");
my @removed = splice(@a, 1, 2);
printf "removed [%s]\n", join(" ", @removed);
printf "remaining [%s]\n", join(" ", @a);removed [b c]
remaining [a d e]Starting at position 1, two elements — b and c — were removed, returned, and the array closed up behind them. @a is now three elements, and the remaining ones have moved down: d is at position 1 where b used to be.
That renumbering is the thing to hold onto. Unlike Chapter 5.2 §5's $# truncation, which just discards the tail, splice shifts everything after the removed span. So any index you were holding into the array is stale afterwards.
Omit the length to remove to the end
use strict;
use warnings;
my @a = ("a", "b", "c", "d");
my @tail = splice(@a, 2);
printf "tail [%s] left [%s]\n", join(" ", @tail), join(" ", @a);tail [c d] left [a b]Two arguments means "from here to the end". This is the readable way to truncate an array while keeping what you removed — which is exactly what Chapter 5.2 §5 said $#a = n cannot do, because it discards the values.
3. Insert Without Removing
use strict;
use warnings;
my @a = ("a", "d");
splice(@a, 1, 0, "b", "c");
printf "[%s]\n", join(" ", @a);[a b c d]A length of 0 removes nothing, so the replacement list is simply inserted at the offset. The elements from the offset onwards move up to make room.
The 0 is doing real work and is easy to leave out by accident. splice(@a, 1, "b", "c") would be read as offset 1, length "b" — which numifies to 0 with a warning, by Chapter 2.13's rule — and then "c" as the replacement. Write the 0.
4. Replace, Including With a Different Count
use strict;
use warnings;
my @a = ("a", "b", "c", "d");
my @old = splice(@a, 1, 2, "X");
printf "removed [%s]\n", join(" ", @old);
printf "now [%s] count=%d\n", join(" ", @a), scalar(@a);removed [b c]
now [a X d] count=3Two elements out, one in. The array went from four elements to three, and the replacement did not have to match the span's length.
That asymmetry is what makes splice a single answer to all three jobs rather than three functions. A replacement list longer than the span grows the array; shorter shrinks it; equal keeps the length. And in every case the function hands back what was there, so you can log it or check it.
5. The Two Arguments People Guess Wrong
A negative offset counts from the end
use strict;
use warnings;
my @a = ("a", "b", "c", "d");
my @gone = splice(@a, -2, 1);
printf "removed [%s] left [%s]\n", join(" ", @gone), join(" ", @a);removed [c] left [a b d]-2 is the second-from-last position, the same convention as Chapter 5.2 §1. One element was removed from there.
A negative length means "leave this many at the end"
use strict;
use warnings;
my @a = ("a", "b", "c", "d", "e");
my @gone = splice(@a, 1, -2);
printf "removed [%s] left [%s]\n", join(" ", @gone), join(" ", @a);removed [b c] left [a d e]This is the one to measure rather than guess. A negative length does not mean "work backwards". It means stop that many elements short of the end.
So splice(@a, 1, -2) read as "from position 1, up to but not including the last two" — which on a five-element array is positions 1 and 2, giving b and c. The last two, d and e, were left alone.
It is genuinely useful for "drop everything except the header and the last two rows", and genuinely confusing if you assumed it counted backwards. If the expression is not obviously readable, compute the length instead.
6. The Return Value Depends on Context
use strict;
use warnings;
my @a = ("a", "b", "c", "d");
my @list_ctx = splice(@a, 1, 2);
my @b = ("a", "b", "c", "d");
my $scalar_ctx = splice(@b, 1, 2);
printf "list context gave [%s]\n", join(" ", @list_ctx);
printf "scalar context gave [%s]\n", $scalar_ctx;list context gave [b c]
scalar context gave [c]In list context you get everything removed. In scalar context you get the last element removed — c, not b.
7. splice Warns When the Offset Is Past the End
This is worth a section because it is the exception in this module.
use strict;
use warnings;
my @a = ("a", "b");
my @gone = splice(@a, 5, 2);
printf "removed %d left [%s]\n", scalar(@gone), join(" ", @a);removed 0 left [a b]splice() offset past end of array at past-end.pl line 6.Nothing was removed and the array is unchanged — and Perl said so.
Compare that with the rest of the module: Chapter 5.1 §5's out-of-range read, Chapter 5.2 §4's out-of-range write, and Chapter 5.3 §3's out-of-range slice are all completely silent. splice is the one array operation that reports a bad index.
Worth knowing for two reasons: you get a diagnostic here that you have learned not to expect, and when you see splice() offset past end of array in a log it means an offset was computed wrongly — usually because the array was modified between computing the offset and using it, which §2's renumbering point makes easy to do.
8. push and pop Are Special Cases of This
Every function from Chapter 5.4 can be written as a splice:
| The simple form | The splice equivalent |
|---|---|
push @a, $v | splice(@a, scalar(@a), 0, $v) |
pop @a | splice(@a, -1) |
unshift @a, $v | splice(@a, 0, 0, $v) |
shift @a | splice(@a, 0, 1) |
Use the simple forms. The table is here to show that splice is the general operation and the other four are named shortcuts for its common cases — not as a suggestion to write the right-hand column. push @a, $v says what it does; the splice version makes a reader work it out.
That is the practical rule for the whole chapter: reach for splice when you need the middle. At the ends, the named functions are clearer.
9. Why This Matters in Engineering Work
Dropping a header row. A file read into an array arrives with its header as element 0. shift @lines is the right tool for one row; splice(@lines, 0, 3) is right when there are three lines of preamble, and it hands them back so you can check they were what you expected.
Keeping the tail of a log. splice(@lines, 0, scalar(@lines) - 20) leaves the last twenty lines and returns the rest — or, more readably, @lines = @lines[-20 .. -1] with Chapter 5.3's negative slice when you do not need what you dropped.
Inserting a generated row. A report that needs a separator or a subtotal inserted after a group uses the length-0 insert form.
And the renumbering is the thing that bites. Removing elements in a loop while holding an index into the same array is a classic bug: after the first removal every later index is off by one, and the loop silently skips elements. The usual fixes are to iterate backwards, or to build a new array with grep from Chapter 5.10 instead of modifying in place — and the second is almost always clearer.
10. Common Mistakes
Forgetting the 0 when inserting. splice(@a, 1, "b", "c") reads "b" as the length, numifies it to 0 with a warning, and treats "c" as the replacement.
Expecting a negative length to count backwards. It means "stop that many from the end".
Assigning a multi-element splice to a scalar. You get the last element removed and lose the rest.
Holding an index across a splice. Everything after the removed span renumbers.
Removing inside a forward loop over the same array. Elements get skipped. Iterate backwards or build a new array.
Writing splice where push or pop would do. The named functions are clearer at the ends.
Assuming out-of-range is silent here. It is the one array operation that warns.
11. Exercise
Exercise 1 — the three jobs. Starting from ("a","b","c","d","e") each time, use splice to (a) remove b and c, (b) insert X and Y between b and c, and (c) replace c and d with a single Z. Print the array and the returned list for each, and report the count after each.
Exercise 2 — remove to the end. Remove everything from position 2 onwards, keeping what you removed. Print both halves. Then say why $#a = 1 from Chapter 5.2 could not have done this.
Exercise 3 — the negative length. Predict what splice(@a, 1, -2) removes from a six-element array before running it. Then run it and, if you were wrong, state the rule in your own words.
Exercise 4 — the context trap. Splice two elements out of an array into a scalar and print what you got. Say which of the two you received and where the other one went.
Exercise 5 — the warning. Splice at an offset past the end of a two-element array. Capture STDERR and record the exact message. Then say which three other array operations from this module would have stayed silent in the same situation.
Exercise 6 — the renumbering bug. Write a forward loop over 0 .. $#a that splices out every element equal to "drop" from ("keep","drop","drop","keep"). Record which elements survive, explain why it is wrong, then rewrite it with grep and confirm the correct result.
Exercise 7 — prefer the simple form. Write push @a, "x" as a splice, confirm the two behave identically, and then say in one clause which you would commit and why.
12. Summary
splice(ARRAY, OFFSET, LENGTH, LIST)removes, inserts or replaces, depending on which arguments you give it.- Omitting the length removes to the end and returns what it took — which
$#arrayassignment cannot do. - A length of
0inserts without removing. Write the0explicitly. - The replacement list need not match the span's length, so the array can grow or shrink.
- A negative offset counts from the end; a negative length means "stop that many short of the end" — not "backwards".
- In list context it returns everything removed; in scalar context it returns the LAST element removed and the rest are lost.
splicewarns on an offset past the end — the only array operation in this module that reports a bad index.- Removing renumbers everything after the span, so indexes held across a
spliceare stale. push,pop,shiftandunshiftare named special cases. Use them at the ends; usesplicefor the middle.
Chapter 5.6 steps back from the functions to settle a distinction the whole module has been leaning on: what exactly is the difference between the parenthesised thing on the right of an assignment and the array on the left.
Where this fits
Part of the Perl curriculum.
