pg09
テーマ シミュレーションとプログラム
ネタ:
来年用のネタ: Scalaでテキスト処理 アナグラムを探す
開発環境 Scala IDE (eclispe) を使用する。
eclipseはプログラム言語 Java の代表的な開発環境。Java以外のプログラム言語にも対応している。
今回は、
プログラム言語 Scala で 確率シミュレーション の実験をする。
Scala は オブジェクト指向言語 と 関数型言語の両方の特徴をもつ マルチパラダイムなプログラミング言語。
マーチン・オーダスキー教授により開発が進められている。無償で利用可能。
Twitterなど、メジャーなシステムに利用されている。
http://qiita.com/oubakiou/items/31fd45ca337d76113a1e Scala採用企業のまとめ
今回の演習では、Scalaで関数型スタイルでコードを作成し、ランダムな事象(クジ引き、サイコロ)に関するシミュレーション実験をする。
ScalaはJVM系のプログラム言語。参考記事
前振り: iPad のダイスアプリ紹介
統合開発環境(IDE: Integrated Development Environment ) によるプログラム開発の演習を以下の手順で進める。
今回使用する IDE は
オープンソースの Eclipse
他のIDEとしては
Microsoft社 Visual Studio
Apple社 Xcode
Google社 Android Studio
などがある。
Javaのバージョンが上がったことにより、Scala IDE に不具合が出ているので対処する。(参考サイト)
対処作業:
デスクトップの レポートフォルダ(ショートカット) を開き、
小橋一秀 → プログラミング入門 → PG2018_09 のフォルダにある次のファイルをコピー(マウス右クリックから)する。
eclipse.ini
ファイルエクスプローラーから
PC → ローカルディスク(C:) → eclipse を開き、
右クリック → 貼り付け → ファイルを置き換える
※C:ドライブのファイルはログイン時に初期化されるため、大学の実習室の環境では毎回この作業が必要となる。
IDE eclipse の起動と設定:
ファイルエクスプローラーから
PC → ローカルディスク(C:) → eclipse → eclipse.exe をダブルクリックで起動
※発行元が不明というダイアログでは、 実行 を選択
プロジェクトの保存先の設定
標準の保存先が ../workspace となっているが、変更する。
Browse…のボタンを押して、アカウント名のフォルダ(氏名または学籍番号)を選択 →
ドキュメント(Documents) を選択 → 新しいフォルダの作成 ボタンを押す →
フォルダ名を pg09 に変更する → OK を押す
→ Launch を押す
eclipse が起動する。
Scala のアプリ開発を設定していく。
Scalaプロジェクトの作成:
eclipse
-> Package Explorer(パッケージエクスプローラ)を右クリック
-> New(新規)
-> Other...(その他)
->Scala Wizards(ウィザード)
-> ScalaProject(プロジェクト) → Next(次へ)
Project name(プロジェクト名) pg09 と入力
→ Finish(完了)
JVMの設定: 今回の演習では、JVMの設定は不要
Javaは半年に一度のVersion UPが計画されている。開発プロジェクトによって使用するJVMを切り替える必要がある場合がある。
Serachから JVMを検索する:
練習用アプリケーション作成:
パッケージエクスプローラ から
pg09 プロジェクト
src フォルダ
→右クリック → New → Scala App(アプリ) 作成
Name: (名前) Test と入力
→ Finish
メッセージを出力するプログラムを作成して実行する。
次のコードをエディタに入力する(以下の画像参照)。
printf("aaa")
実行環境の設定:
緑の矢印の横の小さな下向き参画ボタンから、
Run Configurations
を選ぶ。
Scala Application をダブルクリック
Main class の設定欄に
Test
と入力する。
下の画像を参照:
プログラムの実行:
緑の矢印の実行ボタンを押す。
Scala Interpreter の利用
インタープリターモードでScalaのコードの動作を確認していく。
この様なコマンドを入力し直ちに実行して結果を表示するという操作環境を REPL (Read Eval Print Loop)という。
Scala Console の起動: 今回はScala Consoleは使用しない。
起動画面と、コードの実行の様子。
※今回はこちらを使用する。
ウインドウ(Window) → ビューの表示(Show View) → Scala Interpreter
プロジェクトを選択して OK を押す。
Scala Interpreter の利用方法:
コードを入力後、 Ctrl + Enter で実行。
複数行のコードを書いて、Ctrl + Enterでまとめて実行できる。カーソルの位置はどこにあってもOK
複数の式を評価できる。ブロックにしたり、; で複数の式を記述して実行できる。
※ 1+2;3+4;5+6 の実行結果は 11 になるが、1+2 と 3+4 の式は無駄なコードになるので、warning がでる。
Ctrl + ↑ ↓ で、履歴の利用が可能。(行単位でなく、実行単位)
メニューに、コードのリセットや再実行のボタンがある。
文字入力の練習
以下の例で、あ 以外は 漢字入力OFF で入力する。
"あ"
あ
1+2*3
val a = 1234+5678*9012
a*3
一番下の行に文字が入力されるように、マウスでクリックする
文字を入力後、Ctrl + Enter を押すと入力内容がプログラムとして実行される
※1行ずつ実行してください。
Scala Interpreter で以下を実行:
コードを
操作対象(データ) 操作方法1 操作方法2 操作方法3…
とデータをメソッド(コマンド)で連続的に処理する 関数スタイル で書いていく。
例1)
先頭から6個のデータを取得する:
コード:
"nagoya bunri univ." take 6
結果:
nagoya
例2)
先頭から6個のデータを取得し、その後並べ替える:
コード:
"nagoya bunri univ." take 6 sorted
結果:
aagnoy
例3)
先頭から6個のデータを取得し、その後並べ替え、その後逆順にする:
コード:
("nagoya bunri univ." take 6 sorted) reverse
結果:
yongaa
※これはInterpreterではなく、ScalaコンソールのREPLで実行している様子。
■数式の計算
1+1
2*3
■文字列の操作
+ で結合
"abc" + "xyz"
* で繰り返しコピー
"abc" * 3
*と+の組み合わせ
"abc" * 3 + "xyz"
"abc" + "xyz" * 3
// Scalaでは + や * はメソッド(プログラムのコマンド)。メソッドの書式で記述すると ("abc".*(3)).+("xyz") や "abc".+("xyz".*(3)) になる。
(n) で n番目の文字(Char)を検索(省略形)
"abc"(1)
"abc"(0)
// 省略無しのコードは "abc".apply(1)
// . と ()は省略可能。 "abc" apply 1
// .apply は省略可能。 "abc"(1)
map で変換処理
処理方法は ( _ + " " ) の部分。 _ + " " で、1文字毎に " " を追加する処理になる。処理結果は、Vector というデータの並びになる。
"abc" map ( _ + " " )
処理 mkString でデータの並びを 文字列 に変換する
"abc" map ( _ + " " ) mkString
キーワード val で 値の変更が不可能な変数を宣言する。変数の値は初期化時に決まり、値の再代入や変更は不能になる。
val hw = "Hello world!"
キーワード size で データ列の長さ(要素数)を調べる。
hw size
hw(0)
■整数リストの操作
List(1,2,3)
キーワード take で 引数で指定した個数(2)の要素をリストから取り出す。
List(1,2,3) take 2
キーワード sum で、リストの全要素の合計値を求める。
List(1,2,3) sum
要素を先頭から2個取り出して、合計する。
List(1,2,3) take 2 sum
1~7の数字のリストを変数 a にセットする。
val a = List(1,2,3,4,5,6,7)
a(1)
a sum
関数 map による変換(繰り返し)処理
リスト a の各要素に 1を加えた値と、2倍した)値からなるリストを作成する。
a map ( _ + 1 )
a map ( _ * 2 )
キーワード if 条件式 値1 else 値2 で 条件式が True なら 値1 False なら 値2 に変換する。
a map ( x => if( x > 3) 1 else 0 )
0 と 1 の列に変換後に集計する。
a map ( x => if( x > 3) 1 else 0 ) sum
偶数なら 1 奇数なら 0 に変換する。
a map ( x => if( x % 2 == 0 ) 1 else 0 )
条件を満たす要素数を調べる。
a count ( _ > 4 )
仮保存:
ここまでの演習内容を、一旦、Webclassの第9回課題にコピペしておきます。
ここから下の実験では、バグなどによりEclipseがフリーズする可能性があります。
ランダムを利用したシミュレーション
ランダム処理用のオブジェクト Random を利用するための準備
import scala.util.Random
0~9 のランダム値を1つ求める。
Random.nextInt(10)
Random.nextInt(10)
0~99 のランダム値を1つ求める。
Random.nextInt(100)
Random.nextInt(100)
ランダム値を繰り返し生成する dice オブジェクトを作成する。dice は使用するたびに異なるランダムな値を生成する。値は必要に応じて何回でも生成可能。
val dice = Iterator.continually( Random.nextInt(100) )
※ここで、dice には無限個のランダム値は格納されない。diceは無限に繰り返し利用することができる乱数発生コードになっている。
diceからランダム値を10個や100個とりだし、リストにする。
dice take 10 toList
dice take 100 toList
diceからランダム値を100個とりだし、集計。 ※ 集計結果が 期待値 と比べて 上か下か 考えてみよう。
dice take 100 sum
dice take 100 sum
練習:
dice に対していろいろな処理を試す。
例)
dice take 10 sum
処理方法は、以下から選ぶとよい。
合計を求める sum
ある数値以下なら1に、そうでなければ0に変換する map ( x => if( x < 10 ) 1 else 0) toList
ある数値の個数を集計 map ( x => if( x == 0 ) 1 else 0) sum
ある数値の個数を集計 count ( _ == 0)
※map と sum を使用した個数の集計よりもシンプルなコード
文字列化して結合 mkString
ガチャガチャのシミュレーション
ランダム処理用のオブジェクト Random を利用するための準備が必要です。
import scala.util.Random
Rare 希少 大当り
Uncommon 珍しい 当り
Common 普通 はずれ
gacha という名前の当たり判定表を作成し、指定した位置の文字を調べたり、ランダムな位置の文字を調べるプログラムの例)
val gacha = "R"*3 + "U"*15 + "C"*82
gacha size
gacha(0)
gacha(50)
gacha(Random.nextInt(100))
何回かガチャを回してみよう
100連続ガチャのシミュレーション
ダイスを 100回振った結果を求めるコード dice take 100
ダイスの数結果(数値)に応じて対応する位置の当たり判定をするコード map gacha toList
当たり(R)の回数を数えるコード rg count (_ == 'R' )
val rg = dice take 100 map gacha toList
rg count( _ == 'R' )
種類ごとに集計する
val kekka = rg map { case 'R' => (1,0,0); case 'U' => (0,1,0); case 'C' => (0,0,1) }
kekka./: (0,0,0) { case ((ar,au,ac), (r,u,c)) => (ar+r, au+u, ac+c) }
解説:
rg map { case 'R' => (1,0,0); 引いたいたカードの各種類 R U C に応じて、 (Rが1枚, Uが1枚, Cが1枚) に変換する
kekka./: (0,0,0) 初期値 RUCの所持枚数は各0枚。 /: は kekkaの各要素に対して順番に処理を繰り返す畳み込み演算。map と異なり、各要素ごとに個別に結果を残さずに処理結果を累積して処理する。全要素の処理後に累積値を計算結果とする。
(ar,au,ac) 累積中のRUCの枚数。
(r,u,c) kekkaから順に取り出された要素。
(ar+r, au+u, ac+c) 累積方法。 kekkaから順に取り出した要素を、対応する累積値に加える。
例) 累積値 (0,3,10) に対して、 要素が(1,0,0) なら、累積値は (1,3,10)に更新される
仮提出:
ここまでの演習内容を、一旦、Webclassの第9回課題に提出しておく。下記の提出方法参照。
時間に余裕があるなら、条件を変えて実験する
例1)
ガチャの構成比率の修正。総数は100から変更せずに、比率を変える。
val gacha = "R"*1 + "U"*17 + "C"*82
例2)
ガチャの種類を増やす。集計方法も (1,0,0,0) のように4種類分で集計するように変更する。
val gacha = "S"*1 + "R"*3 + "U"*15 + "C"*81
※ ゲームなどでSR や UR など2文字で表現されるレアリティーは、今回のコードではうまく動かない
YやXなど1文字に置き換えて表現する
例3)
1%以下の確率のガチャを設定する。 0.1% の例を示す。
排出率を修正する。総数を1000にして構成する。
val gacha = "S"*1 + "R"*39 + "U"*150 + "C"*810
dice を修正する。カードの種類数対応した範囲でランダム値を発生させる。
val dice = Iterator.continually( Random.nextInt(gacha size) )
2000回、ガチャを回す。
val rg = dice take 2000 map gacha toList
Sが出た回数を数える。
rg count( _ == 'S' )
種類ごとに集計する。
val kekka = rg map { case 'S' => (1,0,0,0); case 'R' => (0,1,0,0); case 'U' => (0,0,1,0); case 'C' => (0,0,0,1) }
kekka./: (0,0,0,0) { case ((as,ar,au,ac),(s,r,u,c)) => (as+s,ar+r,au+u,ac+c) }
応用編: ガチャはこわい1
上記の例3の実験で、2000回以内にSが出てくるとは限らない。
そこで、Sが出るまで引き直した回数を求める。
dice は 0~999 の範囲のランダム値なので < 1 で 0.1に相当するので、
dice indexWhere ( _ < 1 )
※ < 1 を < 0 にしないように。0より小さいランダム値は発生させていないので無限ループになる。eclipseを止めるしか終了方法はない模様。
indexWhere は左側のデータについて右の式で指定した条件、上の例では _ < 1 になる値が登場した位置を求める関数。
応用編: ガチャはこわい2
上の実験に続けて、100人でSが出るまでガチャをまわして、回した回数の多い順に並べる実験は、以下で。
( Iterator.continually(dice indexWhere ( _ < 1 )) take 100 ).toList.sorted reverse
※ sorted はデータを昇順に並べ替える関数。reverse はデータを逆順に並べ替える関数。
応用編: ガチャガチャマシンのシミュレーション
今回の課題は、デジタルなガチャなので、アイテムの個数に制限は無く、いくらでもガチャを回せる。
このプログラムを、実在のガチャのように1つアイテムを出すと、機械の中のカプセルが1つ減るようにプログラムを修正する。作成例 → サブページ参照
提出方法
Scala Interpreter の実行結果ウィンドウ(上側)をクリックして、 Ctrl + A を押す。
入力したコードと実行結果が全選択される。
Ctrl + C でコピー、または右クリック → Copy する。
WebClass の第9回課題を開いて、問題1 に貼り付けて提出する。仮提出している場合は、以前の入力内容に上書きして提出する。
余談
乱数を利用した演奏
例)以下のコードをまとめてコピペする。Ctrl+Enter で実行する。ドレミと1秒間隔で再生される。
import javax.sound.midi._
val synthesizer = MidiSystem.getSynthesizer()
synthesizer.open()
val instruments = synthesizer.getDefaultSoundbank().getInstruments()
// 楽器 0 をシンセサイザーに設定
synthesizer.loadInstrument(instruments(0))
val channel = synthesizer.getChannels()(0)
// ド レ ミ と 1秒(1000ms) 間隔で 演奏。
channel.noteOn(60 , 64) // 60の高さで音声出力
Thread.sleep(1000) // 1000ms プログラムを停止
channel.noteOff(128) // 音声停止
channel.noteOn(62 , 64)
Thread.sleep(1000)
channel.noteOff(128)
channel.noteOn(64 , 64)
Thread.sleep(1000)
channel.noteOff(128)
ランダム演奏例)
0.3~0.1秒間音を出すコマンドを定義する。以下は、それぞれ +ド ド -ド。
def playR = {channel.noteOn(72 , 64); Thread.sleep(300); channel.noteOff(128)}
def playU = {channel.noteOn(60 , 64); Thread.sleep(200); channel.noteOff(128)}
def playC = {channel.noteOn(48 , 64); Thread.sleep(100); channel.noteOff(128)}
playR playU playC のコマンドを実行すると、音が出る。
ガチャガチャの結果に応じて、音を出す。
val rg = dice take 100 map gacha toList
rg foreach { case 'R' => playR; case 'U' => playU; case 'C' => playC }
旧資料 2016年度用:
REPL(Read Eval Print Loop)の準備:
Console(コンソール)(+マークの付いたアイコン) ▼メニュー
→ Scala Console をメニューから選択
トラブル対応1
// Entering paste mode (ctrl-D to finish)
コンソールに ↑ と表示されたら、入力が受け付けられなくなる。 赤い四角の停止ボタンを押して、REPLを止める。
できたところまでを、↑↑ の提出方法を参考に保存しておく。
Scalaコンソールを開きなおす。
続けて演習を行う。
トラブル対応2
コンソールのログを保存する際に、「保存したファイルを、編集するかで 「はい」 を選択」した場合、Scalaコンソールが開けない。
eclipse の編集画面(コンソールの上の部分)のタブを、ログファイルから、Test.scala にクリックして切替える。
トラブル対応3
コンソールタブを閉じてしまった場合、 再び表示するには: eclipse のメニュー → ウインドウ → ビューの表示 → その他 → 一般 → コンソール
トラブル対応4
コンソール停止後に演習を再開するとプログラムにエラーが出る
→
import scala.util.Random
val で定義した行
などを再実行する。
トラブルの原因:
複数行でコピペしたときに発生する?
Webからコピペしたときに、先頭文字から選択してコピペすると、改行文字がコピペされることが原因かも。
メモ
素数+アニメーション
マルコフ連鎖
すごろく
ランダム演奏(ループで)