Casio Python - Python らしい反復処理:高速素因数分解(2)

Python Casio Python
 Casioグラフ関数電卓の Python を使ってみる
     - Python らしい反復処理:高速素因数分解(2) 
目次


初版:2020/07/25
修正:2020/07/26
追記修正:2020/11/14

前の記事 - 8. シェル画面入力の工夫 |  次の記事 - 10. 大きな数の計算


9. Python らしい反復処理:高速素因数分解(2) <fx-CG50 OS3.40以降>

前回は、Casio Basic プログラムを、とりあえず動かすことを優先させ、できるだけ忠実に Casio Python に移植しました。
291行もある長大なスクリプトです。似たような処理が何度も繰り返されており、無駄が満載です。

オリジナルの Casio Basic のコードは、その言語仕様から長大なソースになっていますが、今回は、Python らしい反復処理を用いて、大幅にスリム化します。主に、以下に示した5項目 の変更を行って、無駄を減らします。

(1) farc() 関数をユーザーモジュールに追加し、ここでの関数定義を削除する
(2) 不要な記述を削除する
(3) disp() 関数内を、反復処理に置換えて、記述を簡潔にする
(4) を反復処理に置き換えて、記述を簡潔にする
(5) を反復処理に書き換えて、記述を簡潔にする



前回作ったスクリプト - FactorG1.py のダウンロード

fx-CG50 Pythonモード:高速素因数分解 - FactorG1.py
"""Sample script

Exercise;
ported from Casio Basic
"Prime Factor 10 digits"
factorG.py
ver 1.0

by Krtyski/e-Gadget
"""

from u import *

def ckpwr():
 global a,b,c,d,e,z
 e+=1
 z[e]=b
 while 1:
  a=int(d)
  z[e+11]+=1
  d=a/b
  if frac(d):
   break
 c=int(sqrt(a))

def disp():
 global a,b,c,d,e,f,z
 if a>1:     ◀ (3) disp() は素因数探索が終わって最後に実行されるので、
  e+=1      ◀ (3) この if 文も 探索囚虜後に1度だけ実行される。
  z[e]=a    ◀ (3) この if 文は、最後に探索した素因数を z[ ] に格納する処理で、
  a=1       ◀ (3) 表示処理の一部ではないので、ここから削除し、
  z[e+11]=1   ◀ (3) disp() 関数実行の直前に記述することにする。
 d=int(e/6)   ◀ (2) 複数ページ切替のためのページ番号 d を計算⇒ 不要、削除する
 if e-6*d>0:   
 (2) 複数ページ切替のためのページ番号 d を計算⇒ 不要、削除する
  d+=1     ◀ (2) 複数ページ切替のためのページ番号 d を計算⇒ 不要、削除する
 c=1

 clear_screen()
 locate(00f3'm'0)
 b 6*(c-1)+1   (3) 複数ページ切替機能を使わないので、bは常に1 ⇒ b=1 と書き換える
 locate(0, 1, z[b], 1, 'm', 0) ◀ (3) ここから disp() 関数終わりまでは反復処理に置き換える
 locate(10, 1, '^(', 2, 'm', 0)                    ↓
 locate(12, 1, z[b+11], 1, 'm', 0)
 locate(15, 1, ')', 2, 'm', 0)
 if b+1<=e:
  locate(0, 2, z[b+1], 1, 'm', 0)
  locate(10, 2, '^(', 2, 'm', 0)
  locate(12, 2, z[b+12], 1, 'm', 0)
  locate(15, 2, ')', 2, 'm', 0)
 if b+2<=e:
  locate(0, 3, z[b+2], 1, 'm', 0)
  locate(10, 3, '^(', 2, 'm', 0)
  locate(12, 3, z[b+13], 1, 'm', 0)
  locate(15, 3, ')', 2, 'm', 0)
 if b+3<=e:
  locate(0, 4, z[b+3], 1, 'm', 0)
  locate(10, 4, '^(', 2, 'm', 0)
  locate(12, 4, z[b+14], 1, 'm', 0)
  locate(15, 4, ')', 2, 'm', 0)
 if b+4<=e:
  locate(0, 5, z[b+4], 1, 'm', 0)
  locate(10, 5, '^(', 2, 'm', 0)
  locate(12, 5, z[b+15], 1, 'm', 0)
  locate(15, 5, ')', 2, 'm', 0)
 if b+5<=e:
  locate(0, 6, z[b+5], 1, 'm', 0)
  locate(10, 6, '^(', 2, 'm', 0)
  locate(12, 6, z[b+16], 1, 'm', 0)
  locate(15, 6, ')', 2, 'm', 0)
 if b+6<=e:
  locate(0, 7, z[b+6], 1, 'm', 0)
  locate(10, 7, '^(', 2, 'm', 0)
  locate(12, 7, z[b+17], 1, 'm', 0)
  locate(15, 7, ')', 2, 'm', 0)
 if b+7<=e:
  locate(0, 8, z[b+7], 1, 'm', 0)
  locate(10, 8, '^(', 2, 'm', 0)
  locate(12, 8, z[b+18], 1, 'm', 0)
  locate(15, 8, ')', 2, 'm', 0)
 if b+8<=e:
  locate(0, 9, z[b+8], 1, 'm', 0)
  locate(10, 9, '^(', 2, 'm', 0)
  locate(12, 9, z[b+19], 1, 'm', 0)
  locate(15, 9, ')', 2, 'm', 0)
 if b+9<=e:
  locate(0, 10, z[b+9], 1, 'm', 0)
  locate(10, 10, '^(', 2, 'm', 0)
  locate(12, 10, z[b+20], 1, 'm', 0)
  locate(15, 10, ')', 2, 'm', 0)
 if b+10<=e:
  locate(0, 11, z[b+10], 1, 'm', 0)
  locate(10, 11, '^(', 2, 'm', 0)
  locate(12, 11, z[b+21], 1, 'm', 0)    
  locate(15, 11, ')', 2, 'm', 0)     ◀ (3) ここまでを反復処理で置き換える
 locate(0, 0, '', 0, 'm', 1)

def frac(x):          ◀ (1) この関数をユーザーモジュール u.py ver 1.4 に追加し
 retuen x-int(x)         これを削除する


while
1:
 try:
  inp=str(eval(input('Number:')))
 except (SyntaxError,TypeError,NameError) as e:
  print(e)
  print('*must be number or\n expression')
  continue
 if '.' in inp:
  print('*must be integer')
  continue
 elif inp.isdigit():
  if len(inp)>10:
   print('*must be 10 or less')
   continue
  else:
   f=int(inp)
   break
 else:
  continue

z=list(range(23))
for e in range(1,23):
 z[e]=0
z[1]=0    ◀ (2) 上の for文で 0 に初期化されているので不要、削除する
z[12]=0    
◀ (2) 上の for文で 0 に初期化されているので不要、削除する
e=0
a=f
c=int(sqrt(a))

b=2;d=a/b         ◀ (4) ここから whle 1: の上までを反復処理に置き換える
if frac(d)==0:ckpwr()        
if b>c:disp()          disp() 関数は、素因数探索が終わってから実行される
b=3;d=a/b           ので、ここで disp() を実行する必要が無い。
if frac(d)==0:ckpwr()     そこで、この部分の disp() は break に置換えても
if b>c:disp()          問題ない。disp() は最後に1度だけ実行すれば良い
b=5;d=a/b
if frac(d)==0:ckpwr()
if b>c:disp()
b=7;d=a/b
if frac(d)==0:ckpwr()
if b>c:disp()
b=11;d=a/b
if frac(d)==0:ckpwr()        
if b>c:disp()        ◀ (4) ここまでを反復処理で置き換える

while 1:
 b+=2;d=a/b       ◀ (5) ここから disp() の前までを反復処理に置き換える
 if frac(d)==0:ckpwr()       
 if b>c:break
 b+=4;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=2;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=4;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=6;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=2;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=6;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=4;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=2;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=4;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=6;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=6;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=2;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=6;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=4;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=2;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=6;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=4;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=6;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=8;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=4;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=2;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=4;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=2;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=4;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=8;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=6;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=4;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=6;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=2;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=4;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=6;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=2;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=6;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=6;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=4;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=2;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=4;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=6;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=2;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=6;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=4;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=2;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=4;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=2;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=10;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=2;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=10;d=a/b
 if frac(d)==0:ckpwr()       ↓
 if b>c:break        (5) ここまでを反復処理で書き換える

disp()



9.1 frac() 関数をユーザーモジュール (u.py) に追加

この関数は、浮動小数点の数値から小数部を取り出すもので、Casio Python には備わっていません。便利な関数なのでユーザー関数としてユーザーモジュールに追加します。バージョンアップしたユーザーモジュールは下記からダウンロードできます。

ユーザーモジュール - u.py ver 1.4 のダウンロード


9.2 不要な処理を削除

上記の5箇所 (赤文字で表記した部分) を削除します。


9.3 disp() 関数のスリム化

disp() 関数の冒頭にある if 文
 if a>1:
  e+=1
  z[e]=a
  z[e+11]=1

を削除します。

というのは、この部分は最後に探索した素因数を リスト z[ ] に格納する処理なので、disp() から切り離し、disp() の直前に記述するように変更します。disp() は全ての素因数を見つけた後に、1度だけ実行されるので、この切り離しは機能に影響はなく、ソースの見やすさが改善されると思います。

さらに、
b = 6*(c-1)+1

b=1
に変更します。

というのも、変数 c は、結果表示のページを示し、c=1 の時は1ページ目、c=2 の時は2ページ目を示します。
現在の Casio Python ではインタラクティブなキー入力を取得する機能が実装されていないので、実行中にキー入力を受け付けて表示ページを切り替えることはできません(現在の Casio Python の仕様です)。そこで、全ての素因数を1ページ内に表示する仕様に変更しなくてはなりません。従ってページ数のカウントは不要になり、常に c=1 となります。すると
b = 6*(c-1)+1 は、常に b=1 となるので、変数 b の初期化をこのように書き換えます。

これまでをまとめると、 disp() は下記になります。

def disp():
 global a,b,c,d,e,f,z
 clear_screen()
 locate(00f3'm'0)
 b=1                  (3) 複数ページ切替機能を使わないので、常に b=1、b=1 に書き換えた
 locate(0, 1, z[b], 1, 'm', 0) ◀ (3) ここから disp() 関数終わりまでは反復処理に置き換える
 locate(10, 1, '^(', 2, 'm', 0)                    ↓
 locate(12, 1, z[b+11], 1, 'm', 0)
 locate(15, 1, ')', 2, 'm', 0)
 if b+1<=e:
  locate(0, 2, z[b+1], 1, 'm', 0)
  locate(10, 2, '^(', 2, 'm', 0)
  locate(12, 2, z[b+12], 1, 'm', 0)
  locate(15, 2, ')', 2, 'm', 0)
 if b+2<=e:
  locate(0, 3, z[b+2], 1, 'm', 0)
  locate(10, 3, '^(', 2, 'm', 0)
  locate(12, 3, z[b+13], 1, 'm', 0)
  locate(15, 3, ')', 2, 'm', 0)
 if b+3<=e:
  locate(0, 4, z[b+3], 1, 'm', 0)
  locate(10, 4, '^(', 2, 'm', 0)
  locate(12, 4, z[b+14], 1, 'm', 0)
  locate(15, 4, ')', 2, 'm', 0)
 if b+4<=e:
  locate(0, 5, z[b+4], 1, 'm', 0)
  locate(10, 5, '^(', 2, 'm', 0)
  locate(12, 5, z[b+15], 1, 'm', 0)
  locate(15, 5, ')', 2, 'm', 0)
 if b+5<=e:
  locate(0, 6, z[b+5], 1, 'm', 0)
  locate(10, 6, '^(', 2, 'm', 0)
  locate(12, 6, z[b+16], 1, 'm', 0)
  locate(15, 6, ')', 2, 'm', 0)
 if b+6<=e:
  locate(0, 7, z[b+6], 1, 'm', 0)
  locate(10, 7, '^(', 2, 'm', 0)
  locate(12, 7, z[b+17], 1, 'm', 0)
  locate(15, 7, ')', 2, 'm', 0)
 if b+7<=e:
  locate(0, 8, z[b+7], 1, 'm', 0)
  locate(10, 8, '^(', 2, 'm', 0)
  locate(12, 8, z[b+18], 1, 'm', 0)
  locate(15, 8, ')', 2, 'm', 0)
 if b+8<=e:
  locate(0, 9, z[b+8], 1, 'm', 0)
  locate(10, 9, '^(', 2, 'm', 0)
  locate(12, 9, z[b+19], 1, 'm', 0)
  locate(15, 9, ')', 2, 'm', 0)
 if b+9<=e:
  locate(0, 10, z[b+9], 1, 'm', 0)
  locate(10, 10, '^(', 2, 'm', 0)
  locate(12, 10, z[b+20], 1, 'm', 0)
  locate(15, 10, ')', 2, 'm', 0)
 if b+10<=e:
  locate(0, 11, z[b+10], 1, 'm', 0)
  locate(10, 11, '^(', 2, 'm', 0)
  locate(12, 11, z[b+21], 1, 'm', 0)    
  locate(15, 11, ')', 2, 'm', 0)     ◀ (3) ここまでを反復処理で置き換える
 locate(0, 0, '', 0, 'm', 1)



変数 b に着目すると、b は リスト z[ ] のインデックスとして利用されていて、このインデックスは、1 から 11 まで 1 づつ増えています。そこで、それぞれの if 節にある locate() 関数4行分をワンセットとして、for 文による反復処理に書き換えます。

ついでに、入力値 inp の桁数を表示する機能を追加します。

clear_screen() 以下を書き換えます。

 clear_screen()
 locate(00f3'm'0)
 locate(160': ' str(len(inp)) + ' digits'3'm'0) #入力値の桁数表示
 line(0,15,383,15,1,0)
 for i in range(112):
  if i<=e:
   locate(,,z[i], 1'm'0)
   locate(10i'^('2'm'0)
   locate(12iz[i+21], 1'm'0)
   locate(15i')'2'm'0)
 locate
(00''0'm'1)


さて、このように書き換えた結果、変数 b, c, d  はこの関数内では不要になりました。従ってグローバル変数の宣言において、不要になった変数 b, c, d を削除して、gobal 文を書き換えます。

range(1, 23) は、リスト [1, 2, 3, 4, 5, ... ,21, 22, 23] を生成して、その要素を最初から最後まで順に i に適用して反復を行います。range(23) は 要素が 0 から始まりますが、range(1, 23) とすれば、要素は 1 から始まります。

Note: range() の書式:
range(start=0, stop, step=1)
・引数が1つの場合は、range(stop) で、デフォルト start=0、step=1 が適用されます。
・引数が2つの場合は、range(start, stop) で、デフォルト step=1 が適用されます。
・引数が3つの場合は、range(start, stop, step) と全てを明示的に指定します。


完成した disp() 関数は下記のようになります。スッキリと無駄を省けました。

def disp():
 global a,e,f,z
 clear_screen()
 locate(00f3'm'0)
 locate(160': ' str(len(inp)) + ' digits'3'm'0)
 line(0,15,383,15,1,0)
 for i in range(112):
  if i<=e:
   locate(,,z[i], 1'm'0)
   locate(10i'^('2'm'0)
   locate(12iz[i+21], 1'm'0)
   locate(15i')'2'm'0)
 locate
(00''0'm'1)



9.4  素数が素因数かどうかをチェックする部分のスリム化

以下の部分を反復処理に置き換えます。

b=2;d=a/b
if frac(d)==0:ckpwr()
if b>c:disp()
b=3;d=a/b
if frac(d)==0:ckpwr()
if b>c:disp()
b=5;d=a/b
if frac(d)==0:ckpwr()
if b>c:disp()
b=7;d=a/b
if frac(d)==0:ckpwr()
if b>c:disp()
b=11;d=a/b
if frac(d)==0:ckpwr()
if b>c:disp
()


上の方針でも書きましたが、disp() は本来 (Casio Basic プログラムを見れば分かります) 全ての素因数探索が終わったあとで実行されます。従って、ここに disp() は不要で、break に置き換えることで、無駄な関数呼び出しを避けます。これにより実行時間も多少は短くなります。disp() は今回のスクリプトの最後で1度だけ実行されれば問題ありません。

なお、ここでは if 文 は1行で書いていますが、Python の公式サイトでは、この書き方を推奨していません。Casio Python (fx-CG50) は、スクリプトのスタックが限定的で 300行が最大なので、行数を減らす記法が有効になるので、敢えてPC用の Python の推奨に従わないことも Casio Python では必要になることがあります。

変数 b に着目すると、b の値が 2, 4, 5, 7, 11 と異なるだけで、あとは同じ処理が列挙されています。これを反復処理に書き換えるには、リストを使った for 文が使えます。

この b の値は素数になっており、これらの素数を要素としたリスト prime_list = [2, 3, 5, 7, 11] を定義して使うことにします。リストは、かぎ括弧を使って定義できます。

そして、以下のような リストを使ったfor 文が書けるのは、Python のようなオブジェクト指向言語の便利なところです。

反復処理への書き換えは、以下のようになります。


prime_list=[2,3,5,7,11]
for b in prime_list:
 d=a/b
 if frac(d)==0ckpwr()
 if b>c: break


リスト prime_list の要素を最初から最後まで順に b に適用し、反復処理を行うことができます。リストの要素の最後が適用されれば反復が終わります。ここでは反復回数を明示的に設定せずに、リストというシーケンス型オブジェクトに依存して for 文の反復が行っています。この書き方はオブジェクト指向言語の特徴の1つです。Casio BasicC 言語のような関数型言語では出てこないと思います。

余談になりますが、for i in range(10): という記述は、i を 0 ~ 9 まで1つづつ適用するのですが、実はリストの要素を生成しながら、その要素を順次適用して反復を行うといった内部動作をしていて、リストの要素を最初から最後まで順に適用していて、リストの要素を適用することに似ています。

もう少し詳しく言えば、range(10) は、リスト [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] の要素を順次生成し、その要素を 変数 i  に適用しながら反復処理を行いますが、for 文ではリスト全体を生成するのではなく、要素を生成しながら適用させるといった働き (ジェネレータの動作) をしています。実際にメモリ上にリストを生成しているのではないので、リストを格納するメモリが節約されます。リストが非常に大きな場合は、メモリ節約の効果が極めて大きくなり、このような時にはジェネレータの効果が絶大になります。

一方、
num = list(range(10))

と書くと、実際にリストを生成して、リスト num = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] が定義されます。


9.5 素因数を探索する残りの部分のスリム化

以下の部分 (while 1: 以下 144行の部分) を反復処理で書き換えます。
while 1:
 b+=2;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=4;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 ・・・
 ・・・
 ・・・
 b+=2;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=10;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break

変数 b に着目すると、b を増やしながら同じ処理を列挙しています。但し b の増分に簡単な規則性はなく、簡単な計算では求められません。この増分がどうやって決められているのかは、Casio Basic プログラムを公開している記事を参照してください。
fx-5800P 素因数分解 - 高速化

Python の for 文では、既に上で紹介したように、b の増分を要素としたリストを使えば簡単に反復処理ができます!

増分 (インクリメント) のリストなので、リストの名前を increment とし、以下のように定義します。

increment = [2, 4, 2, 4, 6, 2, 6, 4, 2, 4, 6, 6, 2, 6, 4, 2, 6, 4, 6, 8, 4, 2, 4, 2, 4, 8, 6, 4, 6, 2, 4, 6, 2, 6, 6, 4, 2, 4, 6, 2, 6, 4, 2, 4, 2, 10, 2, 10]

要素数は48個あります。

反復処理に書き換えると、以下のようになります。

increment=[2, 4, 2, 4, 6, 2, 6, 4, 2, 4, 6, 6, 2, 6, 4, 2, 6, 4, 6, 8, 4, 2, 4, 2, 4, 8, 6, 4, 6, 2, 4, 6, 2, 6, 6, 4, 2, 4, 6, 2, 6, 4, 2, 4, 2, 10, 2, 10]
while 1:
 for i in increment:
  b+=i; d=a/b
  if frac(d)==0: ck_pwr()
  if b>c: break
 if b>c: break

反復処理への書き換えに伴い、赤文字と青文字部分を追加しています。

赤文字は、for 文による反復のために追加した部分です。
リスト increment の要素を最初から最後まで順に 変数 i に適用しながら、反復を行います。
for 文の中にある if b>c: break の break は、b>c が成立するときに  for ループから抜けるだけで、while ループからの脱出ができません。ここでは一気に while ループからも脱出する動作にしたいところです (オリジナルの Casio Basic のソースでは Goto によるジャンプで、一気にループから脱出しています) 。

そこで、for ループから抜けたところで、もう一度青文字で追加した if b>c: break を実行することで、while ループからも一気に抜けるようにします。

=====

ところで、リスト increment の定義は、要素が48個もあるので、長々と記述します。電卓の狭い画面では見づらいので、改行して見やすくします。

スクリプトの動作に影響を与えずにソースを改行するためには、改行したいところにバックスラッシュ \ を入れます。

但し、括弧の中は改行してもスクリプトの動作に影響を与えないので、電卓の画面で見やすいように適宜改行を入れます。

increment=\
[2,4,2,4,6,2,6,4,
 2,4,6,6,2,6,4,2,
 6,4,6,8,4,2,4,2,
 4,8,6,4,6,2,4,6,
 2,6,6,4,2,4,6,2,
 6,4,2,4,2,10,2,10]

while 1:
 for i in increment:
  b+=id=a/b
  if frac(d)==0ckpwr()
  if b>cbreak
 if b>c
break


格段に無駄を省いて、スリム化できました!


9.6 スリム化したスクリプト - FactorG2.py

291行もあったスクリプトが、反復処理を使って書き換えることで 91行までスリム化できました。

Note: Casio Python スクリプトファイルのサイズ制限
今回、実際に長大なスクリプトを編集している際、Casio Python は、300 行を超えると電卓上ではこれ以上改行がができなくなることが分かりました。
さらに、PC上のエディタで 300 行を超えるスクリプトを書いてから電卓に転送して、このスクリプトファイルを開こうとすると、"Invalid Data Size" という通信エラーが発生し、ファイルを開けません。
300 行で改行ができなくてなっても、既存行内への入力はできます。おそらくファイルサイズの上限までは行内の入力は可能で、保存も可能です。ファイルサイズの上限はまだ確認できていません。
1ファイルあたり最大 300 行の制限は CGモデル (fx-CG50) の Casio Python の仕様
1ファイルあたり最大 150行の制限は FXモデル (fx-9750GIII, fx-9860GIII) の Casio Python の仕様


高速素因数分解 - 10桁対応版:FactorG2.py のダウンロード

fx-CG50 Pythonモード:高速素因数分解 - FactorG2.py
"""Sample script

Exercise;
ported from Casio Basic
"Prime Factor 10 digits"
factorG.py
ver 2.0

by Krtyski/e-Gadget
"""

from u import *

def ckpwr():
 global a,b,c,d,e,z
 e+=1
 z[e]=b
 while 1:
  a=int(d)
  z[e+11]+=1
  d=a/b
  if frac(d):
   break
 c=int(sqrt(a))

def
disp():
 global a,e,f,z
 clear_screen()
 locate(00f3'm'0)
 locate(160': ' str(len(inp)) + ' digits'3'm'0)
 line(0,15,383,15,1,0)
 for i in range(112):
  if i<=e:
   locate(,,z[i], 1'm'0)
   locate(10i'^('2'm'0)
   locate(12iz[i+21], 1'm'0)
   locate(15i')'2'm'0)
 locate
(00''0'm'1)


while
1:
 try:
  inp=str(eval(input('Number:')))
 except (SyntaxError,TypeError,NameError) as e:
  print(e)
  print('*must be number or\n expression')
  continue
 if '.' in inp:
  print('*must be integer')
  continue
 elif inp.isdigit():
  if len(inp)>10:
   print('*must be 10 or less')
   continue
  else:
   f=int(inp)
   break
 else:
  continue

z=list(range(23))
for e in range(1,23):
 z[e]=0
e=0
a=f
c=int(sqrt(a))

prime_list=[2,3,5,7,11]
for b in prime_list:
 d=a/b
 if frac(d)==0ck_pwr()
 if b>c: break


increment=\
[2,4,2,4,6,2,6,4,
 2,4,6,6,2,6,4,2,
 6,4,6,8,4,2,4,2,
 4,8,6,4,6,2,4,6,
 2,6,6,4,2,4,6,2,
 6,4,2,4,2,10,2,10]

while 1:
 for i in increment:
  b+=id=a/b
  if frac(d)==0ck_pwr()
  if b>cbreak
 if b>c
break

if a>1:
 e+=1
 z[e]=a
 a=1
 z[e+11]=1

disp
()




FactorG2_output 

この入力値での実行時間は 0.8秒後半の 0.9 秒で、前回の FactorG1.py より僅かに速くなっています。




目 次

前の記事 - 8. シェル画面入力の工夫

次の記事 - 10. 大きな数の計算





応援クリックをお願いします。励みになるので...
にほんブログ村 IT技術ブログ 開発言語へ


 


keywords: fx-CG50Pythonプログラム関数電卓

リンク集 | ブログ内マップ

テーマ : プログラム関数電卓
ジャンル : コンピュータ

Casio Python - シェル画面入力の工夫

Python Casio Python
 Casioグラフ関数電卓の Python を使ってみる
     - シェル画面入力の工夫:高速素因数分解(1) 
目次


初版:2020/07/23
追記修正:2020/11/14

前の記事 - 7. テキスト出力関数の追加 |  次の記事 - 9. Python らしい反復処理


8. シェル画面入力の工夫:高速素因数分解(1) <fx-CG50 OS3.40以降>

本連載は、Casio Basic で書いた フラクタル図形 - シダの葉描画プログラムを Casio Python に移植したことから始まっています。そして、その爆速っぷりから Casio Python に興味を持ち、本連載を始めたいきさつを書きました。

プログラミング習得は、実際に動く面白いプログラムを書きながら細かいところを覚えてゆくのが最善の方法だと思っています。そこで前回までは、以前 Casio Basic で作ったプログラム - モンテカルロ法シミュレーション を Casio Python に移植しながら、学習してきました。

今回の記事からしばらくは、以前 Casio Basic で作った高速素因数分解プログラム - FactorGCasio Python に移植してゆきます。

このプログラムの詳細は、fx-5800P 素因数分解 - 高速化 をご覧ください。高速素因数分解のロジックを詳しく説明しています。

与えられた整数の平方根以下の整数に対して2と奇数で総当り方式で素因数を散策するアルゴリズムに対して、4倍の高速化が達成されています。

FactorG_CasioBasic FactorG4_comparer 
左の画面は、fx-CG50 で実行した Casio Basic プログラム FactorG の出力です。
右の画面は、Casio Python で作った FactorG スクリプトでの出力です。

オリジナルのCasio Basicプログラムは、1画面に全ての素因数が表示しきれない時は、特定キーを押して次のページ / 前のページ を切り替えて表示できるようにしています。一方、現バージョン (OS3.40) の Casio Python はキー操作でインタラクティブにキー入力を取得するために必要な関数が用意されていないので、全ての素因数を1画面に表示する必要があります。そのため中サイズのフォントでグラフィックス画面に出力しています。

FactorG4_10digit 
これは、7849516203 の素因数分解の結果ですが、fx-CG50 で走らせた Casio Basic のプログラムの実行時間は 12秒 で、Casio Python では、0.9 秒 となり、10倍以上高速に計算しています。やはり速いです!

FactorG4_15digit 
この画面は、入力値の桁数制限を15桁まで拡張したスクリプトでの結果です。

Casio Basic は10桁の精度がありますが、Casio Python は15桁の精度があるので、15桁まで拡張可能です。素因数の種類が12個以上の場合は、画面を分割して右側に表示するようにしています。

Casio Basic では入力時に数値だけでなく、計算式も入力でき、計算結果が入力値になります。そこで同じ機能を Casio Python スクリプトに実装しました。上の例では、素数の積の式 2*3*5*7*11*13*17*19*23*29*31*37*41 を入力した結果です。

さて、Casio Python は、シェル画面でしか入力ができません。今回は、とりあえず Casio Basic プログラムと同じように入力できるようにします。その上で、Casio Basic プログラムをできるだけ忠実に Casio Python へ移植して、とりあえず動くようにします。

そして、次回以降で Python らしいスクリプトに改造してゆくことにします。

今回作成するスクリプトは、以下からダウンロードできます;
fx-CG50 Casio Python 用 高速素因数分解スクリプト - FactorG1.py




8.1 高速素因数分解プログラム - FactorG のざっくりした分析

先ずは、グラフ関数電卓 (fx-9860G シリーズや fx-CGシリーズ) の Casio Basic で動作する FactorG のコードを眺めながら、Casio Python への移植作業の方針をざっくりと考えてみます。

FactorG は全く同じコードで fx-9860G シリーズと fx-CGシリーズで動作します。ここでは fx-CG用の g3m ファイルを具体的に眺めてみます。

このプログラムは結構な行数があるので、電卓でポチポチと入力するのは大変です。そこで、PC上でエディタを使って、編集&入力すべきでしょう。Casio Basic のコードをエディタで読み込み、それを Casio Python スクリプトに変更・編集しました。動作確認のためには、fx-CG50 に転送し、実際に走らせる必要があります。

以下からダウンロードした zip ファイルには グラフ関数電卓用の Casio Basic プログラムファイルに加えて、テキストファイルも含んでいるので、これをエディタで読み込み、Casio Python に書き換えてゆきます。

グラフ関数電卓用高速素因数分解プログラム - FactorG のダウンロード


FactorG_g3m_comment


WFSUB


以下のようなざっくりとした作戦で進めてゆこうと思います。
  1. 入力と入力値のチェックの部分は、Casio Basic と同様の動作になるように Casio Python 向けのロジックで書きます。 
  2. 変数の初期化は、サクッと移植します。
  3. 素数が素因数かどうかをチェックして素因数の乗数を計算するサブルーチン WFSUB を関数化し (ckpwr() の作成)、Goto 1 のジャンプ先の処理も関数化します (disp() の作成)。
  4. While ループのエラストテレスの篩い部分も、上で関数化したものを使って変更します。但し、While ループを Goto で脱出している部分は、Casio Python ではそのまま実装できません。Casio Python には goto が実装されておらず、while ループの脱出は break のみで行えるので、このあたりはロジックの変更が必要です。
  5. Lbl 1Lbl 2 の部分はまとめて関数化します (disp() の作成)。
  6. インタラクティブなキー入力は、Casio Python ではできないので、この部分は移植不可能です。そこでdisp() 関数の処理では、全ての素因数を1画面内で表示できるように、中サイズのフォントを使ってグラフィックス画面(描画画面) へ出力するようにします。
  7. 移植作業全体として、変数のスコープ、大域変数 (グローバル変数) の扱いと Goto の代わりの処理に留意します。

8.2 入力と入力値のチェック

この部分の Casio Basic のコードは以下です。

"NUMBER"?→F
If F<1 Or F≧1Exp10
Then "NUMBER MUST BE ≧1 And <1Exp10"
Stop
IfEnd
If F≠Int (F)
Then "NUMBER MUST BE AN INTEGER"
Stop
IfEnd

プログラムを強制終了させる Stop に相当する関数は、一般の Python では quit()exit() 関数が用意されていますが、 Casio Python には実装されていません。

そこで、ロジックを変更して、この入力部分を while 1:  無限ループの中に入れ、10桁以下の整数 の時だけ break でループから脱出し、10桁を超える入力や小数の入力の時はループを継続して、再入力させるようにします。つまり、正しく入力できるまでループが回り続け、先に進めないロジックに変更します。

さて、Casio Python では、シェル画面で使える input() が唯一の入力関数で、この関数は文字列を返します。
input()

さらに Casio Basic では ?→ 命令は、関数や式を入力した時の結果の数値を取得できますので、Casio Python でも同じような機能を盛り込みます。そこで、式 (文字列) を評価してその結果を返す eval() 関数を使います。

f=eval(input('Number:'))

入力値が10桁を超える場合、そして小数の場合、さらにテンキー以外のキー入力の場合は while ループを継続して、入力待ちの状態を継続し、それ以外の場合を整数入力になるように分岐方法を考え、その時は変数 f に整数を格納してループを抜けるロジックを考えました。

一例として、以下のようにしました。

while 1:
 inp=str(eval(input('Number:')))
 if '.' in inp:
  print('must be integer')
  continue
 elif inp.isdigit():
  if len(inp)>10:
   print('must be <= 10 digit')
   continue
  else:
   f=int(inp)
   break
 else:
  continue


これで、Casio Basic の入力とほぼ同じ機能が得られます。

小数の判定には、文字列中に小数点 . があるかどうかで調る
これは、Pythonらしい記述にしてみました。
if '.' in inp:
文字列 inp の中に、1文字だけの文字列 '.' があれば...という意味です。
inp に 小数点が含まれていれば、'.' in inpTrue (真) を返します。
小数点が含まれていれば、小数を入力したことになるので、continuewhile ループを継続し、入力待ちの状態を継続します。

入力が数値かどうかを判定後、桁数の判定を行う
入力が数値かどうかは、文字列が数値であるかどうかを判定する関数 isdigit() を使います。この関数は、文字列のまま、その内容が数値であるかどうかを調べられます。isdigit() は、文字列の帰属関数であり、今は文字列 inp について調べるので、
inp.isdigit()
と記述します。
isdigit() は数値であるなら、True (真) を返すので、if 文の条件として使いました。
print() での出力文字列は、電卓の画面に収まるように、簡潔に短くしました。

入力が小数でなく、11桁以上でない時、入力文字列を数値に変換して変数 f に格納
文字列 inp の長さを調べる関数 len() を使って、len(inp) が10を超えると print('must be <= 10') でエラー表示をして、continue でループを継続させ、入力待ちを継続するようにしました。 
整数の文字列の長さを10を超えない時は、10桁以下の整数であると判定し、文字列を整数に変換して 変数 f に代入し、このときだけ while 1: ループを抜けて、処理を先に進めるようにしました。

最後の else: 節は、上記以外の入力のとき while ループを継続することで、入力待ちを継続


さて、このスクリプトでは、テンキー以外を押した時に強制終了しますが、Casio Basic プログラムでは強制終了しません。

[EXE] を押しただけのときの挙動の違い - SyntaxError
Casio Basic では、数値を入力せず単に [EXE]を押すだけでは、入力待ちになります。一方、Casio Python の上のスクリプトでは SyntaxError となり、強制終了します。

 式として不完全な入力をした時の挙動の違い - SyntaxError
Casio Basic では、(、()、*、+、-、/、** など、式として不完全な入力後に [EXE] を押して入力を確定すると SyntaxError が発生しますが、プログラムは強制終了せず、修正して正しい式を入力して [EXE] を押すまで入力待ちが継続します。これは ?→  命令の仕様です。一方、Casio Python では、SyntaxError となり強制終了します。

引数に誤りのある関数を入力した時の挙動の違い - TypeError
Casio Basic では、例えば引数を指定しないで sin()in() などを入力して [EXE] を押すと SyntaxError が発生しますが、プログラムは強制終了せず、修正して正しく入力して [EXE] を押すまで入力待ちが継続します。これは ?→ 命令の仕様です。一方、Casio Python では、引数を正しく指定せずに入力すると TypeError が発生し、強制終了します。

アルファベット入力時の挙動の違い
Casio Basic  では、アルファベットを入力すると、アルファベットに何か数値が格納されているときはその数値を取得し、そうでない時は 0 を取得します。一方、Casio Python では、NameError が発生して強制終了します。 

Casio Python でのこれらの挙動は、上のスクリプトで利用している eval() で発生するエラーです。eval() は必要なのでこれを使いつつ、これらのエラーが発生しても強制終了させないために、スクリプトを改造してみます。赤文字が追加する部分です。

while 1:
 try:
  inp=str(eval(input('Number:')))
 except (SyntaxError, TypeError, NameError) as e:
  print(e)
  print('*must be number or\n expression')
  continue
 if '.' in inp:
  print('*must be integer')
  continue
 elif inp.isdigit():
  if len(inp)>10:
   print('*must be 10 digit\n or less')
   continue
  else:
   f=int(inp)
   break
 else:
  continue

エラーが発生すると、スクリプトの制御を失い、強制終了してしまいます。そこで、エラーを検知しても制御を取り戻すことで、強制終了を回避できます。そのような時には Try ステートメントが役立ちます。

エラーを検出し制御を取り戻す - Try / except
エラーの検出には、Try ステートメントと except ステートメントを利用します。
Try: 節except: 節 の間に、エラーを検出したい処理を挟みます。
今回、エラーを検出したい処理は
inp = str(eval(input('Number:')) 
で、インデントレベルを下げて記述します。

エラーは、eval() 関数で発生し、エラーの種類は上で調べたように、SyntaxErrorTypeErrorNameError の3種類です。これら3種類のエラーを検出するためには、
except (SyntaxError, TypeError, NameError): とタプル型で複数のエラーの種類を記述すれば、これら3種類のエラーのいずれかが発生した時、制御を取り戻せます。

さらに、
except (SyntaxError, TypeError, NameError) as e:
と記述すると、文字列変数 e にエラーの詳細が格納されます。
今回のスクリプトでは、
・SyntaxError 発生時は、e に "invalid syntax" が、
・TypeError 発生時は、e"function missing 1 required positional argument" が、
・NameError 発生時は、e"'x' is not defined" が、
格納されます。

そこで、print(e) により、エラーの詳細を出力します。
except 節は、Try 節と同じインデントレベルにします。print(e) もそのインデントが必要です。

続けて、エラーにならない入力のアドバイスを出力します。同じインデントレベルで下記を追加します。
print('*must be number or\n expression')
電卓の画面は狭いので、出力文字列の中にエスケープシーケンス \n を追加し、そこで改行しています。このエスケープシーケンスについては以前紹介しています。
出力は以下のようになります。
*must be number or
 expression


except 節の最後に continue を記述し、while へジャンプさせ、ループを継続させます。


上のスクリプトで、青色で示した
print('*must be 10 digit\n or less')
も、表現を変更し、さらに電卓画面に収まりやすいように、\n で改行します。出力は以下のようになります。
*must be 10 digit
 or less


以上で、入力と入力値のチェックの部分の移植が完了しました。


8.3 変数の初期化

上で検討した 入力と入力値のチェック では、Casio Basic の {1,22}→DIM Mat Z については無視しました。これは見つかった素因数とその乗数を格納するための行列 Z を定義して必要なメモリを確保するためのものです。メモリ確保は重要なのでプログラムの冒頭で実施してるわけです。

Casio Python への移植では、行列 Z の代わりに リストz (配列) を使います。リスト定義は、変数の初期化の部分で一緒に行うことにします。Casio Basic のコードを忠実に移植します。

z=list(range(23))
for e in range(1,23):
 z[e]=0
z[1]=0
z[12]=0
e=0
a=f
c=int(sqrt(a))



リスト Z の定義と初期化
先ず、要素数22個のリストz を以下のように定義します。
z=list(range(23))
この状態だと、リストの要素は [0, 1, 2, 3, 4,...20, 21, 22] となります。
次に、全ての要素を 0 にするため、for 文を使って、
for e in range(1,23):
 z[e]=0

続いて、z[1]=0z[12]=0 としています。z[1] は 1つめに見つかった素因数で、z[12] は1つめに見つかった素因数の乗数です。これらはすでに 0 で初期化されているので不要ですが、重要なので明示的に初期化を2回行っている Casio Basic に忠実に移植しました。

その他変数の初期化
リストz のインデックス e0 で初期化、入力値を素因数で割った値 a は 入力値 f で初期化、素因数探索範囲の最大値 c√a に近い整数で初期化しています。

8.1 と 8.2 の検討結果を、以下にまとめます。

while 1:
 try:
  inp=str(eval(input('Number:')))
 except (SyntaxError,TypeError,NameError) as e:
  print(e)
  print('*must be number or\n expression')
  continue
 if '.' in inp:
  print('*must be integer')
  continue
 elif inp.isdigit():
  if len(inp)>10:
   print('*must be 10 or less')
   continue
  else:
   f=int(inp)
   break
 else:
  continue

z=list(range(23))
for e in range(1,23):
 z[e]=0
z[1]=0
z[12]=0
e=0
a=f
c=int(sqrt(a))


8.4 素数が素因数かどうかをチェック

Casio Basic のコード;

2→B:A÷B→D:Frac (D)=0⇒Prog "WFSUB":B>C⇒Goto 1

Casio Python に移植するには、スクリプト構造を検討します。Casio Python には goto がないので、Goto 1 のジャンプ先である Lbl 1 とそれに続く Lbl 2 までを関数 disp() にまとめ、Goto 1disp() に置き換えます。

Prog "WFSUB" は、関数コールに置換え、WFSUB の内容を ckpwr() にまとめます。サブルーチン WFSUB は、見つかった素因数の乗数を求めるので、check power (乗数をチェック) の意味で、ckpwr() としました。

関数 dsip()ckpwr() は後で作るとして、先ずは上記の1行を Casio Python で書き換えます。

b=2;d=a/b
if frac(d)==0:ckpwr()
if b>c:disp()


なお、Casio Basic に実装されている関数 Frac() は、小数から小数部を得ます。ところが Casio Python には Frac() の相当する関数が無いので、新たに作成して使いました。

def frac(x):
 return x-int(x)


ところで、if frac(d)==0:ckpwr() は、本来下記のように記述することが Python 公式サイトで推奨されています。

if frac(d)==0:
 ckpwr()


しかし、1行に書いても正しく動作します。

b=2;d=a/b
という記述も、Python 公式サイトでは以下のように2行で書くことを推奨しています。
b=2
d=a/b



Note: Python の 単文 と 複文
b=2d=a/b は単文で、セミコロン ; で区切って、複数の単文を1行に記述できます。
しかし、if 文のような複文を セミコロン ; で区切って1行に記述しても正常に動作しません。
例えば、以下のように記述すると、エラーになります。
b=2;d=a/b;if frac(d):ckpwr();if b>c:disp() #エラー

これまでのところをまとめ、スクリプトの全体像は以下のようにします。

from u import *

def ckpwr():
 #あとで作ります

def disp():
 #あとで作ります

def frac(x):
 retuen x-int(x)

while
1:
 try:
  inp=str(eval(input('Number:')))
 except (SyntaxError,TypeError,NameError) as e:
  print(e)
  print('*must be number or\n expression')
  continue
 if '.' in inp:
  print('*must be integer')
  continue
 elif inp.isdigit():
  if len(inp)>10:
   print('*must be 10 or less')
   continue
  else:
   f=int(inp)
   break
 else:
  continue

z=list(range(23))
for e in range(1,23):
 z[e]=0
z[1]=0
z[12]=0
e=0
a=f
c=int(sqrt(a))

b=2;d=a/b
if frac(d)==0:ckpwr()
if b>c:disp()
b=3;d=a/b
if frac(d)==0:ckpwr()
if b>c:disp()
b=5;d=a/b
if frac(d)==0:ckpwr()
if b>c:disp()
b=7;d=a/b
if frac(d)==0:ckpwr()
if b>c:disp()
b=11;d=a/b
if frac(d)==0:ckpwr()
if b>c:disp()

#2の倍数、3の倍数、5の倍数、7の倍数以外の探索数で "エラストテレスの篩い" で素因数を探索
while 1:
 #この部分もあとで作ります


8.5 "エラストテレスの篩い" で素因数を探索

上記の「素数が素因数かどうかをチェックする」は、素数 2,3、5、7、11 を探索数として、"エラストテレスの篩い" で素因数を探索しています。

それに続いて、2の倍数以外、3の倍数以外、5の倍数以外、7の倍数以外の探索数を用いて、素因数を探索します。Casio Basic のコードは以下のようになっています。

While 1
B+2→B:A/B→D:Frac (D)=0⇒Prog "WFSUB":B>C⇒Goto 1
B+4→B:A/B→D:Frac (D)=0⇒Prog "WFSUB":B>C⇒Goto 1
B+2→B:A/B→D:Frac (D)=0⇒Prog "WFSUB":B>C⇒Goto 1
B+4→B:A/B→D:Frac (D)=0⇒Prog "WFSUB":B>C⇒Goto 1
B+6→B:A/B→D:Frac (D)=0⇒Prog "WFSUB":B>C⇒Goto 1
B+2→B:A/B→D:Frac (D)=0⇒Prog "WFSUB":B>C⇒Goto 1
B+6→B:A/B→D:Frac (D)=0⇒Prog "WFSUB":B>C⇒Goto 1
・・・
・・・
(48行)
・・・
WhileEnd


Whileループの中のそれぞれ1行は、上記で作ったものと殆ど同じです。行頭で、探索数に 2, 4, 6, 8, 10 のいずれかを順に加算しているところだけが違っています。

この探索数の決め方の数学的背景は、fx-5800P 素因数分解 - 高速化 に書いてあるので、気になる場合は参照してうださい。

While ループ内の一番上の行

B+2→B:A/B→D:Frac(D)=0⇒Prog "WFSUB":B>C⇒Goto 1

Casio Pyton に書き換えると、以下のようになります;

b+=2;d=a/b
if frac(d)==0:ckpwr()
if b>c:disp()


この書式は While ループの中に 48 行あります。そこで、Casio Python で while 1: の下に、上記の処理を48個記述すればそれで良いかと言えば、それがうまく動作しません

何が問題なのか?

Casio Basic で上記のコードが実行されると、While ループの中から Goto で対応する Lbl へジャンプすると同時にループを抜けます。しかし、Casio Python では、while ループの中で disp() を呼び出し、disp() の処理が終われば、呼出位置に戻り、ループを抜けられません。従って、うまく動作しません。

そこで、Goto 1 の代わりに break でループを抜け、抜けた直後に disp() を実行するように記述すれば、Casio Basic と同じ動作になります。

具体的には、以下のようになります。

while 1:
 b+=2;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=4;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=2;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 ・・・
 ・・・
 (48行)
 ・・・
disp()



これで、全体のロジックが決まったので、これまで作った部分 と これから作る部分 をまとめて、スクリプト全体の構成を以下に示します。


fx-CG50 Pythonモード:高速素因数分解 - FactorG1.py
from u import *

def ckpwr():
 #あとで作ります (8.5 参照)

def disp():
 #あとで作ります (8.6 参照)

def frac(x):
 retuen x-int(x)

while
1:
 try:
  inp=str(eval(input('Number:')))
 except (SyntaxError,TypeError,NameError) as e:
  print(e)
  print('*must be number or\n expression')
  continue
 if '.' in inp:
  print('*must be integer')
  continue
 elif inp.isdigit():
  if len(inp)>10:
   print('*must be 10 or less')
   continue
  else:
   f=int(inp)
   break
 else:
  continue

z=list(range(23))
for e in range(1,23):
 z[e]=0
z[1]=0
z[12]=0
e=0
a=f
c=int(sqrt(a))

b=2;d=a/b
if frac(d)==0:ckpwr()
if b>c:disp()
b=3;d=a/b
if frac(d)==0:ckpwr()
if b>c:disp()
b=5;d=a/b
if frac(d)==0:ckpwr()
if b>c:disp()
b=7;d=a/b
if frac(d)==0:ckpwr()
if b>c:disp()
b=11;d=a/b
if frac(d)==0:ckpwr()
if b>c:disp()

while 1:
 b+=2;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=4;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=2;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=4;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=6;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=2;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=6;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=4;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=2;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=4;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=6;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=6;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=2;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=6;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=4;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=2;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=6;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=4;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=6;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=8;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=4;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=2;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=4;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=2;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=4;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=8;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=6;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=4;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=6;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=2;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=4;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=6;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=2;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=6;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=6;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=4;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=2;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=4;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=6;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=2;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=6;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=4;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=2;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=4;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=2;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=10;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=2;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break
 b+=10;d=a/b
 if frac(d)==0:ckpwr()
 if b>c:break

disp()


8.6 ckpwr() 関数の作成

Casio Basic の WFSUB サブルーチンは下記です。

WFSUB

これを Casio Python の関数 ckpwr() にまとめます。この関数名は check power (乗数をチェック) からきています。

個々の処理を Casio Python のスクリプトに書き換えるのは、特に難しいところは無いと思います。

ここで非常に重要なポイントとして、変数のスコープについて考えなければなりません。というのも、Casio Basic では全ての変数は究極のグローバル変数 (大域変数) です。一般にグローバル変数とは、1つのプログラムの中の変数はどこからでもアクセスでき、どこからでもその値の変更が有効である、そのような変数です。対してローカル変数は関数やサブルーチンの中だけでアクセスや値の変更ができ、その関数やサブルーチンの外にある同じ名前の変数には影響を及ぼさない、そのような変数です。

Casio Basic では、変数は、メモリ内にある全てのプログラムやサブルーチンで共有され、どこからでも有効にアクセスでき、どこからでもその値を有効に変更できるもので、一般のグローバル変数よりもさらにグローバル、つまり究極のグローバル変数と言えます。メモリ内のいずれかのプログラムで変数の内容が変われば、全てのプログラムやサブルーチンで同じ名前の変数を参照すると、変化した値になっています。

ところが、Casio Python では、変数スコープについて、C や C# などとも少々事情が異なります。変数の代入が行われる場所が関数の中か外かでスコープが決まります。
  • 関数内で変数に代入が行われると、その変数はローカル変数 (局所変数) となり、その関数内だけで有効になります。その変数は関数外では不定になり、関数外で使えばエラーになります。
  • 関数外で変数に代入が行われると、その変数はグローバル変数となり、その変数は全ての関数内で有効になります。
変数が有効になる範囲を変数のスコープといいます。

Casio Python の変数スコープの仕様は一般的なもので、むしろ Casio Basic が特殊です。

但し、C, C++, C# に比べて Casio Python の変数スコープは少々曖昧に思えるところがあります。スクリプトを実行する際、まず最初にスクリプトの上から下までざっと解釈を行います。そして、関数を使う前(上)で関数定義を行う必要があります。

もし、関数定義の後(下)で関数の外で変数に代入すると、その変数はグローバル変数となり、本来は関数内でも有効になるはずですが、関数定義よりは後に変数に代入されているので、関数内で使おうとすると不定となり、エラーになります。この点で、Casio Python での変数スコープは曖昧で、
  • 変数への代入が関数内で行われるのか関数外で行われるかで、変数スコープ(グローバルかローカルか)が決まります。
  • 変数への代入が、どこで行われるかによって、有効になるか無効になるか (エラーになるか) が決まります。
そこで、Python では変数のスコープは原則ローカルだと思ってコーディングすることが重要です。もし関数内でグローバル変数を使いたい場合は、上の曖昧さを排除するために、関数内で global ステートメントを使って、明示的にグローバルだと宣言するのが良いと思います。

さて、関数 ckpwr() を定義する場合は、global ステートメントで 変数を列挙して、それらがグローバル変数であることを明示的に宣言します。

def ckpwr():
 global a,b,c,d,e,z
 e+=1
 z[e]=b
 while 1:
  a=int(d)
  z[e+11]+=1
  d=a/b
  if frac(d):
   break
 c=int(sqrt(a))


8.7 disp() 関数の作成

Casio Python スクリプトに書き換える対象の Casio Basic のコードは以下です。

Lbl 1
If A>1
Then Isz E
A→Mat Z[1,E]
1→A
1→Mat Z[1,E+11]
IfEnd
Int (E/6)->D
E-6*D>0⇒Isz D
1→C

Lbl 2
ClrText
Locate 1,1,F
Locate 12,1,C
Locate 13,1,":"
Locate 14,1,D
6*(C-1)+1⇒B
Locate 1,2,Mat Z[1,B]
Locate 11,2,"^("
Locate 13,2,Mat Z[1,B+11]
Locate 16,2,")"
If B+1≦E
Then Locate 1,3,Mat Z[1,B+1]
Locate 11,3,"^("
Locate 13,3,Mat Z[1,B+12]
Locate 16,3,")"
IfEnd
If B+2≦E
Then Locate 1,4,Mat Z[1,B+2]
Locate 11,4,"^("
Locate 13,4,Mat Z[1,B+13]
Locate 16,4,")"
IfEnd
If B+3≦E
Then Locate 1,5,Mat Z[1,B+3]
Locate 11,5,"^("
Locate 13,5,Mat Z[1,B+14]
Locate 16,5,")"
IfEnd
If B+4≦E
Then Locate 1,6,Mat Z[1,B+4]
Locate 11,6,"^("
Locate 13,6,Mat Z[1,B+15]
Locate 16,6,")"
IfEnd
If B+5≦E
Then Locate 1,7,Mat Z[1,B+5]
Locate 11,7,"^("
Locate 13,7,Mat Z[1,B+16]
Locate 16,7,")"
IfEnd

Casio Basic の Locate に対応する Casio Python の関数 locate() は既に作っており、ユーザーモジュール u.py (ver 1.3以降) から呼び出して使います。

locate() の書式:
locate(column, row, obj, color=1, size='m', show=1)

Casio Basic の Locate コマンドの第1引数、第2引数、第3引数は、そのまま column, row, obj に対応します。
予め移植の方針として、中サイズのフォントを使うことにしているので、第5引数には 'm' を指定します。
locate() を沢山使うので、第6引数には 0 を指定し、最後に locate(0, 0, '', 'm', 1) を追加して、第6引数で 1 を指定することで、全ての locate() の出力を一気に VRAM から画面へ転送することにします。

関数内で使う変数については、全て global ステートメントでグローバル変数の宣言を行います。

def disp():
 global a,b,c,d,e,f,z
 if a>1:
  e+=1
  z[e]=a
  a=1
  z[e+11]=1
 d=int(e/6)
 if e-6*d>0:
  d+=1
 c=1

 clear_screen()
 locate(00f3'm'0)
 b 6*(c-1)+1
 locate(0, 1, z[b], 1, 'm', 0)
 locate(10, 1, '^(', 2, 'm', 0)
 locate(12, 1, z[b+11], 1, 'm', 0)
 locate(15, 1, ')', 2, 'm', 0)
 if b+1<=e:
  locate(0, 2, z[b+1], 1, 'm', 0)
  locate(10, 2, '^(', 2, 'm', 0)
  locate(12, 2, z[b+12], 1, 'm', 0)
  locate(15, 2, ')', 2, 'm', 0)
 if b+2<=e:
  locate(0, 3, z[b+2], 1, 'm', 0)
  locate(10, 3, '^(', 2, 'm', 0)
  locate(12, 3, z[b+13], 1, 'm', 0)
  locate(15, 3, ')', 2, 'm', 0)
 if b+3<=e:
  locate(0, 4, z[b+3], 1, 'm', 0)
  locate(10, 4, '^(', 2, 'm', 0)
  locate(12, 4, z[b+14], 1, 'm', 0)
  locate(15, 4, ')', 2, 'm', 0)
 if b+4<=e:
  locate(0, 5, z[b+4], 1, 'm', 0)
  locate(10, 5, '^(', 2, 'm', 0)
  locate(12, 5, z[b+15], 1, 'm', 0)
  locate(15, 5, ')', 2, 'm', 0)
 if b+5<=e:
  locate(0, 6, z[b+5], 1, 'm', 0)
  locate(10, 6, '^(', 2, 'm', 0)
  locate(12, 6, z[b+16], 1, 'm', 0)
  locate(15, 6, ')', 2, 'm', 0)
 if b+6<=e:
  locate(0, 7, z[b+6], 1, 'm', 0)
  locate(10, 7, '^(', 2, 'm', 0)
  locate(12, 7, z[b+17], 1, 'm', 0)
  locate(15, 7, ')', 2, 'm', 0)
 if b+7<=e:
  locate(0, 8, z[b+7], 1, 'm', 0)
  locate(10, 8, '^(', 2, 'm', 0)
  locate(12, 8, z[b+18], 1, 'm', 0)
  locate(15, 8, ')', 2, 'm', 0)
 if b+8<=e:
  locate(0, 9, z[b+8], 1, 'm', 0)
  locate(10, 9, '^(', 2, 'm', 0)
  locate(12, 9, z[b+19], 1, 'm', 0)
  locate(15, 9, ')', 2, 'm', 0)
 if b+9<=e:
  locate(0, 10, z[b+9], 1, 'm', 0)
  locate(10, 10, '^(', 2, 'm', 0)
  locate(12, 10, z[b+20], 1, 'm', 0)
  locate(15, 10, ')', 2, 'm', 0)
 if b+10<=e:
  locate(0, 11, z[b+10], 1, 'm', 0)
  locate(10, 11, '^(', 2, 'm', 0)
  locate(12, 11, z[b+21], 1, 'm', 0)
  locate(15, 11, ')', 2, 'm', 0)
 locate(0, 0, '', 0, 'm', 1)


このスクリプトで、グラフィックス画面へ中サイズのフォントで出力すると最大11個の素因数まで出力表示できます。これで十分かどうかを検証します。

1)
素因数として、2、3,5,7,11,13,17,19,23,29 の 10個の場合、これを得る数値はこれらの積で、
2 x 3 x 5 x 7 x 11 x 13 x 17 x 19 x 23 x 29 = 6,469,693,230 は10桁の数です。つまり結果表示は10桁あれば十分で、このスクリプトの表示行数の範囲内です。

2)
素因数として、2、3、5、7、11、13、17、19、23、29、31 の 11個の場合、これを得る数値はこれらの積で、
2 x 3 x 5 x 7 x 11 x 13 x 17 x 19 x 23 x 29 x 31 = 200,560,490,130 は12桁の数です。つまり結果表示は11桁あれば十分で、このスクリプトの表示行数の範囲内です。

3)
なお、今回作ったスクリプトは、入力値が最大10桁に制限される Casio Basic プログラムと同等なので、入力値が10桁に制限される場合の素因数の種類は最大数は 10 個 です。

以上から、10桁制限の今回のスクリプトでは、全ての素因数を表示できることが確認できました。

FactorG1_output1 



目 次

前の記事 - 7. テキスト出力関数の追加

次の記事 - 9. Python らしい反復処理





応援クリックをお願いします。励みになるので...
にほんブログ村 IT技術ブログ 開発言語へ


 


keywords: fx-CG50Pythonfx-9750GIIIfx-9860GIIIプログラム関数電卓


テーマ : プログラム関数電卓
ジャンル : コンピュータ

Casio Python - ユーザー関数 line()

Python Casio Python
 Casioグラフ関数電卓の Python を使ってみる
     - リファレンス 
目次
line()

ユーザーモジュール u.py に含まれる関数 u.py ver 1.5 ダウンロード

初版:2020/11/08

[対応モデル] - fx-CG50 OS3.20 以降、fx-9750GIII / fx-9860GIII OS3.21 以降

線分を描画します。

書 式line(x1, y1, x2, y2, color=1, show=1)

引 数
- 第1引数 - x1: 描画する線分の端の座標 (x1, y1) の x座標、0 以上の整数
- 第2引数 - y1: 描画する線分の端の座標 (x1, y1) の y座標、0 以上の整数
- 第3引数 - x2: 描画する線分のもう一方の端の座標 (x2, y2) の x座標、0 以上の整数
- 第4引数 - y2: 描画する線分のもう一方の端の座標 (x2, y2) の y左表、0 以上の整数
- 第5引数 -  color: パラメータ引数で、省略可能でデフォルトは 1 です。
       具体的な設定は grp_color() のリファレンスを参照。
- 第6引数 - show: パラメータ引数で、省略可能でデフォルトは 1 です。

引数を5つ、値だけを指定するとエラーになります ⇒ 関数の引数は こちら を参照。
引数を5つだけ設定する場合は、パラメータと共に設定します。
  例) line(5, 50, 30, 60, color=4) / line(5, 50, 30, 60, show=0)


関数定義:

from casioplot import *

def
line(x1y1, x2, y2, color=1, show=1):
 rgb = grp_color(color)
 dx x2 x1; dy = y2 - y1
 if dx==0 and dy==0: #avoid division by 0 error
  set_pixel(x1, y1, rgb)
  return
 if abs(dx)>abs(dy):
  if dx: #when dx is not 0
   k = int(dx/abs(dx)) #k=1 or -1
   slope = dy/dx
   for x in range(0, dx, k):
    set_pixel(x1+x, y1+int(x*slope), rgb)
 else:
  if dy: #when dy is not 0
   k = int(dy/abs(dy)) #k=1 or -1
   slope = dx/dy
   for y in range(0, dy, k):
    set_pixel(x1+int(y*slope), y1+y, rgb)
 if show:
  show_screen
()



スクリプトの解説:
  グラフィック出力関数の追加:line() 関数とユーザーモジュールの作成 ⇒ こちら
  grp_color() ユーザー関数について ⇒ こちら




応援クリックをお願いします。励みになるので...
にほんブログ村 IT技術ブログ 開発言語へ


 


keywords: fx-CG50Pythonfx-9750GIIIfx-9860GIIIプログラム関数電卓

リンク集 | ブログ内マップ

テーマ : プログラム関数電卓
ジャンル : コンピュータ

Casio Python - グラフィックス出力関数の追加

Python Casio Python
 Casioグラフ関数電卓の Python を使ってみる
     - グラフィックス出力関数の追加:line() とユーザーモジュールの作成 
目次


初版:2020/07/11
修正:2020/07/17
追記修正:2020/11/08

前の記事 - 5. 関数の作成と活用 |  次の記事 - 7. テキスト出力関数の追加


<fx-CG50 OS3.40 以降、fx-9750GIII, fx-9860GIII OS3.40以降>


6. グラフィックス出力関数の追加:line() とユーザーモジュール の作成

前回は、グラフィックス描画関数で使うための 色指定関数 grp_color() 関数を作り、それを使って 円描画関数 circle() を拡張しました。その上で、モンテカルロ法シミュレーションをカラー化した monteca2.py を作りました。

Casio Python の現在の言語仕様では、Casio Basic で作った実用プログラムを完全に移植できないレベルの貧弱な仕様で、発展途上と言えます。Casio Basic で実用プログラムが作れる最大の要因は、望む位置に数値や文字列を出力できる Locate コマンドと電卓のほぼ全てのキー入力を取得可能な Getkey コマンドの存在です。一方 Casio Python には LocateGetkey に相当するものがありません。

従って、プログラム=スクリプトを実行する際、シェル画面において 唯一の入力関数 input() を用いて必要な入力を済ませ、その後グラフィックス画面(描画画面)で出力を行うといった構造にするのが、現状では実用的なスクリプトを作成する最善の方法だと考えています。

Getkey 相当の関数については、カシオによる今後のOSアップデートに期待するしかありません。Locate に相当する locate() 関数は自作できるので、これについては次回紹介する予定です。

ところで、既に circle() 関数を作っていますので、今回は線分描画の line() 関数を作って、さらにユーザーモジュールに追加します。


6.1 line() 関数の作成 [2020/07/12 大幅修正]

[2020/07/12 ロジック見直し] 読者のK様からのご指摘により、垂直に近い傾きの大きな線分は 反復回数が少なくなるため飛び飛びの点描画になり、きちんと線分が描画されないことが判明しました。そこで、K様のご提案に従って、線分の傾きが常に45度を超えない処理2つを組み合わせることで、十分な反復回数を確保し、確実に線分を描画できるように修正しました。K様、ありがとうございます。

line_schematic 

点描画を反復して線分を描く際、x 座標 を1だけ増減させた時 y 座標は 傾きの分を増減させます。
最初に与えられる (x1, y1)(x2, y2) の2つの座標から、
dx = x2 - x1
dy = y2 - y1
とすると、傾き slope = dy/dx となります。

x 座標が x だけ変化したとき、y 座標は x*slope だけ変化します。(x1, y1) 座標を基準にして、x 座標 が x だけ変化したときの座標は、(x1+x, y1+x*slope) になります。この座標への点の描画を反復して線分を描くのですが、その反復回数を range(dx) で与えます。

線分の傾きが大きいと dx は小さな値になり、range(dx) で十分な反復回数が得られません。画面の左端から右端にわたる線分を描画する場合の反復回数は 191回未満になると、理屈の上では線分がまばらになります。傾きが垂直に近いと range(dx) は数回以下になってしまい、適切に線分を描けなくなります。

反復回数の一番厳しい条件として 最低でも 191 回確保するためには、傾きの絶対値が45度未満が条件で、別の表現をすれば、|dx| > |dy| が条件になります。| | は絶対値です。Python での表現では、適切な線分描画の条件は、abs(dx) > abs(dy) となります。

では、傾きの絶対値が 45 度以上になる場合は、x 座標と y 座標を入れ替えると、傾きの絶対値が 45 度未満になります。直交座標で2つの座標を入れ替えても数学的に等価であることは保証されているので、スクリプトも x 座標と y 座標を入れ替えて、数学的に等価になるようにすれば良いことになります。このときの傾きは、x と y を入れ替えて sllope = dx/dy となります。y 座標を y だけ変化させるとき、x 座標は y*slope だけ変化します。この変化は、(x1, y1) を基準にすると、座標 (x1+y*slope, y1+y) になります。

この問題は、以下の簡単なスクリプトで確認できます。

from u import *
for x in range(383):
 line(0, 0, x, 191)


三角形の領域が塗りつぶされるべきところ、そうなっていなかったことから、K様のご指摘を検証できました。今回の修正の結果、三角形の領域が正しく塗りつぶされることが確認できました。

なお、show が1の場合にVRAMから画面にデータ転送を行う処理は、関数定義の一番最後に一括して記述します。
----------

(x1, y1) と (y1, y2) の間に線分を描きます。返値はなしです。すると関数定義の1行目は次のようになります。

def line(x1, y1, x2, y2, color=1, show=1):

ここで、第5引数の color はデフォルトで 1、前回作った grp_color() の引数仕様をそのまま引き継ぎます。
第6引数の show は、VRAMから画面への転送を行うかどうかを指定し、デフォルトで 1、つまり転送します。

但しここで注意しなければならないのは、dx = 0 の時、つまり垂直な線分を描画しようとするとき、0で除算できないのでエラーになります。これについては、とりあえずスクリプトを書いた後に検討します。

abs(dx) > abs(dy)、つまり線分の傾きが45度未満の場合

def line(x1, y1, x2, y2, color=1, show=1):
 rgb = grp_color(color)
 dx = x2 - x1
 dy = y2 - y1
 if abs(dx)>abs(dy):
  if dx: #when dx is not 0
   slope = dy/dx
   for x in range(dx):
    set_pixel(x1+x, y1+x*slope, rgb)
 else:
   (slope≧1 の時の処理)

 if show:
  show_screen()


最初は、line() の第5引数 color から、タプル型の rgb 値を得るために、grp_color() を使っています。というのも set_pixel() の第3引数にタプル型の rgb 値を使って色指定するのが仕様になっているからです。
 
さて、点の描画を反復させるために for 文を使い、range(dx) により、x を 0 から dx-1 まで1づつ増やしながら set_pixel() で点を描画させます。

この反復処理では、最初の x = 0 のときに (x1, y1) に点を描画します。次の描画、つまり隣の点がどうなるかと言えば、x が 1 増え、点描画の x 座標は x1 + x   なので実際は x1 + 1 となります。x 座標の増分が x なので、そのときの y座標の増分は、x に傾き slope を掛けた値になります。  つまり y1 + x*slope です。

但し、ピクセル単位で点を描画するので、set_pixel() の引数は整数でなければなりません。 しかし slope は整数とは限りませんので、x*slope は小数点を切り捨てて整数にしてやる必要があります。

追加した部分は赤文字にしています。
def line(x1, y1, x2, y2, color=1, show=1):
 rgb = grp_color()
 dx = x2 - x1
 dy = y2 - y1
 if abs(dx)>abs(dy):
  if dx: #when dx is not 0
   slope = dy/dx
   for x in range(dx):
    set_pixel(x1+x, y1+int(x*slope))
 else:
  (slope≧1 の時の処理)

0 での除算エラーは dx=0 で発生します。このとき、abs(dx)=0 なので、if の条件 abs(dx)>abs(dy)0 > abs(dy) となります。絶対値は常に 0 以上なので、この条件は False (偽) となります。つまり除算エラーが 発生する dx=0 の時は、この if 文以下は実行されず、else 節にジャンプします。除算エラーについては、else 節を書く時に改めて検討します。


実は、range() の使い方で、考えなければならない問題があります。

for x in range(dx): の動作を細かくみてゆきます。
仮に dx = 10 だとすると、range(dx) はリスト [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] を作り、左から順に x に適用しながら反復処理を行います。実は、dx が正か負かで動作が変わってしまう問題があります。

dx が正の時は、問題ありません。 しかし dx = x2 - x1 なので、dx は常に正である保証はありません。

dx が負の場合、例えば -10 のときは、点描画の最初の座標 (x1, y1) は (x2, y2) よりもにあり、点描画は右から左へ進んでゆきます。つまり、range(dx) は、リスト [0, -1, -2, -3, -4, -5, -6,  -7, -8, -9] を作り、左から順に x に適用しながら反復処理を行う必要があります。

従って、range(dx) は、dx が正の場合は range(0, dx, 1) で、dx が負の場合は range(0, dx, -1) でないといけないことが分かります。

ちなみに、range(stop) は、range(start, stop, step) と書き換えることができ、start のデフォルトは 0 、step のデフォルトは 1 です。
range() のリファレンス参照

そこで、dx が正でも負での同じ動作にするためには、range(dx) と記述する代わりに range(0, dx, k) とし、dxが正の場合は k=1dxが負の場合は k=-1 となるように k を決めれば、問題を解消できます。

k の決め方は色々な方法があると思いますが、ここでは dxdxの絶対値で割り算すると 1 か -1 かになるので、この方法で k を決定します。但し range() の仕様上 k は整数でなければならないので、int() 関数で小数点以下を切り捨て整数にします。

k = int(dx/abs(dx))

abs() 関数は引数の絶対値を返す関数なので、これを使いました。

追加した部分は赤文字にしています。
def line(x1, y1, x2, y2, color=1, show=1):
 rgb = grp_color()
 dx = x2 - x1
 dy = y2 - y1
 if abs(dx)>abs(dy):
  if dx: #when dx is not 0
   k = int(dx/abs(dx))
   slope = dy/dx
   for x in range(0, dx, k):
    set_pixel(x1+x, y1+int(x*slope))
 else:
  (slope≧1 の時の処理)

 if show:
  show_screen()


これで、abs(dx)>abs(dy) の時の処理は完成です。

from casioplot import*
def
line(x1, y1, x2, y2, color=1, show=1):
 rgb = grp_color()
 dx = x2 - x1
 dy = y2 - y1
 if abs(x)>abs(y):
  if dx: #when dx is not 0
   k = int(dx/abs(dx))
   slope = dy/dx
   for x in range(0, dx, k):
    set_pixel(x1+x, y1+int(x*slope))
 else:
  #abs(dx)>abs(dy) でない時の処理

 if show: 
#data transfer to screen
  show_screen()


abs(dx)>abs(dy) でない時、つまり線分の傾きが 45度以上の場合
  
 [2020/07/12 追記]

直交座標系では、2つの座標を入れ替えても数学的に等価であることは保証されています。そして、x 座標と y 座標を入れ替えると、傾きは 45度未満になって、適切に線分を描けます。

そこで、x と y を入れ替え、dx と dy を入れ替えて作ったスクリプトを書きます。但し、set_pixel() の引数については、上で検討したように、単なる文字の入れ替えではなく、数学的な意味で等価になるように変更します。

from casioplot import*
def
line(x1, y1, x2, y2, color=1, show=1):
 rgb = grp_color()
 dx = x2 - x1
 dy = y2 - y1
 if abs(dx)>abs(dy):
  if dx: #when dx is not 0
   k = int(dx/abs(dx))
   slope = dy/dx
   for x in range(0, dx, k):
    set_pixel(x1+x, y1+int(x*slope))
 else:
  if dy: #when dy is not 0
   k = int(dy/abs(dy))
   slope = dx/dy
   for y in range(0, dy, k):
    set_pixel(x1+int(y*slope), y1+y)

 if show: 
#data transfer to screen
  show_screen()

今追加した else 節について、0 除算のエラーについて検討します。上で検討したように、dx=0 の場合は else 節の処理にジャンプします。dx=0 の時は slope=0 となるので、問題ないことが分かります。

では、dy=0 になる場合を検討します。最初の if の条件 abs(dx)>abs(dy) は、abs(dx)>0 となるので、これは常に True (真) です。つまり dy=0 の場合は、最初の if 文で処理され、slope=0 となり問題ありません。

除算エラーになる最後のケースは、dx=0 かつ dy=0 の場合です。この条件が成り立つ時は、x1 = x2 かつ y1 = y2 です。このとき描画するのは線分でなくて、点になることが分かります。この条件が成り立つ時は、if 文の条件には合わないので else 節へジャンプし、そこで slope の計算で除算エラーが発生します。

そこで、if 文の上に以下の処理を追加して、dxdy がともに 0 の時、座標 (x1, y1) に点を描画した後、return で終了し、それ以下を実行しないようにします。

from casioplot import*
def
line(x1, y1, x2, y2, color=1, show=1):
 rgb = grp_color()
 dx = x2 - x1
 dy = y2 - y1
 if dx==0 and dy==0: # avoid division by 0 error
  set_pixel(x1, y1, rgb)
  return
 if abs(dx)>abs(dy):
  if dx: #when dx is not 0
   k = int(dx/abs(dx))
   slope = dy/dx
   for x in range(0, dx, k):
    set_pixel(x1+x, y1+int(x*slope))
 else:
  if dy: #when dy is not 0
   k = int(dy/abs(dy))
   slope = dx/dy
   for y in range(0, dy, k):
    set_pixel(x1+int(y*slope), y1+y)

 if show: 
#data transfer to screen
  show_screen()


6.2 グラフィックスユーザーモジュールの作成

前回作成した grp_color()circle() そして今回作った line() は、グラフィックス画面に出力する汎用関数なので、いちいち関数定義をコピーして使うのは煩わしいです。そこで、calioplotmath, random モジュールのように呼び出して簡単に使うようにします。

そのためには、自分で作ったユーザー関数の定義を1つのスクリプトファイルに収め、そのスクリプトをモジュールとして呼びだして使います。今回は、このスクリプトファイル名を u.py とします。このモジュール(ユーザーモジュール)を使うには、スクリプトの冒頭に

from u import *

と書くだけです。

u.py (ユーザーモジュール ver 1.5) のダウンロード [2020/11/08 修正]

このモジュールには、grp_color()circle()line() が含まれます。これらは、モノクロ液晶モデル (FXモデル) にも対応しています。但し、line() 関数は FXモデル対応のために変更する必要がなく、そのままです。

これまで作った関数のスクリプトは、電卓内に保存されている筈です。電卓をPCとリンクし、エディタを使ってPC上でカット&ペーストするのが、間違いなく、簡単に u.py を作れます。
エディタについては、Casio Python - はじめに:電卓で作る初めてのスクリプト の 1.3 スクリプと作成と編集の2つの方法 を参照してください。


6.3 line() 関数の動作確認 - ckLine.py [fx-CG50 OS3.4以降専用]

line() 関数を実際に使ってみます。

 ckLine.py のダウンロード  - このスクリプトはCGモデル専用です。

from u import *

line(0,0,383,0,'black')
line(383,0,383,191,'blue')
line(383,191,0,191,'red')
line(0,191,0,0,'magenta')
line(10,10,373,10,'green')
line(373,10,373,181,'cyan')
line(373,181,10,181,'yellow')
line(10,181,10,10,1)
line(0,0,383,191,2)
line(0,191,383,0,3)

実行結果は以下のようになります;

lines_output 

FXモデル (fx-9750GIII, fx-9860GIII OS3.40以降) では、各座標値を3で割った整数部に置き換える必要があります。
例えば、383383//3 に変更します。[2020/11/08 追記]


6.4 line() 関数の動作確認 - ckLine2.py [fx-CG50 OS3.40 以降専用]

line() 関数を使った別のスクリプトです。
左上の座標 (0, 0) を基点にして、扇状に線分を -90度から0度まで反復描画してみます。反復する際にカラーコードを 1 ~ 7 まで繰り返し変更してみると、なんとも不思議な模様が得られました。

ckLine2.py のダウンロード - このスクリプトはCGモデル専用です

from u import *

for x in range(383):
 line(0, 0, x, 191, x%7)
for y in range(191, 0, -1):
 line
(0, 0, 383, y, y%7)



Note: 除算の余りを求める - %
整数 x7 で除算した時の余りは x%7 で得られます。この計算により 0 ~ 7 の整数を算出できます。


ckLine2_3 

ckLine2_5 

ckLine2_6 


FXモデル (fx-9750GIII, fx-9860GIII OS3.40以降) では、各座標値を3で割った整数部に置き換える必要があります。
例えば 383 を 383//3 に変更します。[2020/11/08 追記]



目 次

前の記事 - 5. 関数の作成と活用

次の記事 - 7. テキスト出力関数の追加





応援クリックをお願いします。励みになるので...
にほんブログ村 IT技術ブログ 開発言語へ


 


keywords: fx-CG50Pythonプログラム関数電卓

リンク集 | ブログ内マップ


テーマ : プログラム関数電卓
ジャンル : コンピュータ

Casio Python - ユーザー関数 circle()

Python Casio Python
 Casioグラフ関数電卓の Python を使ってみる
     - リファレンス 
目次
circle()

ユーザーモジュール u.py に含まれる関数 u.py ver 1.5 ダウンロード

初版:2020/11/08

[対応モデル] - fx-CG50 OS3.20 以降、fx-9750GIII / fx-9860GIII OS3.21 以降

円を描画します。

書 式circle(x, y, r, color=1, show=1)

引 数
- 第1引数 - x: 描画する円の中心の x座標、0 以上の浮動小数点
- 第2引数 - y: 描画する円の中心の y座標、0 以上の浮動小数点
- 第3引数 - r: 描画する円の半径、0 以上の浮動小数点
- 第4引数 -  color: パラメータ引数で、省略可能でデフォルトは 1 です。
       具体的な設定は grp_color() のリファレンスを参照。
- 第5引数 - show: パラメータ引数で、省略可能でデフォルトは 1 です。

引数を4つ、値だけを指定するとエラーになります ⇒ 関数の引数は こちら を参照。
引数を4つだけ設定する場合は、パラメータと共に設定します。
  例) circle(100, 50, 30, color=4) / circle(100, 50, 30, show=0)


使用例:

CGモデルの場合 - circleCG.py

from u import *
px = 290
py = 96
r = 91
circle(px, py, r, color=3, show=0)
circle(px, py, r/2, 2)

circle_CG 


FXモデルの場合 - circleFX.py

CGモデルの画面は 384 x 192 ピクセル、一方 FXモデルは 128 x 64 ピクセルで、CGモデルのちょうど 1/3 です。つまり CGモデルで設定する座標値や長さを 1/3 にすれば FXモデルに適用できます。

from u import *
px = 290/3
py = 96/3
r = 91/3

circle(px, py, r, color=3, show=0)
circle(px, py, r, r/2, 2)


circle_FX 


CGモデルとFXモデル共用の場合 - circle.py

from u import *
if isCG():
 px=290
 py=96
 r=91

else:
 px=290/3
 py=96/3
 r=91/2

circle(px, py, r, color=3, show=0)
circle(px, py, r, r/2, 2)


CGモデルとFXモデルを判別する isCG() を使い、円の中心座標 (px, py) と半径 r を場合分けした上で、円を描画します。

circle_CG circle_FX 


関数定義:
from casioplot import *
def
circle(x, y, r, color=1, show=1):
 if isCG():
  px_max = 383
  py_max = 191
 else:
  px_max = 127
  py_max = 63
 rgb = grp_color(color)
 p = r*6.283 #number of plots
 a = 2*pi/p #angle step in radian
 for i in range(p):
  px = x + r*cos(i*a)
  if px<0 or px>px_max:
   continue
  py = y + r*sin(i*a)
  if py<0 or py>py_max:
   continue
  set_pixel(int(px), int(py), rgb)
  if show: #data transfer to screen
   show_screen()


スクリプトの解説:
  CGモデル用 モノクロ 描画 circle() の作成 ⇒ こちら
  CGモデル用 カラー描画 circle() の作成 ⇒ こちら
  CGモデル/FXモデル共用 描画 circle() の作成 ⇒ こちら


円描画のためのユーティリティ:ckCircle.py (ダウンロード)
グラフィック画面で円描画する際、中心の座標と半径を設定した時に実際にどのように見えるかを確認するために作りました。CGモデルとFXモデル共用にしています。

起動画面:
ckCircle_CG1 ckCircle_FX1
デフォルトの値が、CGモデルとFXモデルで異なります。

デフォルト設定で円描画した画面: 起動画面で [4] + [EXE] を押す
ckCircle_CG2 ckCircle_FX2
デフォルトでは、ほぼ同じ位置と同じ半径で描画される設定にしています。

起動画面で [1] + [EXE] を押して、半径 (Radius) を変更できます
ckCircle_CG3 ckCircle_FX3
数値を入力して [EXE] を押す。
そのまま [EXE] を押せば、現在の値で設定します。

起動画面で [2] + [EXE] を押して、中心座標 (Position) を変更できます
ckCircle_CG4 ckCircle_FX4
x座標の数値を入力して [EXE] を押す。
そのまま [EXE] を押せば、現在の値で設定します。

ckCircle_CG5 ckCircle_FX5
y座標の数値を入力して [EXE] を押します。
そのまま [EXE] を押せば、現在の値で設定します。

起動画面で [3] + [EXE] を押して、色 (Color) を変更できます。
ckCircle_CG6 ckCircle_FX6
カラーコード 0 ~ 7 を入力して [EXE] を押します。
そのまま [EXE] を押せば、現在の値で設定します。






応援クリックをお願いします。励みになるので...
にほんブログ村 IT技術ブログ 開発言語へ


 


keywords: fx-CG50Pythonfx-9750GIIIfx-9860GIIIプログラム関数電卓

リンク集 | ブログ内マップ

テーマ : プログラム関数電卓
ジャンル : コンピュータ

Casio Python - circle() 関数のFXモデルへの拡張

Python Casio Python
 Casioグラフ関数電卓の Python を使ってみる
     - circle()関数のFXモデルへの拡張 
目次


初版:2020/11/07


前の記事 - 15. RGBによる色指定 |  次の記事 -  17. shell 画面とグラフィック画面の活用:コラッツ問題


16. circle()関数のFXモデルへの拡張

<fx-CG50 OS3.40以降、fx-9750GIII, fx-9860GIII OS3.40ニ以降に対応>

ユーザーモジュールとして作成した u.py ver 1.4 は、CGモデル (fx-CG50) 専用でした。その中に円を描画する circle() 関数があります。

CGモデル用 モノクロの円描画関数 circle() の作成 ⇒ こちらを参照
CGモデル用に circle() をカラー対応 ⇒ こちらを参照

 u.py ver 1.4 の circle() の関数定義:
def circle(xyr, color=1, show=1):
 rgb = grp_color(color)
 r*6.283
 2*pi/p
 for i in range(p):
  px r*cos(i*a)
  if px<0 or px>383: #CGモデル特有のスクリプト
   continue
  py r*
sin(i*a)
  if py<0 or py>191: #CGモデル特有のスクリプト
   continue 
  set_pixel(int(px)int(py), rgb)
  if show:
   show_screen()

このスクリプトには、上で示したように CGモデル特有のスクリプト が2箇所含まれています。この2箇所は、設定した座標が画面の範囲外の場合に描画をしないようにしている部分です。これは描画の高速化を目的としています。

CGモデルの液晶画面は 384 x 192 ピクセルありますが、FXモデルは 128 x 64 ピクセルしかありません。そこで上記をFXモデルに対応すれば、circle() 関数が FXモデル対応になります。

但し、CGモデル用とFXモデル用の2つの関数を使い分けるのは、ユーザーからみて面倒なので、1つの関数で CGモデルかFXモデルかの判定を行い、判定結果に応じてCGモデル用とFXモデル用のに場合分けすることで、両方に対応させると良いでしょう。

ここでは、既に作った CGモデルかFXモデルかを判定するユーザー関数を使います。
CGモデルかFXモデルを判定するユーザー関数 - isCG() を参照


16.1 CGモデルとFXモデルの場合分け
上記の CGモデル特有のスクリプトでは、CGモデルの画面において、ピクセルの座標の x と y の最大値を使っています。
x座標の最大値(CGモデル): 383
y座標の最大値(CGモデル): 191

FXモデルでのピクセルの x座標とy座標の最大値は、以下になります。
x座標の最大値(FXモデル): 127
y座標の最大値(FXモデル): 63

そこで、x座標の最大値の変数を px_max、y座標の最大値の変数を py_max として、CGモデルかFXモデルかの判定結果に応じて、px_maxpy_max に上記のいずれかの値を代入して使うようにします。

ユーザー関数 isCG() は、CGモデルでは 1 を、FXモデルでは 0 を返します。そこで、px_maxpy_max を以下のように決めます。

 if isCG():
  px_max = 383
  py_max = 191
 else:
  px_max = 127
  py_max = 63


そして、383px_max に、191py_max に置き換えます。


CGモデル、FXモデル共用の circle() 関数
def circle(x, y, r, color=1, show=1):
 rgb = grp_color(color)
 if isCG():
  px_max = 383
  py_max = 191
 else:
  px_max = 127
  py_max = 63
 rgb = grp_color(color)
 p = r*6.283 #number of plots
 a = 2*pi/p #angle step in radian
 for i in range(p):
  px = x + r*cos(i*a)
  if px<0 or px>px_max:
   continue
  py = y + r*sin(i*a)
  if py<0 or py>py_max:
   continue
  set_pixel(int(px), int(py), rgb)
  if show: #data transfer to screen
   show_screen()
これで完成です。他は変更する必要が無いと思います。
ユーザーモジュール u.py ver 1.5 にはここで作った circle() 関数を含んでいます。


16.2 circle() 関数を使ってみる

 書式:
circle(x, y, r, color=1, show=1)

第1~第3引数は浮動小数点、位置パラメータで、設定しないとエラーになります。
第4引数 color は、パラメータ引数で、省略可能でデフォルトは 1 です。
color の設定は grp_color() のリファレンスを参照してください。
第5引数 show は、パラメータ引数で、省略可能でデフォルトは 1 です。
引数を4つ、値だけを指定するとエラーになります ⇒ 関数の引数は こちら を参照。
引数を4つだけ設定する場合は、パラメータと共に設定します。
  例) circle(100, 50, 30, color=4) / circle(100, 50, 30, show=0)

CGモデルの場合 - circleCG.py


from u import *
px = 290
py = 96
r = 91
circle(px, py, r, color=3, show=0)
circle(px, py, r/2, 2)

circle_CG 


FXモデルの場合 - circleFX.py

CGモデルの画面は 384 x 192 ピクセル、一方 FXモデルは 128 x 64 ピクセルで、CGモデルのちょうど 1/3 です。つまり CGモデルで設定する座標値や長さを 1/3 にすれば FXモデルに適用できます。

from u import *
px = 290/3
py = 96/3
r = 91/3

circle(px, py, r, color=3, show=0)
circle(px, py, r, r/2, 2)


circle_FX 


CGモデルとFXモデル共用の場合 - circle.py

from u import *
if isCG():
 px=290
 py=96
 r=91

else:
 px=290/3
 py=96/3
 r=91/2

circle(px, py, r, color=3, show=0)
circle(px, py, r, r/2, 2)


CGモデルとFXモデルを判別する isCG() を使い、円の中心座標 (px, py) と半径 r を場合分けした上で、円を描画します。

circle_CG circle_FX 


目 次

前の記事 - 15. RGBによる色指定

次の記事 - 17. shell 画面とグラフィック画面の活用:コラッツ問題





応援クリックをお願いします。励みになるので...
にほんブログ村 IT技術ブログ 開発言語へ


 


keywords: fx-CG50Pythonfx-9750GIIIfx-9860GIIIプログラム関数電卓

リンク集 | ブログ内マップ


テーマ : プログラム関数電卓
ジャンル : コンピュータ

Casio Python - ユーザー関数 grp_color()

Python Casio Python
 Casioグラフ関数電卓の Python を使ってみる
     - リファレンス 
目次
grp_color()

ユーザーモジュール u.py に含まれる関数 u.py ver 1.5 ダウンロード

初版:2020/11/06

[対応モデル] - fx-CG50 OS3.20 以降、fx-9750GIII / fx-9860GIII OS3.21 以降

複数の色指定からタプル型のRGB値に変換します。

関数の書式grp_color(color=1)

引 数:下記の3通りの色設定のうち1つを引数に設定します。
色コード - 整数色の名前 - 文字列RGB - タプル (R, G, B)
0'white'(255, 255, 255)
1'black'(0, 0, 0)
2'blue'(0, 0, 255)
3'red'(255, 0, 0)
4'magenta'(255, 0, 255)
5'green'(0, 255, 0)
6'cyan'(0, 255, 255)
7'yellow'(255, 255, 0)
  引数を省略した場合は、色コード 1 がデフォルトで指定されます。

戻り値: 対応する タプル型のRGB値


関数定義:
from casioplot import *

def grp_color(color=1):
 #set rgb from argument(color)
 rgb=[(255,255,255),(0,0,0),(0,0,255),(255,0,0),(255,0,255),(0,255,0),(0,255,255),(255,255,0)]
 c_name=['white','black','blue','red','magenta','green','cyan','yellow']

 if type(color) is int:
  if 0<=color<=7:
   c_rgb=rgb[color]
  else:
   c_rgb=(0,0,0)
 elif type(color) is str:
  if color in c_name:
   c_rgb=rgb[c_name.index(color)]
  else:
   c_rgb=(0,0,0)
 elif type(color) is tuple:
  c_rgb=color
 else: #fail-proof
  c_rgb=(0,0,0)

 return c_rgb



解説:
グラフィック画面に出力するユーザー関数を作る際に、簡便かつ柔軟な色設定を行うことを目的として、grp_color() をユーザー関数定義の中で利用するために作成したものです。





応援クリックをお願いします。励みになるので...
にほんブログ村 IT技術ブログ 開発言語へ


 


keywords: fx-CG50Pythonfx-9750GIIIfx-9860GIIIプログラム関数電卓

リンク集 | ブログ内マップ

続きを読む

テーマ : プログラム関数電卓
ジャンル : コンピュータ

Casio Python - ユーザー関数 isCG()

Python Casio Python
 Casioグラフ関数電卓の Python を使ってみる
     - リファレンス 
目次
isCG()

ユーザーモジュール u.py に含まれる関数 u.py ver 1.5 ダウンロード

初版:2020/11/06

[対応モデル] - fx-CG50 OS3.20 以降、fx-9750GIII / fx-9860GIII OS3.21 以降


現在走っている環境が、GCモデルかFXモデルかを判別します。

引 数: なし
戻り値: CGモデルの場合は 1、FXモデルの場合は 0 を返す
       ※ CGモデル:fx-CG50
       FXモデル:fx-9750GIII, fx-9860GIII


関数定義:
from casioplot import *

def isCG():
 if get_pixel(128,64)==None:
  return 0
 else:
  return 1


解説:
casioplot の関数 get_pixel() は、実装されている液晶の範囲外を引数に指定すると None を返します。
これを利用し、CGモデルには存在し、FXモデルには存在しない座標 (128, 64) を引数にすると、None を返す場合は  return 0 とし、そうでない場合は return 1 としています。





応援クリックをお願いします。励みになるので...
にほんブログ村 IT技術ブログ 開発言語へ


 


keywords: fx-CG50Pythonfx-9750GIIIfx-9860GIIIプログラム関数電卓

リンク集 | ブログ内マップ

テーマ : プログラム関数電卓
ジャンル : コンピュータ

Casio Python - RGB値による色設定

Python Casio Python
 Casioグラフ関数電卓の Python を使ってみる
     - RGB値による色設定 
目次


初版:2020/10/31
追記:2020/11/02
修正:202011/03

前の記事 - 14. CGモデルとFXモデルのPythonモードの違い |  次の記事 - 16. circle() 関数のFXモデルへの拡張


15. RGB値による色設定 

Casio Python のグラフィックモジュール - casioplot には、色の値を引数にとったり、戻り値として色の値を返すグラフィック関数 (描画関数) があります。そして色の値は、256階調のRGB値をタプル型の (R, G, B) で使うことが仕様になっています。
 
RGB値 = (R, G, B)と実際の色の関係は、なかなか判りにくいと思いますが、例えば以下のようになります。

  (white) - (255, 255, 255)
(black) - (0, 0, 0)
(blue) - (0, 0, 255)
(red) - (255, 0 ,0)
マゼンタ (magenta) - (255, 0, 255)
(green) - (0, 255, 0)
シアン (cyan) - (0, 255, 255)
(yellow) - (255, 255, 0)


15.1 RGB値の色を確認するプログラム
最初に、256階調のRGB値と実際の色を fx-CG50 で確認するプログラムを紹介します。
RGB指定による色確認プログラム - RGB2ap:RGB Ver 1.2ap <シンプル機能版>

RGB_sample1 RGB_sample2 RGB_sample3 

RGB_sample4 RGB_sample5 RGB_sample6. 

このような感じで、RGB値を指定すると、fx-CG50 の液晶で実際にどのような色になるのかを確認できます。

純正Casio Basic はRGB値での色指定機能が無いので、このプログラムは CGモデル用のアドイン版Casio Basic - C.Basic for CG で走らせる必要があります。C.Basic for CGC.Basic for CG:最新版の入手のページ から Ver 1.45  build 20 を ダウンロードし、解凍して得られる CBCG145.g3afx-CG50 のストレージメモリに転送すれば、インストールできます。

C.Basic for CG のマニュアル類も同じページから参照できます。

RGB2ap は、上記からダウンロードして解凍して居られる RGB2ap.g3mfx-CG50 のストレージメモリか、その下に作った C.Basic 専用フォルダ (例えば @CBASIC) に転送してインストールします。


15.2 fx-CG50 の高精細カラー液晶 - 実は 16bit カラー
Casio Python の仕様として、256階調の RGB値、つまり 24bitカラーで色を表現することになっています。ところが、fx-CG50 の実際の高精細カラー液晶は 16bitカラー液晶です。

・R:0から始まり 248 までの 8 飛びの整数 32 個で指定 - 5bit
・G:0から始まり 252 までの 4 飛びの整数 64 個で指定 - 6bit
・B:0から始まり 248 までの 8 飛びの整数 32 個で指定 - 5bit

※ 5bit + 6bit + 5bit = 16bit 


24bitカラーは 224 = 16,777,216 色であるのに対して、fx-CG50 で実際に表現できる色は 216 = 65,536 色まで少なくなっています。
16bitカラーの液晶に対して、24bitカラーで色指定をしてもエラーにならず、RGB値それぞれ、設定値以下の有効な値が自動的に採用されます。例えば、(255, 255, 255) 白 を指定すると、自動的に (248, 252, 248) となり、実際に白色が指定されます。

fx-CG50 のカラー液晶は、PCやスマホのカラー液晶と異なり、視線の角度が変われば色が変化してしまいます。RGB値での色設定は、あまり細かいことを気にしない方が良いと言えます。従って24bitカラー、つまり 256階調のRGB値による色指定でも大きな問題にならないと思います。むしろ問題になるようなスクリプト/プログラムは fx-CG50 には不向きだと割り切るべきでしょう。

fx-CG50 / CG20 のaRGBカラー設定について詳しくは こちら を参照ください


15.3 モノクロ液晶モデルでのRGB値による色設定
モノクロ液晶を搭載した FXモデルでは、黒 (0, 0, 0) か 白 (255, 255, 255) のどちらかの値しかありません。

実際に使うときは、黒は (0, 0, 0)、白は (255,255,255) を使えば問題ありません。但し、CGモデルのスクリプトを FXモデルに移植する場合は、少しだけ注意が必要です。

そこで、モノクロ液晶を搭載するFXモデル (fx-9750GIII) での動作を検証します。

グラフィック画面の左上の座標 (0, 0) にあるピクセルの色を取得して出力します;
スクリプト:
 from casioplot import *
 print(get_pixel(0, 0, 0))

出力:
 (255, 255, 255)
解説:
 ピクセルの色を取得する時は、白 = 点がない 場合は (255, 255, 255) を返します。
 つまり、256階調 (24bit) カラーとしての仕様に従ってピクセル色を取得しています。
  このスクリプトを fx-CG50 で実行しても (255, 255, 255) が出力されます。

 グラフィック画面の左上の座標 (0, 0) にあるピクセルに黒か白の色を付けます
スクリプト1:RGB値 (247, 251, 247) で描画
 from casioplot import *
 set_pixel(0, 0, (247, 251, 247))
 show_screen()

出力:
 座標 (0, 0) のピクセルが黒くなります

スクリプト2:RGB値 (248, 252, 247) で描画
 from casioplot import *
 set_pixel(0, 0, (248, 252, 248))
 show_screen()

出力:
 座標 (0, 0) のピクセルが白くなります = 点が見えない

解説:
 ピクセルに色を付ける時は、(248, 252, 248) で白になります、
 これより小さい値では黒くなります。
  R < 248, G < 252, B < 248 の (R, G, B) で黒を描画します。
  R ≧ 248, B ≧ 252, B ≧ 248 の (R, G, B) で白を描画します。
 つまり、実際は 16bit カラーを前提にして動作しています。
 CGモデルのスクリプトをFXモデルに移植する際は、この点に留意すれば動作を正しく理解できます。

モノクロのFXモデルでの仕様の混乱は、実際は 16bit カラーのCGモデルのスクリプトとの細かい互換性を得るためだと思われます。


15.4 RGB値でのピクセル色設定と読取りの実験 - CGモデルとFXモデル
以下のスクリプト (CGモデル・FXモデル共用) を作りました。

このスクリプトの概要は、
(1) RGB値を1づつ変化させピクセルに色を付けて線分を描きます
(2) 描いた線分を端から順にスキャンして、各ピクセルの RGB値を読み取ります。

描いた線分の隣り合ったピクセルのRGB値の差は 1 になっています。そこで、
(a) 読み取ったRGB値が 1づつ変化していれば画面は 24bit カラー
(b) 読み取ったRGB値が 4 ないし 8 の倍数の値であれば 16bit カラー
だと判ります。

スクリプトを少し詳しく説明します。
  • このスクリプトは、set_pixel() を使って、RGB値を変えながらドットを x座標 0 から end-1 まで水平に描画します。
  • これを y座標 30、40、60 の位置に3本描きます。
  • 描いた3本の線分の各ピクセルを水平に x座標 start から end-1 まで順に get_pixel() を使ってRGB値を取得し、そのRGB値をシェル画面に出力します。
  • CGモデルかFXモデルかに応じて、startend の初期値を設定します。
  • CGモデルかFXモデルかの判定は、get_pixel() が画面の範囲外のピクセルを指定した時に None を返すことを利用します。
  • FXモデルの画面の範囲外の座標 (128, 64) を使って、get_pixel(128,64) を実行すると、FXモデルの場合は None を返します。
  • None が返されたとき、start=98, end=128 とし、そうでないとき start=195, end=256 と初期化します。

以下の具体的なスクリプトを実際に実行してみてください。

ckPixelCol.py - ダウンロード

from casioplot import *

# CGモデルかFXモデルかで初期値を変える
if get_pixel(128,64)==None:
 start=98;end=128
else:
 start=195;end=256

# RGB値を0から1づつ変化させて、横にピクセルに色を付けてゆく
for i in range(end):
 set_pixel(i,30,(i,0,0)) #(R,G,B)のRを0から1づつ増やし、左端からend-1まで点を打つ
 set_pixel(i,40,(0,i,0)) #(RGB)のGを0から1づつ増やし、左端からend-1まで点を打つ
 set_pixel(i,50,(0,0,i)) #(RGB)のBを0から1づつ増やし、左端からend-1まで点を打つ

# 16bitカラーの白(248,252,248) で 座標(60,60)のピクセルを白くする
r=248;g=252;b=248
set_pixel(60,60,(r,g,b))
show_screen()
# グラフィック画面に3本の線分が描画されたら、[EXIT]キーを押して、シェル画面に切り替える

# シェル画面で、設定したRの値と get_pixel()で読み取ったRGB値を表示する
print('=R=')
for i in range(start,end):
 print(str(i)+':'+str(get_pixel(i,30)))

# シェル画面で、設定したGの値と、get_pixel()で読み取ったRGB値を表示する
print('\n=G=')
for i in range(start,end):
 print(str(i)+':'+str(get_pixel(i,40)))

# シェル画面で「、設定したBの値と、get_pixel()で読み取ったRGN値を表示する
print('\n=B=')
for i in range(start,end):
 print(str(i)+':'+str(get_pixel(i,50)))

# 16bitカラーの白(248,252,248)のピクセルを get_pixel()で読み取って表示する
print('\n=White=')
print('r='+str(r)+',g='+str(g)+',b='+str(b))
print(get_pixel(60,60))




CGモデル (fx-CG50) の出力

FXモデル (fx-9750GIII) の出力

MicroPython x1.9.4
|CASIO COMPUTER CO.,
>>>frim ckPixCol import *
=R=
195:(192, 0, 0)
196:(192, 0, 0)
196:(192, 0, 0)
197:(192, 0, 0)
198:(192, 0, 0)
199:(192, 0, 0)
200:(200, 0, 0)
201:(200, 0, 0)
202:(200, 0, 0)
203:(200, 0, 0)
204:(200, 0, 0)
205:(200, 0, 0)
206:(200, 0, 0)
207:(200, 0, 0)
208:(208, 0, 0)
209:(208, 0, 0)
210:(208, 0, 0)
211:(208, 0, 0)
212:(208, 0, 0)
213:(208, 0, 0)
214:(208, 0, 0)
215:(208, 0, 0)
216:(216, 0, 0)
217:(216, 0, 0)
218:(216, 0, 0)
219:(216, 0, 0)
220:(216, 0, 0)
221:(216, 0, 0)
222:(216, 0, 0)
223:(216, 0, 0)
224:(224, 0, 0)
225:(224, 0, 0)
226:(224, 0, 0)
227:(224, 0, 0)
228:(224, 0, 0)
229:(224, 0, 0)
230:(224, 0, 0)
231:(224, 0, 0)
232:(232, 0, 0)
233:(232, 0, 0)
234:(232, 0, 0)
235:(232, 0, 0)
236:(232, 0, 0)
237:(232, 0, 0)
238:(232, 0, 0)
239:(232, 0, 0)
240:(240, 0, 0)
241:(240, 0, 0)
242:(240, 0, 0)
243:(240, 0, 0)
244:(240, 0, 0)
245:(240, 0, 0)
246:(240, 0, 0)
247:(240, 0, 0)
248:(248, 0, 0)
249:(248, 0, 0)
250:(248, 0, 0)
251:(248, 0, 0)
252:(248, 0, 0)
253:(248, 0, 0)
254:(248, 0, 0)
255:(248, 0, 0)
 
=G=
195:(0, 192, 0)
196:(0, 192, 0)
196:(0, 192, 0)
197:(0, 192, 0)
198:(0, 192, 0)
199:(0, 192, 0)
200:(0, 200, 0)
201:(0, 200, 0)
202:(0, 200, 0)
203:(0, 200, 0)
204:(0, 200, 0)
205:(0, 200, 0)
206:(0, 200, 0)
207:(0, 200, 0)
208:(0, 208, 0)
209:(0, 208, 0)
210:(0, 208, 0)
211:(0, 208, 0)
212:(0, 208, 0)
213:(0, 208, 0)
214:(0, 208, 0)
215:(0, 208, 0)
216:(0, 216, 0)
217:(0, 216, 0)
218:(0, 216, 0)
219:(0, 216, 0)
220:(0, 216, 0)
221:(0, 216, 0)
222:(0, 216, 0)
223:(0, 216, 0)
224:(0, 224, 0)
225:(0, 224, 0)
226:(0, 224, 0)
227:(0, 224, 0)
228:(0, 224, 0)
229:(0, 224, 0)
230:(0, 224, 0)
231:(0, 224, 0)
232:(0, 232, 0)
233:(0, 232, 0)
234:(0, 232, 0)
235:(0, 232, 0)
236:(0, 232, 0)
237:(0, 232, 0)
238:(0, 232, 0)
239:(0, 232, 0)
240:(0, 240, 0)
241:(0, 240, 0)
242:(0, 240, 0)
243:(0, 240, 0)
244:(0, 240, 0)
245:(0, 240, 0)
246:(0, 240, 0)
247:(0, 240, 0)
248:(0, 248, 0)
249:(0, 248, 0)
250:(0, 248, 0)
251:(0, 248, 0)
252:(0, 248, 0)
253:(0, 248, 0)
254:(0, 248, 0)
255:(0, 248, 0)
 
=B=
195:(0, 0, 192)
196:(0, 0, 192)
196:(0, 0, 192)
197:(0, 0, 192)
198:(0, 0, 192)
199:(0, 0, 192)
200:(0, 0, 200)
201:(0, 0, 200)
202:(0, 0, 200)
203:(0, 0, 200)
204:(0, 0, 200)
205:(0, 0, 200)
206:(0, 0, 200)
207:(0, 0, 200)
208:(0, 0, 208)
209:(0, 0, 208)
210:(0, 0, 208)
211:(0, 0, 208)
212:(0, 0, 208)
213:(0, 0, 208)
214:(0, 0, 208)
215:(0, 0, 208)
216:(0, 0, 216)
217:(0, 0, 216)
218:(0, 0, 216)
219:(0, 0, 216)
220:(0, 0, 216)
221:(0, 0, 216)
222:(0, 0, 216)
223:(0, 0, 216)
224:(0, 0, 224)
225:(0, 0, 224)
226:(0, 0, 224)
227:(0, 0, 224)
228:(0, 0, 224)
229:(0, 0, 224)
230:(0, 0, 224)
231:(0, 0, 224)
232:(0, 0, 232)
233:(0, 0, 232)
234:(0, 0, 232)
235:(0, 0, 232)
236:(0, 0, 232)
237:(0, 0, 232)
238:(0, 0, 232)
239:(0, 0, 232)
240:(0, 0, 240)
241:(0, 0, 240)
242:(0, 0, 240)
243:(0, 0, 240)
244:(0, 0, 240)
245:(0, 0, 240)
246:(0, 0, 240)
247:(0, 0, 240)
248:(0, 0, 248)
249:(0, 0, 248)
250:(0, 0, 248)
251:(0, 0, 248)
252:(0, 0, 248)
253:(0, 0, 248)
254:(0, 0, 248)
255:(0, 0, 248)
 
=White=
r=248,g=252,b=248
(255, 255, 255)
MicroPython v1.9.4
|CASIO COMPUTER CO.,
>>>from ckPixCol import *
=R=
98:(0, 0, 0)
99:(0, 0, 0)
100:(0, 0, 0)
101:(0, 0, 0)
102:(0, 0, 0)
103:(0, 0, 0)
104:(0, 0, 0)
105:(0, 0, 0)
106:(0, 0, 0)
107:(0, 0, 0)
108:(0, 0, 0)
109:(0, 0, 0)
110:(0, 0, 0)
111:(0, 0, 0)
112:(0, 0, 0)
113:(0, 0, 0)
114:(0, 0, 0)
115:(0, 0, 0)
116:(0, 0, 0)
117:(0, 0, 0)
118:(0, 0, 0)
119:(0, 0, 0)
120:(0, 0, 0)
121:(0, 0, 0)
122:(0, 0, 0)
123:(0, 0, 0)
124:(0, 0, 0)
125:(0, 0, 0)
126:(0, 0, 0)
127:(0, 0, 0)
 
=G=
98:(0, 0, 0)
99:(0, 0, 0)
100:(0, 0, 0)
101:(0, 0, 0)
102:(0, 0, 0)
103:(0, 0, 0)
104:(0, 0, 0)
105:(0, 0, 0)
106:(0, 0, 0)
107:(0, 0, 0)
108:(0, 0, 0)
109:(0, 0, 0)
110:(0, 0, 0)
111:(0, 0, 0)
112:(0, 0, 0)
113:(0, 0, 0)
114:(0, 0, 0)
115:(0, 0, 0)
116:(0, 0, 0)
117:(0, 0, 0)
118:(0, 0, 0)
119:(0, 0, 0)
120:(0, 0, 0)
121:(0, 0, 0)
122:(0, 0, 0)
123:(0, 0, 0)
124:(0, 0, 0)
125:(0, 0, 0)
126:(0, 0, 0)
127:(0, 0, 0)
 
=B=
98:(0, 0, 0)
99:(0, 0, 0)
100:(0, 0, 0)
101:(0, 0, 0)
102:(0, 0, 0)
103:(0, 0, 0)
104:(0, 0, 0)
105:(0, 0, 0)
106:(0, 0, 0)
107:(0, 0, 0)
108:(0, 0, 0)
109:(0, 0, 0)
110:(0, 0, 0)
111:(0, 0, 0)
112:(0, 0, 0)
113:(0, 0, 0)
114:(0, 0, 0)
115:(0, 0, 0)
116:(0, 0, 0)
117:(0, 0, 0)
118:(0, 0, 0)
119:(0, 0, 0)
120:(0, 0, 0)
121:(0, 0, 0)
122:(0, 0, 0)
123:(0, 0, 0)
124:(0, 0, 0)
125:(0, 0, 0)
126:(0, 0, 0)
127:(0, 0, 0)
 
=White=
r=248,g=252,b=248
(255, 255, 255)


































































































シェル画面への出力を見れば、RGB値は 16bitカラーに対応していることがわかります。
唯一の例外として、最後の白のRGB値を get_pixel() で取得した時のみ 24bit RGB値の (255, 255, 255) を返します。

それ以外は get_pixel()set_pixel() も共に 16bit カラーに対応して動作します。




目 次

前の記事 - 14. CGモデルとFXモデルのPythonモードの違い

次の記事 - 16. circle() 関数のFXモデルへの拡張





応援クリックをお願いします。励みになるので...
にほんブログ村 IT技術ブログ 開発言語へ


 


keywords: fx-CG50Pythonfx-9750GIIIfx-9860GIIIプログラム関数電卓

リンク集 | ブログ内マップ

続きを読む

テーマ : プログラム関数電卓
ジャンル : コンピュータ

Casio Python - テキスト出力関数の追加

Python Casio Python
 Casioグラフ関数電卓の Python を使ってみる
     - テキスト出力関数の追加:locate() をユーザーモジュールに累加 
目次


初版:2020/07/13
追記修正:2020/11/03
前の記事 - 6. グラフィックス出力関数の追加 |  次の記事 - 8. シェル画面入力の工夫


fx-CG50 OS3.40 以降専用


7. テキスト出力関数の追加:locate() をユーザーモジュール に追加

Casio Python の現在の言語仕様では、Casio Basic で作った実用プログラムを完全に移植できないレベルの貧弱な仕様で、発展途上と言えます。Casio Basic で実用プログラムが作れる最大の要因は、望む位置に数値や文字列を出力できる Locate コマンドと電卓のほぼ全てのキー入力を取得可能な Getkey コマンドの存在です。一方 Casio Python には LocateGetkey に相当するものがありません。Getky 相当の機能は、カシオによる OSアップデートを待つしかありませんが、Locate 相当の関数は自作できます。

casioplot モジュールには draw_string() 関数があり、グラフィックス画面(描画画面) にアスキー文字(列)を好みの位置に出力できます。但し、その出力位置は、文字列の最初の文字の左上のピクセルの座標で指定する必要があり、ちょっと面倒です。

さて、fx-CG50 のマニュアルにある draw_string() の説明は下記の通りです。 
draw_string_manual 

グラフィックス画面のピクセル座標で位置指定する代わりに、Casio BasicLocateコマンドのように、文字や数値を文字の大きさに併せて桁と行で指定できる locate() 関数を作ろうと思います。


7.1 locate() 関数の作成

具体的には、以下の仕様にします;
locate(column, row, obj, color=1, size='m', show=1)
  • column: 出力開始桁 (横方向)、0から始まる整数
  • row: 出力行 (縦方向)、0から始まる整数
  • obj: 出力する数値や文字、文字列
  • color: 省略すればデフォルトの黒、grp_color(color) (ココを参照) を利用
  • size: 's', 'm', 'l' と1文字の文字列でサイズを指定 (入力が楽)
  • show: VRAMからグラフィックス画面への出力指定、デフォルトの1で出力、0で出力しない 
この関数内では、最終的に casioplot で提供されている draw_string() で出力します。
draw_string() は、第三引数で色を指定可能(省略時のデフォルトは黒)、第四引数で文字サイズ 'small', 'medium', 'large' のいずれかを指定可能(省略時のデフォルトは 'medium') となっています。これを locate() 関数でフル活用します。

以下がこれから作る locate() 関数の骨格です。

def locate(column, row, obj, color=1, size='m', show=1):
 #1) column から x を算出
 #2) row から y を算出
 #3) size から draw_string() が受け取れる sz ('small', 'medium', 'large' のどれか) に変換

 draw_string(x, y, str(obj), grp_color(color), sz)
 if show:
  show_screen()


#1), #2), #3) を具体的に書けば、locate() 関数の完成となります。


7.1.1 フォントピッチ dx, dy の設定

draw_string() で文字をグラフィックス画面に出力する際には、文字フォントの左上の座標 (x, y) を指定します。
左上座標 (x, y) にある文字の右隣の文字は (x + dx, y) の位置、直下の文字は (x, y + dy) の位置にフォントを描画することになります。
draw_string() では大、中、小と3つの異なるサイズの文字を出力できるので、それぞれサイズのフォントごとにフォントピッチ dx と dy を決めてやれば、フォントの出力位置を桁と行に置き換えることができます。

ある程度の試行錯誤の結果、以下のような収まりの良い結果が得られました。

'small' フォント
dx (x ピッチ):8
dy (y ピッチ):12
横方向の最大桁数 (1行の文字数):48
縦方向の最大行数 (1列の文字数):16
small_font 
'small' フォントでは、dx=8dy=12 とすれば、1画面に 48桁×16行 の文字がうまく収まります。

'medium' フォント
dx (x ピッチ):12
dy (y ピッチ):16
横方向の最大桁数 (1行の文字数):32
縦方向の最大行数 (1列の文字数):12
midium_font 
'medium' フォントでは、dx=12dy=16 にすれば、1画面に 32桁×12行でうまく収まります。

'large' フォント
dx (x ピッチ):16
dy (y ピッチ):24
横方向の最大桁数 (1行の文字数):24
縦方向の最大行数 (1列の文字数):8
large_font 
'large' フォントでは、dx=16dy=24 とすれば、1画面に 24桁×8行で旨く収まります。
これは、シェル画面での桁数と行数と同じです。

ちなみに、上記の出力と条件は locate.py を使って、試行錯誤して得ました。
locate.py のダウンロード

スクリプト内の #small font#medium font#large font とあるそれぞれのセクションのうち、2つをコメントアウトし、コメントを外したフォントサイズで、それぞれ出力したのが、上記の出力画面です。


7.1.2 column から x を、row から y を算出

locate(column, row, obj, color=1, size='m', show=1) 関数の size 引数で指定される 's', 'm', 'l' から、出力位置を示すフォントの左上の座標を算出します。

上の検討から、各フォントサイズに応じたフォントピッチ dxdy を決めました。

size と dx の関係
key's''m''l'
value81216

locate() の引数 size (= 's' or 'm' or 'l') に対応する dx は、この表の通りです。
この関係を使えば、size から dx を得るのに if文で簡単に記述できます。しかし今回は、Casio Pyton の辞書型(dict型) シーケンスを利用して、よりシンプルな記述を試みます。

辞書型 (dict型) は、シーケンス型 (配列) の一種で、要素を コンマ , で区切って列挙します。辞書型の要素は、keyvalue をコロン : で区切って、key:value のように記述したもの。辞書型は、key:value  (要素)を コンマ , 句切りで列挙したものを波括弧 { } で囲んで定義します。上の sizedx の関係を 辞書型オブジェクト dx とすると、dx は以下のように定義できます。

dx = {'s':8, 'm':12, 'l':16}

そして、dx[size] と記述すると、key の 's', 'm', 'l' に対応した value は、それぞれ 8, 12, 16 になります。辞書をひいて見出し語 's' を探すと、そこには 8 という説明があるという感覚なので辞書型 (dict型 - dictionary から dict) です。このように key に関連づけられた value を簡単に得られるのが、辞書型の便利なところです。何行にもわたる if文を書く代わりに、上の1行の定義を行うだけで、欲しい value の値は dx[size] で得られるわけです。

さて、draw_string() の第一引数 x は、フォントの左上の x 座標です。locate() の第一引数 column を使えば、

x = column*dx[size]

のようにして、求める x 座標が得られます。ここで、column は 0 から始まる整数です。

同様にして、sizedy の関係を 辞書型シーケンス dy で定義し、フォントの右上の y 座標を求めます。

size と dy の関係
key's''m''l'
value121624

dy = {'s':12, 'm':16, 'l':24}

と定義すれば、key に対応した値は、dy[size] で得られます。すると、locate() の第二引数 row を用いて、y 座標は以下のようになります。row は、0 から始まる整数です。

y = row*dy[size]

従って、draw_string() 関数の第一引数と第二引数は、上記の xy を使えます。あるいは変数 x, y を用いずに、以下のょうに引数として、式を直接書いても問題ありません。

draw_string(column*dx[size], row*dy[size], str(obj), grp_color(color), sz)


7.1.3 フォントサイズ指定の処理

draw_string() の最後の引数 sz を、locate() 関数の 引数 size (= 's' or 'm' or 'l') からどうやって得るかを考えます。
この引数は、draw_string() の引数 ('small' or 'medium' or 'large')と同じ設定にすれば良いのでしょうが、電卓での文字入力が面倒なので、できるだけ文字数を減らしたいと考えて、1文字で指定するように決めたので、1文字から文字列へ変換が必要になります。

size と sz の関係
key's''m''l'
value'small''medium''large'

実は、これも辞書型オブジェクトで簡単に処理できます。

sz = {'s':'small', 'm':'medium', 'l':'large'}

と定義すれば、sz[size] が求めたい文字列を与えます。

これで、draw_string() の全ての引数が決まりました。

draw_string(column*dx[size], row*dy[size], str(obj), grp_color(color), sz[size])


7.1.4 locate() の完成

これまでの検討をまとめて、完成した locate() の定義を以下に示します。

from casioplot import *
from u import *

def
locate(column, row, obj, color=1, size='m', show=1):
 # set fontsize & pitch
 dx = {'s':8, 'm':12, 'l':16}
 dy = {'s':12, 'm':16, 'l':24}
 sz = {'s':'small', 'm':'medium', 'l':'large'}

 #data transfer to VRAM
 draw_string(column*dx[size], row*dy[size], str(obj), grp_color(color), sz[size])

 if show: #data transfer to screen
  show_screen
()


意外にシンプルな関数定義になりました。


7.2 locate() をユーザーモジュールに追加

前回作ったユーザーモジュール u.py ver 1.2 に今回作った locate() の定義をを追加して、ver 1.3 とします。

ユーザーモジュール ver 1.3 - u.py のダウンロード


7.3 locate() の動作確認 - ckLocate.py

ckLocate.py のダウンロード

ckLocate.py は、シェル画面で必要な入力を行い、その後グラフィックス画面に出力する構造にしています。

シェル画面では、大、中、小の3通りにフォントサイズから1つを選択させます。この入力処理は、while 1: で無限ループにしており、[1][2][3] キーを押したときだけそのメニュー番号を取得して break でループを抜けます。それ以外のキーを押した時は、その入力を無視して continuewhile ループを回り続けます。シェル画面で入力を行う良いサンプルになっていると思いますので、参考にしてください。

入力により変数 m に 0, 1, 2 のいずれかの値が入ります。この m をリストのインデックスとして使います。
リストは、col_rangerow_range, size の3つあり、インデックス 0  は 'small' フォントに関する値、インデックス 1 は 'medium' フォントに関する値、インデックス 2 は 'large' フォントに関する値にしています。

そして、col_range[m]row_range[m] は、2つの for 文の反復回数を決める range() の引数に使い、size[m]locate() 関数で指定するフォントサイズとして 's', 'm', 'l' のいずれかの文字を与えます。

locate() を反復実行して、0 から 9 までの数字で順に画面を埋め尽くし、同時にカラーコード 1 から 7 までを順に繰り返し指定するようにしています。詳しくは、以下のスクリプトをご覧下さい。

from u import *

num_str '1234567890'
0

while 1:
 print('Text Drawing Test')
 print()
 print('1:small font')
 print('2:medium font')
 print('3:large font')
 print()
 inp input('Input menu #:')
 if inp.isdigit():
  m int(inp)
  m -= 1
  if not(0<=m<=2):
   continue
  else:
   break
 else:
  continue

col_range [483224]
row_range [16128]
size ['s''m''l']

clear_screen()
for j in range(row_range[m]):
 k j
 k %= 10
 for i in range(col_range[m]):
  locate(ijnum_str[k], (i+j)%7+1size[m])
  k += 1
  k %= 
10



このスクリプトを実行すると、シェル画面に以下のように出力されます。
ckLocate_in 

[1] ~ [3] 以外のキーを押してから、[EXE] を押すと無視され、再びこの画面が表示されます。

[1] + [EXE] を押せば、small フォントで埋め尽くされます。
ckLocate_1_in ckLocate_1_out 

グラフィックス画面に出力された後、[EXIT] を押せば、スクリプトを実行させた時の画面に戻ります。

[2] + [EXE] を押せば、midium フォントで埋め尽くされます。
ckLocate_2_in ckLocate_2_out 

[3] + [EXE] を押せば、large フォントで埋め尽くされます。
ckLocate_3_in ckLocate_3_out 


7.4 モンテカルロ法(3) - monteca3.py

以前作成したモンテカルロ法(2) - monteca2.py は、スクリプト内に grp_color() 関数 と circle() 関数の定義を記述しました。これらの関数定義に加えて今回作った locate() 関数の定義も、アップデートした ユーザーモジュール u.py ver 1.3 に含めました。そこで、以前作ったモンテカルロ法(2) - monteca2.py を修正して、ユーザーモジュール - u.py ver 1.3 を呼び出して使うようにして、スクリプトファイル名を monteca3.py とします。 汎用のユーザーモジュールを使うことで、スクリプトがとてもスッキリしました。

モンテカルロ法(3) - monteca3.py のダウンロード

"""Sample script

Exercise;
porting from Casio Basic
"Monte Carlo Method"
ver 3.0

by Krtyski/e-Gadget
"""

from random import *
from u import
*

#initializing
n_try 500
ins 0outs 0
prev_outs 0
prev_pai 0
90 #radius
dx 290dy 96 #center

#dislay menu to select try#
while 1:
 print('\nMonte Carlo: calc Pi')
 print('\n# of try:' + str(n_try))
 print('[EXE] to start.')
 temp_try = input('\nChange the #?:')
 if temp_try.isdigit():
  n_try = int(temp_try)
  break
 elif temp_try == '':
  break
 else:
  continue

#display graphics
clear_screen()
circle(290, 96, 90, 'red') #red circle
locate(1, 0, 'C=')
locate(0, 1, 'pi=')
locate(0, 10, '# of try:' + str(n_try))
locate(0, 11, 'AC:Quit')

while outs<n_try
:

 x = 1 - 2*random()
 y = 1 - 2*random()
 set_pixel(int(dx+r*x), int(dy+r*y), grp_color('blue')) #blue plots
 if (x**2+y**2)<1:
  ins+=1
 outs+=1
 locate(3, 0, str(prev_outs), color=0, show=0)
 locate(3, 1, str(prev_pai), color=0, show=0)
 pai = round(4*ins/outs, 11)
 prev_outs = outs
 prev_pai = pai
 locate(3, 0, str(outs), show=0)
 locate(3, 1, str(pai))

locate(0, 11, 'AC:Quit', color=0)
locate(0, 11, 'Finish=>[EXIT]'
)



draw_string()locate() に置き換えられました。locate() の引数に color=0show=0 と書いていますが、この2つは単に 0 と記述しても問題ありません。但し、明示的に白で出力することを示すため color=0 としました。また、VRAMから画面へのデータ転送を行わない場合は、これを明示的に示すために show=0 としました。show パラメータ (引数) を省略すれば、デフォルトの show=1 となり、VRAMから画面へ出力します。

実行した結果のグラフィックス画面への出力は monteca2.py と同じです (当然ではあります)。

monteca2 




目 次

前の記事 - 6. グラフィックス出力関数の追加

次の記事 - 8. シェル画面入力の工夫





応援クリックをお願いします。励みになるので...
にほんブログ村 IT技術ブログ 開発言語へ


 


keywords: fx-CG50Pythonプログラム関数電卓

リンク集 | ブログ内マップ


テーマ : プログラム関数電卓
ジャンル : コンピュータ

最新記事
検索フォーム
最新コメント
カテゴリ
C# (3)
Online Counter
現在の閲覧者数:
プロフィール

やす (Krtyski)

Author:やす (Krtyski)
since Oct 30, 2013


プログラム電卓は、プログラムを作って、使ってナンボ!

プログラム電卓を実際に使って気づいたこと、自作プログラム、電卓での Casio Basic, C.Basic そして Casio Python プログラミングについて書いています。

なお管理人はカシオ計算機の関係者ではありません。いつでもどこでもプログラミングができるプログラム電卓が好きな1ユーザーです。


写真: 「4駆で泥んこ遊び@オックスフォード郊外」

リンク
月別アーカイブ
Sitemap

全ての記事を表示する

ブロとも申請フォーム

この人とブロともになる

QRコード
QR