プチコンTips

剰余を使ったリスト短縮テクニック

【プチコン/mkII 両対応】

ここでは剰余を使ったプログラムリストの短縮法において具体例を交えて書いていきます。
剰余以外のリスト短縮法ははこちらの方に書いています

剰余の場合は一般的な短縮法というものはなく個別で対応せざるを得ないためこのようになりました。ピンポイントなネタで一般的な活用は難しいものもありますが私が実際にプログラム中で使っているものなので少なくとも私自身はちゃんと役に立っています(笑)
すべて私が考えたアルゴリズムを元に書いているためもっと良いもの(分かりやすいもの)があるかもしれませんがその辺はご了承ください。(こんなことを考える人はほとんど居ないだろうけど)

※剰余の処理は重いのでリスト短縮のために剰余を多用すると処理が遅くなる場合があります。ただし、1行プログラム、1画面プログラムといった行数に制約を設けたプログラムを作っている場合は速度よりもリスト短縮が優先される場面が多いために今回のような剰余を多用したリスト短縮は活用できる機会は多いでしょう。


小数部分を求める



 変数Aの小数部分を求めたいという時にはA>0の時はAから整数部分をマイナスすれば良いのでA-FLOOR (A)もしくはA-(0OR A)で求めることが可能ですが、剰余を使えばA%1で求めることが可能であるためリスト短縮できます。

 これを使えばA-A%1 で整数化が可能になります。しかし、0OR Aと比べてリスト短縮できるわけではありません。
 実は、A-A%1と0OR AはA>0の場合においては両者に差はありませんが、A<0の時は両者の動作が異なるのです。0OR Aの方はFLOOR関数と同じくA以下の最大の整数になるのに対してA-A%1の方は単純に符号を除いた部分において(見た目通り)整数化できるのです。
 つまり、下記のようになるわけです。

 A=-2.34の場合
 FLOOR (A)もしくは0OR A →  -3
 A-A%1 →  -2

FLOOR関数でこれと同じような動作を行いたい場合はSGN(A)*FLOOR(ABS(A))などのようにしなくてはならないためリスト短縮に繋がります。

↑TOP


じゃんけんの勝敗判定



剰余を使ったリスト短縮でポピュラーなのはじゃんけんの勝敗判定でしょう。

X(自分)グー=1、チョキ=2、パー=3
Y(相手)グー=1、チョキ=2、パー=3

この場合に勝敗判定を行うと自分がグーを出して相手がパーの時(IF X==1 AND Y==3 THEN〜)・・・などのすべての判定を行うとIF文が9つになります。3通りのあいこをIF X==Y THEN〜と1つにまとめてもIF文は7つです。

しかし、相手と自分の差分を取ってみると相手が自分と同じ(差分がゼロ)なのはあいこなのは当然として相手が自分より1つ大きければ(Y-X=1ならば)勝ち、2つ大きければ(Y-X=2ならば)負けというのも分かると思います。(※相手が自分より小さいならば3を加算して考える)
これは(Y-X)%3で勝敗判定ができることを意味します。

(Y-X+3)%3 = 0 ・・・ あいこ
(Y-X+3)%3 = 1 ・・・ 自分の勝ち
(Y-X+3)%3 = 2 ・・・ 自分の負け
 Y-Xが負数になった場合のために最後に+3としています。(負数の場合は剰余も負数になるため)

 ちなみにじゃんけんの手を0〜2の数字(1〜3ではなく0〜2なのはその方が簡単に処理できるから)に置き換える処理も剰余を使えば単純化できます。
 INPUT命令を使って「G」「C」「P」という文字を入力してそれがA$に入っている場合は次のようになります。
 
  X=71%ASC(A$)%3

 この式は「G」のキャラコードが71ということを元に考えれば簡単に分かります。71%71というのは0になりますからね。このように剰余が0になるものを選択することでふるい分けが可能になります。「C」(キャラコード67)は「G」より4小さいため4%3=1という値になり、「P」(キャラコード80)は「G」より大きいので71%80=71となり71%3=2という値になります。

 Bに[B][Y][X]ボタンのBUTTON関数の値が入っており、それを0〜2の数字に置き換える場合は次のようにできます。

  X=96%B%5

 [B]ボタンの値は32なのでn%B(ただし、nは32の倍数)にするとその時点で[B]ボタンのふるい分けができます。[X]ボタンは64、[Y]ボタンは128であり32の倍数ですが、nの値が64や128の倍数だと[X]ボタンや[Y]ボタンを押した時にも0になってしまい都合が悪いです。
 じゃんけんの手は0〜2だから最後は%3とすべきですが、(nに32の倍数、かつ64や128の倍数でない数を選択した場合)[Y]X=2[X]X=2となり、Bの値に別途加減算を行わない限りは期待通りの値を得ることはできません。
 ここは発想を変えて最後を%3以外にしてみましょう。32の倍数、かつ64や128の倍数でない数の最小値は96なので96%B%mの値がB=128の時に1B=64の時に2になるようなmの値を求めれば良いです。そうすればm=5となるため上記の式が導かれます。(ただし、十字ボタンを同時に入力した場合には0〜2以外の値になる場合があるため注意)  ちなみにグー、チョキ、パーを[B][Y][X]ボタンに当てはめている理由は「B」「Y」「X」の文字の形がグーチョキパーに似ている、縦にグーチョキパーの順番に並んでいる[A]ボタンがフリーなのでそれをシステム用に割り当てられるというメリットがあるためです。

 あとリスト短縮とは関係ないですが、じゃんけんゲームで相手(コンピュータ)はY=RND(3)で0〜2の値を決めてもいいですが、それではつまらないためこちらに書いているような感じの思考ルーチンを作ってみるといいかもしれません。

 このようにグー、チョキ、パーなどの3通りの分類ならば剰余を2回使えば確実にふるい分けが可能になります。鍵盤の黒鍵の配置のように2回の剰余で3通りを大きく越える場合のふるい分けも可能です。

↑TOP


剰余をANDの代わりに使う



 2の累乗の剰余はANDで代用が可能ですが、逆に言えばANDも剰余で代用が可能です。

 B AND 15 → B%16 (※Bは0以上の整数に限る)

 これは15のように2の累乗-1だけではなく他の場合も可能なのです。

 B AND 12 → B%16-B%4

 一見すると1文字も縮まってないですが、他の演算子や等号、不等号と組み合わせる場合にはビット演算の優先度が低いためカッコが必要ということを考えると事実上2文字分短縮できているのです。
これはANDの引数が2のn乗−2のm乗という値になっていればどれでも当てはまります。

 B AND 240 → B%256-B%16

これによってABXYボタンが押されているかどうかという判定が可能になります。(1画面版 棒歌ロイドキーボードで使用)

↑TOP


十字ボタンを使った8方向移動



 8方向移動は左右移動と上下移動を組み合わせれば実現できるので、まず最初に左右移動について考えてみます。

 BにBUTTON関数の値が入っているとすれば普通に論理式を使えば次のようになります。

 X=X-((B AND 4)==4)+((B AND 8)==8)

これはANDを上手く使って短縮すれば次のようになるでしょう。

 X=X-(4AND B)/4+(8AND B)/8 (
PETIT RUNで使用)
 ※B AND 4ではなく4AND Bとすることでさらに2文字分縮めている。

 この方法はX座標移動量が2の時も追加コストがかからず、移動量が4や8のときは逆に2文字分短縮できるというのがメリットになるのですが剰余を使えばさらに短縮が可能です。

 ここでANDを剰余に置き換えることで次のようになります。

 X=X+B%16/8-B%8/8*3+B%4/4

 これで1文字分縮まりましたが、さらに縮まる余地がありそうなので別の方法で最初から考え直します。

 B AND 12で左右ボタンのどちらかが押されているかどうかが分かります。(上下ボタンは押しても値に影響を与えない)
 B AND 12の値は、左を押している時は4、右を押している時は8、押していない時は0になるのですが、これが-1,+1、0になるようにしなくてはなりません。

 そこで3で割ったときの剰余を計算すると(B AND 12)%3の値は左を押した時は1になり右に押した時は2になります。
ではこの値を2倍して3を引けば解決かというとそうではなく押してないときは0になるようにしなくてはなりません。
単純に考えると、((B AND 12)+n)%3-nにおいて上記の条件を満たす最小のnの値を求めれば良いということが分かります。(つまり、押してないときは移動量が0になるようにする)

 ここでnを単純に増やしていっても(3の剰余であるためnは0、1、2のいずれか)これを満たすことはできませんが、nの値が1の時は左を押したら2になり右を押したら0になります。これから1を引けば1と-1になります。その移動量のプラスとマイナスを反転させる(マイナス1を掛ける)と「左を押している、押してない、右を押している」時の値がそれぞれ-1、0、1になり、これで左右移動ができるようになりました。

 X=X-((B AND 12)+1)%3+1

 ここでANDを剰余に置き換えることで次のようになります。

 X=X-(B%16-B%4+1)%3+1

 上記のANDによって上手く縮められているというものと比べた場合には5文字分短縮されており、剰余に置き換え後と比べても4文字短縮されています。

上下移動においてもこれと同じことを行えばY=Y-(B%4+1)%3+1になります。 両者を併せると8方向移動の完成です。

 B=BUTTON()X=X-(B%16-B%4+1)%3+1Y=Y-(B%4+1)%3+1

 これが最短ではないかと思われます。(ちなみにX座標の移動量が2ならば X=X+(B%16-B%4+2)%6-2 、3ならば X=X+66%(B%16-B%4+7)-3 となる)

 上下方向の移動はY=Y+(B%4-B%2*3)/2でも可能です。これはANDを使って表せばY=Y+(2AND B)/2-(1AND B)となるため分かりやすいでしょう。しかし、これよりも上記のものの方が短くなっています。(前者より2文字分、後者より8文字分短縮されている)

 さらに別の観点から考えるとX=X+112%(B%16-B%4+3)-1で左右移動、Y=Y+52%(B%4+3)-1で上下移動が可能になるのですが、上記のものより長くなるため説明は割愛します。ちなみにこの112とか52という数字は下記の鍵盤の黒鍵の配置における81と同じ要領で求めることができます。

↑TOP


制限付きの左右移動



 上記の8方向移動は他のボタンを同時入力した場合に影響を受けずに正常動作するいわゆる万能型なのに対して今度は制限付きで動作させることでさらに短くなる方法を書いていきます。

 BにBUTTON関数の値が入っている場合は下記の方法で左右移動できます。

 X=X-(B+1)%3+1

 実はこれは下記の表を見てのように十字ボタンだけに止まらず3の剰余が1になるボタンで左移動、2になるボタンで右移動が可能になっています。

BUTTON関数の値B%3 の値
1
1
2
2
4
1
8
2
16
1
32
2
64
1
128
2
256
1
512
2
1024
1

 したがって、十字ボタンの左右での左右移動ができると同時に[L][R]ボタンでも左右移動ができています。(プチコンスキーで使用)
 しかし、これでは左右移動以外はできないため注意が必要です。


 また、[L][R]ボタンで左右移動を行い他のボタン操作も行いたい場合は普通に考えれば剰余を使わないならばX=X+!(256AND B)-!(512AND B)論理否定を使用)が最短で剰余を使ってもX=X-(B%1024-B%256+1)%3+1になります。
 しかし、[L][R]ボタンにおいては上記の「条件付き左右移動」を応用して下記のようにできます。

 X=X-(0OR B/256+1)%3+1

 これは配列変数でのリスト短縮と同じくそのプログラム内で使用しているボタンの中で最も大きな値のものに対してのみ有効([L][R]よりも小さな十字ボタンやABXYボタンは同時に使える)となる方法です。もっとも、[L][R]よりも大きな[START]ボタンを同時に押して操作すれば左右が逆になるという問題がありますが、[START]と[L][R]の同時押しを要求するプログラムは極めて限られるため上記のように普通の方法を使えば問題ないでしょう。

↑TOP


鍵盤の黒鍵の配置


 鍵盤演奏のプログラムを作る場合に「ラ」から開始した場合には1オクターブ分の黒鍵の有無は1、0、1、1、0、1、1となります。この場合A番目に黒鍵があるかどうかというのは下記のようなIF文で判断が可能です。(※ELSEはmkIIのみ対応なので初代はIF文の前にB=1を置く必要がある)

 IF A==2 OR A==5 THEN B=0 ELSE B=1

 ここで3つ置きに0が出ているので3で割った時の剰余を求めればパターン化できそうです。
 2番目と5番目が0になるようにAの値に1プラスして(A+1)%3とすればAの値が1増えるごとに(A+1)%3の値は2、0、1、2、0、1、2となります。
 FOR〜NEXTを使って書き込む場合はIの初期値を2にすれば+1は省略可能なのでI%3で済みます。
そして、SGNを使ってSGN(I%3)とすればIが1増えるごとに1、0、1、1、0、1、1の値を取得可能になります。

 これで終わりかというとそういうわけではありません。
 ここで、論理否定がSGNの代わりに使えるため!!(I%3)とすることができさらに1文字分短縮できます。(※mkIIのみ)

 これが限界に見えますが81%I%3とすることでさらに1文字分短縮できます。(1画面版 棒歌ロイドキーボードで使用)
この「81」はn%Iにおいて、Iを2〜8に変化させたときにそれぞれ「1、0、1、1もしくは4、0もしくは3、1もしくは4、1もしくは4もしくは7」を満たすnの値の最小値が81ということから求まります。

↑TOP


最後に



 剰余を使ったリスト短縮は差分を取るなどをしていかにパターン化を行うかが重要になってきます。
 あとは剰余を演算することでそのパターン通りのふるい分けを行えば良いだけです。移動ならば-1、0、+1の3通りで分類できれば良いので3で割ったときの剰余を元にすれば良いとすぐに分かるでしょう。場合によっては1回では駄目で2、3回剰余の演算をしないとふるい分けができないかもしれません。

 プログラムによって個別対応が必要ということは100個のプログラムがあったら100個以上の使い方があるわけなので上記のものを覚えたら十分かというと全然そんなことはありません。(というか、ケースバイケースのものなのでANDを剰余に置き換える方法以外は覚える意味はほとんどなくふるい分けを行う方法だけを覚えればよい)

 慣れたら下記のようにプログラムに応じて自由にパターン化ができるようになると思います。(元ネタは分かる人には分かる)

 CV=10:C2=1:FOR I=7TO 11BEEP,2387+(10323%I+5040%I*16%C2)*341:VSYNC 24761%I*CV:NEXT

 今回挙げた例を元に剰余を使ってどのようにリスト短縮をしていくのかという1つの方法を理解してもらえたら良いかなと思います。

↑TOP


RETURN/RETURN *MAIN

inserted by FC2 system