SCALAの記事一覧

&topicpath;

はじめに

このページは関数脳を作るべく、関数言語を題材にした99個の問題を解くのではなく、 解いてある回答を見て学ぶために作ったページです。

再帰的に解いてあると、関数言語だなーという気分になるのは、じぶんだけでしょうか?

まだ、全問といていませんが、極力1日1問のペースで解いていこうとおもっています。

ページが大きくなってきたので、32問目以降は下記のページに記載しています。

scala 99problem 32~

目次

{{__TOC_PLACEHOLDER__}} こんにちは、みなさんお元気ですか。 このページはプログラミング言語SCALAについてまとめたページです。 Androidアプリケーションの開発やクラウド開発、 そして企業のリソースをWebAPI化するなどの プログラミングには信頼性や資源の多さからJavaが有効だとおもいますが、 SCALAはそのJavaアプリケーションをさらに効率よく開発する可能性を秘めています。

Scalaとは

Scalaは、JVM上で動作するオブジェクト指向+関数型言語で、
Javaとほぼ完全な相互利用が可能な言語です。
これまでの言語の集大成に近い言語です。
そのため、各言語の優れた概念を取り入れているわけです。
軽い気持ちで学ぶと、あちこちの言語の概念の基本を抑えたほうが近道なこともあり、
色々な言語の入門書をわたり歩くはめになります。
作者であるMartin Odersky教授はJavac開発の貢献者であるとともに、
Java5 Genericsの仕様策定にも参加しています。
というわけでSCALAはJava言語と親和性の高い関数型言語です。
コンパイルするとJavaのクラスファイルになるんですよ。
Javaのライブラリは膨大で他の言語に比べて日本語の処理方法がしっかりしています。
そしてコードが簡潔に書けるので生産性が高いといわれています。
たとえばBeanクラスを記述する場合、例は下記に記しますが、3分の1の
コーディングですみます。

Scalaの紹介

http://qcontokyo.com/pdf/qcon_TakashiMiki.pdf

バージョン2.8の説明

http://eed3si9n.github.com/scala-collections-doc-ja/collections_0.html

Scala開眼

http://www.h7.dion.ne.jp/~samwyn/Scala/scalaindex.htm

Scala Wiki Start

英語のサイトであるが、情報が豊富である。

最初のページは検索のテキストボックス程度が表示されている程度だが、

検索エンジンで気になる単語を入れ検索すると、いろいろとScalaの記事が出てくる。

http://scala.sygneca.com/

その他の特徴

  • 関数を生成して返す関数を定義できる
  • 配列のパターンマッチング
  • XMLを簡単に扱える
  • 多重継承ができるので、メソッドを部品のように扱える JavaにはできないことができるのにJavaと親和性が高いのです。

練習問題 S-99 Ninety Nine Scala Problems

何らかの練習問題をこなすことで力がつくとおもいます。自分で解くことも大切ですが模範回答から学ぶことも大切です。
このページは下記の
S-99 Ninety Nine Scala Problems

http://aperiodic.net/phil/scala/s-99/ の回答をつかって学習していくページです。 模範回答をみてえられる知識は実践向きの知識とも言えるかとおもいます。 パズルゲームを解いていくような気分で1日1問を解説していければとおもっています。

99問題の日本語訳 URL

http://study-func-prog.blogspot.com/2009/05/scala-s-99-ninety-nine-scala-problems.html

対象者

SCALAのインストールは完了し、HelloWorldを実行し終えた程度の読者を想定
SCALAをやるのは全くのはじめてという方向け
SCALAはコンパイルすると、JAVA言語のクラスを生成する関数型言語です。
型推論を行ってくれるので、マスターすれば生産性が向上します。

趣旨

有名な練習問題99問が回答付きで英語ながらも存在しているので、
回答をみながら、ポイントをまとめていこうかという趣旨です。
問題自体はリストを処理するAPIを知っている人向けなのですが、
問題が出てくる毎にそのメソッドを紹介していきます。

参考:リストを操作するAPI

https://www.scala-lang.org/docu/files/api/scala/List.html

ローカルにAPI資料をもってきたい場合

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問

1問目は、回答を自分の環境で実行させること自体が学びが大きいです。

手順

  • 回答をP01.scalaというファイル名で保存します。

    インタプリタ形式で確認するには、

    scalaでインタプリタ形式で :load .\P01.scala と入力すると 読み込んでくれます。

    確認方法は

    P01.last(List(1,2,3,4,5)) です。

最後の要素を取得する

.last

Scala Listクラスのメソッドを全部実行してみてるURL

Scalaを学び始めていきなりどんなメソッドがあるのかさっぱりわからないと思う、 片っ端からリストクラスを操作するメソッドを試したつわものがいました。

使い方が大体わかるかも。ただしバージョンは2.7ベースな点に注意。

http://kaitenn.blogspot.com/2010/02/scala-list.html

2.8のListのメソッド一覧

ちなみに2.8のListのメソッド一覧は下記です。

https://www.scala-lang.org/docu/files/api/scala/collection/Traversable.html

2問

2問とは関係ないのだが、今度はEclipseでSCALAを実行させてみたいとおもう。 そのためには下記のプラグインをインストールすることが必要だ。 Scala IDE for Eclipse http://www.scala-lang.org/node/94

Scala Eclipse Pluginの導入

Eclipse開いて、「Help -> Install New Software... ->
Workwithフォルダに
Available Software -> Add Site..」でLocationに「http://www.scala-lang.org/scala-eclipse-plugin」と入れる
あとは、直感的にわかるので省略

確認用メイン

回答をエクリプスから実行するにはメインメソッドが必要ですので、 下記のような実行用のメソッドを用意するといいでしょう。 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問目

 // 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は配列要素をパターンマッチできるようだ。

  2問目の最後から2つ目にマッチ
   case e :: _ :: Nil => e
  1問目の最後にマッチ
   case e :: Nil  => e

::は配列の要素間の区切り文字である。

配列の長さを取得する

.length

右からn個の要素を取得する

.takeRight(2) ()はapplyメソッドと同等らしいです。

先頭を取得する

.head

3問目

 回答に
(n, l) match {

}
という構文が使用されている
第一パラメータっぽい変数nがmatch内で使えるようになっている

n番目の要素を取得する

配列変数(n)

4問目

def内にdefを入れ子にできる

例 def foo = { def bar = {

  }
  bar
}

先頭以外の要素

.tail

5問目

配列を逆にする

.reverse

配列を連結する演算子::と:::

配列を連結する演算子には::と:::があって、知らないと困る

::の例

List(1,2,3) :: List(4,5,6)

結果 2番目の配列の先頭に格納される List(List(1,2,3),4,5,6)

:::の例

List(1,2,3) ::: List(4,5,6)

結果 List(1,2,3,4,5,6)

配列の連結演算子::

match内での先頭とそれ以外の分割(効率はn^2なので小さい配列向き)

case h :: tail => reverse(tail) ::: List(h)

Nil=List() と List(1,2)=1::2::Nil

配列の書き方で
1 :: 2
と書くとエラーになるが
1 :: 2 :: Nil
と書くと
List(1,2)
として認識される

配列の要素をまずは、初期値を処理し、次からは次の要素と処理結果とを処理させる

ややこしいが、その分応用できる箇所をすっきりと記述できる。
.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は別名があり、それぞれ「/:」「:\」とも書ける。

foldLeft は /: とも書けるので次のようにより簡潔に書ける
(1 /: List(2,3,4))(_+_)
初期値が先頭にきている感じだ。
()を閉じ終えてからくる()が、処理。

回答を理解できるようになったので解説してみる

というわけで、次の処理内容をみてみると以下のことが理解できる
def reverse[T](l: List[T]): List[T] = l.foldLeft(List[T]())((r, h) => h :: r)
List[T]()は初期値であり、空の配列である。
最初は空の要素と左の要素が処理され最初の要素がrに格納される
次の要素はhに格納され最初の要素はrなので、(2番目の要素,最初の要素)
が格納される。

応用:Beanを定義する。

ここまでは、配列の中身が数値とかだったので、もう少し込み入った情報を扱いたい場合もあるかとおもいますので、Beanの扱いについて説明いたします。

その前にオーバライトの書き方を紹介します。

Javaメソッドのオーバライド

defの前にoverrideをつけます。 override def toString="(name:"+name+ " value:"+value+")"

case classを使った定義方法

  • 例 case class FieldValue(name:String , value:String ) { override def toString = "(name:"+name+ " value:"+value+")" } たったの3行です。おそらく1行でもいけるでしょう。

通常のBeanの設定方法

Beanの定義

class FieldValue(_name : String , _value : String) {
val name = _name
val value = _value
override def toString="(name:"+name+ " value:"+value+")"
}

コンストラクションを増やしたい場合

例 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インスタンスの作成

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の配列

List(field1,field2,field3,field4)

問6

答えは簡単に def isPalindrome[T](l: List[T]): Boolean = l == l.reverse だった。

scalaのfor文

配列以外でループさせたい場合につかうかもしれない for (x <- (0 to 10)){ println(x) }

takeWhile

ちなみにループ中にループを抜けたい場合は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の本家から回答が帰ってきました。

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 }

配列の連結

配列の連結は先頭にしかできないので どうしても末尾にしたい場合は、 reverseをつかって先頭に追加してから、 またreverseでもどしたりする。

文字列格納用配列の宣言

var result :List[String] = List()

が正解で var result = List() ......NG Error だと、空配列しか受け付けない配列として宣言したことになるようです。

配列の追加

配列が先頭にしか追加できない
result = "a" :: result
だとエラーにならないけど
result = result :: "a"
だとエラーになるってこと

戻り値のあるメソッドの書き方注意点

メソッドの戻り値がある場合は、メソッドの中身を書く際に={中身}とすること
そうしないと
illegal start of declaration
というエラーがかえってきます。

キャストの仕方

クラスの型を正しく指定しないといけませんので指定します。 BufferedSourceでキャストする方法 .asInstanceOf[BufferedSource].

動的変数を持つ関数の作り方

動的変数は try catch の中のプログラムをブロックとして、
あとで追加できるようにする仕組みで、ファイルを閉じるなどの
必ず行うような処理を簡潔に書くことができます。
def 動的関数名(引数)(動的変数 => Unit){

}

動的変数を持つ関数の使い方

 動的関数名(引数){(動的変数 ) =>
    処理内容
 }

文字列を配列に変換する

ファイルから読み込んだ文字列の場合、いったん配列にしてやらないといけない だから文字列を配列に変換するメソッドをつくってみました。

   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
   }

ファイルの読み込みを動的変数で簡潔にする

ファイルの読み込みは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する箇所が省けているのがわかります。

応用:ファイルの内容を一気に配列化するメソッド

   //ファイルをすべて読み配列で返します。
   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

scala.Anyとは

Scala のすべてのクラスの継承元となる基底クラス (Int、Float、Double などの型やその他の数値型) は scala.Any 型です。

入れ子状態になっている配列の入れ子を解消してフラットな配列にする

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

値が一致する要素を削除する

match処理内のcase処理部分でつかいます。 tail.dropWhile(_ == e) 普通に式を評価しようとするとvalue dropwhile is not a member of Listとエラーになります。

問09

Haskellでいうところのgroupメソッドとかなり類似した関数のようですね。

最近、下記のようなところでもりあがっているのを知りました。

http://wordprogress.org/archives/366

それはさておき、ここの回答でつかわれているテクニックに焦点をあててみていきましょう。

多重代入

ScalaもRubyのように多重代入ができるんですねぇ。 scala> val (aa,bb)=(11,22) aa: Int = 11 bb: Int = 22

List形式の場合の多重代入

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

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

えーっとここの問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ファイルのクラスパスをまとめてコンパイルするにはさらなるクラスパスの追加が必要です。

バッチファイルでの特定の拡張だけのパスを集める最小限的のサンプル

@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)では動作しませんでした。

クラスパスの指定に偽ワイルドカード

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

ビルドツールの出番

でもimportするということは、ファイル同士に依存関係が出てくるということです。
ということは、 いまは例題を1から順番にやっているだけなので順番にコンパイルしていけばいいのですが、
業務でつかうとなれば、ビルドツールのANTまたは、MAVENをつかったほうがよさそうです。

ビルドツールMAVEN

ダウンロード 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をつかうためのおまじない

お決まりの操作なので バッチファイルとrubyでつくったスクリプトを用意しました。 なんで、rubyかって?単に簡潔に書けそうな気がしたからです。

  • プロジェクト作成バッチファイル

    mkprj.bat

    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

#プロジェクト名を第一引数にとります。
#パッケージ名を第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

実行

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で実行させてみる。

  • 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))) }

実行

mvn scala:run

とりあえず、ソースをかけば、コンパイルから実行までやってくれるのがわかった。

さすがに、scalecを直接やるよりは遅い

クラスファイルの出力先

プロジェクトフォルダ\target\classes

インタプリタ形式でscalaを動かしたい場合

cd プロジェクトフォルダ\target\classes
scala

問11

単純な括弧、たとえば、(1,2)の要素を取得する

今まで、List型の要素取得方法を学んだ scala> List(1,2)(1) res12: Int = 2

しかし、同様の操作を上記のListを省いた形式にあてはめようとするとえらーになる。 scala> (1,2)(1)

:7: error: scala.Tuple2.apply[Int, Int](1, 2) of type (Int, Int) does not take parameters

単なる括弧の要素を取得する方法

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)

    :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

List.make(2,'a)はList('a,'a)

これまでの問題で定義してきたメソッドをあたかもListのビルトインメソッドのように扱う

問題文では、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

spanの結果を多重代入する

val (packed, next) = l.span(_ == l.head)

問14

flatMapを使った方法を思いつくかという問題

問15

List.make(n, _)をつかって要素を増やした記述

 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だったはずだ。

問16

この問題の回答例1番目で学べるのはカウンターを使って周期的にcase文を選択させていることが学べます。

zipWithIndex

Rubyでのeach_with_indexみたいにインデックス番号付きに変換します。

例文

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

条件式に一致する要素を抽出します。

 def drop[T](n: Int, l: List[T]): List[T] =
   l.zipWithIndex.filter(v => (v._2 + 1) % n != 0).map(_._1)
}

問17

splitAtというビルトインメソッドで分割する例

  • 例 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))

問18

slice関数を使った回答が一番簡単な問題です。 def slice[T](start: Int, end: Int, l: List[T]): List[T] = l.slice(start, end)

問19

dropとtakeを使って:::で連結することで、rotateを実現しています。

問20

問17でも使った、spiltAtとmatchを使って解くと解ける問題

問21

これも問17でも使った、spiltAtとmatchを使って解くと解ける問題

spiltAtを使ってからmatchを使うとpreとpostに分割されている。 そこで、

case (pre, post) =>

と、書きます。

問22

組み込みの変数にRangeが用意されていますが、第二引数未満の整数のリストを作成するようです。 def range(start: Int, end: Int): List[Int] = List.range(start, end + 1)

問23

乱数の使い方を知っていると解ける (new util.Random).nextInt(l.length)

問24

配列を生成するには下記のようにRangeを使うとよい List.range(1, max + 1)

問25

mutable array のupdateを使う

問26

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))
 }
}

難しい記述

  • def flatMapSublists[U](l: List[T], f: (List[T]) => List[U]): List[U] の[U]の書き方
  • sublist@の書き方
  • slの定義について

問27

すでに問題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文

scalaではfor文は配列の要素をループのカウントがわりに用いることができる。

yield文

for文のあとに記述することで、

for文のループ中の要素をyield文中のプログラムを実行することが可能です。

--表記

noA = l -- a

配列lから、配列aの要素を除いたモノを返す

問28

slacaの配列のソートはsortメソッドが用意されています。

このsortメソッドに比較コードを書くことでsortが実施されます。

比較コードには、scala独特の _ で比較対象の要素を示すことが可能です。

問題bの方は、既に問題10で作成した、要素の長さの配列を得るメソッドを活用します。

既に作った関数をうまく使うことができるかどうかという問題でもあるわけですね。

問29

素数を扱う問題で

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で実行してみたところエラーが出ました。

:7: error: not found: value primes (start > 1) && (primes takeWhile { _ <= Math.sqrt(start) } forall { start % _ != 0 })

となってしまいました。


トップ   編集 凍結 差分 履歴 添付 複製 名前変更 リロード   新規 一覧 検索 最終更新   最終更新のRSS
Last-modified: 2013-06-23 (日) 07:21:22