Perl stacks, queues, and word frequencies

Use push, pop, shift, and unshift as stack and queue operations, and count items with a hash.

Perl
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";
Output
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
Guided walkthroughReady
1use strict;
2use warnings;
3
4my @stack;
5push @stack, 1, 2, 3;
6my $top = pop @stack;
7print "$top @stack\n";
8
9my @queue = (1, 2, 3);
10my $front = shift @queue;
11push @queue, 4;
12print "$front @queue\n";
13unshift @queue, 0;
14print "@queue\n";
15
16my $text = "the cat and the hat and the bat";
17my %freq;
18$freq{$_}++ for split ' ', $text;
19print join(" ", map { "$_=$freq{$_}" } sort { $freq{$b} <=> $freq{$a} or $a cmp $b } keys %freq), "\n";
20
21my @even = grep { $_ % 2 == 0 } 1 .. 10;
22my @squares = map { $_ ** 2 } @even;
23print "@even | @squares\n";
24
25my %seen;
26my @unique = grep { !$seen{$_}++ } qw(a b a c b);
27print "@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
Prepared trace of the published program. Some rows group repeated iterations.
Step / lineWhat happensState after stepOutput 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;

Further reading