この記事は モバイルファクトリー Advent Calendar 2020 16日目の記事です。 はじめに 動作環境 逆引き 条件を満たす最初の要素をとる 条件を満たす最後の要素をとる 条件を満たす要素より後ろの要素たちを抽出する 条件を満たす要素以降の要素たちを抽出する 条件を満たす要素より前の要素たちを抽出する 条件を満たす要素以前の要素たちを抽出する 先頭からいくつかの要素を抽出する 末尾からいくつかの要素を抽出する 1つしかない要素のみを抽出する 条件を満たす要素を検索する 条件を満たす要素数を求める 条件を満たさない要素数を求める 条件を満たす要素のインデックスを求める 条件を満たす最初の要素のインデックスを求める 条件を満たす最後の要素のインデックスを求める 条件を満たすただ一つの要素のインデックスを求める 最初にCODE BLOCKが正常終了する要素の結果を求める 最後にCODE BLOCKが正常終了する要素の結果を求める 一つだけCODE BLOCKが正常終了する要素の結果を求める 要素の最大値を求める 文字列の最大を求める 要素の最小値を求める 文字列の最小を求める 最小値と最大値を同時に求める リストを文字列順で並び替える リストを数値順で並び替える 文字列を結合する 要素の合計を求める 要素の積を求める 各要素のうちどれかが条件を満たすならtrue 各要素のうちどれかが条件を満たさないならtrue 全要素が条件を満たすならtrue 全要素が条件を満たさないならtrue 要素がただ1つだけ条件を満たすならtrue それぞれの要素に処理を行いたい 要素をランダムに並び替える 最頻値を求める 重複を弾く 特定の要素の後ろに要素を追加する 2つのリストを同時に操作する 複数のリストを1つのリストにする 1つのリストを複数のリストに仕分ける 各要素を複数のリストに仕分ける 条件ごとの要素数を求める リストからイテレータを作成する リストから複数をまとめて返すイテレータを作成する key-valueリスト操作 key-valueリストをまとめて1要素にする key, valueのまとまりを展開する key-valueリストのkeyのみを抽出する key-valueリストのvalueのみを抽出する key-valueリストから条件にあう全要素を抽出する 条件にあったペア数を出す 条件にあう最初の要素を抽出する 条件にあう要素が見つかったかを調べる key-valueリストにmapと同じことをしたい まとめ はじめに こんにちは、エンジニアの id:Dozi0116 です。 自分は4月に入社してから現在まででいろいろなPerlのコードに触れられたのですが、その中で List::AllUtils というモジュールが印象に残っています。 List::AllUtilsとは、「リスト操作のいろいろが詰まったモジュール」を集めて使えるようにしたモジュールで、 List::Util List::SomeUtils List::UtilsBy の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 さんです!