oranie's blog

旧:iをgに変えると・・・なんだっけ・・・

自分で書いたスクリプトで、foreachのネストが深すぎて絶望した。→助けてもらって復活できた。


簡単に言うとこんな事
1 集計対象のテーブル探す
2 見つかったテーブルから計算・集計に必要な情報取得
3 集計・計算は1時間毎に出す
したくてこんなコード書いた。


で、ネスト深すぎて自分でも頭こんがらがった。

#計算の種類(平均→0秒以上は何件?→1秒以上・・・みたいな感じで一杯計算したい)
my @counttime = ("avg",0,1,2,3,4,5,10,30,60);

eval{
    my @table = 処理対象のテーブル取ってくる処理;
    foreach my $table_value(@table){
        my @id_list = テーブル内にあるIDを取ってくる処理;

        foreach my $id (@id_list){

            foreach my $sec_time (@counttime){
                my @day_list = テーブル内の初日〜終日までの日付を取ってくる処理;

                foreach my $day (@day_list) {

                    $pm->start and next;
                    ここで1日毎に$table_valueと$idと$dayと$sec_timeを使って
          さらに1時間ごとの集計・計算してDB書き込み;
          #なのでここでも0〜23までのfor文が回っている。
                    $pm->finish;

                }
                $pm->wait_all_children;
            }
        }
    }

};if($@) {
    print "$@    ERROR!!!!!!!!!!!!!!!!!!!!!!!"
}

確実にこんなネストしなくても出来そうな気がする><

※追記
上記のコードを見てアドバイスを2つ頂きました!(´Д⊂
1個目は「別のメソッド/関数に分けた方が良い」by @kazeburoさん
という事で以下のコードにしてみた。

eval{
    my @table = 処理対象のテーブル取ってくる処理;
    foreach my $table_value(@table){
        my @id_list = テーブル内にあるIDを取ってくる処理;

        foreach my $id (@id_list){
            &make_day_list();
        }
    }

    sub make_summary {
        my @day_list = @_;
        foreach my $day (@day_list) {
            $pm->start and next;
            ここで1日毎に$table_value$id$day$sec_timeを使って
          さらに1時間ごとの集計・計算してDB書き込み;
          #なのでここでも0〜23までのfor文が回っている。
            $pm->finish;
        }
        
        exit 0;
    }

    sub make_day_list{
        foreach my $sec_time (@counttime){
            my @day_list = テーブル内の初日〜終日までの日付を取ってくる処理;
            &make_summary(@day_list);
        }
        
        exit 0;
    }

}

ちゃんと僕が意味を理解できているのか不安ですが、まずネストが二つ目までになって、
実行部分が普通に読んでも分かりやすい!関数自体も短いので追いやすい!


次に「集計対象リスト [テーブル-対象ID-日時] だけをまずネストしたループで作って、
あとは最後に1次元リストに対して集計するループを回すようにする」by @tagomorisさん
という形で
「最終的には集計対象の抽出と集計処理自体を別バッチに切り出して、抽出のみ同期処理、
集計処理の方は非同期でジョブキュー化」など先々の事も考えた修正。

※書き換え次第追記しますw→2011/7/22に追記

my @counttime = ("avg",0,1,2,3,4,5,10,30,60);

eval{

    my @All_list;  #処理対象リスト格納用
    my $tmp;       #上記のリスト生成用に。

    my @table = 処理対象のテーブル取ってくる処理;
    foreach my $table_value(@table){
        my @id_list = テーブル内にあるIDを取ってくる処理;

        foreach my $id (@id_list){

            foreach my $sec_time (@counttime){
                my @day_list = テーブル内の初日〜終日までの日付を取ってくる処理;

                foreach my $day (@day_list) {
                    #まず処理対象のリストを作る。面倒くさかったので1行レコードに。
                    #別バッチにする場合は、ファイルに書くかジョブキューに格納とか
                    $tmp = join(',',$table_value,$id,$day);
                    push(@All_list,$tmp);
                }
            }
        }
    }

    #ここからは別バッチでも良いんだけど、とりあえず同一バッチで。
    #処理対象のリストと計算種類のカウントを使って実際に計算

    foreach my $sec (@sec_counttime){

        foreach my $i (@All_list){
            $pm->start and next;
            # $secと$iを使って計算頑張る;
            #ここで1日毎にさらに1時間ごとの集計・計算してDB書き込み;
      #なのでここでも0〜23までのfor文が回っている。
            $pm->finish;

        }
        $pm->wait_all_children;

    }

};if($@) {
    print "$@    ERROR!!!!!!!!!!!!!!!!!!!!!!!"
}

確かにこう書くと、処理対象のリストを作る所と実際に計算させる部分を分ける事により、
もし計算部分が大量に出来た場合は処理させるマシンを分割するとかの見通しも
しやすいですね・・・!


Perl使いの人達の優しさで今日もギリギリ生きています・・・!