この記事はモバイルファクトリー Advent Calendar 2020 16日目の記事です。
- はじめに
- 動作環境
- 逆引き
- 条件を満たす最初の要素をとる
- 条件を満たす最後の要素をとる
- 条件を満たす要素より後ろの要素たちを抽出する
- 条件を満たす要素以降の要素たちを抽出する
- 条件を満たす要素より前の要素たちを抽出する
- 条件を満たす要素以前の要素たちを抽出する
- 先頭からいくつかの要素を抽出する
- 末尾からいくつかの要素を抽出する
- 1つしかない要素のみを抽出する
- 条件を満たす要素を検索する
- 条件を満たす要素数を求める
- 条件を満たさない要素数を求める
- 条件を満たす要素のインデックスを求める
- 条件を満たす最初の要素のインデックスを求める
- 条件を満たす最後の要素のインデックスを求める
- 条件を満たすただ一つの要素のインデックスを求める
- 最初にCODE BLOCKが正常終了する要素の結果を求める
- 最後にCODE BLOCKが正常終了する要素の結果を求める
- 一つだけCODE BLOCKが正常終了する要素の結果を求める
- 要素の最大値を求める
- 文字列の最大を求める
- 要素の最小値を求める
- 文字列の最小を求める
- 最小値と最大値を同時に求める
- リストを文字列順で並び替える
- リストを数値順で並び替える
- 文字列を結合する
- 要素の合計を求める
- 要素の積を求める
- 各要素のうちどれかが条件を満たすならtrue
- 各要素のうちどれかが条件を満たさないならtrue
- 全要素が条件を満たすならtrue
- 全要素が条件を満たさないならtrue
- 要素がただ1つだけ条件を満たすならtrue
- それぞれの要素に処理を行いたい
- 要素をランダムに並び替える
- 最頻値を求める
- 重複を弾く
- 特定の要素の後ろに要素を追加する
- 2つのリストを同時に操作する
- 複数のリストを1つのリストにする
- 1つのリストを複数のリストに仕分ける
- 各要素を複数のリストに仕分ける
- 条件ごとの要素数を求める
- リストからイテレータを作成する
- リストから複数をまとめて返すイテレータを作成する
- key-valueリスト操作
- まとめ
はじめに
こんにちは、エンジニアの id:Dozi0116 です。 自分は4月に入社してから現在まででいろいろなPerlのコードに触れられたのですが、その中で List::AllUtils というモジュールが印象に残っています。
List::AllUtilsとは、「リスト操作のいろいろが詰まったモジュール」を集めて使えるようにしたモジュールで、
の3つのモジュールが一体化したモジュールです。
実際の開発でも使っていますが、たくさんの関数が詰まっているため、どんなことができるのか?を全く理解しきれていません。また、既に書かれているソースコードをすぐにList::AllUtilsのものと理解ができず、新しい関数を見かけるたびにこんなことまでできるのか!と驚いていました。
せっかくこんな便利モジュールを使っているのに使いこなせないのはもったいない… そこで、List::AllUtilsができることを理解するため、やりたいことから使うべきコードを見つけやすくなるように、そして他に同じように困っている人のハードルを下げるため、この逆引きを作りました。
動作環境
Perl 5.30.2
List::AllUtils 0.18
List::SomeUtils 0.56
List::Util 1.45
List::UtilsBy 0.11
逆引き
今回載せている例はこのテストコードで検証しています。
今回の逆引きでは、reduce
などの発想次第でなんでもできそうな関数は、既にある関数と差別化ができる場合にのみ載せています。ご了承ください。
また、List::AllUtilsの依存バージョン関係上、使えない関数がいくつかあります。
条件を満たす最初の要素をとる
@list = (4, 7, 1); $result = first { $_ > 5 } @list; # 7 $result = first_value { $_ > 5 } @list; # 7 $result = firstval { $_ > 5 } @list; # first_valueのエイリアス
extract_first_by
は条件を満たした要素をオリジナルのリストから消す
@list = (4, 7, 1); $result = extract_first_by { $_ > 5 } @list; # 7 print(@list); # (4, 1);
reduce
で書くこともできる
@list = (4, 7, 1); $result = reduce { defined($a) ? $a : $b > 5 ? $b : undef } undef, @list;
条件を満たす最後の要素をとる
@list = (4, 7, 1); $result = last_value { $_ < 5 } @list; # 1 $result = lastval { $_ < 5 } @list; # 同じ(last_valueのエイリアス)
条件を満たす要素より後ろの要素たちを抽出する
@list = (2, 4, 6, 8, 10); @result = after { $_ > 5 } @list; # (8, 10)
条件を満たす要素以降の要素たちを抽出する
@list = (2, 4, 6, 8, 10); @result = after_incl { $_ > 5 } @list; # (6, 8, 10)
条件を満たす要素より前の要素たちを抽出する
@list = (2, 4, 6, 8, 10); @result = before { $_ > 5 } @list; # (2, 4)
条件を満たす要素以前の要素たちを抽出する
@list = (2, 4, 6, 8, 10); @result = before_incl { $_ > 5 } @list; # (2, 4, 6)
先頭からいくつかの要素を抽出する
@list = 1..10; @result = head 3, @list; # (1, 2, 3) @result = head -2, @list; # (1, 2, 3, 4, 5, 6, 7, 8)
末尾からいくつかの要素を抽出する
@list = 1..10; @result = tail 3, @list; # (8, 9, 10) @result = tail -2, @list; # (3, 4, 5, 6, 7, 8, 9, 10)
1つしかない要素のみを抽出する
@list = (1, 1, 1, 2, 3, 3, 4, 5); @result = singleton @list; # (2, 4, 5)
条件を満たす要素を検索する
@list = 1...10; @result = grep { $_ == 4 } @list; # (4)
二分探査をするため、 CODE BLOCKは 比較した要素が小さいなら-1を、大きいなら1を、ちょうどなら0を返す必要が、また@list
はソートされている必要がある
@sorted_list = 1...10; @result = bsearch { $_ <=> 4 } @sorted_list; # (4)
extract_by
は見つけた要素をresult
に抜き出して、オリジナルから消える
@list = 1...10; @result = extract_by { $_ == 4 } @list; # (4) print(@list); # (1, 2, 3, 5, 6, 7, 8, 9, 10)
条件を満たす要素数を求める
@list = 1...10; $result = true { $_ < 4 } @list; # 3
条件を満たさない要素数を求める
@list = 1...10; $result = false { $_ < 4 } @list; # 6
条件を満たす要素のインデックスを求める
二分探査をするため、 CODE BLOCKは 比較した要素が小さいなら-1を、大きいなら1を、ちょうどなら0を返す必要が、また@list
はソートされている必要がある
@sorted_list = 1...10; $result = bsearch_index { $_ <=> 4 } @sorted_list; # 3 $result_b = bsearchidx { $_ <=> 4 } @sorted_list; # 同じ(bsearch_indexのエイリアス)
複数のインデックスをまとめて求めるなら indexes
を使う
@list = (1, 1, 1, 2, 4, 1); @result = indexes { $_ == 1 } @list; # (0, 1, 2, 5)
条件を満たす最初の要素のインデックスを求める
@list = (1, 1, 1, 2, 4, 1); $result = first_index { $_ == 1 } @list; # 0 $result = firstidx { $_ == 1 } @list; # 同じ(first_indexのエイリアス)
条件を満たす最後の要素のインデックスを求める
@list = (1, 1, 1, 2, 4, 1); $result = last_index { $_ == 1 } @list; # 5 $result = lastidx { $_ == 1 } @list; # 同じ(last_indexのエイリアス)
条件を満たすただ一つの要素のインデックスを求める
要素が複数あった場合、 -1 が返る
@list = (1, 1, 1, 2, 4, 1); $result = only_index { $_ == 2 } @list; # 3 $result = onlyidx { $_ == 2 } @list; # 同じ(only_indexのエイリアス) $result = only_index { $_ == 1 } @list; # -1
最初にCODE BLOCKが正常終了する要素の結果を求める
@list = (4, 7, 1); $result = first_result { $_ ** 2 if $_ > 3 } @list; # 16 $result = firstres { $_ ** 2 if $_ > 3 } @list; # 同じ(first_indexのエイリアス)
最後にCODE BLOCKが正常終了する要素の結果を求める
@list = (4, 7, 1); $result = last_result { $_ ** 2 if $_ > 3 } @list; # 49 $result = lastres { $_ ** 2 if $_ > 3 } @list; # 同じ(last_indexのエイリアス)
一つだけCODE BLOCKが正常終了する要素の結果を求める
@list = (4, 7, 1); $result = only_result { $_ ** 2 if $_ > 5 } @list; # 49 $result = onlyres { $_ ** 2 if $_ > 5 } @list; # 同じ(last_indexのエイリアス) # 正常終了する要素が複数ある場合、undefを返す $result = only_result { $_ ** 2 if $_ > 3 } @list; # undef
要素の最大値を求める
@list = (1, 4, 3); $result = max @list; # 4
max_by
で書けば、CODE BLOCKは自由に記述できるので器用なことができる
@list = ({ value => 2, id => 1 }, { value => 5, id => 2 }, { value => 5, id => 3 }); $result = max_by { $_->{value} } @list; # { value => 5, id => 2 }
リストコンテキストを返り値に期待すれば、全部の要素を取得できる
@list = ({ value => 2, id => 1 }, { value => 5, id => 2 }, { value => 5, id => 3 }); @result = max_by { $_->{value} } @list; # ({ value => 5, id => 2 }, { value => 5, id => 3 })
文字列の最大を求める
ここでいう文字列の最大とは、文字コード比較での最大を指す
@list = qw/a b c/; $result = maxstr @list; # c
@list = ({ name => 'a' }, { name => 'b' }, {name => 'c' }); $result = reduce { $a->{name} gt $b->{name} ? $a : $b } @list; # { name => 'c' }
要素の最小値を求める
@list = (2, 3, 1); $result = min @list; # 1
min_by
で書けば、CODE BLOCKは自由に記述できるので器用なことができる
@list = ({ value => 2, id => 1 }, { value => 1, id => 2 }, { value => 1, id => 3 }); $result = min_by { $_->{value} } @list; # { value => 1, id => 2 }
リストコンテキストを返り値に期待すれば、全部の要素を取得できる
@list = ({ value => 2, id => 1 }, { value => 1, id => 2 }, { value => 1, id => 3 }); @result = min_by { $_->{value} } @list; # ({ value => 1, id => 2 }, { value => 1, id => 3 })
文字列の最小を求める
ここでいう文字列の最小とは、文字コード比較での最小を指す
@list = qw/b c a/; $result = minstr @list; # a
@list = ({ name => 'b' }, { name => 'a' }, {name => 'c' }); $result = reduce { $a->{name} lt $b->{name} ? $a : $b } @list; # { name => 'a' }
最小値と最大値を同時に求める
@list = (2, 3, 1); ($min,$max) = minmax @list; # (1, 3)
minmax_by
で書けば、CODE BLOCKは自由に記述できるので器用なことができる
@list = ({ value => 2, id => 1 }, { value => 1, id => 2 }, { value => 1, id => 3 }); ($min, $max) = minmax_by { $_->{value} } @list; # ({ value => 1, id => 2 }, { value => 2, id => 1 })
リストを文字列順で並び替える
@list = ('banana', 'melon', 'apple'); @result = sort @list; # ('apple', 'banana', 'melon')
比較するものが組み込み関数のsort
と違って省略できるため、sort_by
を使えば比較的簡潔に書くことができる
@list = ({ name => 'banana' }, { name => 'melon' }, { name => 'apple' }); @result = sort_by { $_->{name} } @list; # ({ name => 'apple' }, { name => 'banana' }, { name => 'melon' })
sort_by
を降順で使いたい時は rev_sort_by
が使える
@list = ({ name => 'banana' }, { name => 'melon' }, { name => 'apple' }); @result = rev_sort_by { $_->{name} } @list; # ({ name => 'melon' }, { name => 'banana' }, { name => 'apple' })
リストを数値順で並び替える
@list = (23, 1, 12); @result = sort { $a <=> $b } @list; # (1, 12, 23)
比較するものが組み込み関数のsort
と違って省略できるため、nsort_by
を使えば比較的簡潔に書くことができる
@list = ({ value => 23 }, { value => 1 }, { value => 12 }); @result = nsort_by { $_->{value} } @list; # ({ value => 1 }, { value => 12 }, { value => 23 })
nsort_by
を降順で使いたい時は rev_nsort_by
が使える
@list = ({ value => 23 }, { value => 1 }, { value => 12 }); @result = rev_nsort_by { $_->{value} } @list; # ({ value => 23 }, { value => 12 }, { value => 1 })
文字列を結合する
@list = qw/a b c/; $result = join '', @list; # "abc"
reduce
で書けば、CODE BLOCKは自由に記述できるので器用なことができる
@list = qw/a b c/; $result = reduce { uc($a) . uc($b) } @list; # "ABC"
要素の合計を求める
@list = 1..10; $result = sum @list; # 55 # 要素がない時はundefを返す @list = (); $result = sum @list; # undef
sum0
を用いると要素0のリストの場合は0を返す
@list = (); $result = sum0 @list; # 0
@list = ({ value => 2 }, { value => 4 }, { value => 1 }); $result = reduce { $a + $b->{value} } 0, @list; # 7
要素の積を求める
@list = 1..10; $result = product @list; # 3628800 # 要素がない時は1を返す @list = (); $result = product @list; # 1
@list = ({ value => 2 }, { value => 4 }, { value => 1 }); $result = reduce { $a * $b->{value} } 1, @list; # 8
各要素のうちどれかが条件を満たすならtrue
@list = ({ flag => 1 }, { flag => 0 }, { flag => 1 }); $result = any { $_->{flag} } @list; # 1 # 空リストの場合、falseを返す @list = (); $result = any { $_->{flag} } @list; # ""
any_u
は空リストの場合にundef
を返す
@list = (); $result = any { $_->{flag} } @list; # "" $result = any_u { $_->{flag} } @list; # undef
各要素のうちどれかが条件を満たさないならtrue
@list = ({ flag => 1 }, { flag => 0 }, { flag => 1 }); $result = notall { $_->{flag} } @list; # 1 # 空リストの場合、falseを返す @list = (); $result = notall { $_->{flag} } @list; # ""
notall_u
は空リストの場合にundef
を返す
@list = (); $result = notall { $_->{flag} } @list; # "" $result = notall_u { $_->{flag} } @list; # undef
全要素が条件を満たすならtrue
@list = ({ flag => 1 }, { flag => 1 }, { flag => 1 }); $result = all { $_->{flag} } @list; # 1 # 空リストの場合、trueを返す @list = (); $result = all { $_->{flag} } @list; # 1
all_u
は空リストの場合にundef
を返す
@list = (); $result = all { $_->{flag} } @list; # 1 $result = all_u { $_->{flag} } @list; # undef
全要素が条件を満たさないならtrue
@list = ({ flag => 0 }, { flag => 0 }, { flag => 0 }); $result = none { $_->{flag} } @list; # 1 # 空リストの場合、trueを返す @list = (); $result = none { $_->{flag} } @list; # 1
none_u
は空リストの場合にundef
を返す
@list = (); $result = none { $_->{flag} } @list; # 1 $result = none_u { $_->{flag} } @list; # undef
要素がただ1つだけ条件を満たすならtrue
@list = ({ flag => 0 }, { flag => 1 }, { flag => 0 }); $result = one { $_->{flag} } @list; # 1 # 空リストの場合、falseを返す @list = (); $result = one { $_->{flag} } @list; # ""
one_u
は空リストの場合にundef
を返す
@list = (); $result = one { $_->{flag} } @list; # "" $result = one_u { $_->{flag} } @list; # undef
それぞれの要素に処理を行いたい
@list = (4, 7, 1); @result = map { $_ *= 2 } @list; # (8, 14, 2) print(@list); # (8, 14, 2);
apply
で書けば、元のリストは変更されない
@list = (4, 7, 1); @result = apply { $_ *= 2 } @list; # (8, 14, 2) print(@result); # (4, 7, 1)
bundle_by
で書けば、複数の要素をまとめて処理できる
@list = (1...8); @result = bundle_by { [$_[0], $_[1], $_[2]] } 3, @list; # ([1, 2, 3], [4, 5, 6], [7, 8, undef])
要素をランダムに並び替える
@list = ('a', 'b', 'c', 'd'); @result = shuffle @list; # 何が出るかは神のみぞ知る
weighted_shuffle_by
で書けば、重みをつけたランダムになる
@list = ('a', 'b', 'c'); @result = weighted_shuffle_by { { a => 1, b => 0, c => 99 }->{$_} } @list; # ほぼほぼ ('c', 'a', 'b')
最頻値を求める
@list = ('apple', 'pineapple', 'apple', 'banana', 'apple', 'apple', 'apple'); @result = mode @list; # ('apple')
重複を弾く
後に出てきた重複要素が消される
@list = ('hoge', 'hoge', 22, 35, 10, 22); @result = uniq @list; # ('hoge', 22, 35, 10) @result = distinct @list; # 同じ(uniqのエイリアス)
要素が数値or文字列で一定なら以下の関数も使える
@list = (1, 2, 3, 1, 5); @result = uniqnum @list; # (1, 2, 3, 5)
@list = ('a', 'A', 'aa', 'a'); @result = uniqstr @list; # ('a', 'A', 'aa')
uniq_by
で書けば、CODE BLOCKは自由に記述できるので器用なことができる
@list = ({ value => 2, id => 1 }, { value => 1, id => 2 }, { value => 1, id => 3 }); @result = uniq_by { $_->{value} } @list; # ({ value => 2, id => 1 }, { value => 1, id => 2 })
特定の要素の後ろに要素を追加する
@list = (1, 2, 3, 5); my $result = insert_after { $_ == 3 } 4, @list; @list; # (1,2,3,4,5)
stringの等価比較をするなら、insert_after_string
が使える
@list = ('first', 'second', 'third', 'fifth'); my $result = insert_after_string 'third', 'fourth', @list; @list; # ('first', 'second', 'third', 'fourth', 'fifth')
2つのリストを同時に操作する
@list_a = ('a', 'b', 'c'); @list_b = (1, 2, 3); @result = pairwise { { str => $a, num => $b } } @list_a, @list_b; # ( { str => 'a', num => 1 }, { str => 'b', num => 2 }, { str => 'c', num => 3 }, )
複数のリストを1つのリストにする
@list_a = ('a', 'b', 'c'); @list_b = (1, 2); @result = mesh @list_a, @list_b; # ('a', 1, 'b', 2, 'c', undef) @result = zip @list_a, @list_b; # 同じ(meshのエイリアス)
zip_by
で書けば、CODE BLOCKは自由に記述できるので器用なことができる
@list_a = ('a', 'b', 'c'); @list_b = (1, 2); @result = zip_by { $_[0], $_[1] } \@list_a, \@list_b; # ('a', 1, 'b', 2, 'c', undef)
1つのリストを複数のリストに仕分ける
# CODE BLOCK は 仕分け先のインデックスを期待している @list = (1, 2, 1, 1, 2); @result = part { $_ } @list; # ( undef, [1, 1, 1], [2, 2] )
partition_by
を使うと、仕分ける時の値をkeyとしたハッシュで返してくれる
@list = (1, 2, 1, 1, 2); %result = partition_by { $_ } @list; # { 1 => [ 1, 1, 1 ], 2 => [ 2, 2] }
各要素を複数のリストに仕分ける
@list = ({id => 1, name => 'hoge', }, {id => 2, name => 'fuga'}, {id => 3, name => 'piyo'}); ($ids, $names) = unzip_by { $_->{id}, $_->{name} } @list; # ids: (1, 2, 3), names: ('hoge', 'fuga', 'piyo')
条件ごとの要素数を求める
@list = (1, 2, 1, 1, 2); %result = count_by { $_ } @list; # { 1 => 3, 2 => 2 }
リストからイテレータを作成する
@list = (1, 2, 3); $it = each_array(@list); $it->(); # 1 $it->(); # 2 $it->(); # 3 $it->(); # undef
リファレンスから作成するなら each_arrayref
が使える
$list = [1, 2, 3]; $it = each_array($list); $it->(); # 1 $it->(); # 2 $it->(); # 3 $it->(); # undef
リストから複数をまとめて返すイテレータを作成する
@list = (1...8); $it = natatime 3, @list; $it->(); # (1, 2, 3) $it->(); # (4, 5, 6) $it->(); # (7, 8) $it->(); # undef
key-valueリスト操作
ここでのkey-valueリスト
とはリストの要素が (key1, value1, key2, value2, ...)
となっているもの。
@kvlist = ('jp', 'こんにちは', 'en', 'hello'); %hash = @kvlist; $hash{jp}; # こんにちは
key-valueリストをまとめて1要素にする
@list = ('k1', 'v1', 'k2', 'v2'); @result = pairs @list; # ( ['k1', 'v1'], ['k2', 'v2'] )
key, valueのまとまりを展開する
@list = ( ['k1', 'v1'], ['k2', 'v2'] ); @result = unpairs @list; # ('k1', 'v1', 'k2', 'v2')
key-valueリストのkeyのみを抽出する
@list = ('k1', 'v1', 'k2', 'v2'); @result = pairkeys @list; # ( 'k1', 'k2' )
key-valueリストのvalueのみを抽出する
@list = ('k1', 'v1', 'k2', 'v2'); @result = pairvalues @list; # ( 'v1', 'v2' )
key-valueリストから条件にあう全要素を抽出する
@list = ('k1', 'v1', 'k2', 'v2', 'k3', 'v1'); @result = pairgrep { $b eq 'v1' } @list; # ('k1', 'v1', 'k3', 'v1')
条件にあったペア数を出す
2つ1セットで見るため、最大値は要素の半分になることに注意
@list = ('k1', 'v1', 'k2', 'v2', 'k3', 'v1'); $result = pairgrep { $a eq 'k1' && $b eq 'v1' } @list; # 1
条件にあう最初の要素を抽出する
@list = ('k1', 'v1', 'k2', 'v2', 'k3', 'v1'); ($key, $value) = pairfirst { $b eq 'v1' } @list; # $key = 'k1', $value = 'v1'
条件にあう要素が見つかったかを調べる
@list = ('k1', 'v1', 'k2', 'v2', 'k3', 'v1'); $result = pairfirst { $b eq 'v1' } @list; # 1
key-valueリストにmapと同じことをしたい
@list = ('k1', 'v1', 'k2', 'v2'); @result = pairmap { "$a-$b" } @list; # ( 'k1-v1', 'k2-v2' )
まとめ
PerlのモジュールであるList::AllUtilsの逆引きを作りました。 もちろんこれが正解というわけではなく、いろいろな書き方があるので、この記事を読んだ方も書き方や活用例があれば教えてください。 自分みたいに全容を理解できていない人の助けになったら嬉しいです。 最後になりますが、この記事に書くにあたって協力してくれた社員のみなさん、ありがとうございました!
明日の記事は id:summer_gift さんです!