プロが教えるわが家の防犯対策術!

実行結果が下のようになっているプログラム(パスカルの三角形)を作成しました。
表示部分のコードは「printf("%5d",$combination);」です。

1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
1 6 15 20 15 6 1
1 7 21 35 35 21 7 1
1 8 28 56 70 56 28 8 1
1 9 36 84 126 126 84 36 9 1
1 10 45 120 210 252 210 120 45 10 1


これを、最終行の長さを基準にして、数値が奇数と偶数の部分で、
センター揃えにしたいのですが、printfでは簡単にできないのでしょうか?
できるとすればどうすればよいのでしょうか?
また、man page of printfには、次のような関数があるそうです。
fprintf, sprintf, snprintf, vprintf, vfprintf, vsprintf, vsnprintf
これらの使い分けはどのようにすればよいのでしょうか?
一番長い名前のものを使いこなせると、一見レベルが高そうに思えますが…。


よろしくお願い致します。

A 回答 (5件)

お返事が遅くなって申し訳ありません。



> 「# パスカル三角形の数値を計算し、2次元配列で返す」の箇所から既にわかりません。
> @$や\@の基本的な意味は書籍に書いてありますが、コードを追うことが困難です。Cのポインタは多少わかりますので、リファレンスも同じようなものだという理解はあります。

ここでの配列の使い方は、C言語で言うところのmallocによるポインタ配列の確保と同じことをやっています。
配列outputは配列のポインタを入れるための配列で、outputの各要素に動的に生成した配列のポインタを格納しています。
これにより、多次元配列が実現できます。
動的な配列生成やmallocの意味がわからない場合は、そちらから先に勉強してください(^^;
Cの入門書でも、mallocに関する話は必ず載っていると思いますし。
あと、「ポインタのポインタ」も理解しておくといいと思います。プログラムをやっていくならこの概念の理解は必須です。

> $rangeと@outputの宣言でshiftや()を代入しているところはなぜそうしているのかわかりません。
> 「my $range;」や「my @output;」だけではないのは、どのような意図があるのでしょうか?
> 特に、$rangeはforeachで1..$rangeとしているので、不思議です。

一番最初の
my $range = shift;
でスコープされている変数は@_です(つまり関数の引数)。
関数pascal_triangleの引数はパスカルの三角形の深度をあらわします。

@outputの宣言時に空配列()を代入しているのは、デバッグ段階の試行の名残です(^^;
my $output;もmy $output = ();も結果は同じなので気にしないでください。

> それと2次元配列が理解できていません。最初にpush(@output,[1,1])というのは、2つの1を@outputに入れているようには見えます。
> しかし、その後のコードでは加算した1つの値を@aryに代入しているだけのように思えます。
> (@aryは1次元の配列でしかないように見えます)
> 配列の使い方が視覚的に表現されていればわかりやすいですが…。
> C言語では、配列のどこにどんな値を入れるかがarray[x][y]=valueという形で見やすいですが、Perlはそれが見えにくいです。

一番上にも書いたように、配列の要素を動的に生成しています。
ご指摘の通り、@aryは普通の一次元配列です。
@aryが各辺のデータで、@outputには@aryのリファレンス(ポインタ)が配列で入っています。
2次元配列の中身のデータを先にmallocしてるようなものです。

> push(@ary,1);
> for(my $j = 0; $j <= $#{$output[$i-1]}-1; $j++)
> {
> push(@ary,${$output[$i-1]}[$j] + ${$output[$i-1]}[$j+1]);
> }
> push(@ary,1);
上の部分は、まず@aryの中に、パスカル三角形の次の辺を作ります。
(一番最初の辺は[1,1]という配列です。次の辺は[1,2,1]になりますね)

> push(@output,\@ary);
この部分で、作った辺のデータ@aryのリファレンスを@outputに格納します。
データのイメージとしては、

@output =
[
 [1,1]
 [1,2,1]
 [1,3,3,1]
 [1,4,6,4,1] ←中の配列一つ一つはもともと@aryだった

   :
   :
]

正確な書き方ではありませんが、イメージとしてはこんな感じです。

多次元配列のアクセスのしかたとして、配列の次元要素ごとに{}で囲って${$output[1]}[2]みたいな書き方もできます…が、これは理解の外でいいです。もっとリファレンスに慣れてから。

> 一番大きい数値を探すところで、$max = $n if($max<$n)という書き方があるんですね。

Perlでは、処理部分がが1行だけのifはif節を倒置して書くことができます。
便利ですがソースが読みにくくなります…

この回答への補足

こんにちは、twinkleluzさん。
丁寧なご回答にとても感謝しています。
徐々に理解できてきましたので、もう少しだけおつき合いください。

1ループごとに各変数の内容を表示してみたところ、どのように処理されているかよくわかるようになりました。

------------------------------------------
121
ARRAY(0x9e62c28)ARRAY(0x9e7af24)
1331
ARRAY(0x9e62c28)ARRAY(0x9e7af24)ARRAY(0x9e7b050)
14641
ARRAY(0x9e62c28)ARRAY(0x9e7af24)ARRAY(0x9e7b050)ARRAY(0x9e7b0b0)
------------------------------------------

ARRAY(0x9e62c28)などリファレンスが変数名になっているのがわかります。
実務ではこのように書くのが一般的なのでしょうか?
私はまだメモリの内容まで意識してプログラミングできません。

それと、shiftの意味がまだよくわかっていません。
Webや本を調べると、先頭の要素を取り出すとあります。
また、返す値がない場合はundefを返すともあります。

ですが、次のフィボナッチ数列を再帰処理で書いたものをshiftで初期化するのとしないのでは、なぜか結果が異なります。
返す値が何もない時はundefを返すのであれば、undefをあらかじめ$xに代入しておいてもよさそうですが、それだとうまく動作しません。
(引数が5の場合、shift有りだと正しく8と表示され、shift無しだと1になります)

#--------------------------------------#
#!/usr/bin/perl

sub fibonacci {
my $x=shift;
# my $x;
($x == 0 || $x == 1) ? 1 : &fibonacci($x - 1 ) + &fibonacci($x - 2);
}
print &fibonacci(@ARGV[0]);
print "\n";
#--------------------------------------#

よろしくお願いします。

補足日時:2005/12/28 14:29
    • good
    • 0

お返事が遅くなりました。



> 実務ではこのように書くのが一般的なのでしょうか?
> 私はまだメモリの内容まで意識してプログラミングできません。

実務で一般的かどうかはわかりません。
が、プログラミングの一般的な方策として、メモリ管理をうまく行うことにより
効率化や高速化を図ることができます。
メモリ管理が上手い人とそうでない人の差は大きいです。
頑張って慣れてください。

shift関数についてです。
引数の配列の先頭の要素を抜き出して返します。

@list = (1,2,3,4,5);
という配列があったとき、

$x = shift(@list);

とすると、$xには1が入り、@listは
@list = (2,3,4,5)
となります。

引数を省略すると、サブルーチン内では@_、メインルーチン内では@ARGVが対象になります。
返す値がundefになるのは、引数の配列にもう要素がないときです。
空の配列に対してshiftを行うと、undefが返ります。

undefは未定義を表し、変数が未定義かどうかはdefined関数を使って調べることができます。

> #!/usr/bin/perl
>
> sub fibonacci {
> my $x=shift;
> # my $x;
> ($x == 0 || $x == 1) ? 1 : &fibonacci($x - 1 ) + &fibonacci($x - 2);
> }
> print &fibonacci(@ARGV[0]);
> print "\n";

このプログラムですが、
> my $x=shift;
この部分で、関数の引数の最初の要素を$xに入れています。
shiftで引数の値がはいる理由は、前述の通り省略時には@_が対象になるからです。
my $x;だけの場合、$xは何も中身がない変数として初期化されます。
中身がない変数は、一番最初に数値として評価されたとき、0として扱われます。
なので、
> ($x == 0 || $x == 1) ? 1 : &fibonacci($x - 1 ) + &fibonacci($x - 2);
この行で$xは0とみなされ、結果として1が帰ってきます。
    • good
    • 0
この回答へのお礼

ありがとうございます。
スッキリしました。

Perlでお決まりの、対象を省略した場合に対象になる$_や@_ですね。
print文などで対象を省略した場合の例はテキストにもありましたが、
頭が固いせいか、shift関数でもそうなっていたとは気づきませんでした。
ただ文法を覚えるだけじゃなくて、いろいろ可能性を考えて実験もしてみます。
インタプリタのPerlでは、メモリ管理による効率化・高速化は必須ですね。
twinkleluzさんのプログラミングのテクニックを見習ってハイレベルなプログラミングができるようにがんばります。

丁寧にご回答いただき、本当にありがとうございました。

お礼日時:2006/01/04 20:17

どのあたりがわからないでしょうか?


リファレンスの部分は確かに難しいと思うので、参考URLにリファレンスの説明が載っているリンクを張りました。
(ソース中で@$や\@とかで始まっている変数のお話です)
C言語のポインタの理解があれば、リファレンスの理解も早いと思います。
アルゴリズムについてはかなりベタな書き方をしているつもりですが、わからない箇所を指摘してください。

ちなみに、このコードを書くのにかかった時間はだいたい30-40分くらいだったと思います。

参考URL:http://www.rfs.jp/sb/perl/02/10.html

この回答への補足

こんばんは、Twinkleluzさん。

「# パスカル三角形の数値を計算し、2次元配列で返す」の箇所から既にわかりません。
@$や\@の基本的な意味は書籍に書いてありますが、コードを追うことが困難です。Cのポインタは多少わかりますので、リファレンスも同じようなものだという理解はあります。

$rangeと@outputの宣言でshiftや()を代入しているところはなぜそうしているのかわかりません。
「my $range;」や「my @output;」だけではないのは、どのような意図があるのでしょうか?
特に、$rangeはforeachで1..$rangeとしているので、不思議です。

それと2次元配列が理解できていません。最初にpush(@output,[1,1])というのは、2つの1を@outputに入れているようには見えます。
しかし、その後のコードでは加算した1つの値を@aryに代入しているだけのように思えます。
(@aryは1次元の配列でしかないように見えます)
配列の使い方が視覚的に表現されていればわかりやすいですが…。
C言語では、配列のどこにどんな値を入れるかがarray[x][y]=valueという形で見やすいですが、Perlはそれが見えにくいです。

一番大きい数値を探すところで、$max = $n if($max<$n)という書き方があるんですね。
@pascal_triがどういった形で値が格納されているか見えないので、整形する部分はまだ解読していません。
現時点ではその程度の理解です。

よろしくお願いします。

補足日時:2005/12/24 03:01
    • good
    • 0

printfでは中央揃えというのはできないので、桁と数値を与えて中央揃えに整形した文字列を出力する関数を自作する必要があります。


また、各数値の桁揃えをするためには最終行まで一度計算しなければならないので、最終行までの数値を2次元配列にして保持しておきます。
さらに、各行を中央揃えにするため、一番下の行の長さを元に各行の両端にどのくらいスペースを追加すればいいかを計算し、両端にスペースを追加します。

そうするとソースは以下のようになります。

@pascal_tri = &pascal_triangle(10);

# 桁数計算
$max = &search_max(@pascal_tri);
$scale = length($max);

# 数値を中央揃えにして出力(1行ごとの配列)
foreach $line(@pascal_tri)
{
$out_line = "";
foreach $n(@$line)
{
$out_line .= &center_print($n,$scale).' ';
}
push(@output,$out_line);
}

# 両端のスペースがいくつあればいいかを計算する(@space_countに保存)
@pascal_tri = @output;
$base_count = length($pascal_tri[$#pascal_tri]);
@space_count = map(int(($base_count - length($_)) / 2),@pascal_tri);
@output = ();

# 両端にスペースをくっつける
for($i = 0; $i <= $#pascal_tri; $i++)
{
$count = $space_count[$i];
$line = $pascal_tri[$i];
push(@output, ' ' x $count . $line . ' ' x $count);
}

# 各行を改行で連結して出力
print join("\n",@output);



# パスカル三角形の数値を計算し、2次元配列で返す
sub pascal_triangle
{
my $range = shift;
my @output = ();
# 初期値の挿入
push(@output,[1,1]);

foreach my $i(1..$range)
{
my @ary;
push(@ary,1);
for(my $j = 0; $j <= $#{$output[$i-1]}-1; $j++)
{
push(@ary,${$output[$i-1]}[$j] + ${$output[$i-1]}[$j+1]);
}
push(@ary,1);
push(@output,\@ary);
}
return @output;
}

# パスカル三角形の数値で一番大きいものを探し出す
sub search_max
{
my @pascal_tri = @_;
my $max = 0;

foreach my $line(@pascal_tri)
{
foreach my $n(@$line)
{
$max = $n if($max < $n);
}
}
return $max;
}

# 中央揃えで出力する関数。$nは数値、$scaleは桁数。
# 桁が奇数の時は左側にスペースが1つ多く入る
sub center_print
{
my $n = shift;
my $scale = shift;

my $length = $scale - length($n);
my $left = 0;
my $right = 0;
if($length % 2 == 1)
{
$length--;
$left++;
}
$left += int($length/2);
$right += int($length/2);

return ' ' x $left . $n . ' ' x $right;
}

この回答への補足

ご回答ありがとうございます。

レベルが高いので理解が困難となっています。
もう少しコメントを多くして解説していただけませんでしょうか?
配列の使い方とpushのところが一応の基本理解はありますが、
応用になるとわからなくなってしまいます。

それと、twinkleluzさんはプロのプログラマーでしょうか?
プロの人がこのコードを書くのにどのくらいの時間を要するのでしょうか?
質問をしてからそれほど時間が経ってないので驚きです。

よろしくお願いします。

補足日時:2005/12/21 00:09
    • good
    • 0

左側にスペースを入れて山のような感じで表示したいということですか? スペースを最初にいくつ入れるかについては規則性があるからそれを考えればいいだけでは? (段数に応じて何段目はいくつ左にスペースを入れる、というのは数えれば分かります。

分かったらあとは規則を見付ければいいだけ)。

> また、man page of printfには

それってC言語のことでは? perl のマニュアルページは perldoc ですよ。組込み関数は perldoc -f printf のようにして出します。で、Perl の場合は print と sprintf と printf (それと IO::Handle の print と printf) だけで全てのことができます。

この回答への補足

お返事ありがとうございます。

man page of printfはC言語だったとは気づきませんでした。。。
perldocというものがあるのも知りませんでした。

さすがにセンター揃えをする関数はないということでしょうか。
よくよく考えると、表示させる値の桁数を固定長にしない限り、
最終行を計算しないとセンターが出せませんね。
通常のプログラミングでは、左のスペースで調整するのを理解しました。


各行を表示する前に以下のコードを入れ、

for ($k=1; $k<=$n-$i; $k++){
printf "スペース4つ";
}

数値の桁数を8桁に固定してみました。

printf("%8d",$combination);

スペースの個数は4で桁数の半分です。
これを実行すると山型にはなります。

任意の桁数に対応させたい場合はどのようにしたらよいのでしょうか?
printfの""の中の8が困りました。

よろしくお願いします。

補足日時:2005/12/20 22:16
    • good
    • 0

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!