1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Perlでクロージャを使ってデータの組み合わせを取り出す

Last updated at Posted at 2014-06-09

何をしたの?

何と言ったら正しいのかよくわからないのですが、、、
Perlでクロージャを使って、プログラム内で使用するデータを次々に生成してくれるようなものを作りました。

数パターンのデータを自動的に返してくれるような感じと言うか、
雰囲気的には、変則的な桁繰上りをしてくれるような感じです。

コード

use strict;
use warnings;

my $get_status = sub {
	use POSIX qw(floor);
	use bignum;
	my $count = 0;
	my $skip = [];	#スキップしたい回数目があれば、ここに整数値を配列で入れる
	my $rule = [	#ルール名・最小値・加算値・最大値をハッシュの配列で持たせる
		{ name => 'a', min => 1, step => 1, max => 2 },
		{ name => 'b', min => 2, step => 1, max => 4 },
		{ name => 'c', min => 0.4, step => 0.2, max => 8 },
		{ name => 'd', min => 0.5, step => 0.1, max => 2 },
	];
	my $status = {};
	my $maximum = 1;
	foreach my $tmp_rule ( @{$rule} ){
		$status->{pattern}->{$tmp_rule->{name}} = $tmp_rule->{min};
		$tmp_rule->{roundup} = ( grep { $_->{length} } @{$rule} )
			? do { my $tmp = 1; map { $tmp *= $_->{length} if( $_->{length} ); } @{$rule}; $tmp; }
			: 1;
		$tmp_rule->{length} = ( $tmp_rule->{max} - $tmp_rule->{min} ) / $tmp_rule->{step} + 1;
		$maximum *= $tmp_rule->{length};
	}
	return sub {
		while(1){
			last if ( !( grep { $_ == $count } @{$skip} ) );
			$count++;
		}
		foreach my $tmp_rule ( @{$rule} ){
			$status->{count} = $count;
			$status->{pattern}->{$tmp_rule->{name}} = floor( ($count / $tmp_rule->{roundup}) % $tmp_rule->{length} ) * $tmp_rule->{step} + $tmp_rule->{min};
		}
		$count++;
		return ( $count >= $maximum+1 ) ? undef : $status;
	};
}->();

my $params;
while( 1 ){
	$params = $get_status->();
	last if( !$params );
	
	print $params->{pattern}->{d}, ', ', $params->{pattern}->{c}, ', ', $params->{pattern}->{b}, ', ', $params->{pattern}->{a}, ': ', $params->{count}, "\n";
}

exit;

実行結果

0.5, 0.4, 2, 1: 0
0.5, 0.4, 2, 2: 1
0.5, 0.4, 3, 1: 2
0.5, 0.4, 3, 2: 3
0.5, 0.4, 4, 1: 4
0.5, 0.4, 4, 2: 5
0.5, 0.6, 2, 1: 6

...(中略)...

2, 7.8, 4, 2: 3737
2, 8, 2, 1: 3738
2, 8, 2, 2: 3739
2, 8, 3, 1: 3740
2, 8, 3, 2: 3741
2, 8, 4, 1: 3742
2, 8, 4, 2: 3743

なんで必要だったの?

ループ(上コードの中では、最後のwhile)の中で計算してもいいのだけど、見た目がごちゃっとして、後々どこが何なのかわかりにくくなるな、と思ったのでした。
でも、サブルーチン切り分けていちいち引数で回数の値を渡すのもなんだかなぁ、かといってカウント値をblessして使いまわすのも変だなぁ、と思ったりもしたのでした。

使いどころは?

テストの際などに、決められたルールの上で自動的に値を出してくれるので、たまに役に立つかな? とか。
今回は、ループ内で値を変えながら何回も繰り返して、最終的に値ごとの計算結果を比較する、みたいな処理がしたくて、そこで使いたかったのでした。
使いどころは限られますが、割と汎用性はあるような気がしています。
クロージャ内で保持しているのは単純な整数値(上コードの$count)なので、割と取り回しもしやすいかな、と。
あとは、$skipに外部データから読み込むようにすれば、中断・再開にも少しだけ対応できてうれしいな、と思っています。

追記

なんだか実行結果に変なところがあったので、一度下書きに戻して修正しよう、と思ったら誤って記事ごと削除しちゃいましたので再投稿です。すみません。

変だった部分は、剰余絡みの部分で、オーバーフロー起こしておかしな数値をたたき出すことがあるようでした。
というか、なんか剰余を出す途中で処理が止まって、剰余ではなくて割り算の結果(の、小数点以下切り捨てされた値)が出てくることがありました。

ので、use bignum;を入れて解決しました。でも、何倍も遅くなっちゃいましたが、、、

あとは、後から気になった細かい部分をちょっと修正したりもしました。

1
1
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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?