patternMinor
Calculate all anagrams of a word with Perl
Viewed 0 times
anagramswithallperlwordcalculate
Problem
In order to become more familiar with Perl I have implemented a code kata from here.
The task was to write a program which prints out all anagrams for a given word. I'd appreciate your review of my solution:
get_anagrams.pl
anagrams.pm
anagrams.t
```
use strict;
use war
The task was to write a program which prints out all anagrams for a given word. I'd appreciate your review of my solution:
get_anagrams.pl
use strict;
use warnings;
require 'anagrams.pm';
use feature 'say';
my $number_of_arguments = $#ARGV + 1;
if ($number_of_arguments != 1) {
say 'Usage: get_anagrams.pl word';
exit 1;
}
foreach my $word (get_permutations($ARGV[0])) {
say $word;
}anagrams.pm
use strict;
use warnings;
sub get_permutations
{
my $string = shift;
my @permutations = ();
my @letters = split //, $string;
my $number_of_letters = scalar(@letters);
if ( $number_of_letters == 1 ) {
push @permutations, $letters[0];
}
elsif ( $number_of_letters == 2 ) {
push @permutations, $letters[0] . $letters[1];
push @permutations, $letters[1] . $letters[0];
}
elsif ( $number_of_letters > 2 ) {
for ( my $i = 0 ; $i < $number_of_letters ; $i++ ) {
my $first_letter_in_word = $letters[$i];
my $other_letters = remove_letter_by_index($string, $i);
foreach my $sub_permutation ( get_permutations($other_letters) ) {
push @permutations, $first_letter_in_word . $sub_permutation;
}
}
}
return @permutations;
}
sub remove_letter_by_index
{
my ( $string, $index ) = @_;
my $result = '';
my @letters = split //, $string;
my $number_of_letters = scalar(@letters);
for ( my $i = 0 ; $i < $number_of_letters ; $i++ ) {
if ( $i != $index ) {
$result = $result . $letters[$i];
}
}
return $result;
}
sub is_element_in_list
{
my ( $element, @list ) = @_;
if ( not $element ) {
return 0;
}
foreach my $item (@list) {
if ( $item eq $element ) {
return 1;
}
}
return 0;
}
return 1;anagrams.t
```
use strict;
use war
Solution
You have written very self-documenting code! That's good. However, you seem somewhat new to Perl, so I'll walk you through some Perl-specific best practices and idioms.
Namespaces/Packages/Modules,
You have put your implementation into a separate file
Assuming you do not intend to distribute your code, using the name
To load a module, there are three main techniques available:
The problem with namespaces is that symbol names become tediously long. For example, the
Then in
And in
In larger software systems, exporting symbols doesn't really scale and may still introduce name clashes. The
Previously, I said that Perl searches for modules in “all registered module locations”. These can be inspected in the
This assumes a standard project directory layout such as
For tests, I don't use
All Perl scripts should use a shebang – generally
Error messages should go on STDERR, not STDOUT. This becomes really important when your program is used in a pipe, or with shell redirection. E.g.
Note that after
Namespaces/Packages/Modules,
use vs. requireYou have put your implementation into a separate file
anagrams.pm, which you load via require 'anagrams.pm'. When writing a Perl module, we should put it into its own namespace. There is a relationship between file paths and package names: a package Foo::Bar::Baz should reside in a file Foo/Bar/Baz.pm. It is customary for package names to use PascalCase. Lower-case names are only used for pragmas such as strict or warnings that modify how Perl itself behaves. While package names look hierarchical, they are only so by convention: Foo::Bar is not technically an “inner package” of Foo, although names are often used in this manner. Top-level package names should not be used for “real” code, since this is likely to clash with package names from CPAN (the open-source Perl module repository). The Local:: namespace can be used to avoid clashes, since no CPAN module will ever use it. However, if you want to distribute your module, you should reserve a namespace: either by publishing your code in CPAN under a specific module name, or by registering a PAUSE account name. The PAUSE account name can be used as a reserved namespace. E.g. I have some unpublished personal software under the AMON:: prefix.Assuming you do not intend to distribute your code, using the name
Local::Anagrams might be sensible.To load a module, there are three main techniques available:
require 'path'is rarely used, since the module system is often preferable.
require Module::Namesearches forModule/Name.pmin all registered module locations at runtime. This form is only used for deferred module loading, where we only want to load a module if a specific code path was taken.
use Module::Nameis likerequire Module::Name, but at “compile time”. It is the preferred module loading mechanism, since it allows the loaded module to put symbols into the loading namespace.
The problem with namespaces is that symbol names become tediously long. For example, the
get_permutations sub from Local::Anagrams would be accessed as Local::Anagrams::get_permutations. When loading our module with use, we can allow certain symbols to be exported from our namespace. Traditionally, the built-in Exporter module is used. In the package-global variable @EXPORT_OK, we list the names of all subroutines we want to make available. The useing module can then list the sub names it wants to import:package Local::Anagrams;
use strict;
use warnings;
use Exporter 'import';
our @EXPORT_OK = qw/ get_permutations is_element_in_list /;
...Then in
get_anagrams.pl:use Local::Anagrams 'get_permutations';And in
anagrams.t:use Local::Anagrams qw/ get_permutations is_element_in_list /;In larger software systems, exporting symbols doesn't really scale and may still introduce name clashes. The
Sub::Exporter module allows the importing module to customize the imported subroutines, but at some point you might give up and prefer to deliver your functionality via an object-oriented rather than procedural interface.Previously, I said that Perl searches for modules in “all registered module locations”. These can be inspected in the
@INC global variable. For example, the current working directory . is usually part of @INC. But unless you install your custom modules via CPAN, they will not generally be in one of the expected locations. If we want to load a module relative to the location of a script, we have to add a new module location:# in get_anagrams.pl:
use FindBin; # finds the location of our script
use lib "$FindBin::Bin/../lib";
use Local::Anagrams;This assumes a standard project directory layout such as
anagrams-project/
bin/
get_anagrams.pl
lib/
Local/
Anagrams.pm
t/
anagrams.t
For tests, I don't use
Find::Bin. Instead, I supply the library directory to the test runner, e.g. perl -Ilib t/anagrams.t or prove -l t.get_anagrams.plAll Perl scripts should use a shebang – generally
#!/usr/bin/perl or #!/usr/bin/env perl. While this is only used on Unix systems or in some web servers, I'd still recommend using it even on Windows.my $number_of_arguments = $#ARGV + 1 is a complicated way of saying my $number_of_arguments = @ARGV. Actually, the variable $number_of_arguments is useless here, and you could use @ARGV directly.Error messages should go on STDERR, not STDOUT. This becomes really important when your program is used in a pipe, or with shell redirection. E.g.
perl get_anagrams.pl > anagrams.txt would write the error to the file rather than displaying it to the user. Instead:say STDERR "Usage: get_anagrams.pl ";
exit 1;Note that after
STDERR there is no comma. You could also use methoCode Snippets
package Local::Anagrams;
use strict;
use warnings;
use Exporter 'import';
our @EXPORT_OK = qw/ get_permutations is_element_in_list /;
...use Local::Anagrams 'get_permutations';use Local::Anagrams qw/ get_permutations is_element_in_list /;# in get_anagrams.pl:
use FindBin; # finds the location of our script
use lib "$FindBin::Bin/../lib";
use Local::Anagrams;say STDERR "Usage: get_anagrams.pl <word>";
exit 1;Context
StackExchange Code Review Q#97314, answer score: 6
Revisions (0)
No revisions yet.