[[SCALAの記事一覧]] &topicpath; *はじめに [#c3d68e63] このページは関数脳を作るべく、関数言語を題材にした99個の問題を解くのではなく、 解いてある回答を見て学ぶために作ったページです。 再帰的に解いてあると、関数言語だなーという気分になるのは、じぶんだけでしょうか? まだ、全問といていませんが、極力1日1問のペースで解いていこうとおもっています。 ページが大きくなってきたので、32問目以降は下記のページに記載しています。 [[scala 99problem 32~]] *目次 [#b66f8a7d] #contents こんにちは、みなさんお元気ですか。 このページはプログラミング言語SCALAについてまとめたページです。 Androidアプリケーションの開発やクラウド開発、 そして企業のリソースをWebAPI化するなどの プログラミングには信頼性や資源の多さからJavaが有効だとおもいますが、 SCALAはそのJavaアプリケーションをさらに効率よく開発する可能性を秘めています。 *Scalaとは [#g9395f0f] Scalaは、JVM上で動作するオブジェクト指向+関数型言語で、 Javaとほぼ完全な相互利用が可能な言語です。 これまでの言語の集大成に近い言語です。 そのため、各言語の優れた概念を取り入れているわけです。 軽い気持ちで学ぶと、あちこちの言語の概念の基本を抑えたほうが近道なこともあり、 色々な言語の入門書をわたり歩くはめになります。 作者であるMartin Odersky教授はJavac開発の貢献者であるとともに、 Java5 Genericsの仕様策定にも参加しています。 というわけでSCALAはJava言語と親和性の高い関数型言語です。 コンパイルするとJavaのクラスファイルになるんですよ。 Javaのライブラリは膨大で他の言語に比べて日本語の処理方法がしっかりしています。 そしてコードが簡潔に書けるので生産性が高いといわれています。 たとえばBeanクラスを記述する場合、例は下記に記しますが、3分の1の コーディングですみます。 *Scalaの紹介 [#xa9d618e] http://qcontokyo.com/pdf/qcon_TakashiMiki.pdf **Scala開眼 [#p43ad847] http://www.h7.dion.ne.jp/~samwyn/Scala/scalaindex.htm **その他の特徴 [#i82a745f] -関数を生成して返す関数を定義できる -配列のパターンマッチング -XMLを簡単に扱える -多重継承ができるので、メソッドを部品のように扱える JavaにはできないことができるのにJavaと親和性が高いのです。 **練習問題 S-99 Ninety Nine Scala Problems [#zc5b14a2] 何らかの練習問題をこなすことで力がつくとおもいます。自分で解くことも大切ですが模範回答から学ぶことも大切です。 このページは下記の S-99 Ninety Nine Scala Problems http://aperiodic.net/phil/scala/s-99/ の回答をつかって学習していくページです。 模範回答をみてえられる知識は実践向きの知識とも言えるかとおもいます。 パズルゲームを解いていくような気分で1日1問を解説していければとおもっています。 **99問題の日本語訳 URL [#h1e5ee9e] http://study-func-prog.blogspot.com/2009/05/scala-s-99-ninety-nine-scala-problems.html *対象者 [#a4017fbd] SCALAのインストールは完了し、HelloWorldを実行し終えた程度の読者を想定 SCALAをやるのは全くのはじめてという方向け SCALAはコンパイルすると、JAVA言語のクラスを生成する関数型言語です。 型推論を行ってくれるので、マスターすれば生産性が向上します。 *趣旨 [#k9b945e2] 有名な練習問題99問が回答付きで英語ながらも存在しているので、 回答をみながら、ポイントをまとめていこうかという趣旨です。 問題自体はリストを処理するAPIを知っている人向けなのですが、 問題が出てくる毎にそのメソッドを紹介していきます。 *参考:リストを操作するAPI [#dc8ce051] https://www.scala-lang.org/docu/files/api/scala/List.html ***ローカルにAPI資料をもってきたい場合 [#t5cff7df] sbazというインストーラからダウンロードします。 名前の由来は「Scala Bazaars」です。 DOSプロンプトで操作します。 rem はDOSのコメント行を意味しています。 rem sbazのあるディレクトリにカレントディレクトリを移します。 cd C:\scala\bin rem sbazを使えるようにします。 sbaz installed rem 一覧を取得します。 sbaz available rem ドキュメントをダウンロードします。 sbaz install scala-devel-docs 下記のフォルダにAPI資料をダウンロードできます。 C:\scala\doc\scala-devel-docs\api *1問 [#c993a2a7] 1問目は、回答を自分の環境で実行させること自体が学びが大きいです。 **手順 [#l78cdce7] -回答をP01.scalaというファイル名で保存します。 ***インタプリタ形式で確認するには、 [#scb13ea3] scalaでインタプリタ形式で :load .\P01.scala と入力すると 読み込んでくれます。 ***確認方法は [#xa5e1ff8] P01.last(List(1,2,3,4,5)) です。 ***最後の要素を取得する [#ffe2eca1] .last **Scala Listクラスのメソッドを全部実行してみてるURL [#zf18b096] Scalaを学び始めていきなりどんなメソッドがあるのかさっぱりわからないと思う、 片っ端からリストクラスを操作するメソッドを試したつわものがいました。 使い方が大体わかるかも。ただしバージョンは2.7ベースな点に注意。 http://kaitenn.blogspot.com/2010/02/scala-list.html **2.8のListのメソッド一覧 [#x7906894] ちなみに2.8のListのメソッド一覧は下記です。 https://www.scala-lang.org/docu/files/api/scala/collection/Traversable.html *2問 [#n5ca2709] 2問とは関係ないのだが、今度はEclipseでSCALAを実行させてみたいとおもう。 そのためには下記のプラグインをインストールすることが必要だ。 Scala IDE for Eclipse http://www.scala-lang.org/node/94 **Scala Eclipse Pluginの導入 [#fe8b9747] Eclipse開いて、「Help -> Install New Software... -> Workwithフォルダに Available Software -> Add Site..」でLocationに「http://www.scala-lang.org/scala-eclipse-plugin」と入れる あとは、直感的にわかるので省略 **確認用メイン [#u8fc4086] 回答をエクリプスから実行するにはメインメソッドが必要ですので、 下記のような実行用のメソッドを用意するといいでしょう。 object HelloScala { def main(args:Array[String]) { println("Hello Scala!") println("P03") println(P03.nth(2,List(1,2,3,4))) System.exit(0) } } **本題の2問目 [#ne66d693] // But pattern matching also makes it easy. def penultimate[T](l: List[T]): T = l match { case e :: _ :: Nil => e case _ :: tail => penultimate(tail) case _ => throw new NoSuchElementException } 1問目と比較してみる def last[T](l: List[T]): T = l match { case e :: Nil => e case _ :: tail => last(tail) case _ => throw new NoSuchElementException } ***scalaは配列要素をパターンマッチできるようだ。 [#p80b5dac] 2問目の最後から2つ目にマッチ case e :: _ :: Nil => e 1問目の最後にマッチ case e :: Nil => e ::は配列の要素間の区切り文字である。 ***配列の長さを取得する [#h772481a] .length ***右からn個の要素を取得する [#n1cb2746] .takeRight(2) ()はapplyメソッドと同等らしいです。 ***先頭を取得する [#z25fced1] .head *3問目 [#k49dbbd9] 回答に (n, l) match { } という構文が使用されている 第一パラメータっぽい変数nがmatch内で使えるようになっている ***n番目の要素を取得する [#gb93f5e2] 配列変数(n) *4問目 [#c6f8d542] ***def内にdefを入れ子にできる [#y1cc836a] 例 def foo = { def bar = { } bar } ***先頭以外の要素 [#o852f411] .tail *5問目 [#ma5248d6] ***配列を逆にする [#r0ba9d89] .reverse **配列を連結する演算子::と::: [#m7f4bb61] 配列を連結する演算子には::と:::があって、知らないと困る ***::の例 [#g9697964] List(1,2,3) :: List(4,5,6) 結果 2番目の配列の先頭に格納される List(List(1,2,3),4,5,6) ***:::の例 [#q33c0004] List(1,2,3) ::: List(4,5,6) 結果 List(1,2,3,4,5,6) ***配列の連結演算子:: [#s5798690] ***match内での先頭とそれ以外の分割(効率はn^2なので小さい配列向き) [#y9c37311] case h :: tail => reverse(tail) ::: List(h) ***Nil=List() と List(1,2)=1::2::Nil [#r3204da6] 配列の書き方で 1 :: 2 と書くとエラーになるが 1 :: 2 :: Nil と書くと List(1,2) として認識される **配列の要素をまずは、初期値を処理し、次からは次の要素と処理結果とを処理させる [#b244a202] ややこしいが、その分応用できる箇所をすっきりと記述できる。 .foldLeft 左からぱたぱた倒れていくような感じの関数で初期値と、 名前なしの関数を与えるようなイメージ -例 (List(2,3,4).foldLeft (1)) {(x,y) => x+y} -結果 10 これは1から4までの数字を合計するというもの まず、1と2が、xとyに格納されて計算結果が自動的に左側のxに格納される 次にxと3が計算され、xに格納 どうようにxと4が計算され結果が出力されるという感じだ。 xとかyとかがなくても、次の書式で対応可能だ。 (List(2,3,4) foldLeft 1) {_+_} ***foldLeft・foldRightは別名があり、それぞれ「/:」「:\」とも書ける。 [#j35ee226] foldLeft は /: とも書けるので次のようにより簡潔に書ける (1 /: List(2,3,4))(_+_) 初期値が先頭にきている感じだ。 ()を閉じ終えてからくる()が、処理。 ***回答を理解できるようになったので解説してみる [#mddb4ff8] というわけで、次の処理内容をみてみると以下のことが理解できる def reverse[T](l: List[T]): List[T] = l.foldLeft(List[T]())((r, h) => h :: r) List[T]()は初期値であり、空の配列である。 最初は空の要素と左の要素が処理され最初の要素がrに格納される 次の要素はhに格納され最初の要素はrなので、(2番目の要素,最初の要素) が格納される。 **応用:Beanを定義する。 [#hf8ee7ab] ここまでは、配列の中身が数値とかだったので、もう少し込み入った情報を扱いたい場合もあるかとおもいますので、Beanの扱いについて説明いたします。 その前にオーバライトの書き方を紹介します。 ***Javaメソッドのオーバライド [#d7458651] defの前にoverrideをつけます。 override def toString="(name:"+name+ " value:"+value+")" **case classを使った定義方法 [#jfa5c6e4] -例 case class FieldValue(name:String , value:String ) { override def toString = "(name:"+name+ " value:"+value+")" } たったの3行です。おそらく1行でもいけるでしょう。 **通常のBeanの設定方法 [#hae76bdb] ***Beanの定義 [#e770c35c] class FieldValue(_name : String , _value : String) { val name = _name val value = _value override def toString="(name:"+name+ " value:"+value+")" } ***コンストラクションを増やしたい場合 [#z7935a70] 例 class FieldValue(_name : String , _value : String) { val name = _name val value = _value override def toString="(name:"+name+ " value:"+value+")" def this(_name:String) = this(_name,null) } ***Beanインスタンスの作成 [#ta18a7e3] val field1 = new FieldValue("key1","data1") val field2 = new FieldValue("key2","data2") val field3 = new FieldValue("key2","data2") val field4 = new FieldValue("key2","data2") ***Beanの配列 [#eea500d5] List(field1,field2,field3,field4) *問6 [#jb8a4b39] 答えは簡単に def isPalindrome[T](l: List[T]): Boolean = l == l.reverse だった。 **scalaのfor文 [#cad712c0] 配列以外でループさせたい場合につかうかもしれない for (x <- (0 to 10)){ println(x) } ***takeWhile [#b6a12347] ちなみにループ中にループを抜けたい場合はtakeWhileをつかう var continue = true for (x <- (1 to 20).takeWhile(e => continue)) { println(x) continue = x < 5 } var continue = true (1 to 20).takeWhile(e => continue).foreach { x => println(x) continue = x < 5 } -ただし、上記の例でバージョン2.7.7では意図した結果になるのに対して、2.8Betaだとcontinueのスコープがずれてしまい、別々の変数とみなされてしまいます。 とりあえず、バクかなとおもって本家にバグ報告してみたところ下記の回答がかえってきました。 **SCALAの本家から回答が帰ってきました。 [#v9d8ed93] this is the result of an intentional change. Range is strict now. one way to get lazy behavior would be to insert toStream: "(1 to 20).toStream.takeWhile". in general, though, mixing for comprehensions and side-effects is discouraged. つまり下記のように(1 to 20)の部分を(1 to 20).toStreamとすればよいようです。 var continue = true for (x <- (1 to 20).toStream.takeWhile(e => continue)) { println(x) continue = x < 5 } **配列の連結 [#lb9151aa] 配列の連結は先頭にしかできないので どうしても末尾にしたい場合は、 reverseをつかって先頭に追加してから、 またreverseでもどしたりする。 **文字列格納用配列の宣言 [#m0b9dc5b] var result :List[String] = List() が正解で var result = List() ......NG Error だと、空配列しか受け付けない配列として宣言したことになるようです。 ***配列の追加 [#of21c1dc] 配列が先頭にしか追加できない result = "a" :: result だとエラーにならないけど result = result :: "a" だとエラーになるってこと ***戻り値のあるメソッドの書き方注意点 [#mc57b072] メソッドの戻り値がある場合は、メソッドの中身を書く際に={中身}とすること そうしないと illegal start of declaration というエラーがかえってきます。 ***キャストの仕方 [#h4071f02] クラスの型を正しく指定しないといけませんので指定します。 BufferedSourceでキャストする方法 .asInstanceOf[BufferedSource]. ***動的変数を持つ関数の作り方 [#a10ec873] 動的変数は try catch の中のプログラムをブロックとして、 あとで追加できるようにする仕組みで、ファイルを閉じるなどの 必ず行うような処理を簡潔に書くことができます。 def 動的関数名(引数)(動的変数 => Unit){ } ***動的変数を持つ関数の使い方 [#ib5c9174] 動的関数名(引数){(動的変数 ) => 処理内容 } **文字列を配列に変換する [#nc5cd2c6] ファイルから読み込んだ文字列の場合、いったん配列にしてやらないといけない だから文字列を配列に変換するメソッドをつくってみました。 def to_a(text:String):List[String] ={ var result :List[String] = List() var len = text.length; for (x <- (0 to text.length -1)){ result = text.substring(len-x-1,len-x) :: result } result } 別解:JavaのtoCharArrayをつかう例。これだけでもいいけどArray型になってしまうため、型変換メソッドも用意しました。 //ArrayをList配列に変換します。 def toListoReverse(array:Array[Char]):List[Char]={ var result :List[Char] = List() array.foreach{ x=>result = x :: result } result } //文字列を配列に変換します。 def toList(string:String):List[Char]={ toListoReverse(string.toCharArray).reverse } **ファイルの読み込みを動的変数で簡潔にする [#s98a5e3d] ファイルの読み込みはclose処理をいれなきゃいけないのですが、 動的関数をつかえば、簡潔に表現できます。 import scala.io.Source import scala.io.BufferedSource import java.io._ // ファイルの読み込みを動的変数で簡潔にする def read(path: String)(block: Source => Unit) ={ var fis :FileInputStream =null var sou :Source =null try { fis= new FileInputStream(path) sou= Source.fromInputStream(fis) block(sou) }finally{ sou.asInstanceOf[BufferedSource].close fis.close } } //readメソッドの使い方はこんな感じ read("sample.txt"){(in:Source) => for(line <- in.getLines){ println(line.stripLineEnd) } } ファイルをcloseする箇所が省けているのがわかります。 ***応用:ファイルの内容を一気に配列化するメソッド [#re0a257f] //ファイルをすべて読み配列で返します。 def readAll(path: String):List[String]={ var result :List[String] = List() read(path){(in:Source) => for(line <- in.getLines){ result = line.stripLineEnd :: result } } result.reverse } //使い方 readAll("sample.txt").foreach(println _) とってもシンプルになりました。 *問07 [#u51f94f7] **scala.Anyとは [#sea29c93] Scala のすべてのクラスの継承元となる基底クラス (Int、Float、Double などの型やその他の数値型) は scala.Any 型です。 **入れ子状態になっている配列の入れ子を解消してフラットな配列にする [#pfa20fee] flatMapをつかいます。flatMapは基礎的メソッドです。 def flatten(l: List[Any]): List[Any] = l flatMap { case l: List[_] => flatten(l) case e => List(e) } -flatmapもmapもArray()で行えば、Arrayをかえしますし、Listで行えばListをかえします。 - => 記号は左側にブロック変数を指定できるので重要です。 - さらにブロック変数はcaseによってリストのパターンごとの処理が可能になっています。 *問08 [#e36ac1fa] **値が一致する要素を削除する [#v5207dcb] match処理内のcase処理部分でつかいます。 tail.dropWhile(_ == e) 普通に式を評価しようとするとvalue dropwhile is not a member of Listとエラーになります。 *問09 [#m0f568a4] Haskellでいうところのgroupメソッドとかなり類似した関数のようですね。 最近、下記のようなところでもりあがっているのを知りました。 http://wordprogress.org/archives/366 それはさておき、ここの回答でつかわれているテクニックに焦点をあててみていきましょう。 **多重代入 [#r04dde2d] ScalaもRubyのように多重代入ができるんですねぇ。 scala> val (aa,bb)=(11,22) aa: Int = 11 bb: Int = 22 ***List形式の場合の多重代入 [#tfc30d30] scala> var list = List(1,2,3) list: List[Int] = List(1, 2, 3) scala> var List(aa,bb,cc)=list aa: Int = 1 bb: Int = 2 cc: Int = 3 ***一致するシンボルと、それ以降を分離する。span [#b07ff58f] spanをつかいます。 -例 l.span(_ == l.head) scala> var l = List('a, 'a, 'a, 'a, 'b, 'c, 'c, 'a, 'a, 'd, 'e, 'e, 'e, 'e) scala> l.span(_ == l.head) res16: (List[Symbol], List[Symbol]) = (List('a, 'a, 'a, 'a),List('b, 'c, 'c, 'a, 'a, 'd, 'e, 'e, 'e, 'e)) *問10 [#d9f67211] えーっとここの問10の記事でバッチファイル作りなど、いろいろ脱線した記事を書いているので、無視してもらっていいです。 前回で定義したメソッドと連携して最小限のメソッド実装で問題を解いていきます そのためには前回定義したメソッドをimportします。 import P09.pack インポートするためには参照元の.scalaファイルがコンパイルされている必要があります。 さもなくば、下記のエラーが出ます。 P09 is not a member of problem というわけで、 scalac P09.scala でコンパイルすればいいんですが、 今後プロジェクトでこの99の問題をコンパイルし続けるとディレクトリ内はソースとクラスファイルでごちゃごちゃになってしまいます。 それを防ぐためには、ソースファイルとクラスファイルの出力先を分けたたほうがいいですし、 そして問題の回答としてできてくる99個のファイルは一つのパッケージにしなきゃつらいですね。 となると、package化の必要があります。と、なると -フォルダ構成は プロジェクト名 +---src +---problem <---パッケージ名 +---target +classes <----出力フォルダ で、いちいちscalacのオプションを書くのは面倒なのでバッチファイルをつくりました。 拡張子の.scalaは省略してもいいようにしてあります。 --ただし、jarファイルのクラスパスをまとめてコンパイルするにはさらなるクラスパスの追加が必要です。 **バッチファイルでの特定の拡張だけのパスを集める最小限的のサンプル [#r054caaa] @echo off set CP=. set BASE=. setlocal ENABLEDELAYEDEXPANSION for %%f in (%BASE%\*.jar) do set CP=!CP!;%%f echo %CP% ---setlocalはこのバッチファイルだけで有効なことを意味します。 ---ENABLEDELAYEDEXPANSIONを指定しないと、for文が並列で評価されてしまい、クラスパスが連結されません。 ---forには /R というサブディレクトリも検索対象にするオプションがあるはずですが、なぜか自分の環境(XP)では動作しませんでした。 **クラスパスの指定に偽ワイルドカード [#z3e0e43b] Java SE 6 では、クラスパスの指定に偽ワイルドカードが使えるようになるようです。 なんで偽かっていうと ---java -cp ./*.jar;./lib/*.jar のように *.jar とか書くとダメ ---java -cp ./*;./lib/* のみ対応 設定箇所はプロジェクトのディレクトリです。 -compile.bat REM プロジェクトのディレクトリを設定 SET PROJECT=C:\scala\problem SET SRC=%PROJECT%\src SET CLASS=%PROJECT%\bin scalac -sourcepath %SRC% -classpath %CLASS% -d %CLASS% %SRC%\%1.scala -実行方法 compile.bat problem\P09 **ビルドツールの出番 [#j9107d6c] でもimportするということは、ファイル同士に依存関係が出てくるということです。 ということは、 いまは例題を1から順番にやっているだけなので順番にコンパイルしていけばいいのですが、 業務でつかうとなれば、ビルドツールのANTまたは、MAVENをつかったほうがよさそうです。 **ビルドツールMAVEN [#p35eafcc] ダウンロード http://maven.apache.org/download.html 仮に解凍したディレクトリの名称をmavenに変更して下記に設置したとします。 C:\maven -環境変数を設定します。 -M2_HOME=C:\maven -M2=%M2_HOME%\bin -PATH=%PATH%;%M2% -mavenの環境変数設定の確認 mvn --version -mvnでscalaをつかうためのおまじない --参考 ---http://codezine.jp/article/detail/4476?p=2 ---http://akisute.com/2009/11/maven2scalahello-world.html 参考サイトによればできたプロジェクトにpom.xmlがありますが、プラグインのバージョンを合わせるためこれを修正しなくてはなりませんが、 お決まりの操作なので バッチファイルとrubyでつくったスクリプトを用意しました。 なんで、rubyかって?単に簡潔に書けそうな気がしたからです。 --プロジェクト作成バッチファイル **mkprj.bat [#wec0a917] SET PROJECT=%1 SET PACKAGE=%2 SET RUBYSCRIPT=c:\scala\bin\scalaproject.rb @echo off if exist %PROJECT% GOTO PROCESS1 SET CREATE=org.apache.maven.plugins:maven-archetype-plugin:1.0-alpha-7:create SET PARAM= -DarchetypeGroupId=org.scala-tools.archetypes SET PARAM=%PARAM% -DarchetypeArtifactId=scala-archetype-simple SET PARAM=%PARAM% -DarchetypeVersion=1.2 SET PARAM=%PARAM% -DremoteRepositories=http://scala-tools.org/repo-releases mvn %CREATE% %PARAM% -DgroupId=%PACKAGE% -DartifactId=%PROJECT% :PROCESS1 if exist %PROJECT%\pom.xml.bat GOTO PROCESS2 ruby %RUBYSCRIPT% c:\scala\bin\scalaproject.rb %PROJECT% %PACKAGE% %3 :PROCESS2 --使い方 ---したに示したテキストファイル処理Rubyスクリプトがあることが前提のバッチファイルです。 ---プロジェクトフォルダがないことが前提です。 cd プロジェクトの親ディレクトリ mkprj プロジェクト名 パッケージ名 合わせたいscalaバージョン --例 mkprj problem99 problem 2.7.7 **scalaproject.rb [#qfa58e1d] #プロジェクト名を第一引数にとります。 #パッケージ名を第2引数にとります。 #バージョンを第3引数にとります。 puts "argv[0]:"+ARGV[1] projectname =ARGV[1] filename = projectname + "\\pom.xml" file = open(filename) # 保存用バッファ buffer = file.read().split("\n"); file.close # バックアップ用ファイルを開く bkup = File.open(filename + ".bak" , "w"); # ファイルから読み込んでバックアップに書き込む bkup.write(buffer); bkup.close # ファイルを書き込みモードで開き直す file = File.open(filename , "w"); #処理 cnt = 0 buffer.each {|line| cnt = cnt + 1 #64行目から66行目をカットします。 if !(64..66).include?(cnt) #以前のバージョン2.7.0を今のバージョンに置き換えます。 file.puts line.gsub("2\.7\.0",ARGV[3]) #初期値では if cnt==94 heredoc= <<EOS <!-- このlaunchers要素を新規に作る。idとmainClassは必須。 --> <launchers> <launcher> <id>app</id> <mainClass>PACKAGE.App</mainClass> <!-- 以下、任意要素。 args と jvmArgs を指定できます。 --> <!-- <args> <arg>arg1</arg> </args> <jvmArgs> <jvmArg>-Xmx128m</jvmArg> <jvmArg>-Djava.library.path=...</jvmArg> </jvmArgs> --> </launcher> </launchers> EOS file.puts heredoc.gsub("PACKAGE",ARGV[2]) end end } file.close **実行 [#c825ff25] mkprj.bat problem99 problem 2.7.7 はなぜかコンパイルし終わると停止するので2度実行しています。 mkprj.bat problem99 problem 2.7.7 mkprj.bat problem99 problem 2.7.7 cd problem99 mvn scala:run **問09をMavenで実行させてみる。 [#i8df2544] -P09.scala package problem object P09 { def pack[T](l: List[T]): List[List[T]] = { if (l.isEmpty) List(List()) else { val (packed, next) = l.span(_ == l.head) if (next == Nil) List(packed) else packed :: pack(next) } } } -P10.scala package problem object P10 { import problem.P09.pack def encode[T](l: List[T]): List[(Int, T)] = pack(l).map(e => (e.length, e.head)) } -App.scala 改 package problem /** * Hello world! * */ object App extends Application { println( "Hello World!" ) import problem.P10._ println(encode(List('a, 'a, 'a, 'a, 'b, 'c, 'c, 'a, 'a, 'd, 'e, 'e, 'e, 'e))) } **実行 [#tf761882] mvn scala:run とりあえず、ソースをかけば、コンパイルから実行までやってくれるのがわかった。 さすがに、scalecを直接やるよりは遅い ***クラスファイルの出力先 [#t56c78be] プロジェクトフォルダ\target\classes ***インタプリタ形式でscalaを動かしたい場合 [#eccca17d] cd プロジェクトフォルダ\target\classes scala *問11 [#y2180116] **単純な括弧、たとえば、(1,2)の要素を取得する [#nb895eea] 今まで、List型の要素取得方法を学んだ scala> List(1,2)(1) res12: Int = 2 しかし、同様の操作を上記のListを省いた形式にあてはめようとするとえらーになる。 scala> (1,2)(1) <console>:7: error: scala.Tuple2.apply[Int, Int](1, 2) of type (Int, Int) does not take parameters ***単なる括弧の要素を取得する方法 [#p3f396f6] scala> (1,2)._1 res11: Int = 1 --この単なる括弧の正体はscala.Tuple2である。 scala> scala.Tuple2.apply[Int, Int](1, 2) == (1,2) res16: Boolean = true 引数の型の数だけTupleクラスが用意されていて、APIを見る限り22個までは用意してあるらしい -22個の例 scala> (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22) res22: (Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int) = (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22) 正常に解釈される -23個の例 scala> (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23) <console>:7: error: value Tuple23 is not a member of package scala (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23) 宣言されていないのでエラーとなった。 *問12 [#vba60999] **List.make(2,'a)はList('a,'a) [#v18c1998] **これまでの問題で定義してきたメソッドをあたかもListのビルトインメソッドのように扱う [#bbf78db3] 問題文では、decode(List( (4, 'a), (1, 'b), (2, 'c), (2, 'a), (1, 'd), (4, 'e))) であったが、これを -List((4, 'a), (1, 'b), (2, 'c), (2, 'a), (1, 'd), (4, 'e)).decode で答えが出るようにするには、 case class P12[T](val nums : List[(Int, T)]){ def decode:List[T] = nums.flatMap(e => List.make(e._1, e._2)) } implicit def xxx[T]( num:List[(Int, T)]) = P12(num) -対比のため掲載するが、もとの回答ではつぎのようになる object P12 { def decode[T](l: List[(Int, T)]): List[T] = l.flatMap(e => List.make(e._1, e._2)) } -ポイント --case classでは classで引数をとるようになっており、defの引数が一つ減っている。 *問13 [#jdfa5258] **spanの結果を多重代入する [#y5b22cf6] val (packed, next) = l.span(_ == l.head) *問14 [#t5c21c38] **flatMapを使った方法を思いつくかという問題 [#p995fdd9] #areaedit *問15 [#v7bc29a2] **List.make(n, _)をつかって要素を増やした記述 [#q74067a4] def drop[T](n: Int, l: List[T]): List[T] = { def dropR(c: Int, curList: List[T]): List[T] = (c, curList) match { case (_, Nil) => Nil case (1, _ :: tail) => dropR(n, tail) case (_, e :: tail) => e :: dropR(c - 1, tail) } dropR(n, l) } 回答をみて気がつくのは、defの定義方法である。通常{}でくくるかとおもうが、 いきなり( )表現をつかっている。一つの配列からつくられる高階関数内に処理を書けるからだとおもう。 case文の使い方で気がつくことは、eとtailである、この2つは、どこにも定義や代入の記述がないことをみると、予約されている変数だろう。パターンマッチの書き方も()に応じて用意してあるのだろう。たしか()はTuple2だったはずだ。 #areaedit(end) #areaedit *問16 [#mf5b3ecb] **この問題の回答例1番目で学べるのはカウンターを使って周期的にcase文を選択させていることが学べます。 [#w54f70f2] **zipWithIndex [#v99716b0] Rubyでのeach_with_indexみたいにインデックス番号付きに変換します。 ***例文 [#wd42b26d] List(2, 3, 5).zipWithIndex.foreach(t => println(t._2 + ":" + t._1)) for ((d, i) <- List(2, 5, 6).zipWithIndex) {println(i + ":" + d)} List(7, 3, 1).zipWithIndex.foreach{case (d, i) => println(i + ":" + d)} **filter [#ee114a99] 条件式に一致する要素を抽出します。 def drop[T](n: Int, l: List[T]): List[T] = l.zipWithIndex.filter(v => (v._2 + 1) % n != 0).map(_._1) } #areaedit(end) *問17 [#g18f5769] **splitAtというビルトインメソッドで分割する例 [#p6015899] -例 def split[T](n: Int, l: List[T]): (List[T], List[T]) = l.splitAt(3) -例 takeとdropをつかって先頭と末尾を分割し取得する例 def split[T](n: Int, l: List[T]): (List[T], List[T]) = (l.take(n), l.drop(n)) #areaedit *問18 [#bfd8a868] slice関数を使った回答が一番簡単な問題です。 def slice[T](start: Int, end: Int, l: List[T]): List[T] = l.slice(start, end) #areaedit(end) #areaedit *問19 [#k6bcc473] dropとtakeを使って:::で連結することで、rotateを実現しています。 #areaedit(end) #areaedit *問20 [#t3f24544] 問17でも使った、spiltAtとmatchを使って解くと解ける問題 #areaedit(end) #areaedit *問21 [#t3f24544] これも問17でも使った、spiltAtとmatchを使って解くと解ける問題 spiltAtを使ってからmatchを使うとpreとpostに分割されている。 そこで、 case (pre, post) => と、書きます。 #areaedit(end) *問22 [#v30bba7a] #areaedit 組み込みの変数にRangeが用意されていますが、第二引数未満の整数のリストを作成するようです。 def range(start: Int, end: Int): List[Int] = List.range(start, end + 1) #areaedit(end) *問23 [#lae006e4] #areaedit 乱数の使い方を知っていると解ける (new util.Random).nextInt(l.length) #areaedit(end) *問24 [#mfb2e24e] #areaedit 配列を生成するには下記のようにRangeを使うとよい List.range(1, max + 1) #areaedit(end) *問25 [#s702653f] #areaedit mutable array のupdateを使う #areaedit(end) *問26 [#yfe9b244] #areaedit sublist@を使う // P26 (**) Generate the combinations of K distinct objects chosen from the N // elements of a list. // In how many ways can a committee of 3 be chosen from a group of 12 // people? We all know that there are C(12,3) = 220 possibilities (C(N,K) // denotes the well-known binomial coefficient). For pure mathematicians, // this result may be great. But we want to really generate all the possibilities. // // Example: // scala> combinations(3, List('a, 'b, 'c, 'd, 'e, 'f)) // res0: List[List[Symbol]] = List(List('a, 'b, 'c), List('a, 'b, 'd), List('a, 'b, 'e), ... object P26 { def combinations[T](n: Int, l: List[T]): List[List[T]] = { // flatMapSublists is like list.flatMap, but instead of passing each element // to the function, it passes successive sublists of L. def flatMapSublists[U](l: List[T], f: (List[T]) => List[U]): List[U] = l match { case Nil => Nil case sublist@(_ :: tail) => f(sublist) ::: flatMapSublists(tail, f) } if (n == 0) List(Nil) else flatMapSublists(l, (sl) => combinations(n - 1, sl.tail).map((c) => sl.head :: c)) } } **難しい記述 [#p0dc7cf4] -def flatMapSublists[U](l: List[T], f: (List[T]) => List[U]): List[U] の[U]の書き方 -sublist@の書き方 -slの定義について #areaedit(end) *問27 [#f0a6d1f6] #areaedit すでに問題26で得ている解法というか、メソッドを使えば、この問題27を単純化できることに気がつく object P27 { import P26.combinations def group3[T](l: List[T]): List[List[List[T]]] = for { a <- combinations(2, l) noA = l -- a b <- combinations(3, noA) } yield List(a, b, noA -- b) def group[T](ns: List[Int], l: List[T]): List[List[List[T]]] = ns match { case Nil => List(Nil) case n :: ns => combinations(n, l).flatMap(c => group(ns, l -- c).map(rest => c :: rest)) } } **配列の組み合わせによるfor文 [#m087461b] scalaではfor文は配列の要素をループのカウントがわりに用いることができる。 **yield文 [#zca5424d] for文のあとに記述することで、 for文のループ中の要素をyield文中のプログラムを実行することが可能です。 ** --表記 [#d0d5cd6c] noA = l -- a 配列lから、配列aの要素を除いたモノを返す #areaedit(end) *問28 [#pb5e47d7] #areaedit slacaの配列のソートはsortメソッドが用意されています。 このsortメソッドに比較コードを書くことでsortが実施されます。 比較コードには、scala独特の _ で比較対象の要素を示すことが可能です。 問題bの方は、既に問題10で作成した、要素の長さの配列を得るメソッドを活用します。 既に作った関数をうまく使うことができるかどうかという問題でもあるわけですね。 #areaedit(end) *問29 [#va10fb0e] 素数を扱う問題で 7.isPrime と記述できるなんて、数値がオブジェクトでありなおかつ、そのメソッドを定義できるということですよね。 Scala言語がいかに洗練された言語かがわかります。 class S99Int(val start: Int) { def isPrime = primes.takeWhile(_ <= Math.sqrt(start)).forall(start % _ != 0) } object S99Int { def primes: Stream[Int] = Stream.cons(2, Stream.from(3, 2).filter(_.isPrime)) } 上記のコードを2.8.0 RC3で実行してみたところエラーが出ました。 <console>:7: error: not found: value primes (start > 1) && (primes takeWhile { _ <= Math.sqrt(start) } forall { start % _ != 0 }) となってしまいました。