Skip to content
VLSI Mentor

Perl Part I · Module 5 · Arrays and Lists

grep and map

These two functions do what a loop would, in one expression, and they are everywhere in Perl you will inherit. One takes a list and keeps the elements matching a condition; the other takes a list and replaces every element with something computed from it. Learning them is quick. The detail that matters is what the loop variable inside the block actually is, because it is not a copy — it is an alias to the element in the original list. Read through it and everything behaves as you expect. Assign to it and you have silently rewritten the array you were reading, with no warning, in a function whose name promises a transformation rather than a modification.

Foundation15 min readPerlPart IFundamentalsArraysIdiomsDebugging

Part I · Module 5 · Chapter 5.10 · grep and map

1. One Keeps, One Transforms

Azvya Education Pvt. Ltd.VLSI Mentor
both.pl
use strict;
use warnings;

my @nums = (1, 2, 3, 4, 5, 6);

my @over    = grep { $_ > 3 } @nums;
my @doubled = map  { $_ * 2 } @nums;

printf "grep : %s\n", join(" ", @over);
printf "map  : %s\n", join(" ", @doubled);
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
grep : 4 5 6
map  : 2 4 6 8 10 12
  • grep { CONDITION } @list returns the elements for which the block is true. Six in, three out.
  • map { EXPRESSION } @list returns what the block produced for each element. Six in, six out.

Inside the block, the current element is in $_ — the default variable from Chapter 2.12 §4. That is the whole interface.

Both are the loop you would otherwise write:

Azvya Education Pvt. Ltd.VLSI Mentor
What they replace
my @over;
for my $n (@nums) {
    push @over, $n if $n > 3;
}

Four lines against one. The one-line form is preferred when the block is short, because the intent — filter this list — is visible at the start of the line rather than assembled from a loop and a conditional push.

2. grep in Scalar Context Is a Count

Azvya Education Pvt. Ltd.VLSI Mentor
grep-count.pl
use strict;
use warnings;

my @status = ("PASS", "FAIL", "PASS", "PASS");

my @passing = grep { $_ eq "PASS" } @status;
my $n       = grep { $_ eq "PASS" } @status;

printf "list   : %s\n", join(" ", @passing);
printf "scalar : %d of %d\n", $n, scalar(@status);
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
list   : PASS PASS PASS
scalar : 3 of 4

The same grep, assigned two ways. In list context you get the matching elements; in scalar context you get how many matched.

That is Chapter 5.7's rule applied to a function's return value, and it makes the counting idiom short:

Azvya Education Pvt. Ltd.VLSI Mentor
count-idioms.pl
use strict;
use warnings;

my @names = ("alu", "fifo", "decoder");

my $failed = grep { $_ eq "FAIL" } ("PASS", "FAIL", "FAIL");

printf "failures counted : %d\n", $failed;
printf "is fifo present  : %s\n", (grep { $_ eq "fifo" } @names) ? "yes" : "no";
printf "is cpu present   : %s\n", (grep { $_ eq "cpu"  } @names) ? "yes" : "no";
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
failures counted : 2
is fifo present  : yes
is cpu present   : no

The membership test works because a condition is scalar context, so grep returns a count, and 0 is the only false count — Chapter 2.8. Chapter 5.11 takes these idioms further.

3. map Is Not One-to-One

The name suggests one output per input. It does not work that way, and both directions are useful.

It can expand

Azvya Education Pvt. Ltd.VLSI Mentor
map-expand.pl
use strict;
use warnings;

my @a = (1, 2);

my @pairs = map { ($_, $_ * 10) } @a;

printf "count=%d  [%s]\n", scalar(@pairs), join(" ", @pairs);
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
count=4  [1 10 2 20]

Two elements in, four out. The block returned a two-element list for each input, and all of those were collected into one flat result.

That flattening is Chapter 5.12's subject, and it is what makes the expansion work.

It can filter

Azvya Education Pvt. Ltd.VLSI Mentor
map-filter.pl
use strict;
use warnings;

my @a = (1, 2, 3, 4);

my @odd = map { $_ % 2 ? $_ : () } @a;

printf "count=%d  [%s]\n", scalar(@odd), join(" ", @odd);
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
count=2  [1 3]

Returning the empty list () contributes nothing to the result, so the even numbers vanished. map just did grep's job.

That is worth knowing because you will see it, and worth not writing: if you are filtering, grep { $_ % 2 } says so. Use the () return when you need to filter and transform in one pass — which is a real case, and the only one where it is clearer than two steps.

4. $_ Is an Alias, Not a Copy

Here is the behaviour to take seriously.

Azvya Education Pvt. Ltd.VLSI Mentor
alias.pl
use strict;
use warnings;

my @safe = ("a", "b", "c");
my @upper = map { uc $_ } @safe;

printf "read only  : orig=[%s] result=[%s]\n", join(" ", @safe), join(" ", @upper);

my @unsafe = ("a", "b", "c");
my @also = map { $_ = uc $_ } @unsafe;

printf "assigned   : orig=[%s] result=[%s]\n", join(" ", @unsafe), join(" ", @also);
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output — STDOUT
read only  : orig=[a b c] result=[A B C]
assigned   : orig=[A B C] result=[A B C]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet

Read the second line. @unsafe is now [A B C]. The source array was rewritten.

The two blocks differ by three characters:

  • { uc $_ } reads $_ and returns a new value. The original is untouched.
  • { $_ = uc $_ } assigns to $_. Since $_ is an alias to the element in the source list, assigning to it writes through to the original array.

Nothing on STDERR. Both blocks produce the same returned list, so the only visible difference is in the array you thought you were only reading.

grep behaves the same way:

Azvya Education Pvt. Ltd.VLSI Mentor
grep-alias.pl
use strict;
use warnings;

my @names = ("x", "y");

my @kept = grep { $_ = uc $_; 1 } @names;

printf "orig=[%s]\n", join(" ", @names);
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
orig=[X Y]

5. The Expression Form

Both functions have a second syntax without braces:

Azvya Education Pvt. Ltd.VLSI Mentor
expression-form.pl
use strict;
use warnings;

my @names = ("alu", "fifo", "decoder");

my @long_block = grep { length($_) > 3 } @names;
my @long_expr  = grep  length($_) > 3,   @names;

printf "block form      : %s\n", join(" ", @long_block);
printf "expression form : %s\n", join(" ", @long_expr);
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
block form      : fifo decoder
expression form : fifo decoder

Both syntaxes gave the same two names. alu is three characters, which is not greater than three, so it was correctly dropped by both.

The difference is only notation:

  • The block form takes a block in braces and no comma after it.
  • The expression form takes an expression and a comma before the list.

Use the block form. It scales to a multi-statement condition, it is visually distinct from an ordinary function call, and mixing the two forms in one codebase is a small readability tax for no gain. The expression form is here so you recognise it in inherited code.

6. Chaining Them

Because each returns a list, they compose — and they are read right to left.

Azvya Education Pvt. Ltd.VLSI Mentor
chain.pl
use strict;
use warnings;

my @rows = ("alu,184,177", "fifo,92,92", "decoder,1240,1198", "regfile,300,271");

my @low = map  { (split /,/, $_)[0] }
          grep { my @f = split /,/, $_; $f[2] / $f[1] < 0.95 }
          @rows;

printf "below 95%%: %s\n", join(", ", @low);
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
below 95%: regfile

Read it from the bottom: take @rows, keep those whose pass rate is under 0.95, then extract the name from each. One expression, and the pipeline order on the page is the reverse of the reading order — which is the one genuine cost of chaining.

If a chain needs more than two stages, name the intermediate results instead:

Azvya Education Pvt. Ltd.VLSI Mentor
named-stages.pl
use strict;
use warnings;

my @rows = ("alu,184,177", "fifo,92,92", "decoder,1240,1198", "regfile,300,271");

my @failing_rows = grep { my @f = split /,/, $_; $f[2] / $f[1] < 0.95 } @rows;
my @failing_names = map { (split /,/, $_)[0] } @failing_rows;

printf "below 95%%: %s\n", join(", ", @failing_names);
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
below 95%: regfile

Two lines, two names, and each stage is printable when something looks wrong. For anything a colleague will maintain, that is the better trade.

7. Why This Matters in Engineering Work

Filtering a result set is the commonest use. "Which tests failed", "which paths have negative slack", "which cells are above the area budget" — all grep with a condition, and all one line.

Counting without a loop. my $n = grep { ... } @list is the count, and if (grep { ... } @list) is the membership test. Both read as the question being asked.

Extracting one field from every row is map { (split /,/, $_)[N] }, which combines this chapter with Chapter 5.6 §5's list slice.

And the alias hazard is a real bug with a long fuse. A map that rewrites its input array produces correct output and corrupt state, and the corruption surfaces wherever that array is read next. Reviewing a map block for an = on $_ takes two seconds and is worth doing, because use warnings will not tell you.

8. Common Mistakes

Assigning to $_ inside the block. It writes through to the source array, silently. The block should return a value.

Expecting map to return one element per input. It returns whatever each block returned, flattened — so it can expand or filter.

Using map to filter when grep would say it. Reserve the () return for filter-and-transform in one pass.

Forgetting grep's scalar context gives a count. That is the feature, not a surprise — but my $x = grep {...} is a number, not a list.

Mixing the block and expression forms. Pick the block form.

Chaining three or more stages. Read right to left is tolerable for two; name the stages beyond that.

Splitting inside both a grep and a map in the same chain. You are parsing each row twice. Fine for small lists; name the stages and split once when it matters.

9. Exercise

Exercise 1 — both functions. From (1 .. 10), use grep to keep the values above 6 and map to produce each value squared. Report both lists and their counts.

Exercise 2 — the count. Given eight status strings of which five are PASS, get the count of passes in one expression, then write the membership test for whether any TIMEOUT is present.

Exercise 3 — the alias, measured. Build an array of three lowercase names. Uppercase them with map { uc $_ } and print the original. Then do it again with map { $_ = uc $_ } and print the original. Record both, confirm STDERR is empty, and say which three characters caused the difference.

Exercise 4 — the same hazard in a loop. Modify an array in place with for my $v (@a) { $v = uc $v }. Confirm the array changed. Then say in one clause when you would write that deliberately and what you would add to the line.

Exercise 5 — expand. Use map to turn ("a", "b") into ("a", "A", "b", "B") in one expression. Report the element count.

Exercise 6 — filter with map, then with grep. Keep the even numbers from (1 .. 8) twice: once with map returning (), once with grep. Confirm both give the same list and say which you would commit.

Exercise 7 — a chain, then named stages. From four rows of name,total,passed, produce the names whose pass rate is below 1.0, first as a single map/grep chain and then as two named steps. Say which you would hand to a colleague and why.

10. Summary

  • grep { COND } @list keeps the matching elements; map { EXPR } @list returns what the block produced for each.
  • The current element is in $_ inside both blocks.
  • grep in scalar context returns the count, which makes counting and membership tests one-liners.
  • map is not one-to-one. A block returning several values expands the result; a block returning () filters.
  • $_ is an alias to the original element. Reading it is safe; assigning to it rewrites the source array, silently.
  • A foreach loop variable is an alias too — occasionally useful deliberately, always worth a comment.
  • The block form and the expression form are equivalent. Prefer the block.
  • Chains read right to left. Two stages is fine; name the intermediates beyond that.

Chapter 5.11 collects the short answers to three questions this chapter made possible — is it in there, how many match, and what does it look like reversed — including the one where reverse does something completely different depending on the context it is called in.

Where this fits

Part of the Perl curriculum.