2

データベーステーブルをSTDOUTに出力したいと思います。min_widthテーブル幅が画面幅よりも大きい場合は、テーブルが画面に収まるまで、(テーブル幅に達していない限り)同じパーセンテージで列をそれぞれ切り取りたいと思います。私は投稿されたサブルーチンでこれを解決しようとしました。誰かがこの問題を解決するためのより短くてより洗練されたアルゴリズムを知っていますか?

sub cal_size {
    my ( $maxcols, $ref ) = @_;         
    # $maxcols => screen width
    # $ref => ref to an AoA; holds the table
    my ( $max ) = cal_tab( $ref );
    # $max => ref to an array; holds the length of the longest string of each column
    # $tab = 2;
    if ( $max and @$max ) {
        my $sum = sum( @$max ) + $tab * @$max; 
        $sum -= $tab;
        my @max_tmp = @$max;
        my $percent = 0;
        while ( $sum > $maxcols ) {
            $percent += 0.5;
            if ( $percent > 99 ) {
                return;
            }
            my $count = 0;

            for my $i ( 0 .. $#max_tmp ) {
                # $min_width => columns width should not be less than $min_width if possible 
                next if $min_width >= $max_tmp[$i]; 
                # no need to cut if the column width id less than min_width
                next if $min_width >= minus_x_percent( $max_tmp[$i], $percent ); 
                # don't cut if column width become less than min_width 
                $max_tmp[$i] = minus_x_percent( $max_tmp[$i], $percent );
                $count++;
                last if $sum <= $maxcols;   
            }
            $min_width-- if $count == 0 and $min_width > 1; 
            # if no cuts but $sum > $maxcols reduce $min_width
            $sum = sum( @max_tmp ) + $tab * @max_tmp; 
            $sum -= $tab;
        }
        my $rest = $maxcols - $sum;
        while ( $rest > 0 ) { # distribute the rest
            my $count = 0;
            for my $i ( 0 .. $#max_tmp ) {
                if ( $max_tmp[$i] < $max->[$i] ) {
                    $max_tmp[$i]++;
                    $rest--;
                    $count++;
                    last if $rest < 1;
                }
            } 
            last if $count == 0;
            last if $rest < 1;
        }
        $max = [ @max_tmp ] if @max_tmp;
    }
    return $max;
}


sub minus_x_percent {
    my ( $value, $percent ) = @_;
    return int $value - ( $value * 1/100 * $percent );
}
4

2 に答える 2

2

フィールド幅の下限がなければ、この問題は簡単です。フィールドをこれ以上小さくすることができない場合は、大きいフィールドのみがスケーリングの対象となるため、計算は、すべてのフィールド、まったくスケールダウンされていないフィールド、または一部のフィールドが限界まで縮小されているかどうかによって異なります。

スケーリングには、固有のフィールド幅ごとに 1 つずつ、いくつかのバンドがあります。フィールドは同じ比率で縮小されるため、最も小さいフィールドが最初に最小フィールド サイズ制限に達します。その後、2 番目に小さいサイズも制限に達するまで、最小サイズよりも大きい列のみをさらに縮小できます。

これは、すべての列が最小サイズに達するまで続き、その後、使用可能なスペースが列間で均等に分割されます。

このプログラムはそのアルゴリズムの計算を実装しています。

返されるフィールド幅は浮動小数点値であり、必要に応じて丸める必要があることに注意してください。

use strict;
use warnings;

use List::Util 'max';

my $min_col_width = 10;
my $tab = 2;

my $widths = recalc_widths(80, [ 10, 15, 20, 25, 30 ]);
print join '  ', map sprintf('%.3f', $_), @$widths;
print "\n";

sub recalc_widths {

  my ($target, $widths) = @_;
  $target -= (@$widths - 1) * $tab;

  my @sorted_widths = sort { $a <=> $b } @$widths;

  my $num_limited = 0;
  my $adjustable_total_width = 0;
  $adjustable_total_width += $_ for @sorted_widths;

  while (@sorted_widths) {

    my $boundary = $sorted_widths[0];

    my $scale = ($target - $num_limited * $min_col_width) / $adjustable_total_width;

    if ($boundary * $scale >= $min_col_width) {
      return [ map max($_ * $scale, $min_col_width), @$widths ];
    }

    while (@sorted_widths and $sorted_widths[0] == $boundary) {
      shift @sorted_widths;
      $adjustable_total_width -= $boundary;
      $num_limited++;
    }
  }

  return [ ($target / $num_limited) x $num_limited ];
}

出力

10.000  10.333  13.778  17.222  20.667
于 2012-07-20T00:45:00.210 に答える
-1

CPANの適切なモジュールを使用する方が良いと思います。各列の幅を計算できる場合は、 Text::Formatを使用して各行の印刷を構成します

于 2012-07-19T19:17:36.097 に答える