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 素因数分解 - 高速化 をご覧ください。高速素因数分解のロジックを詳しく説明しています。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 で上記のコードが実行されると、Whle ループの中から 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 に対応します。
予め移植の方針として、中サイズのフォントを使うことにしているので、第4引数には 'm' を指定します。
locate() を沢山使うので、第5引数には 0 を指定し、最後に locate(0, 0, '', 'm', 1) を追加して、第5引数で 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プログラム関数電卓


関連記事

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

コメントの投稿

非公開コメント

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

やす (Krtyski)

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


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

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

なお管理人はカシオ計算機の関係者ではなく、Casio Basicが面白いと感じる1ユーザーです。


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

リンク
月別アーカイブ
Sitemap

全ての記事を表示する

ブロとも申請フォーム

この人とブロともになる

QRコード
QR