[今日のPiet]GridPietGenerator入門編3(ループの応用1)

どもです。

GridPietGeneratorの 入門編、第3弾です。

今回はループの応用編です。

ループ処理色々

前回説明したループ処理を改造して、色々な処理をしてみましょう。

基本形は、前回紹介したこのプログラムです。

push3 dup mul  #1
:loop
dup if::end
dup outn       #2
push1 sub
goto:loop
:end
pop end

ymos-hobby-programing.hatenablog.com

実はループの基本形は大体同じで、

つぎのように組めば大体なんとかなります(多分)。

  1. 上記のプログラムを書く
  2. 1行め(コメント「#1」の行)でスタックを初期化する
  3. 4行め(コメント「#2」の行)でループ内の処理をする

カウンタの2乗を出力する

まずは練習問題として「カウンタの2乗」を出力する

プログラムを書いてみます。

前回のプログラムでは、9、8、・・・、1の順に値が出力されました。

そこで今回のプログラムでは、それぞれの2乗、つまり、

81、64、・・・、1の順に値を出力させます。

 

さて、実はこの2乗出力プログラムは、

前回までに出てきた命令だけで書くことができます。

もし興味のある方は、先に進む前に

どうすれば2乗を出力するプログラムが作れるか考えてみてください。

 

では解説です。冒頭でも説明した通り、

ループは以下の3点を気にすればいいのです。

  1. 上記のプログラムを書く
  2. 1行め(コメント「#1」の行)でスタックを初期化する
  3. 4行め(コメント「#2」の行)でループ内の処理をする

1つめは、有限のループを作るためのテンプレみたいなものです。

(詳細は前回の解説を参照してください。)

ymos-hobby-programing.hatenablog.com

2つめは、スタックの初期化です。

複雑な処理にするほど色々な初期化が必要になりますが、

今回は前回と同様、ループの回数iをプッシュするだけでOKです。

 

3つめ、ループ内の処理、ここがポイントです。

この場所で、

この2つを実行すれば、目的のプログラムは完成です。

 

結果、つぎのようなプログラムになります。

push3 dup mul    #1
:loop
dup if::end
dup dup mul outn #2
push1 sub
goto:loop
:end
pop end

前回のプログラムと比較すると、違うのは4行め、

コメント「#2」の行のみ変化しています。

 

この行でのスタックの変化はつぎの通りです。

まず、前回にも解説した通り、

4行めの最初のスタックの状態は、つぎのようになっています。

f:id:y-mos:20210818213544p:plain
 

ここから、つぎの手順で命令を実行します。

  1. dup命令でiを複製
  2. dup命令でさらにiを複製
  3. mul命令でスタック上部の2つの値を掛け算
  4. outn命令で掛け算した結果を出力する

この過程でスタックはつぎのように変化します。

f:id:y-mos:20210818213544p:plainf:id:y-mos:20210819220439p:plainf:id:y-mos:20210819220501p:plainf:id:y-mos:20210819220522p:plainf:id:y-mos:20210819220540p:plain
4行め開始時点→dup実行後→dup実行後→mul実行後→outn実行後(4行め終了時点)

4行め終了時点で、スタックの状態は再びつぎのようになります。

f:id:y-mos:20210819220540p:plain
 

前回のプログラムの4行め終了時点でも、同じスタックの状態になっていたため、

このまま置き換えてもその後の処理には影響しないことがわかります。

Pietソースコード

Piet画像はつぎのようになります。

f:id:y-mos:20210821171712p:plain
2乗出力プログラム

npietデバッグすると次のようになります。

f:id:y-mos:20210821171739p:plain
2乗プログラムのデバッグ画像

前回のプログラムと同じような構造になっていることがわかります。

書き換えた箇所が少しだけなので、当たり前ですが・・・。

2乗プログラムの問題点

さて、この2乗プログラムの出力をnpietで確認すると、つぎのようになります。

816449362516941

一瞬戸惑いますが、ちょっと考えれば納得できると思います。

スペースなく81、64、49、36、25、16、9、4、1とすれば、

上記のような数字の羅列が出力されることがわかります。

理解はできますが、やはり読みにくいですね。

2乗出力プログラム(出力改善ver)

先ほどのプログラムでは、出力が繋がっていて読みにくいという問題があります。

そこで、半角スペースで数値を区切りながら出力するプログラムを書いてみます。

 

さて、半角スペースの出力をどこに書くかですが、

やはり先ほどの3点に立ち返ります。

  1. ループのプログラムテンプレを書く
  2. 1行め(コメント「#1」の行)でスタックを初期化する
  3. 4行め(コメント「#2」の行)でループ内の処理をする

今回も、ループ内の処理に関わるところなので、

4行めに手を加えていけばよいだろうと予想できます。

 

これを踏まえて、半角スペースの区切り込みの

ループプログラムはつぎのようになります。

push3 dup mul     #1
:loop
dup if::end
dup dup mul outn  #2-1
push2 dup dup add #2-2
dup mul mul outc  #2-3
push1 sub
goto:loop
:end
pop end

ちょっと長いので、4行めを3分割して

コメントに「#2-1」、「#2-2」、「#2-3」と番号を振りました。

解説

「#2-1」は、先ほどの2乗出力のプログラムと同じです。

「#2-2」と「#2-3」を今回新たに追加しました。

 

「#2-1」の終了時点で、スタックの状態はつぎのようになっています。

f:id:y-mos:20210819220540p:plain
 

この状態からはじめて、まずpush2命令で整数2をプッシュします。

f:id:y-mos:20210819230615p:plain
 

続いて、2回dup命令を実行し、スタックに3つの数値2が積まれた状態にします。

f:id:y-mos:20210819230706p:plainf:id:y-mos:20210819230726p:plain
 

この状態から、add命令を実行します。

add命令は、加算命令、つまり、足し算です。

つまり、スタック最上部の2つの値をポップして、最上部にあった値と、その下にあった値の和を計算し、

結果をスタックにプッシュします。

今の例だと、スタック最上部の2つの「2」をポップして、

その和を「4」をスタックに再びプッシュします。

この処理の後、スタックは次の状態になっています。

f:id:y-mos:20210819231036p:plain
 

その後、#2-3の行で、dup命令を実行し、値「4」を複製します。

f:id:y-mos:20210819231205p:plain
 

そして、mul命令を2回実行して、スタックに積まれている

イテレータ以外の値全てを掛け算します。

f:id:y-mos:20210819231330p:plainf:id:y-mos:20210819231418p:plain
mul命令1回め実行後→mul命令2回め実行後

これでスタックには、イテレータiと値「32」が積まれた状態になりました。

 

さて、なぜ値「32」を作ったのか。

それは、半角スペースの文字コードが「32」だからです。

 

そして、この状態からoutc命令を実行します。

outc命令は、スタック最上部の値をポップし、ポップした値と同じ文字コードを持つ文字を出力します。

したがって、コンソール画面の出力には、

文字コード「32」の""、つまり、半角スペースが出力されます。

 

outc命令は実行時に値をひとつポップするので、

実行後のスタックはつぎの状態になります。

f:id:y-mos:20210819220540p:plain
 

この状態は、当初の4行めの最後の状態と同じなので、

このまま処理をつぎの行に引き継ぐことができます。

Pietソースコード

では、この処理のPietコードを出力してみます。

f:id:y-mos:20210821171905p:plain
2乗出力プログラム(改善ver)

半角スペースの文字コード32を作っているのは右辺に沿った部分です。

この過程が少々長いため、全体的に縦長のコードになっています。

 

また、npietの出力結果を以下に示します。

f:id:y-mos:20210821171927p:plain
2乗出力プログラム(改善ver)のデバッグ画像

Pietインタプリタの移動ルートの形状は、これまでとあまり変わりませんね。

一部の処理がただ長くなっただけだということがわかります。

 

そして最後に、このプログラムの出力結果を示します。

81 64 49 36 25 16 9 4 1 

数値と数値の間に半角スペースが入っていることがわかります。

これで読みやすくなりました。

おわりに

今回はループの応用方法について説明しました。

ループ内の処理を変更して、少々複雑な処理を実行しました。

 

また、今回は新しい命令add命令outc命令を紹介しました。

前回までの命令と合わせると、これまでに10個の命令を紹介したことになります。

命令の挙動は複雑なので、適宜チートシートなどを見返しながら、

プログラムを組んでみてください。

 

次回もループの基本的な使い方を説明します。

その中で、Pietでも重要な命令の一つである

roll命令を紹介します。

roll命令をマスターできれば、より柔軟にスタックを

操作することができるようになります。

(それだけに、若干挙動が複雑ですが・・・。)

 

じゃ、今日はこの辺で。

では。