名称
Algorithm::Loops - ループの構成:NestedLoops, MapCar*, Filter, NextPermute*
概要
use Algorithm::Loops qw(
Filter
MapCar MapCarU MapCarE MapCarMin
NextPermute NextPermuteNum
NestedLoops
);
my @copy= Filter {tr/A-Z'.,"()/a-z/d} @list;
my $string= Filter {s/\s*$/ /} @lines;
my @transposed= MapCarU {[@_]} @matrix;
my @list= sort getList();
do {
usePermutation( @list );
} while( NextPermute( @list ) );
my $len= @ARGV ? $ARGV[0] : 3;
my @list= NestedLoops(
[ ( [ 1..$len ] ) x $len ],
sub { "@_" },
);
もしサンプルコードを動作させたいなら、以下の特定の関数のセクションを参照すること。上記サンプルは単に、関数が典型的に動作するかを示すためのものである。
関数
Algorithm::Loops は以下に列挙する関数を提供する。デフォルトではどの関数もユーザの名前空間にはエクスポートされない。これらの関数からどのモジュールを利用しているかを明示し、終了させるために Algorithm::Loops ステートメントを利用することで関数を列挙する必要がある。
Filter
-
map と似ているが、s/// や他の再帰演算子とともに使用することが想定されている。リストの修正されたコピーを返す。
MapCar, MapCarU, MapCarE, and MapCarMin
-
map と似ているが、同時に複数のリストに対してループ処理を行う。
NextPermute and NextPermuteNum
-
重複した要素があってもリストの全ての(ユニークな)組み合わせを効果的に見つける。
NestedLoops
-
任意の深さでネストした foreach ループを計算する。
Filter(\&@)
概要
値のリストの修正されたコピーを生成する。s/// として利用することが理想である。 map(または grep)内部で s/// または tr/// として使うのであれば代わりに Filter を使うべきである。例えば、以下の通りである。
use Algorithm::Loops qw( Filter );
@copy = Filter { s/\\(.)/$1/g } @list;
$text = Filter { s/^\s+// } @lines;
The same process can be accomplished using a careful and more complex invocation of map, grep, or foreach. However, many incorrect ways to attempt this seem rather seductively appropriate so this function helps to discourage such (rather common) mistakes.
使用法
Filter has a prototype specification of (\&@).
This means that it demands that the first argument that you pass to it be a CODE reference. After that you can pass a list of as many or as few values as you like.
For each value in the passed-in list, a copy of the value is placed into $_ and then your CODE reference is called. Your subroutine is expected to modify $_ and this modified value is then placed into the list of values to be returned by Filter.
If used in a scalar context, Filter returns a single string that is the result of:
$string= join "", @results;
Note that no arguments are passed to your subroutine (so don't bother with @_) and any value Ced by your subroutine is ignored.
Filter's prototype also means that you can use the "map BLOCK"-like syntax by leaving off the sub keyword if you also leave off the comma after the block that defines your anonymous subroutine:
my @copy= Filter sub {s/\s/_/g}, @list;
# becomes: v^^^ v ^
my @copy= Filter {s/\s/_/g} @list;
Most of our examples will use this shorter syntax.
Note also that by importing Filter via the C
use Algorithm::Loops qw( Filter );
it gets declared before the rest of our code is compiled so we don't have to use parentheses when calling it. We I if we want to, however:
my @copy= Filter( sub {s/\s/_/g}, @list );
"Function BLOCK LIST" バグに関するノート
Note that in at least some versions of Perl, support for the "Filter BLOCK ..." syntax is somewhat fragile. For example:
... Filter( {y/aeiou/UAEIO/} @list );
これは次のようなエラーを出すかも知れない。
Array found where operator expected
which can be fixed by dropping the parentheses:
... Filter {y/aeiou/UAEIO/} @list;
So if you need or want to use parentheses when calling Filter, it is best to also include the sub keyword and the comma:
# v <--------- These ---------> v
... Filter( sub {y/aeiou/UAEIO/}, @list );
# require ^^^ <--- these ---> ^ (sometimes)
so your code will be portable to more versions of Perl.
使用例
Good code ignores "invisible" characters. So instead of just chomp()ing, consider removing all trailing whitespace:
my @lines= Filter { s/\s+$// } ;
or
my $line= Filter { s/\s+$// } scalar ;
[ Note that Filter can be used in a scalar context but always puts its arguments in a list context. So we need to use C or something similar if we want to read only one line at a time from C above. ]
Want to sort strings that contain mixtures of letters and natural numbers (non-negative integers) both alphabetically and numerically at the same time? This simple way to do a "natural" sort is also one of the fastest.
Great for sorting version numbers, file names, etc.:
my @sorted= Filter {
s#\d{2}(\d+)#\1#g
} sort Filter {
s#(\d+)# sprintf "%02d%s", length($1), $1 #g
} @data;
[ Note that at least some versions of Perl have a bug that breaks C if you write sub { as part of building the list of items to be sorted but you don't provide a comparison routine. This bug means we can't write the previous code as:
my @sorted= Filter {
s#\d{2}(\d+)#\1#g
} sort Filter sub {
s#(\d+)# sprintf "%02d%s", length($1), $1 #g
}, @data;
because it will produce the following error:
Undefined subroutine in sort
in some versions of Perl. Some versions of Perl may even require you to write it like this:
my @sorted= Filter {
s#\d{2}(\d+)#\1#g
} sort &Filter( sub {
s#(\d+)# sprintf "%02d%s", length($1), $1 #g
}, @data );
Which is how I wrote it in ex/NaturalSort.plx. ]
Need to sort names? Then you'll probably want to ignore letter case and certain punctuation marks while still preserving both:
my @compare= Filter {tr/A-Z'.,"()/a-z/d} @names;
my @indices= sort {$compare[$a] cmp $compare[$b]} 0..$#names;
@names= @names[@indices];
You can also roll your own simple HTML templating:
print Filter {
s/%(\w*)%/expand($1)/g
} $cgi->...,
...
$cgi->...;
Note that it also also works correctly if you change how you output your HTML and accidentally switch from list to scalar context:
my $html= '';
...
$html .= Filter {
s/%(\w*)%/expand($1)/g
}$cgi->...,
...
$cgi->...;
Motivation
A reasonable use of map is:
@copy= map {lc} @list;
which sets @copy to be a copy of @list but with all of the elements converted to lower case. But it is too easy to think that that could also be done like this:
@copy= map {tr/A-Z/a-z/} @list; # 間違い
The reason why these aren't the same is similar to why we write:
$str= lc $str;
not
lc $str; # Useless use of 'lc' in void context
and we write:
$str =~ tr/A-Z/a-z/;
not
$new= ( $old =~ tr/A-Z/a-z/ ); # 間違い
That is, many things (such as lc) return a modified copy of what they are given, but a few things (such as tr///, s///, chop, and chomp) modify what they are given I.
This distinction is so common that we have several ways of switching between the two forms. For example:
$two= $one + $other;
# vs.
$one += $other;
or
$two= substr($one,0,4);
# vs.
substr($one,4)= '';
I've even heard talk of adding some syntax to Perl to allow you to make things like C become reflexive, similar to how += is the reflexive form of +.
But while many non-reflexive Perl operations have reflexive counterparts, there are a few reflexive Perl operations that don't really have non-reflexive counterparts: s///, tr///, chop, chomp.
You can write:
my $line= ;
chomp( $line );
# or
chomp( my $line= );
but it somehow seems more natural to write:
my $line= chomp( ); # 間違い
So, if you dislike hiding the variable declaration inside of a function call or dislike using two lines and repeating the variable name, then you can now use:
my $line= Filter {chomp} ''.;
[ I used C<''.> to provide a scalar context so that only one line is read from STDIN. ]
Or, for a better example, consider these valid alternatives:
my @lines= ;
chomp( @lines );
# または
chomp( my @lines= );
And what you might expect to work (but doesn't):
my @lines= chomp( ); # 間違い
And what you can now use instead:
my @lines= Filter {chomp} ;
Here are some examples of ways to use map/grep correctly to get Filter's functionality:
Filter { CODE } @list
# vs
join "", map { local($_)= $_; CODE; $_ } @list
# vs
join "", grep { CODE; 1 } @{ [@list] }
Not horribly complex, but enough that it is very easy to forget part of the solution, making for easy mistakes. I see mistakes related to this quite frequently and have made such mistakes myself several times.
Some (including me) would even consider the last form above to be an abuse (or misuse) of C.
You can also use C/C to get the same results as Filter:
my @copy= Filter { CODE } @list;
# vs
STATEMENT foreach my @copy= @list;
# or
my @copy= @list;
foreach( @copy ) {
CODE;
}
MapCar*
=over 4
=item MapCar(\&@)
=item MapCarU(\&@)
=item MapCarE(\&@)
=item MapCarMin(\&@)
=back
Motivation
=head3 Usage
The MapCar* functions are all like C