LoginSignup
9
9

各メジャーバージョンの Perl に同梱されている List::Util で使える関数一覧

Last updated at Posted at 2020-03-16

リスト処理に有用な関数群を収録している Perl モジュール List::Util はコアモジュールであり、原則的にどの Perl 環境でも同梱されています。

とはいえ、Perl のバージョンアップに伴って、同梱されている List::Util もバージョンアップしており、収録されている関数にも差異があります。2010年前後の古い Perl だと外部モジュール List::MoreUtils を別途インストールしないと出来なかったことも、2020年代の新しい Perl であれば List::Util のみで完結することもあります。

以下、Perl のバージョンと、それに同梱されている List::Util のバージョン、そしてその List::Util が提供する関数一覧を表にしてみました。なお Perl 5.8 より前のバージョンの Perl は省略しています。

Perl List::Util List::Util で使える関数一覧
5.8.0 1.07_00 first min max minstr maxstr reduce sum shuffle
5.10.0 1.19 first min max minstr maxstr reduce sum shuffle
5.12.0 1.22 first min max minstr maxstr reduce sum shuffle
5.14.0 1.23 first min max minstr maxstr reduce sum shuffle
5.16.0 1.23 first min max minstr maxstr reduce sum shuffle
5.18.0 1.27 first min max minstr maxstr reduce sum sum0 shuffle
5.20.0 1.38 all any first min max minstr maxstr none notall product reduce sum sum0 shuffle pairmap pairgrep pairfirst pairs pairkeys pairvalues
5.22.0 1.41 all any first min max minstr maxstr none notall product reduce sum sum0 shuffle pairmap pairgrep pairfirst pairs pairkeys pairvalues
5.24.0 1.42_02 all any first min max minstr maxstr none notall product reduce sum sum0 shuffle pairs unpairs pairkeys pairvalues pairmap pairgrep pairfirst
5.26.0 1.46_02 all any first min max minstr maxstr none notall product reduce sum sum0 shuffle uniq uniqnum uniqstr pairs unpairs pairkeys pairvalues pairmap pairgrep pairfirst
5.28.0 1.50 all any first min max minstr maxstr none notall product reduce sum sum0 shuffle uniq uniqnum uniqstr head tail pairs unpairs pairkeys pairvalues pairmap pairgrep pairfirst
5.30.0 1.50 all any first min max minstr maxstr none notall product reduce sum sum0 shuffle uniq uniqnum uniqstr head tail pairs unpairs pairkeys pairvalues pairmap pairgrep pairfirst
5.32.0 1.55 all any first min max minstr maxstr none notall product reduce reductions sum sum0 sample shuffle uniq uniqint uniqnum uniqstr head tail pairs unpairs pairkeys pairvalues pairmap pairgrep pairfirst
5.34.0 1.55 all any first min max minstr maxstr none notall product reduce reductions sum sum0 sample shuffle uniq uniqint uniqnum uniqstr head tail pairs unpairs pairkeys pairvalues pairmap pairgrep pairfirst
5.36.0 1.62 all any first min max minstr maxstr none notall product reduce reductions sum sum0 sample shuffle uniq uniqint uniqnum uniqstr zip zip_longest zip_shortest mesh mesh_longest mesh_shortest head tail pairs unpairs pairkeys pairvalues pairmap pairgrep pairfirst
5.38.0 1.63 all any first min max minstr maxstr none notall product reduce reductions sum sum0 sample shuffle uniq uniqint uniqnum uniqstr zip zip_longest zip_shortest mesh mesh_longest mesh_shortest head tail pairs unpairs pairkeys pairvalues pairmap pairgrep pairfirst

私の場合、よく uniq が使いたくて List::MoreUtils を入れたり自分で uniq を書いたりしていたのですが、Perl 5.26 以降だと List::Util が uniq を提供していることが分かります。

各関数の説明

先ほどの表は Perl と List::Util のバージョンが切り口でしたが、各関数を切り口に、その簡単な説明と初出の Perl バージョンを表にしてみました。

なお、同様に Perl 5.8 より前のバージョンは省略しています。Perl 5.8 以前に同梱されていた List::Util で使えた関数も、初出バージョンは 5.8 としています。

  • 概ね第1引数はリストです
    • 解説がない場合はリストです
  • 第1引数の前、間接オブジェクトスロットに無名ブロックまたはサブルーチンリファレンスを取る sort 型の関数について
    • いわゆる foo { $a <=> $b } @listbar \&hint @list といった呼び出し方をされる関数です
    • 解説では必要に応じて単に「(与えられた)ブロック」と書いています
    • 説明の冒頭に記号注釈を書いています
      • ブロックが1つの引数 $_ を取るものは &($_)
      • ブロックが2つの引数 $a および $b を取るものは &($a, $b)
    • ブロックが引数 $_ を取るものは、リストの左側から順番に要素を $_ に代入、そのブロックにある最後に評価された文の評価結果に応じて何かが行われます
  • reduce はセルに入れるには説明が複雑になったので省いていますが、多くのプログラミング言語にある reduce と同様です
    • (I) 最初に呼び出されるブロックでは ($a, $b) = @list[0,1] とする
    • (II) $a $b におけるブロックの評価値を改めて $a とする
    • (III) $list[2]$b とする
    • (IV) II を行う
    • (V) $list[$i] の添字を1増分して III を行う
    • (VI) 添字がリストの要素数を超過する直前まで上記 IV〜V を繰り返す
    • (VII) 最後の $a の結果が reduce 全体の評価結果となる
  • reductions は reduce の処理途中を見せてくれる版です
    • 例として
      • say join ",", reductions { $a+$b } 1..10 を実行すると 1,3,6,10,15,21,28,36,45,55 を表示します
      • say join ",", reductions { $a . "-" . $b } 1..5 を実行すると 1,1-2,1-2-3,1-2-3-4,1-2-3-4-5 を表示します
    • reductions が返す最後の要素が、同じ引数で reduce を実行したときの結果です
  • uniq 系について
    • uniq は通常の重複を省きます。重複判定は eq で行われると考えるとわかりやすいです
    • uniqnum は uniq の数値評価版です。重複判定は == で行われると考えるとわかりやすいです
    • uniqint は uniq の整数評価版です。重複判定は int したものについて == で行われると考えるとわかりやすいです
    • @elements = (111, 222, 333, "222.0", "333.1") を比較すると違いが分かります
      • uniq(@elements); # => 111|222|333|222.0|333.1
      • uniqnum(@elements); # => 111|222|333|333.1
      • uniqint(@elements); # => 111|222|333
  • zip mesh 系について
    • zip はいわゆる転置行列を作るものだと考えるとわかりやすいかもしれません
    • mesh は zip と似ていますが、返り値はフラットなリストとなります
      • mesh の返り値のリストは、同じ引数の zip の返り値のリストを map { @$_ } したものと考えて良いでしょう
    • 2個の配列リファレンスを引数に取る mesh の返り値をハッシュに代入する使い方が有効でしょう
      • my %hash = mesh \@keys, \@values;
  • ペア系関数については詳細な解説を省いています
    • 今後の記事更新で追加するかもしれません
  • 詳細は perldoc(英語日本語)を参照下さい
関数 説明 初導入 Perl バージョン
first &($_) ブロックの評価結果が真となる最初の引数リストの要素を返す 5.8
min リストにある数の最小値を返す(数値評価) 5.8
max リストにある数の最大値を返す(数値評価) 5.8
minstr リストにある文字列の辞書順で最も最初のものを返す(文字列評価) 5.8
maxstr リストにある文字列の辞書順で最も最後のものを返す(文字列評価) 5.8
reduce &($a, $b) (別記) 5.8
sum リストの合計値(総和)を返す(数値評価) 5.8
shuffle リストの順序をランダムに入れ替えた新しいリストを返す 5.8
sum0 sum とほぼ同じ。ただし、引数リストが空だった場合、 sumundef を返すが、sum00 を返す 5.18
all &($_) ブロックの評価結果が全ての引数リストの要素で真の場合、真を返す 5.20
any &($_) ブロックの評価結果が何らかの引数リストの要素で真の場合、真を返す 5.20
none &($_) ブロックの評価結果が全ての引数リストの要素で偽の場合、真を返す 5.20
notall &($_) none の別名 5.20
product リストの全ての積の結果(総乗)を返す 5.20
pairmap &($a, $b) map のペアバージョン 5.20
pairgrep &($a, $b) grep のペアバージョン 5.20
pairfirst &($a, $b) first のペアバージョン 5.20
pairs 各要素がペア配列リファレンスであるリストを返す 5.20
pairkeys 与えられたリストをペアリストとみなし、偶数添字の要素のみ抜き出したリストを返す 5.20
pairvalues 与えられたリストをペアリストとみなし、奇数添字の要素のみ抜き出したリストを返す 5.20
unpairs pairgrep などで得られる、各要素がペア配列リファレンスであるリストの配列リファレンスをデリファレンスしたリストを返す 5.24
uniq 重複する要素を省いたリストを返す 5.26
uniqnum 数値として評価した結果、重複する要素を省いたリストを返す 5.26
uniqstr 文字列として評価した結果、重複する要素を省いたリストを返す 5.26
head head $size, @list の形で呼び出され、最大 $size 個で元のリストを左側に切り詰めた新しいリストを返す 5.28
tail tail $size, @list の形で呼び出され、最大 $size 個で元のリストを右側に切り詰めた新しいリストを返す 5.28
reductions &($a, $b)reduce と合わせて別記) 5.32
sample sample $count, @list の形で呼び出され、 @list からランダムに $count 個の要素を取り出した新しいリストを返す。 (shuffle @list)[0..($count-1)] と同等。 5.32
uniqint 整数として評価した結果、重複する要素を省いたリストを返す 5.32
zip 配列リファレンスのリストを取って、転置となる配列リファレンスのリストを返す 5.36
zip_longest zip の別名 5.36
zip_shortest zip と同様だが、引数の中で最も要素数が少ない配列リファレンスに処理を合わせる( undef を埋めない) 5.36
mesh zip と同様の引数を取るが、zip の返り値となる配列リファレンスを展開したリストを返す 5.36
mesh_longest mesh の別名 5.36
mesh_shortest mesh と同様だが、引数の中で最も要素数が少ない配列リファレンスに処理を合わせる(undef を埋めない) 5.36

List::Util に無ければ List::MoreUtils を探すサンプル

前述の uniq のように、古くは List::MoreUtils を使っていたものがコアモジュールの List::Util で使えるようになりました。

「古い perl のために List::MoreUtils から uniq 関数をインポートするコードも残したいけれど、新しい perl では List::Util から uniq 関数をインポートしたい(あえて List::MoreUtils をインストールしたくない)」という場合、例えば以下のように書くと良いでしょう。

# use constant DEBUG => $ENV{DEBUG};
BEGIN {
    local $@;
    eval {
        require List::Util;
        import  List::Util qw(uniq);
        #print "success load List::Util::uniq\n" if DEBUG;
    };
    return if !$@;
    undef $@;
    eval {
        require List::MoreUtils;
        import  List::MoreUtils qw(uniq);
        #print "success load List::MoreUtils::uniq\n" if DEBUG;
    };
    die $@ if $@
}

import List::Util qw(uniq) は間接オブジェクト記法 (indirect object syntax) という書き方。 require と並列すると縦に揃うのでこういうときに限って使われたりしますが、一般的には混乱が多い記法ということで Perl 5.32 から Perl 7 にかけて明示的に非推奨になっていく動きのようです。将来を見据えるなら以下のように間接オブジェクト記法を使わない書き方が安心かも。

# use constant DEBUG => $ENV{DEBUG};
BEGIN {
    local $@;
    eval {
        require List::Util;
        List::Util->import(qw(uniq));
        #print "success load List::Util::uniq\n" if DEBUG;
    };
    return if !$@; 
    undef $@;
    eval {
        require List::MoreUtils;
        List::MoreUtils->import(qw(uniq));
        #print "success load List::MoreUtils::uniq\n" if DEBUG;
    };
    die $@ if $@;
}

なお、難読にはなりますが、上記を for ループを使ってさらに短く書くとすると以下のようになります。require "List::Util"require に文字列を与えると、字面通りの "List::Util" という名前のファイルを @INC から探そうとするので、裸のワードが渡るよう require List::Util として実行するため、例外補足のためのブロック eval から文字列 eval に代えています。

BEGIN {
    my $e;
    for my $module (qw(List::Util List::MoreUtils)) {
        local $@;
        eval qq{require $module; import $module qw(uniq);};
        return if !($e = $@)
    }
    die $e if $e;
}

余談:上記の表の出し方

上記の表、最初は手で作成しようと思っていたのですが、以下の方針で多少の自動化をしてみました。

  • List::Util の Git リポジトリを clone する
  • 特定の Perl バージョンが同梱している List::Util のバージョンを corelist コマンドで調査する
  • リポジトリ内に lib/List/Util.pm が存在し、そこの @EXPORT_OK を見ると関数一覧がある
  • リポジトリには List::Util のバージョン番号に対応したタグが大体打たれているので、知りたいバージョンに対応したタグで checkout して前述の @EXPORT_OK を抜き出す

List::Util の GitHub リポジトリ を clone、リポジトリの作業ディレクトリトップに移動して、以下のような雑に書いたプログラムを保存、そして実行しました。

list-util-exports.pl
#!/usr/bin/env perl
use strict;
use warnings;
use feature qw(say signatures);
no warnings qw(experimental::signatures);

# vX.Y.Z
use constant VERSION_Y_MIN => 8;
use constant VERSION_Y_MAX => ($^V =~ /^v5\.(\d+)/);

die if !-f "lib/List/Util.pm";

my %special_vtag_mapping = (
    "v1.07_00" => "v1.07",
    "v1.42_02" => "v1.42_002",
    "v1.46_02" => "v1.46",
);

my @versions = list_util_versions();

# say join "\t", @$_ for @versions;
# say "---";

for my $pair_version (@versions) {
    my ($perl_version, $list_util_version) = @$pair_version;
    my $vtag = "v$list_util_version";
    if ( local $_ = $special_vtag_mapping{$vtag} ) {
        $vtag = $_;
    }
    git_checkout($vtag);
    my @methods = list_util_methods();
    printf "%s\t%s\t%s\n",
        $perl_version, $list_util_version, join " ", @methods;

}

sub git_checkout($arg) {
    my $cmd = "git checkout $arg >> debug.log 2>&1";
    my $rv = system $cmd;
    if ( $rv > 0 ) {
        die "command error ($rv): $cmd";
    }
}

sub list_util_versions {
    my @y_versions = (VERSION_Y_MIN .. VERSION_Y_MAX);
    my @versions; # [PERL_VERSION, LU_VERSION], ...
    for my $y ( grep { $_ % 2 == 0 } @y_versions ) {
        my $perl_version = "5.$y.0";
        my $output = capture( 'corelist', '-v', $perl_version, 'List::Util' );
        #printf "%s\t%s\n", $perl_version, $output;
        my ($list_util_version) = $output =~ /(\S+)$/;
        push @versions, [$perl_version, $list_util_version];
    }
    return @versions;
}

sub list_util_methods {
    my $content = slurp("lib/List/Util.pm");
    $content =~ m{
        \@EXPORT_OK
        \s* = \s*
        qw\( (?<exports>.*?) \)
    }sx;
    my $exports = $+{exports} || die;
    my @methods = $exports =~ /(\S+)/g;
    return @methods;
}

sub slurp($filename) {
    open my $fh, '<', $filename or die;
    my @content = <$fh>;
    close $fh;
    return join "", @content;
}

sub capture (@command) {
    my $pid = open my $pipe, '-|', @command;
    my @output = <$pipe>;
    chomp @output;
    close $pipe;
    return wantarray ? @output : join "\n", @output;
}

Markdown のテーブルにするため縦棒を入れたいなと

$ perl list-util.pl | perl -pe 's/\t/ | /g; s/^/| /; s/$/ |/'

という風に実行、そして記事中に貼り付けてテーブル見出しを書き加えたりしました。たぶん年1回の記事更新時にも使えるかな。

9
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
9