List::Util と List::MoreUtils - リスト処理の強力なツール
Perlでリストやリストを扱う作業は日常茶飯事です。ループを書いて処理することもできますが、List::Util と List::MoreUtils を使えば、より簡潔で読みやすいコードが書けます。これらのモジュールは、関数型プログラミングのエッセンスをPerlに持ち込み、リスト処理を格段に効率化します。
List::Util - 標準のリストユーティリティ
List::Util は Perl のコアモジュール(5.7.3以降)なので、別途インストールする必要はありません。基本的なリスト操作関数を提供します。
基本的な集計関数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| use List::Util qw(sum sum0 product min max);
use feature qw(say);
my @numbers = (1, 2, 3, 4, 5);
# 合計
say "合計: ", sum(@numbers); # 15
say "合計: ", sum0(@numbers); # 15 (空リストでも0を返す)
say "空の合計: ", sum0(); # 0
# 積
say "積: ", product(@numbers); # 120
# 最小値・最大値
say "最小: ", min(@numbers); # 1
say "最大: ", max(@numbers); # 5
# 文字列での最小・最大
my @words = qw(apple banana cherry);
say "辞書順最小: ", min(@words); # apple
say "辞書順最大: ", max(@words); # cherry
# 数値での最小・最大(明示的)
use List::Util qw(min max);
my @mixed = qw(10 2 100 20);
say "数値最小: ", min(@mixed); # 2 (数値として比較)
|
minstr / maxstr - 文字列比較
1
2
3
4
5
6
7
8
9
10
11
12
| use List::Util qw(minstr maxstr);
use feature qw(say);
my @numbers_as_strings = qw(10 2 100 20);
# 文字列として比較
say "文字列最小: ", minstr(@numbers_as_strings); # 10
say "文字列最大: ", maxstr(@numbers_as_strings); # 2
# 数値として比較(通常のmin/max)
say "数値最小: ", min(@numbers_as_strings); # 2
say "数値最大: ", max(@numbers_as_strings); # 100
|
reduce - 強力な集約関数
reduce は、リストの要素を順番に処理して単一の値に集約します。$a と $b が特殊変数として使用されます:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
| use List::Util qw(reduce);
use feature qw(say);
my @numbers = (1, 2, 3, 4, 5);
# 合計(sum の実装例)
my $sum = reduce { $a + $b } @numbers;
say "合計: $sum"; # 15
# 積(product の実装例)
my $product = reduce { $a * $b } @numbers;
say "積: $product"; # 120
# 最大値(max の実装例)
my $max = reduce { $a > $b ? $a : $b } @numbers;
say "最大: $max"; # 5
# 文字列連結
my @words = qw(Hello World from Perl);
my $sentence = reduce { "$a $b" } @words;
say $sentence; # Hello World from Perl
# 複雑な例:ハッシュの値の合計
my %scores = (
Alice => 85,
Bob => 92,
Carol => 78,
);
my $total = reduce { $a + $b } values %scores;
say "合計点: $total"; # 255
# リストのリストを平坦化
my @nested = ([1, 2], [3, 4], [5, 6]);
my @flat = reduce { [@$a, @$b] } @nested;
say "平坦化: @flat"; # 1 2 3 4 5 6
|
first - 条件に合う最初の要素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| use List::Util qw(first);
use feature qw(say);
my @numbers = (1, 3, 5, 8, 9, 12, 15);
# 偶数を探す
my $first_even = first { $_ % 2 == 0 } @numbers;
say "最初の偶数: $first_even"; # 8
# 10以上の数を探す
my $first_large = first { $_ >= 10 } @numbers;
say "最初の10以上: $first_large"; # 12
# 見つからない場合は undef
my $first_negative = first { $_ < 0 } @numbers;
say "最初の負数: ", $first_negative // "なし"; # なし
# ハッシュの検索
my @users = (
{ name => 'Alice', age => 25 },
{ name => 'Bob', age => 30 },
{ name => 'Carol', age => 28 },
);
my $adult = first { $_->{age} >= 30 } @users;
say "30歳以上: $adult->{name}"; # Bob
|
any / all / none / notall - 真偽値チェック
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| use List::Util qw(any all none notall);
use feature qw(say);
my @numbers = (2, 4, 6, 8, 10);
# いずれかが条件を満たすか
say "偶数がある: ", any { $_ % 2 == 0 } @numbers ? 'はい' : 'いいえ'; # はい
say "奇数がある: ", any { $_ % 2 == 1 } @numbers ? 'はい' : 'いいえ'; # いいえ
# すべてが条件を満たすか
say "すべて偶数: ", all { $_ % 2 == 0 } @numbers ? 'はい' : 'いいえ'; # はい
say "すべて正: ", all { $_ > 0 } @numbers ? 'はい' : 'いいえ'; # はい
# いずれも条件を満たさないか
say "奇数なし: ", none { $_ % 2 == 1 } @numbers ? 'はい' : 'いいえ'; # はい
# いずれかが条件を満たさないか(all の否定)
my @mixed = (2, 4, 5, 8);
say "偶数でないものがある: ", notall { $_ % 2 == 0 } @mixed ? 'はい' : 'いいえ'; # はい
# 実用例:バリデーション
my @ages = (25, 30, 18, 22);
if (all { $_ >= 18 } @ages) {
say "全員成人です";
} else {
say "未成年が含まれています";
}
|
uniq / uniqnum / uniqstr - 重複削除
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| use List::Util qw(uniq uniqnum uniqstr);
use feature qw(say);
# 数値として一意化
my @numbers = (1, 2, 2, 3, 1, 4, 3, 5);
my @unique_nums = uniqnum(@numbers);
say "一意な数値: @unique_nums"; # 1 2 3 4 5
# 文字列として一意化
my @strings = qw(apple banana apple cherry banana);
my @unique_strs = uniqstr(@strings);
say "一意な文字列: @unique_strs"; # apple banana cherry
# 汎用版(Perl 5.26+)
my @mixed = (1, "1", 2, "2", 1);
my @unique = uniq(@mixed);
say "一意: @unique"; # 1 1 2 2 1 (数値の1と文字列の"1"は別物)
# 実用例:重複メールアドレスの削除
my @emails = qw(
user1@example.com
user2@example.com
user1@example.com
user3@example.com
);
my @unique_emails = uniqstr(@emails);
say "一意なメールアドレス: @unique_emails";
|
pairs / pairkeys / pairvalues - ペア処理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| use List::Util qw(pairs pairkeys pairvalues pairmap pairgrep);
use feature qw(say);
my @kvlist = (name => 'Alice', age => 25, city => 'Tokyo');
# ペアのリストに変換
my @pairs = pairs(@kvlist);
for my $pair (@pairs) {
say "$pair->[0] => $pair->[1]";
}
# name => Alice
# age => 25
# city => Tokyo
# キーだけ取り出す
my @keys = pairkeys(@kvlist);
say "キー: @keys"; # name age city
# 値だけ取り出す
my @values = pairvalues(@kvlist);
say "値: @values"; # Alice 25 Tokyo
# ペアごとに処理(map のペア版)
my @uppercased = pairmap { uc($a) => $b } @kvlist;
say "@uppercased"; # NAME Alice AGE 25 CITY Tokyo
# ペアをフィルタ(grep のペア版)
my @filtered = pairgrep { $b =~ /\d/ } @kvlist;
say "@filtered"; # age 25
|
List::MoreUtils - さらに便利な関数
List::MoreUtils は List::Util を拡張し、より多くの便利な関数を提供します。
インストール
zip - リストの転置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| use List::MoreUtils qw(zip);
use feature qw(say);
my @names = qw(Alice Bob Carol);
my @ages = (25, 30, 28);
my @cities = qw(Tokyo Osaka Kyoto);
my @zipped = zip(@names, @ages, @cities);
say "@zipped"; # Alice 25 Tokyo Bob 30 Osaka Carol 28 Kyoto
# ハッシュに変換
my %people;
for (my $i = 0; $i < @names; $i++) {
$people{$names[$i]} = {
age => $ages[$i],
city => $cities[$i],
};
}
use Data::Dumper;
print Dumper(\%people);
|
each_array - 複数配列の同時反復
1
2
3
4
5
6
7
8
9
10
11
12
13
| use List::MoreUtils qw(each_array);
use feature qw(say);
my @names = qw(Alice Bob Carol);
my @ages = (25, 30, 28);
my $iter = each_array(@names, @ages);
while (my ($name, $age) = $iter->()) {
say "$name is $age years old";
}
# Alice is 25 years old
# Bob is 30 years old
# Carol is 28 years old
|
part - 条件によるグループ分け
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| use List::MoreUtils qw(part);
use feature qw(say);
my @numbers = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
# 偶数と奇数に分類
my $i = 0;
my @groups = part { $_ % 2 } @numbers;
say "偶数: @{$groups[0]}"; # 2 4 6 8 10
say "奇数: @{$groups[1]}"; # 1 3 5 7 9
# 3つのグループに分類(3で割った余りで)
my @three_groups = part { $_ % 3 } @numbers;
say "3で割り切れる: @{$three_groups[0] // []}"; # 3 6 9
say "余り1: @{$three_groups[1] // []}"; # 1 4 7 10
say "余り2: @{$three_groups[2] // []}"; # 2 5 8
|
before / after - 条件の前後で分割
1
2
3
4
5
6
7
8
9
10
11
12
| use List::MoreUtils qw(before after);
use feature qw(say);
my @numbers = (1, 2, 3, 4, 5, 6, 7, 8);
# 5より小さい要素
my @before_five = before { $_ >= 5 } @numbers;
say "5未満: @before_five"; # 1 2 3 4
# 5以上の要素
my @after_five = after { $_ >= 5 } @numbers;
say "5以上: @after_five"; # 5 6 7 8
|
indexes - 条件を満たすインデックス
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| use List::MoreUtils qw(indexes);
use feature qw(say);
my @numbers = (10, 25, 30, 15, 40, 20);
# 20以上の要素のインデックス
my @indices = indexes { $_ >= 20 } @numbers;
say "20以上のインデックス: @indices"; # 1 2 4 5
# 実用例:該当要素を削除
my @words = qw(apple banana cherry date elderberry);
my @to_remove_indices = indexes { length($_) > 6 } @words;
# 後ろから削除(インデックスがずれないように)
for my $idx (reverse @to_remove_indices) {
splice @words, $idx, 1;
}
say "残った単語: @words"; # apple banana cherry date
|
firstidx / lastidx - インデックスを返す first
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| use List::MoreUtils qw(firstidx lastidx);
use feature qw(say);
my @numbers = (1, 3, 5, 7, 5, 3, 1);
# 最初の5のインデックス
my $first_five = firstidx { $_ == 5 } @numbers;
say "最初の5: インデックス $first_five"; # 2
# 最後の5のインデックス
my $last_five = lastidx { $_ == 5 } @numbers;
say "最後の5: インデックス $last_five"; # 4
# 見つからない場合は -1
my $not_found = firstidx { $_ == 10 } @numbers;
say "10のインデックス: $not_found"; # -1
|
minmax - 一度に最小値と最大値を取得
1
2
3
4
5
6
7
8
9
| use List::MoreUtils qw(minmax);
use feature qw(say);
my @numbers = (3, 1, 4, 1, 5, 9, 2, 6);
my ($min, $max) = minmax(@numbers);
say "最小: $min, 最大: $max"; # 最小: 1, 最大: 9
# 1回のループで取得できるため効率的
|
実用的なリスト処理パターン
データの集計と分析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| use List::Util qw(sum max min);
use List::MoreUtils qw(minmax);
use feature qw(say);
my @scores = (85, 92, 78, 90, 88, 95, 73);
my $total = sum(@scores);
my $average = $total / @scores;
my ($min, $max) = minmax(@scores);
say "=== スコア統計 ===";
say "合計: $total";
say "平均: ", sprintf("%.2f", $average);
say "最高点: $max";
say "最低点: $min";
say "範囲: ", $max - $min;
# 中央値を求める
my @sorted = sort { $a <=> $b } @scores;
my $median = @sorted % 2
? $sorted[@sorted / 2]
: ($sorted[@sorted / 2 - 1] + $sorted[@sorted / 2]) / 2;
say "中央値: $median";
|
ログファイルの解析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| use List::Util qw(sum max);
use List::MoreUtils qw(part);
use feature qw(say);
my @log_lines = (
'INFO: Process started',
'WARN: Low memory',
'ERROR: Connection failed',
'INFO: Process completed',
'ERROR: Timeout',
'WARN: High CPU usage',
);
# レベル別に分類
my @by_level = part {
/^INFO/ ? 0 :
/^WARN/ ? 1 :
/^ERROR/ ? 2 : 3
} @log_lines;
say "INFO: ", scalar(@{$by_level[0] // []}), " 件";
say "WARN: ", scalar(@{$by_level[1] // []}), " 件";
say "ERROR: ", scalar(@{$by_level[2] // []}), " 件";
# ERROR のみ表示
if ($by_level[2]) {
say "\n=== エラーログ ===";
say for @{$by_level[2]};
}
|
ショッピングカートの処理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| use List::Util qw(sum reduce);
use feature qw(say);
my @cart = (
{ name => 'リンゴ', price => 100, quantity => 3 },
{ name => 'バナナ', price => 150, quantity => 2 },
{ name => 'みかん', price => 80, quantity => 5 },
);
# 各商品の小計
my @subtotals = map { $_->{price} * $_->{quantity} } @cart;
# 合計金額
my $total = sum(@subtotals);
say "=== お買い物カート ===";
for my $item (@cart) {
my $subtotal = $item->{price} * $item->{quantity};
say sprintf("%s: %d円 × %d = %d円",
$item->{name}, $item->{price}, $item->{quantity}, $subtotal);
}
say "合計: ${total}円";
# 割引適用(1000円以上で10%オフ)
if ($total >= 1000) {
my $discounted = int($total * 0.9);
say "割引後: ${discounted}円(10%オフ)";
}
|
ユーザーデータのフィルタリングとソート
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| use List::Util qw(all any);
use List::MoreUtils qw(uniq);
use feature qw(say);
my @users = (
{ name => 'Alice', age => 25, active => 1 },
{ name => 'Bob', age => 30, active => 1 },
{ name => 'Carol', age => 28, active => 0 },
{ name => 'Dave', age => 22, active => 1 },
{ name => 'Eve', age => 35, active => 0 },
);
# アクティブユーザーのみ
my @active_users = grep { $_->{active} } @users;
say "アクティブユーザー: ", scalar(@active_users), "人";
# 25歳以上のアクティブユーザー
my @adult_active = grep { $_->{age} >= 25 && $_->{active} } @users;
say "25歳以上のアクティブユーザー: ";
say " - $_->{name} ($_->{age}歳)" for @adult_active;
# 年齢でソート
my @sorted_by_age = sort { $a->{age} <=> $b->{age} } @users;
say "\n年齢順:";
say " $_->{name}: $_->{age}歳" for @sorted_by_age;
# 全員25歳以上か?
say "\n全員25歳以上: ", all { $_->{age} >= 25 } @users ? 'はい' : 'いいえ';
# 誰か30歳以上がいるか?
say "30歳以上あり: ", any { $_->{age} >= 30 } @users ? 'はい' : 'いいえ';
|
パフォーマンス考慮
ループ vs List::Util
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| use Benchmark qw(cmpthese);
use List::Util qw(sum);
my @numbers = (1..10000);
cmpthese(10000, {
'loop' => sub {
my $sum = 0;
$sum += $_ for @numbers;
},
'List::Util::sum' => sub {
my $sum = sum(@numbers);
},
});
|
多くの場合、List::Util の関数は C 実装されているため、Perlのループより高速です。ただし、非常に単純な処理では、オーバーヘッドによりループの方が速い場合もあります。
適切な関数の選択
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| use List::Util qw(first any);
use feature qw(say);
my @numbers = (1..1000000);
# 存在チェックだけなら any を使う(効率的)
say "偶数あり: ", any { $_ % 2 == 0 } @numbers ? 'はい' : 'いいえ';
# 値が必要なら first を使う
my $first_even = first { $_ % 2 == 0 } @numbers;
say "最初の偶数: $first_even";
# 全部必要なら grep を使う
my @evens = grep { $_ % 2 == 0 } @numbers;
say "偶数の個数: ", scalar(@evens);
|
まとめ
List::Util と List::MoreUtils は、Perlでのリスト処理を劇的に改善します。
よく使う関数:
- 集計:
sum, product, min, max - 検索:
first, any, all, none - 変換:
reduce, uniq - ペア処理:
pairs, pairmap, pairgrep
List::MoreUtils の便利機能:
- 分類:
part - インデックス:
indexes, firstidx, lastidx - 複数配列:
zip, each_array
これらの関数を使うことで:
- コードが短く読みやすくなる
- バグが減る(ループの範囲エラーなど)
- 意図が明確になる
- パフォーマンスが向上することが多い
リスト処理は Perl の得意分野です。これらのツールを活用して、効率的で美しいコードを書きましょう!