Perl Part I · Module 5 · Arrays and Lists
Searching, Counting and Reversing
Three questions come up constantly and all three have one-line answers that beat writing a loop: is this value in the list, how many elements match a condition, and what does the list look like in the opposite order. The first two are the function from the last chapter used in two different contexts. The third is a function with a genuine split personality — asked for a list it reverses the order of the elements, and asked for a single value it joins them together and reverses the characters instead. Those are not two views of one result; they are different operations, and the one people reach for most often is the one that quietly does nothing at all.
Foundation13 min readPerlPart IFundamentalsArraysContextIdioms
Part I · Module 5 · Chapter 5.11 · Searching, Counting and Reversing
1. Is It In There?
use strict;
use warnings;
my @blocks = ("alu", "fifo", "decoder");
for my $wanted ("fifo", "cpu") {
printf "%-5s present: %s\n", $wanted,
(grep { $_ eq $wanted } @blocks) ? "yes" : "no";
}fifo present: yes
cpu present: noThe grep sits in a condition, which is scalar context, so by Chapter 5.10 §2 it returns the count of matches. One match or more is true; zero is false — Chapter 2.8.
Note the comparison inside is eq, not ==. Chapter 3.5 measured why that matters: with ==, every non-numeric name would match every other, and the test would report yes for everything.
2. How Many Match?
use strict;
use warnings;
my @status = ("PASS", "FAIL", "PASS", "PASS");
my $passed = grep { $_ eq "PASS" } @status;
my $failed = grep { $_ eq "FAIL" } @status;
printf "%d passed, %d failed, of %d\n", $passed, $failed, scalar(@status);
printf "rate: %.1f%%\n", $passed / scalar(@status) * 100;3 passed, 1 failed, of 4
rate: 75.0%Two counts and a rate, with no loop and no accumulator. my $n = grep { ... } @list is the idiom, and it is the same function as §1 — only the surrounding context differs.
Three related counts, for reference:
| Question | Expression |
|---|---|
| how many elements in total | scalar(@list) |
| how many match a condition | grep { COND } @list in scalar context |
| how many do not match | grep { !(COND) } @list |
And a caution carried from Chapter 5.6 §7: do not reach for the () = count-of idiom here. grep already returns a count in scalar context, so my $n = grep {...} is both shorter and correct. The idiom is for expressions that do not have a documented scalar-context behaviour.
3. reverse Has Two Completely Different Behaviours
use strict;
use warnings;
my @a = ("a", "b", "c");
my @as_list = reverse @a;
my $as_scalar = reverse @a;
printf "list context : [%s] (%d elements)\n", join(" ", @as_list), scalar(@as_list);
printf "scalar context : [%s] (%d characters)\n", $as_scalar, length($as_scalar);list context : [c b a] (3 elements)
scalar context : [cba] (3 characters)The same call, two sigils on the left, two different operations:
- List context reverses the order of the elements. Three elements out, in the opposite order.
- Scalar context joins the elements into one string and then reverses its characters. One value out, three characters.
That second behaviour is not "the list squeezed into a scalar". reverse concatenated a, b, c into abc and then reversed the characters to cba. With multi-character elements the difference is unmistakable:
use strict;
use warnings;
my @words = ("alu", "fifo");
my @as_list = reverse @words;
my $as_scalar = reverse @words;
printf "list : [%s]\n", join(" ", @as_list);
printf "scalar : [%s]\n", $as_scalar;list : [fifo alu]
scalar : [ofifula]ofifula is alufifo reversed character by character. Nothing warns, and in a report that string looks like corruption rather than like a context mistake.
4. Which Makes Reversing a String a Trap
This is the most common reverse bug and it is worth meeting directly.
use strict;
use warnings;
my $s = "hello";
print "without scalar : ", reverse($s), "\n";
print "with scalar : ", scalar(reverse $s), "\n";without scalar : hello
with scalar : ollehThe first line printed the string unchanged.
The reason is Chapter 5.7 §5: print supplies list context. So reverse($s) received a one-element list, reversed the order of that one element — which changes nothing — and handed back the same single value.
scalar(reverse $s) forces the scalar behaviour and reverses the characters.
5. Reversing a Sort
reverse on a list composes with sort, and there are two ways to get descending order:
use strict;
use warnings;
my @counts = (10, 2, 100, 9);
my @by_comparator = sort { $b <=> $a } @counts;
my @by_reverse = reverse sort { $a <=> $b } @counts;
printf "comparator : %s\n", join(" ", @by_comparator);
printf "reverse : %s\n", join(" ", @by_reverse);comparator : 100 10 9 2
reverse : 100 10 9 2Both give the same answer. Which to prefer:
sort { $b <=> $a }is one pass and keeps the ordering decision in one place. Prefer it.reverse sort { ... }reads more obviously as "sorted, then flipped" and is fine for a one-off.
What you should not write is both: reverse sort { $b <=> $a } cancels out and gives ascending order, which is the kind of double negative that survives review.
6. The Functions This Chapter Does Not Cover
Three things you might expect here, and where they actually live:
- A minimum or maximum.
sortthen take the first or last —(sort { $a <=> $b } @n)[0]— works and is what Part I uses. The standard moduleList::Utilprovidesmin,max,sumandfirst, and is in core Perl; using it is a judgement about whether auseline is worth it for one call. Module 16 covers modules. - Stopping at the first match.
grepscans everything.List::Util'sfirststops early, and a plain loop withlastdoes too — Module 9's. - Removing duplicates. This needs a hash and is Module 6's. There is no array-only way that is not O(n²).
Naming these now means you know the gap is deliberate rather than an omission.
7. Why This Matters in Engineering Work
Counting by status is the body of most summary reports. my $passed = grep { $_ eq "PASS" } @status; and a rate on the next line. Two lines, no loop, nothing to get wrong.
The membership check guards a lookup. "Is this block in the list we were asked to run" before doing work on it. For a short list grep is right; when the list is long or the check is in a loop, Module 6's hash is the answer and this chapter's callout says so.
reverse on an array is how you get newest-first. A log read into an array arrives oldest-first, and reverse @lines gives the order a reader usually wants.
And the print reverse($s) bug wastes real time because the output is the input. There is no error, no warning and no partial result — just a string that did not change. If a reverse appears not to have happened, the first thing to check is whether anything supplied scalar context.
8. Common Mistakes
Using == in a membership grep. Every non-numeric string matches every other — Chapter 3.5.
Using grep for repeated lookups in a long list. It scans every time. Use a hash from Module 6.
Expecting print reverse($s) to reverse a string. print is list context, so nothing happens. Use scalar(reverse $s).
Expecting my $x = reverse @a to give a reversed list. It joins the elements and reverses the characters.
Writing reverse sort { $b <=> $a }. The two reversals cancel.
Reaching for the () = idiom to count a grep. grep already returns a count in scalar context.
Assuming there is an array-only way to remove duplicates. There is not a good one; that is what hashes are for.
9. Exercise
Exercise 1 — membership. Given a list of five block names, test two values — one present, one absent — and print yes/no for each. Then change eq to == and record how many now report yes, explaining why.
Exercise 2 — three counts. From eight status strings, count the passes, count the non-passes, and print both with the total and a percentage. Confirm the two counts sum to the total.
Exercise 3 — the two reverses. Reverse a three-element array of multi-character words into an array and into a scalar. Print both with their element count and character length, and explain in one sentence what the scalar form actually did.
Exercise 4 — the silent string reverse. Print reverse($word) and scalar(reverse $word) for a five-letter word. Record both, and say which context print supplied and why that made the first one a no-op.
Exercise 5 — newest first. Read six log lines into an array and print them newest-first with reverse. Then say why this is the list behaviour and not the scalar one.
Exercise 6 — two routes to descending. Sort four numbers descending with a comparator and with reverse sort. Confirm both agree, then write reverse sort { $b <=> $a } and record what order you get.
Exercise 7 — the biggest value. Get the largest of (10, 2, 100, 9) using only sort and a subscript. Then say which chapter would give you a max function and why this track does not use it yet.
10. Summary
- Membership is
grepin a condition — scalar context gives a count, and zero is the only false count. Useeq, not==. - Counting is
my $n = grep { COND } @list. No loop, no accumulator, no count-of idiom needed. grepscans the whole list every time. For repeated lookups, a hash from Module 6 is the right tool.reversein list context reverses the element order.reversein scalar context joins the elements and reverses the characters —("alu","fifo")becomesofifula.print reverse($s)does nothing, becauseprintsupplies list context. A string reverse always needsscalar(reverse $s).- Descending order: prefer
sort { $b <=> $a }.reverse sortworks; writing both cancels out. - Minimum, maximum, first-match and deduplication are deliberately not here —
List::Utiland hashes cover them.
Chapter 5.12 closes the module with the behaviour that has been quietly behind several of its results — why putting an array inside a list does not nest it, and why that limitation is the reason the next module exists.
Where this fits
Part of the Perl curriculum.
