授業資料/11 の変更点


#contents

* 前回の課題について [#b8d1786f]

総仕上げだけでなく,前回の課題全般に難しいと思った学生もいるようなので,少し解説しよう.

** 3の倍数 or 3がつく数についてのみ特殊な表示を… [#wcd19674]
これは簡単だ.
まず,丁寧なケースを見せよう(短いことであるし,プログラム全体を見せよう).
// programu source 表記
#highlighter(language=ruby,number=on,cache=on){{
include Math

# (3 の倍数か or3 がつく数字か) をチェックして表示してしまえ
def check(n)

  # 3 がつくかどうか丁寧にチェックするなら, 「3 が含まれなかったら?」と考えて…
  if (n.to_s.index("3") == nil) then
      with_three = false
    else
      with_three = true
  end

  # あとは 3 の倍数かどうかとあわせてチェック
  if ((n % 3 == 0) || with_three ) then
      print("!!!! \n")
    else
      print(n,"\n")
  end
end

# 以下,プログラム本体

# まず,最大値をもらう
n_upper = ARGV[0].to_i

# 1 から順にチェック & 表示する
for n in 1..n_upper do
  check(n)
end
}}
ここでは ''index'' コマンドを使ってみたが,他にもいろいろ使える.
というか,Rubyの解説書(購入が強く推奨されているはずだ)には「文字が含まれているか」を知るためのすばりの方法 ''.include?()'' などが書いてある.
えーと思う人もいるかもしれないが,どちらかというとやはり
&br;
CENTER:&size(24){''初心者がその言語の解説書も無しで''};
CENTER:&size(24){''プログラムを書くのは無理があるから早く買え''};
&br;

ということなんだろう…と思うのがよさそうだ.


*** (上級者向け 1) 「3 が含まれていたら」=「''index("3")'' の結果が数字」というやり方でいくならば [#s8581d99]
// programu source 表記
#highlighter(language=ruby,number=off,cache=on){{
  # 3 がつくかどうか丁寧にチェック
  if ( n.to_s.index("3").is_a?(Integer) ) then
      with_three = true
    else
      with_three = false
  end
}}
としてもよい.
これは,「〇〇に所属しますか?」というためのコマンド ''.is_a?(〇〇)'' を使っている.

*** (上級者向け 2) そもそも ''nil'' と ''false'' は同じ扱い(前回記述)なのだから [#z9e816e5]
あっさりと次のように書ける.というか,これが王道だな.
// programu source 表記
#highlighter(language=ruby,number=off,cache=on){{
# (3 の倍数か or3 がつく数字か) をチェックして表示してしまえ
def check(n)
  if ((n % 3 == 0) || (n.to_s.index("3"))) then
      print("!!!! \n")
    else
      print(n,"\n")
  end
end
}}

&br;
** 逆さの数字も素数か? [#te6f06e3]
とりあえず全体構造を考えよう.

// programu source 表記
#highlighter(language=ruby,number=on,cache=on){{
include Math

# 素数かどうかをチェックする(以前のコピー)
def is_prime(n)
  answer = true
  for i in 2..sqrt(n).floor do
    if (n % i == 0) then
      answer = false
      break
    end
  end

  return answer
end

# 数字を前後ひっくり返す
def inv(n)
  # 実装は後で
  return n
end

# その数字と,ひっくり返した数字が素数かどうかをチェックする
def inv_couple(n)
  if (is_prime(n) && is_prime(inv(n))) then
    return true
  end
  return false
end

# 以下,プログラム本隊
# 以下,プログラム本体

# 調べる範囲の上限をもらう
n_upper = ARGV[0].to_i

# 11以上,上限以下までチェックする
for i in 11..n_upper do
  if inv_couple(i) then
      print("(",i, ", ",inv(i),") are prime numbers.\n")
    else
#      print(i,"\n")
  end
end
}}
これを見ると,実装しないといけない関数は数字を前後ひっくり返す部分だけである.
そこで,そこだけじっくり考えればよい.
とはいえ,順番をひっくり返すだけなので簡単で,例えば次のようになるだろう.
// programu source 表記
#highlighter(language=ruby,number=off,cache=on){{
# 数字を前後ひっくり返す
def inv(n)
  #もらった数字を文字列に直しておく
  in_st = n.to_s
  # ついでにその文字列の長さも覚えておく
  len = in_st.length

  # まず空っぽの文字列を用意する
  ans_st = ""

  # あとは,もらった数字を最後から付け足していくだけ…
  for i in 1..len do
    ans_st << in_st[len-i]
  end

  # あとは数字に戻して答えを返す
  return ans_st.to_i
end
}}
これまた,よくみれば何も難しいことはない.
また,Ruby の本を見ればもっと簡単な方法があることに気づくだろう…

&br;
** 数当てゲーム [#fc397240]
授業時は二段階に分けてあったが,解説はまとめてしまおう.まずはルールを再掲しよう.以下の通りだ.

二つの「桁数が同じ」数桁の整数 n, m があるとする.
そして,n の 1の位の数を a_1, 10 の位の数を a_2, 100 の位の数を a_3, ... m の 1の位の数を b_1, 10 の位の数を b_2, 100 の位の数を b_3, ... とおく.
このとき,以下のように記号の意味を定義する.

+ X と Y という数を次のように定義する.
++ もしも a_i = b_i ならば X_i =  1, そうでなければ X_i = 0.
++ a_i = b_i ではないが,b_i と同じ a_j があるならば Y_i = 1, そうでなければ Y_i = 0.
++ X = X_i の合計,Y= Y_i の合計
+ n, m に対して,上の X, Y を計算し,出力する関数 xy(n,m) がプログラム中にある.

そして,必要なのは次のようなプログラムだ.

+ 「何桁の n にするか」の桁数はプログラムの起動時にパラメータとして与えられる.
+ n は乱数で生成される.
+ 人間の入力 m は ''$stdin.gets'' で受ける.
+ 入力を受けたら n,m に対して  X, Y を出力し,再び入力を待つようにする.
+ n と全く同じ整数 m を人間が入力したら,その旨を表示して終了する.

さて,どう考えようか.まずはともかく全体構造だ.
// programu source 表記
#highlighter(language=ruby,number=on,cache=on){{
include Math

# n, m をもらって X, Y を返す
def xy(n,m)
  # 実装は後で.
  return n, m
end

# 指定された桁数の数字を乱数で作る
def make_n(digit)
  # 実装は後で
  return 0
end

# 以下,本体

# 桁数をもらう
digit = ARGV[0].to_i

# 乱数による指定された桁数の n の生成
n = make_n(digit)

# プログラムの動作確認のために,完成までは答えを表示してしまう(インチキ)
print("Ans: ", n, "\n")

# 以下,入力受付
print("m? : ")
while (s = $stdin.gets) do
  s.chomp!
  m = s.to_i

  # X, Y を求める.
  x,y = xy(n,m) 

  # 完全一致ならおめでとうで,そうでなければ x,y を表示&再チャレンジ
  if (x == digit) then
      print("Conguratulaions!\n")
      # while を脱出
      break;
    else
      print("X: ", x, ", Y: ", y,"\n")
  end

  print("m? : ")
end
}}
ちなみに,''break'' という命令を使っているが,これは ''while'' や ''for'' などのループを脱出する命令である.この場合は ''while'' の外へ抜けるので,プログラムの最後にたどり着き,終了することになる.
もちろん ''break'' を使わない方法でも良い.たとえば,終わったか? という情報を格納しておく変数(こういう変数をフラグ変数という)を用意するなどが考えられる.

&br;
*** 指定された桁数の数字を乱数で [#r9ee20d5]
いろいろやり方はあるが,冷静に考えれば難しくない.
たとえば, ''4桁の数字 = 1000以上 10000未満 = 1000 + (0以上9000未満) = 10^3 + (0以上 9*10^3 未満)'' と理解すれば,ごく簡単に次のように作れる.
// programu source 表記
#highlighter(language=ruby,number=off,cache=on){{
# 指定された桁数の数字を乱数で作る
def make_n(digit)
  n = 10**(digit-1)
  return n + rand( 9 * n )
end
}}
うまい方法が思いつかなかったという人は,そもそも 4桁の数字 = 1000以上 10000未満という認識ができなかったのではないだろうか.
とするとそれは''普段からものごとをいいかげんにしか捉えてない'' ということなので,少し反省が必要だろう.

&br; 
*** 条件にあう箇所の数え上げ [#ncb1e2af]
さて,肝心の X,Y を数える方法だ.
こういうややこしい条件に沿って何かを数えるときは,そもそも正しく理解しているか慎重にならねばならないし,また,プログラムが間違えないようにどうするかという工夫が必要だ.
逆に言えば,ただ単に数え上げていくと上のいずれかに大体失敗する.
強調しておこう.
&br;
CENTER:&size(24){''ややこしい条件に対して工夫なしでいどむと''};
CENTER:&size(24){''失敗する確率が非常に高い''};
&br;

とくに今回の問題はカウントしてはならないケースをカウントしてしまう「''重複カウントミス''」が多発しそうである.もう少し丁寧に言えば,「チェックして該当とわかったら,次からはそこはチェックに使わない」数字があるので,このことをどうやって確実に実行するかということになる.こういう場合は,「既にチェックした箇所をもうチェックしないようにマーキングしてしまう」工夫が有効だ.
丁寧に言うと,まず,
&br;
CENTER:&size(24){''数える順番の工夫等だけでは重複カウントミスを防ぎきれない''};
&br;

ので,

&br;
CENTER:&size(24){''重複カウントミスを防ぐには,''};
CENTER:&size(24){''もう該当しないよとデータにマークして書き換えてしまえ''};
&br;

ということになる.

さて,ではこのマーキングをどう行うのか,実際のコードを見て理解しよう.
// programu source 表記
#highlighter(language=ruby,number=off,cache=on){{
def xy(n,m)
  n_st = n.to_s
  m_st = m.to_s

  size = n_st.size

  # 文字も位置も一致するケースをチェック
  for i in 1..size do
    if (n_st[i-1] == m_st[i-1]) then
      n_st[i-1] = "x" # 一致したらその場所に x とマーク.これでこの文字はもう数字ではない.
      m_st[i-1] = "-" 
        # m のこの文字はもう一致カウントに使わない.間違えて重複カウントしないよう,
        # n のどの文字とも一致しない文字を入れておく
    end
  end

  # x となっている箇所を数えるだけ.
  x = 0
  for i in 1..size do
    if (n_st[i-1].chr == "x") then
      x += 1
    end
  end

  # 文字「のみ」が一致するケースをチェック
  for i in 1..size do
    for j in 1..size do
      if ( (m_st[i-1] == n_st[j-1])  ) then
        n_st[j-1] = "y" # 一致したらその場所に y とマーク. これでこの文字はもう数字ではない.
        m_st[i-1] = "-" 
          # m のこの文字はもう一致カウントに使わない.間違えて重複カウントしないよう,
          # n のどの文字とも一致しない文字を入れておく
      end
    end
  end

  # y となっている箇所を数えるだけ.
  y = 0
  for i in 1..size do
    if (n_st[i-1].chr == "y") then
      y += 1
    end
  end

  # 結果を返す
  return x, y
end
}}
こうすれば,難しく考えることなく間違えないように出来る.
カウントに失敗するプログラムになってしまった人は,この書き方で自分なりにやり直してみよう.

&br;
* ファイル入出力,ファイル操作 [#xdf385a9]
さて,今回も少しライトな内容である.
これまで,データの入出力は,キーボード, 画面,もしくはリダイレクト( | や <,  > というやつ)についてのみ学んできたが,これには複数のファイルを同時に扱えないという限界がある.
また,大事なファイルを扱う際は,プログラム側で最初にバックアップを取るなどの配慮もしたい.
そういう意味でも,ファイル入出力と最低限のファイル操作について学ぼう.

** ファイル操作 [#x03e146a]
まずはファイルを「コピー」「移動」「名前を変更」「消す」などの, ファイル操作について学ぼう.
これらができるようになると,ファイルのバックアップも自由自在だ.

さて,これらの操作については,Ruby の ''fileutils'' モジュールを使うのがよい. そこでまずは
&br;
CENTER:&size(24){''ファイル操作を行う際は,プログラムの最初に''};
CENTER:&size(24){''require "fileutils" という行を入れよう''};
&br;

さて,このおまじないがされている場合,次のようなファイル操作コマンドが扱えるようになる.

''ファイル操作 簡易表''
| 操作 | 文法 | 説明 |h
| (存在しなければ)ファイルを作る | FileUtils.touch(A) | 空っぽのファイルが出来る.存在するファイルを指定した場合は,そのファイルの更新日時を新しくするだけ |
| ファイルコピー | FileUtils.cp(A,B) | A から B にコピー. B はファイル名でなくディレクトリ名でも良い |
| ファイル移動 | FileUtils.mv(A,B) | A から B に移動. B はファイル名でなくディレクトリ名でも良い.&color(blue){A と B が入っているディレクトリが同じだと,単なる名前の変更ということになる}; |
| ファイル消去 | FileUtils.rm(A) |  |
| ディレクトリ作成 | FileUtils.mkdir(A) |  |
|>|>| その他に沢山ある… |

さて,少し慣れよう.

*** 一定のルールで名前が決められたファイルを大量に作る [#w96c30dd]
例えば,''test_00.dat'', ''test_01.dat'', ... , ''test_30.dat'' というようにファイルを新たに作成する必要が生じたとしよう.
そういう時は,次のようなプログラムを組むことになる.
// programu source 表記
#highlighter(language=ruby,number=on,cache=on){{
include Math
require "fileutils"

def make_fn(n)
  if (n > 9) then
      st = n.to_s
    else
      st = "0" + n.to_s
  end

  return "test_" + st + ".dat"
end

for i in 0..30 do
  FileUtils.touch(make_fn(i))
end
}}

&ref(/materials/notes.png); 以降の実習のために,このプログラムを実行しておこう.

&br;
*** 一定のルールで名前が決められたファイルをコピー [#f35995c1]
では,上記で作ったファイル31個のバックアップ,すなわちコピーをとる必要に迫られたとしよう.
ただし,コピーの名前が決まっていて,''test_00.dat'', ''test_01.dat'', ... , ''test_30.dat'' という名前をそれぞれ ''backup_00.dat'', ''backup_01.dat'', ... , ''backup_30.dat'' という名前にしてバックアップしたい.

&ref(/materials/notes.png); この目的を実現するプログラムを書いて,実行しよう.

具体的には,そのプログラムを bk.rb とすると,
  ruby -w bk.rb

としてプログラムを実行すると,もう31個のファイルが ''backup_00.dat'',''backup_01.dat'', ... , ''backup_30.dat'' として現れるということになる.

&br;
*** 一定のルールで名前が決められたファイルを消去 [#i7d8610b]

&ref(/materials/notes.png); この実習でできた62個のファイル ''test_00.dat'', ''test_01.dat'', ... , ''test_30.dat'', ''backup_00.dat'', ''backup_01.dat'', ... , ''backup_30.dat'' を消去するプログラムを書いて,それを実行してこれらのファイルを消そう.

&br;
これで,前回教わった文字列処理と合わせればファイルをだいぶ自由に操作できることになる.

&br;
** ファイルの入出力 [#ff3b38a4]
さて,今度は自分で名前を指定したファイルの中身を読んだり(入力),そのファイルに何かを書き込んだり(出力)することを学ぼう.

まず,ファイルの入出力にあたっての約束事を書いておこう.
ファイルの入出力の為には,次の行為が必要である.
&br;
CENTER:&size(24){''ファイルの入出力の前には open コマンドの実行が,''};
CENTER:&size(24){''後には close コマンドの実行が必要である''};
&br;

以下,解説しよう.

&br;
*** open コマンド: ファイルを使うよ〜と宣言する [#sa856d04]

ファイルを使う前に,そのファイルを「どう使うのか」を宣言して,''ファイル変数'' に結びつける必要がある.
文法は以下のとおりである.
&br;
&size(24){''&color(blue){ファイル変数}; = open(&color(blue){ファイル名};,&color(blue){モード記号};)''};
&br;

この ''open'' のあとに,ここで宣言されたファイル変数に対して gets だの print だのを使うことになる.
また,モード記号は以下のとおりである.分かりにくいが,よくよく注意しよう.

''open のモード''
| 記号 | モード | 説明 |h
| "r" |  読み込みのみ | モードを指定しないとこれになる |
| "r+" |  読み書き両方 |  |
| "w" | 新規作成 書き込み | &color(blue){既にファイルがある場合空っぽにされるので注意!}; |
| "w+" | 新規作成 読み書き両方 | &color(blue){既にファイルがある場合空っぽにされるので注意!}; |
| "a" | 追加書き込み |  |
| "a+" | 追加書き込み,読み込み |  |

とりあえず,モード "w" と "w+" は要注意だと覚えておこう.

&br;
*** close コマンド: ファイルを使い終わったよ〜と宣言する [#nee736f9]

ファイルを使い終わったら,そのファイルを「使い終わったよ」と宣言してファイルを自由にしてやる必要がある.
文法は以下のとおりで, これは簡単である.
&br;
&size(24){''&color(blue){ファイル変数};.close''};
&br;
&br;

そして,肝心の入出力であるが,これまでに学んだものがほぼそのまま使えて,以下のようになる.

&br;
*** gets コマンド: ファイルから読み込む(入力) [#d6893fee]

使い方は簡単で,以前の方法に対して頭に ファイル変数. を以下のように付けるだけである.
&br;
&size(24){''&color(blue){ファイル変数};.gets''};
&br;

&br;
*** print コマンド: ファイルへ書き込む(出力) [#ua5a6b79]

これも同様に以前の方法に対して頭に ファイル変数. を以下のように付けるだけである.
&br;
&size(24){''&color(blue){ファイル変数};.print(&color(blue){書き込みたいもの};)''};
&br;
&br;

さて,ファイル入出力に対してピンと来ない向きもあるだろうから,以下の例を試してみよう.
&ref(/materials/notes.png); 以下の例を自分でも実際に行って理解しよう.

例えば,''input.dat'' という名前のファイルに,
> Your apple is large and my apple is small.
> His orange is yellow.

という文が書かれているとする.そして,この文中の apple を lemon に修正したものを ''output.dat'' というファイルに書き込むことを考えよう.
すると,そのプログラムは例えば以下のようになる.読んで理解しよう.
// programu source 表記
#highlighter(language=ruby,number=on,cache=on){{
include Math

# 入力用, 出力用にそれぞれファイルを open する
in_fn = open("input.dat","r")
out_fn = open("output.dat","w")

# 行がある限り入力用ファイルから読み込み (while との組み合わせは以前やったね)
while (line = in_fn.gets) do
  line.chomp!

  # 文字列の置き換え(前回の授業)
  out_line = line.gsub("apple","lemon")

  # 出力用ファイルに出力
  out_fn.print(out_line,"\n")
end

# 終わったので,そろぞれのファイルを解放
in_fn.close
out_fn.close
}}
実際,このプログラムを動かすと ''output.dat'' というファイルが新たに作られ(すでにある場合は中身が破棄されて),
> Your lemon is large and my lemon is small.
> His orange is yellow.

という中身になっているはずだ.
さて,これで入出力に関しては今までの ''gets'' や ''print'' の使い方とほぼ同じことがよくわかるだろう.


* 今日の総仕上げ [#tbde6640]
ファイル操作とファイル入出力の両方を使う必要がある,次の条件を満たすプログラムを書こう.
条件は以下の通りである.

+ 入力用のデータが入っているファイル名を一つめの起動パラメータとしてもらう
+ 実行時にまずは最初にファイルのバックアップを行う
+ 二つめの起動パラメータは1文字である.その文字を c と表しておくと,プログラムは入力データの各行がその文字 c を何文字含んでいるかを調べ,''output.dat'' というファイルに行番号と共に出力する.

これを具体的に行うとつぎのようになるだろう.
まず例えば,''input.txt'' というファイルに,阪大 web の Meet the President (「ようこそ総長室へ」の英語版)の総長の挨拶文の一部を
>  The motto "Live Locally, Grow Globally" at Osaka University has a deep meaning
>  beyond the historical significance of the university's roots which reach back to
>  Kaitokudo1 and Tekijuku2. These two establishments were not only places of
>  learning open to the public, they were also schools possessing cutting-edge
>  knowledge in their day, places of unimpeded study for citizens. The
>  inspirational spirit of Kaitokudo and Tekijuku defines the future course for
>  Osaka University, an institution imparting "knowledge" both locally and
>  globally. 
>  &br;
>  Osaka University has always emphasized: (1) Cutting-edge research, (2) the
>  creation of, in the University's tradition, new research fields by amalgamating
>  previously unknown fields, (3) an atmosphere that reveres study and research
>  (which is why we have been called a "University of Education"), (4) respect for
>  a liberal arts education at our graduate schools, and (5) active contributions
>  to society encouraged through university-industry cooperation. Now it's time for
>  the University to embrace this unique education and research style as the
>  definitive "Handai style [Osaka University style]." 
>  &br;
>  Osaka University strives to nurture researchers and professionals engaged in
>  cutting-edge research, doing so with the full trust of society. In other words,
>  producing people with true wisdom, refinement, and the power to design and
>  communicate. Centering on these characteristics and looking to the future, the
>  University aims to become a university looking ahead as times change; not a
>  university only for one nation or only for researchers, but a university walking
>  forward in harmony with the citizens of Osaka. This is the style of "knowledge"
>  we aim to impart. Osaka University's staff, students, researchers join hands to
>  build such a university, and the finest evaluation we can receive is when
>  society looks and says, "People who've studied at Osaka University really are
>  one step ahead." 

といれておいたものを入力データファイルとしよう.
そして,自分の書いたプログラムが例えば ''c_count.rb'' だとすると,文字 ''e'' の数え上げを

  ruby -w c_count.rb input.txt e

として行わせると,''input.txt'' と同じ中身の ''input.txt.bak'' というバックアップファイルが作られ,そして ''output.dat'' というファイルに
>  1 : 6
>  2 : 6
>  3 : 8
>  4 : 9
>  5 : 8
>  6 : 6
>  7 : 3
>  8 : 0
>  9 : 0
>  10 : 8
>  11 : 7
>  12 : 9
>  13 : 8
>  14 : 4
>  15 : 6
>  16 : 10
>  17 : 5
>  18 : 0
>  19 : 9
>  20 : 7
>  21 : 9
>  22 : 9
>  23 : 7
>  24 : 6
>  25 : 6
>  26 : 6
>  27 : 9
>  28 : 8
>  29 : 3

というデータが書き込まれている,という結果になる.

&br;
* レポート [#t1752a22]

本日受けた講義および行った実習について,簡単にまとめた報告をせよ.
また,総仕上げの部分で自分で書いたプログラムを報告せよ.

もちろん各自の

+ 所属(学部,学科)
+ 学籍番号
+ 学年
+ 氏名
+ 日時
+ 肝心のレポート内容(得た知見,作業について気づいたこと等も)

を書くのを忘れないように. 

* about Icons, ClipArts [#xc4ee8c4]
Some icons in this page are downloadable at [[ICONFINDER:http://www.iconfinder.net/]].

The "note" icon &ref(/materials/notes.png); designed by [[Marco Martin:http://www.notmart.org/]] is distributed with the LGPL licence,
the "warning" icon &ref(/materials/warning.png); designed by [[Alexandre Moore:http://nuovext.pwsp.net/]] with the GPL licence
and the "triangle" icon &ref(/materials/JNorth_arrow-right-sm.png); designed by [[Joseph North:http://sweetie.sublink.ca/]] is distributed with the [[Creative Commons (Attribution-Noncommercial-Share Alike 3.0 Unported):http://creativecommons.org/licenses/by-nc-sa/3.0/]] licence.

Some clip arts used in this page are downloadable at [[Open Clip Art Library:http://www.openclipart.org/]].
We deeply appreciate their superb works. With licence, they describe that "the actual clipart content on open clipart library is Public domain" in the web.

// ━┃┏┓┛┗┣┳┫┻╋


// コマンドライン入力は「行頭をブランクで始める」.
// コマンドライン出力は「行頭を > で始める」.

// 実習アイコン
// &ref(/materials/notes.png); 

// 注意アイコン
// &ref(/materials/warning.png);

// Link アイコン
// &ref(/materials/JNorth_arrow-right-sm.png);

// OK アイコン
// &ref(/materials/OK.png);

// NG アイコン
// &ref(/materials/NG.png);

// 大文字での強調 
// CENTER:&size(24){''ほげほげ''};

// programu source 表記
// #highlighter(language=ruby,number=on,cache=on){{}}