sironekotoroの日記

Perl で楽をしたい

Perl入学式 2019 in東京 第2回 お疲れ様でした

受講された方、サポーターの方、お疲れ様でした。
講師をやったジャージの人です。

カリキュラムを改版して臨んだ第2回となります。

講義に利用したスライドはMarkdown形式で公開しています。復習に使ってください。

www.perl-entrance.org

また、復習問題を用意しています。
第1回と第2回でやった内容のみで解ける問題となっています。ぜひ挑戦してみてください。
復習問題の解答例もありますので、参考にしてください。

問題の意味がわからない、とか、このような解答例はどうだろう?という方はSlackのPerl入学式チャンネル(招待フォーム)やtwitterハッシュタグ #Perl入学式 をつけて聞いてみてください。
応答速度、監視頻度などの面からSlackの方をお勧めします。

ピザ会のお題

Perl入学式 in東京 では講義終了後、希望者で集まりピザ会と称した懇親会を行っています。
(ジュース・ピザ代は実費)
昨年からそこで余興として xtetsuji さんが課題を出し、そこにチャレンジャーが回答して公開する、というようなことをやってます。

xtetsujiさんからのお題ですが、今回は「正弦波を描く」でした。

三角関数、昨年冬あたりに高校数学の復習でやっていたので

  • 正弦波: sin(サイン)を使う
  • 最大は 1
  • 最少は -1

というところまでは知っていたのですが、それをコードに起こすロジックがわからない・・・(5分くらいは考えた)

しかし、それでも何とか、それっぽいのを・・・と頑張った結果がこれです!それっぽい何かが出力されます。

xtetsujiさん曰く「似ている、近似・・・というよりも禁じ手ですね」

bitbucket.org

こんなの世に出していいんだろうか。ってまぁ、こういうのこそ晒していきましょう。晒しただけ強くなれます(強さとは

コードにコメントも書きましたが、Perl入学式の第1回, 第2回の内容で書いています。

リベンジマッチ正弦波を描く・実況中継

そもそも、昨年冬に俺は三角関数の復習をしたんじゃなかったのか?という悔しさがあります。
俺がやったのは問題の解き方だけなのか?という反省もあります。
ということでリベンジマッチです。

まず、Perlのsin関数とは、というところを調べます。

perldoc.jp

sin EXPR
sin

(ラジアンで示した) EXPR の正弦を返します。 EXPR が省略されたときには、$_ の正弦を返します。

ふむふむ。ラジアンが何かはともかく、数学用語だから数字入れてみればいいのかな?

print sin(1) . "\n"   # 0.841470984807897

やはり数字が返ってきた。でも、ラジアンが何者かを理解しないとsin関数はうまく使えないみたい。ではラジアンとは?

(ここで「ラジアン 中学生」「ラジアン 高校」「ラジアン わからない」「ラジアンとは」などでググる

ほーほー、三角関数を求める時の角度ではなく、円の半径と弧の長さから導き出されるものなのね。しかも、角度とラジアンの変換公式もある。

となると、こういう戦略はどうだろう

  1. $kakudo って変数を用意する。初期値は0
  2. whileループを用意・・・いや、無限ループが怖いからfor文にしておこう。最初は20回くらい処理を繰り返してみる
  3. ループ処理の中では、$kakudoを +30 していく
  4. $kakudoは360を超えても気にしない。2週目に入ったという感じで。スノーボードの720ターンみたいな(2回転するターン)
my $kakudo = 0;
for ( 1 .. 20 ) {
    print $kakudo . "\n";
    $kakudo = $kakudo + 30;
}

# 出力
# 0
# 30
# 60
# 90
# 120
# ...
# 570

よさそう。では、この角度をラジアンにするロジックを考える・・・その前に、Perlにそういう関数がないかな?って調べてみる。

Perl ラジアン」でググる

tutorial.perlzemi.com

やっぱりあった。しかもタイトルが既にうちの求めてるものそのまま。おなじみ、木本先生のサイト。安心して参考にさせてもらう。

use Math::Trig 'pi', 'deg2rad';

my $kakudo = 0;
for ( 1 .. 20 ) {
    print '角度:' . $kakudo . "\n";
    print 'ラジアン:' . deg2rad($kakudo) . "\n";

    $kakudo = $kakudo + 30;
}

# 出力
# 角度:0
# ラジアン:0
# 角度:30
# ラジアン:0.523598775598299
# 角度:60
# ラジアン:1.0471975511966
# ...
# 角度:570
# ラジアン:3.66519142918809

うんうん、よさそう。   でも、この変数 $kakudo わかりやすいけど、角度とラジアンの変換関数に揃える形で変数名を変えておこう。
あと、このラジアンの値をsin関数に与えるから、それ用の変数も用意しておこう。

use Math::Trig 'pi', 'deg2rad';

my $deg = 0;

for ( 1 .. 20 ) {
    # ラジアン
    my $rad = deg2rad($deg);
    # サイン
    my $sin = sin($rad);

    print 'サイン:' . $sin . "\n";
    print '-' x 10 . "\n";

    $deg = $deg + 30;

# 出力
# サイン:0
# サイン:0.5
# サイン:0.866025403784439
# サイン:1
# サイン:0.866025403784439
# サイン:0.5
# サイン:1.22464679914735e-16
# サイン:-0.5
# サイン:-0.866025403784438
# サイン:-1
# サイン:-0.866025403784439
# サイン:-0.5
# サイン:0
# ...
}

ちゃんと増えて、減って、増えて、を繰り返してる。けど、値が小さすぎて * で表示した時に困りそう。
あと、マイナスの値の時はどうしよう?

sinの最大値は1 , 最小値は -1 だから・・・1を足せば、0から2の間の値をとるようになるはず。
値が小さすぎる問題は、単純にsinの値を10倍くらいにしてみる。
sinって変数に足したり引いたりしちゃうと本来の意味が変わってしまうから、ちゃんと * の数を格納する変数 $star を新規に用意しよう。

use Math::Trig 'pi', 'deg2rad';

my $deg = 0;

for ( 1 .. 20 ) {
    # ラジアン
    my $rad = deg2rad($deg);
    # サイン
    my $sin = sin($rad);

    # sin + 1 を加えて正の値にしたものを
    # グラフを構成する * の数とする
    my $star = $sin + 1;

    # 最大値2,最小値0ではグラフに華がないので、
    # 10倍の値にする
    $star *= 10;

    # 小数点は邪魔なので、int関数で小数点を切り捨てた
    # 値に丸める
    $star = int($star);

    print 'グラフの * の数:' . $star . "\n";

    $deg = $deg + 30;

# 出力
# グラフの * の数:10
# グラフの * の数:15
# グラフの * の数:18
# グラフの * の数:20
# グラフの * の数:18
# グラフの * の数:15
# グラフの * の数:10
# グラフの * の数:4
# グラフの * の数:1
# グラフの * の数:0
# グラフの * の数:1
# グラフの * の数:4
# グラフの * の数:10
# グラフの * の数:15
# グラフの * の数:18

}

ちょっと数字の増減のペースが気になるけど、行けそうな気がする。
* を数字分だけ表示してグラフっぽくしてみる。
さらに、ループの数も増やして1000回くらいやってみる。 0.5秒ごとにループが回るように、高精度のsleepもいれちゃおう。 このあたりは昨日スクリーンで見た xtetsuji さんの模範解答を参考に。

そして完成したコードがこちら。

bitbucket.org

ということで、うちの回答リベンジ編でした。

おまけ

完成したコードの33行目を以下のように変更すると、同じ行でグラフが伸びたり縮んだりします

    printf("%-20s\r", '*' x $star);