不思議ちゃん

不思議ちゃん

みんクロ大好きな不思議ちゃんの日記
みんクロ無くなっちゃって悲しい

鶏ドリアンさんの情報の講義のC言語の課題その3

パソコン/インターネット

 これは、ほぼ同じタイトル(その2)のブログの続編です。
 前回は
http://www.nicotto.jp/blog/detail?user_id=288939&aid=16781656です。
 初回は、
http://www.nicotto.jp/blog/detail?user_id=288939&aid=16691800
です。

 まずテーマの確認。

課題は以下の通り。

自然数nを入力させる。

そのnに対して、「1」、「2」、…「n-1」、「n」の番号が書かれたn枚のカードを仮定する。
このn枚のカードを「n」が最上面になるように番号の順に積む。

その後、「最上面のカードを最下面に移動し、その次に最上面になったカードを捨てる」

という操作を繰り返し、最後にのこったカードの番号を出力せよ。


というわけです。

 さて、ここで、枚数を2で割りながら、偶数枚の時にはそのまま、奇数枚の時には、一番上のカードを取り除く、その下のカードは、枚数を2で割った回数分2のべき乗、という感じの手続きまでたどりつきました(詳しくはその2を参照)
 さて、これは、イメージとして、Nを2進表記して、下の方からビットを見ていき、0なら何もせず、1なら、その桁位置*2の数を引く、という操作をしていることになります。ただし最上位ビットには何もしない。
 これをビット操作で見ると、
(1)最上位ビットを取り除く。
(2)左シフト(2倍する)
(3)その結果を元の数から引く。
という一連の操作で解が求まることが分かります。

 こういう作業は、マシン語の方が簡単で、ビットを調べる操作は要するに2で割っていく操作ですから、前回のルーチンと大同小異ということになります。
 今回は、上の(1)~(3)の操作を、2進変換せずに強引に関数計算で計算しちゃおうというわけです。

(1)最上位ビットを取り除く。
 最上位ビットの桁を求めるには、要するに2進の桁数を見ればいいので、
INT(LOG2(N)) です。
 最上位ビットだけの数は、2^INT(LOG2(N))となります。(LOG2(x)は2を底としたxの対数を与える「なでしこ」の組み込み関数です)
 これをNから取り除くのですから、N-2^INT(LOG2(N)) が該当する計算式になります。
(2)2倍する。
 {(N-2^INT(LOG2(N))}*2
(3)元の数から引く
N-{N-2^INT(LOG2(N))}*2=2^INT(LOG2(N))*2-N

ということで、プログラムはこちら。
##################################################
# トリさんの課題、別解2
# 2010/08/03

# 自然数 N の入力
「自然数 N を入力してください」で尋ねてNに代入する。

# 入力チェック
もし(Nの整数部分<>N)OR (N<=0)ならば
 「入力するのは自然数だけにしてね(^_-)-☆」と言う。
 終わる。

# 先頭カード番号=N-(N-2^INT(LOG2(N)))*2=2^INT(LOG2(N))*2-N
先頭カード番号=2^INT(LOG2(N))*2-N

先頭カード番号を言う。
終わる。

# main routine 終了

##################################################

 処理は実質1行になってしまいました。
 ただ関数を使っているので、正しく計算できる数の上限はちと気になります。実務プログラミングでは、ちゃんと計算可能な範囲をチェックするか、あるいは不確実な実数演算を使うよりは、一つ前のルーチンを使う方が安全確実バグ防止になるかもしれないです。
 しかし、こうなると、「仕様」とプログラム(この1行の式)との関連性は、一見しただけでは全く分からないですね~。他人どころか、自分でも1ヵ月もしたら、なんでこの式になるんだっけ(;゚-゚)??となってしまいそうです。もちろん、その2で述べたように、仕様変更に対して極めて脆弱、お手上げです。

 今回は、最初のプログラム(単純にコーディング)で済むはずでしたが、鶏ドリアンさんの解法を見て、思わずのめり込んでしまいました~~。久しぶりにプログラミング領域で楽しかったです。いや、こんな事するなら、朝みんクロ研究室で使うプログラムの開発を先に進めろってね(^_^;)

  • 不思議ちゃん

    不思議ちゃん

    2010/08/03 22:44:36

    なるほど~。logは別に使わないでも出来ますね~。ただループが1つ必要ですけれど。

    # トリさんの課題、別解3
    # 2010/08/03

    # 自然数 N の入力
    「自然数 N を入力してください」で尋ねてNに代入する。

    # 入力チェック
    もし(Nの整数部分<>N)OR (N<=0)ならば
     「入力するのは自然数だけにしてね(^_-)-☆」と言う。
     終わる。

    # N を2進数で考えた時の、最上位ビットを捨て、2倍する。
    # Nからそれを引く。
    # というのが、解になるのだが、Nを2進数で考えたときの最上位ビットだけが立った数を
    # 「最上位ビット桁位置数」という名前を付けると、
    # 答えは N-(N-最上位ビット桁位置数)*2
    # = 最上位ビット桁位置数*2 - N
    # となる。
    # 最上位ビット桁位置数とは、2のべき乗数で、Nを超えない最大のものである。
    # 最上位ビット桁位置数*2とは、2のべき乗数で、Nより大きな物の内の最小のものである。
    # べき乗関数も対数関数も使わないという条件でコーディングする。

    # 最上位ビット桁位置数*2を「最上位ビット桁位置数乃弐倍」という変数名を付けておく

    最上位ビット桁位置数乃弐倍=1
    N>=最上位ビット桁位置数乃弐倍の間
     最上位ビット桁位置数乃弐倍に2を直接掛ける

    先頭カード番号=最上位ビット桁位置数乃弐倍 - N

    先頭カード番号を言う。
    終わる。

    # main routine 終了

     単に2を掛け続けて、Nを超えた時の値が最上位ビット桁位置数乃弐倍なので、これからNを引くだけですね~(^^)

  • 鶏ドリアン

    鶏ドリアン

    2010/08/03 07:42:33

    うちの大学、Linux環境で
    なぜかわからないんですが、math.h読み込んでもpow関数が使えないんですよ。
    (未定義がど~たらこ~たらという趣旨と思わしき英文がダラダラと)

    しょうがないので再定義するはめになりまして
    そうなると、log2関数も再定義せなアカンわけですよ。

    そうなると…逆に全文が長くなりそうなんですよね。
    なので、今の形に落ち着いたんです