コンピュータアルゴリズムの授業の宿題の解答

問題

  • ちょっと変な課題を出してみたいと思う。教科書59頁のリスト3-10(Array4.java)に対して,プログラミングを始めたばかりの高校生が以下のような質問をしてきた。君なら何と答える?自分の言葉で説明するように!
    • 「Array4.java において配列 a, b を定義して,a は {1, 2, 3} で初期化しているから,各々の要素に対する実体(メモリ)が確保されて,そこに 1, 2, 3 が代入されています。その後 b=a; と a を b に「代入」しています。この場合,b 用に実体(メモリ)が確保されて,b の実体に対して,b[0]=a[0], b[1]=a[1] というように各々の値がコピーされるのでは「なく」,b=a; というのは,b を a の「別名,あだ名」として初期化している,,,,と説明されました。でも,a, b が普通の int 型なら,b=a; は,a の内容を b にコピーする,という意味なんだから,a, b が配列の時だって,b=a; というのは,a の内容が b にコピーされる,と解釈するのが自然だと思います。配列の時だって,a の内容が b にコピーされる,というように考えちゃいけないんですか?あだ名とか,別名とか,何か変!そもそも,b=a; という見た目が同一の表現にどうして複数の解釈を要求するのか,それがよく分かりません!変なの!」 <<

解答案

問題文に対抗して、会話するように解答する。

「そうだね、一見変かもしれない。僕もプログラミングを始めたころは、値のコピーとあだ名の違いを混同して、よく困っていた。
なぜ同じ = を使うのに、"代入"と"あだ名"があるのか考えてみよう。その前に、説明に使う言葉についてちょっと話そう。配列やなんかの"あだ名"である変数のことを、専門用語で"参照型変数"と言うんだ。それに対応して、integerみたいな値を扱う変数を基本データ型変数、または基本型変数という。これからは参照型変数、基本型変数という言葉を使うことにするよ。
なぜ基本型変数と、参照型変数というものがあるのか。一見わかりにくい気もするけど、Javaを作った人たちだって馬鹿じゃないはずだ。きっと色々考えた結果、Javaにはこの2種類の変数が必要だという結論に達したから、Javaは今のような姿で存在するはずだよね。
配列で = を使ったときに値がコピーされるようにすると、実は色々なデメリットがあるんだ。余り正確ではないけど、ホテルの予約の例で考えてみよう。
ホテルを予約すると決める、あるいはホテルに『予約取りたいんですけど』と電話をする行為が、まず配列変数(とりあえずintの配列としておこう)int a; を宣言することになる。そして、実際に部屋が5部屋取れたとすると、a = new int[5]; と実際に配列を生成することにあたる。そうなると、配列への値の代入は、ホテルの部屋に人が入ることだね。
さて、ここで新しく配列変数をint
b; と宣言して、b = a; という操作はなににあたるだろうか。あまり自然なたとえではないんだけど、配列の場合はb も a もデータがあるメモリ上の"番地"を表しているんだから、a の部屋番号にb という別の呼び名をつけたことになる。a が"山田家が泊まっている部屋番号"とすると、b はたとえば"さっきチェックインした人達の部屋番号"にでもなるのかな。
これが、配列変数の代入が、値がコピーされるようになっていた場合どうなるだろうか。まず、新たに5部屋分ホテルの部屋を新しく準備しなきゃいけない。そして、中身をコピー!しなきゃいけない。"山田家"を"さっきチェックインした人達"と言うためには、新しく部屋を確保して中にいる人達をコピーしなきゃいけない。クローン人間かよ!って話だよね。実は、配列の中身をそれぞれコピーして新しい配列を作るために、cloneというメソッドがあるように、配列を値ごとコピーするのはクローン人間を作るようなものなんだ。
今見てきたように、配列変数を代入する操作で、値が全部コピーされるとすると、メモリ上に新しく配列のデータを格納する領域を確保して、それぞれデータをコピーしなければいけない。一方、あだ名をつけるだけなら、番地をコピーするだけでいいから、コンピュータの作業量が圧倒的に違ってくるんだ。
それに、値を全部コピー"しなきゃいけない"場面というのは意外とすくない。意外と少ないけど、たまにあるからcloneという方法も一応準備されているんだけどね。

ところで、関数型言語のSchemeならset!とかの副作用つきprocedureを使わない限りメモリの番地とか面倒なことは気にしなくていいのでいっそのことSchemeに(ry」

ちなみに、クラスなどの変数が値ではなく参照の場合、ある状態から複数の発展方法をシミュレートするときに結構面倒。ある状態を1つのインスタンスとして、そのインスタンスを複製して状態を発展させていくときに、参照と値の違いをしらないと、ちゃんとシミュレートできない。まあ、大抵の場合、状態はパラメタをくまなく集めてこれば決定されるから、パラメタをくまなく集めてコピーすればそれでインスタンスの複製ができちゃうけど。

参考になりそうなソース

問題

教科書 76頁において,加算演算子('+')が多種多様な機能を持つことを学んだ。「文字列 '+' 数値」なんてのは,ピントこなかった人もいると思う。授業でこれまで扱った型(基本型と文字列)を使って,色んなものを「足してみて」Javaが定義する '+' の多様性を確認せよ。数値と言っても,float やら double やらあるでしょう。また,'+' 記号にここまで多様な機能を持たせる,というのは,作ったプログラムが「思いも寄らぬ動作」をする('+' 記号が,思った以上のことをしてしまうが為に,こちらの意図通りに動作してくれない)可能性がでてくると思われるが,この点についてどの思うか?素直な感想を述べよ(君がプログラミング言語を設計する場合,どういう戦略を立てるのか,というのを考えてみよ)。

解答案

Javaは型に厳しい印象があるのに、文字列 + 数値 => 文字列 という自動キャストは気持ち悪いので嫌いです。違う種類の変数は自動キャストすべきじゃないと思います。紛らわしいので。演算子がオーバーライドできないのも不便。

自分が言語設計をする場合は、変数の種類をツリー状に分類して、根に近いほうから優先度を高く設定していく。異なる型同士の変数を演算する場合は、ノードを共有する場合(floatとintegerなど)、共有するノードの中で最も優先度が低い型に両方を自動キャストして演算するようにします。ノードを共有しない場合(integerとstringなど)は、明示的にキャストしないとエラー。

問題

int型のキャスト「(int)」を用いて double 型の変数 x を四捨五入して int 型にするにはどのようにすればよいか。x が負の数の時のことも考慮して回答せよ。

解答案

ある少数aは、0以上で1より小さい少数fと、整数nの組み合わせで書けるの。aを四捨五入した値は
f >= 0.5 のとき n+1
f < 0.5 のとき n
とするなら、

  int round(double arg){
    int ret = (int)arg;
    double offset = arg - ret;
    
    if (offset < 0) {offset++; ret--;}
    
    if (offset >= 0.5){
      ret++;
    }
    
    return ret;
  }

という関数roundを使えばよい。