Perl stacks, queues, and word frequencies
Use push, pop, shift, and unshift as stack and queue operations, and count items with a hash.
use strict;
use warnings;
my @stack;
push @stack, 1, 2, 3;
my $top = pop @stack;
print "$top @stack\n";
my @queue = (1, 2, 3);
my $front = shift @queue;
push @queue, 4;
print "$front @queue\n";
unshift @queue, 0;
print "@queue\n";
my $text = "the cat and the hat and the bat";
my %freq;
$freq{$_}++ for split ' ', $text;
print join(" ", map { "$_=$freq{$_}" } sort { $freq{$b} <=> $freq{$a} or $a cmp $b } keys %freq), "\n";
my @even = grep { $_ % 2 == 0 } 1 .. 10;
my @squares = map { $_ ** 2 } @even;
print "@even | @squares\n";
my %seen;
my @unique = grep { !$seen{$_}++ } qw(a b a c b);
print "@unique\n";3 1 2 1 2 3 4 0 2 3 4 the=3 and=2 bat=1 cat=1 hat=1 2 4 6 8 10 | 4 16 36 64 100 a b c
Explanation
A Perl array is also a stack and a queue. push adds at the end and pop removes from the end, which gives a last-in, first-out stack. shift removes from the front and unshift adds at the front, so push with shift is a first-in, first-out queue. All four operations are efficient, and no extra module is needed.
A hash makes counting easy. $freq{$word}++ creates the key with the value 1 the first time, because an undefined value counts as zero in numeric context. grep filters a list with a condition, map transforms each element, and the idiom grep { !$seen{$_}++ } removes duplicates while keeping the first occurrence of each item.
Explore the dry run
use strict;use warnings; my @stack;push @stack, 1, 2, 3;my $top = pop @stack;print "$top @stack\n"; my @queue = (1, 2, 3);my $front = shift @queue;push @queue, 4;print "$front @queue\n";unshift @queue, 0;print "@queue\n"; my $text = "the cat and the hat and the bat";my %freq;$freq{$_}++ for split ' ', $text;print join(" ", map { "$_=$freq{$_}" } sort { $freq{$b} <=> $freq{$a} or $a cmp $b } keys %freq), "\n"; my @even = grep { $_ % 2 == 0 } 1 .. 10;my @squares = map { $_ ** 2 } @even;print "@even | @squares\n"; my %seen;my @unique = grep { !$seen{$_}++ } qw(a b a c b);print "@unique\n";Follow the example
Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.
View the complete dry-run table
| Step / line | What happens | State after step | Output so far |
|---|---|---|---|
| pop removes the last element: the most recently pushed one. | — | — | |
| 3 came out; 1 and 2 remain. | — | 3 1 2 | |
| shift removes the first element: the oldest one. | — | 3 1 2 | |
| 1 left the front; 4 joined the back. | — | 3 1 2 1 2 3 4 | |
| After unshift, 0 is at the front. | — | 3 1 2 1 2 3 4 0 2 3 4 | |
| Count every word. A missing key starts at zero. | — | 3 1 2 1 2 3 4 0 2 3 4 | |
| Highest counts first, ties broken alphabetically. | — | 3 1 2 1 2 3 4 0 2 3 4 the=3 and=2 bat=1 cat=1 hat=1 | |
| grep keeps the even numbers and map squares them. | — | 3 1 2 1 2 3 4 0 2 3 4 the=3 and=2 bat=1 cat=1 hat=1 2 4 6 8 10 | 4 16 36 64 100 | |
| !$seen{$_}++ is true only the first time an item is seen. | — | 3 1 2 1 2 3 4 0 2 3 4 the=3 and=2 bat=1 cat=1 hat=1 2 4 6 8 10 | 4 16 36 64 100 a b c |
Cost and common mistakes
push, pop, shift, and unshift are O(1) amortized. Counting is O(n).
- shift and pop on an empty array return undef, and with use warnings, printing undef gives a warning.
- In grep and map blocks, $_ is the current element and is an alias to the original. Modifying it changes the array.
- Hash order is random. Sort the keys whenever the output order matters.
Try it yourself
How would you count only words longer than three letters?
Reveal answer and reasoning
Add a condition: $freq{$_}++ for grep { length($_) > 3 } split ' ', $text;