Top / scala
このページは関数脳を作るべく、関数言語を題材にした99個の問題を解くのではなく、 解いてある回答を見て学ぶために作ったページです。
再帰的に解いてあると、関数言語だなーという気分になるのは、じぶんだけでしょうか?
まだ、全問といていませんが、極力1日1問のペースで解いていこうとおもっています。
ページが大きくなってきたので、32問目以降は下記のページに記載しています。
こんにちは、みなさんお元気ですか。 このページはプログラミング言語SCALAについてまとめたページです。 Androidアプリケーションの開発やクラウド開発、 そして企業のリソースをWebAPI化するなどの プログラミングには信頼性や資源の多さからJavaが有効だとおもいますが、 SCALAはそのJavaアプリケーションをさらに効率よく開発する可能性を秘めています。
Scalaは、JVM上で動作するオブジェクト指向+関数型言語で、 Javaとほぼ完全な相互利用が可能な言語です。 これまでの言語の集大成に近い言語です。 そのため、各言語の優れた概念を取り入れているわけです。 軽い気持ちで学ぶと、あちこちの言語の概念の基本を抑えたほうが近道なこともあり、 色々な言語の入門書をわたり歩くはめになります。 作者であるMartin Odersky教授はJavac開発の貢献者であるとともに、 Java5 Genericsの仕様策定にも参加しています。 というわけでSCALAはJava言語と親和性の高い関数型言語です。 コンパイルするとJavaのクラスファイルになるんですよ。 Javaのライブラリは膨大で他の言語に比べて日本語の処理方法がしっかりしています。 そしてコードが簡潔に書けるので生産性が高いといわれています。 たとえばBeanクラスを記述する場合、例は下記に記しますが、3分の1の コーディングですみます。
http://qcontokyo.com/pdf/qcon_TakashiMiki.pdf
http://www.h7.dion.ne.jp/~samwyn/Scala/scalaindex.htm
英語のサイトであるが、情報が豊富である。
最初のページは検索のテキストボックス程度が表示されている程度だが、
検索エンジンで気になる単語を入れ検索すると、いろいろとScalaの記事が出てくる。
何らかの練習問題をこなすことで力がつくとおもいます。自分で解くことも大切ですが模範回答から学ぶことも大切です。 このページは下記の S-99 Ninety Nine Scala Problems
http://aperiodic.net/phil/scala/s-99/
の回答をつかって学習していくページです。 模範回答をみてえられる知識は実践向きの知識とも言えるかとおもいます。 パズルゲームを解いていくような気分で1日1問を解説していければとおもっています。
http://study-func-prog.blogspot.com/2009/05/scala-s-99-ninety-nine-scala-problems.html
SCALAのインストールは完了し、HelloWorldを実行し終えた程度の読者を想定 SCALAをやるのは全くのはじめてという方向け SCALAはコンパイルすると、JAVA言語のクラスを生成する関数型言語です。 型推論を行ってくれるので、マスターすれば生産性が向上します。
有名な練習問題99問が回答付きで英語ながらも存在しているので、 回答をみながら、ポイントをまとめていこうかという趣旨です。 問題自体はリストを処理するAPIを知っている人向けなのですが、 問題が出てくる毎にそのメソッドを紹介していきます。
https://www.scala-lang.org/docu/files/api/scala/List.html
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問目は、回答を自分の環境で実行させること自体が学びが大きいです。
scalaでインタプリタ形式で :load .\P01.scala と入力すると 読み込んでくれます。
P01.last(List(1,2,3,4,5)) です。
.last
Scalaを学び始めていきなりどんなメソッドがあるのかさっぱりわからないと思う、 片っ端からリストクラスを操作するメソッドを試したつわものがいました。
使い方が大体わかるかも。ただしバージョンは2.7ベースな点に注意。
http://kaitenn.blogspot.com/2010/02/scala-list.html
ちなみに2.8のListのメソッド一覧は下記です。
https://www.scala-lang.org/docu/files/api/scala/collection/Traversable.html
2問とは関係ないのだが、今度はEclipseでSCALAを実行させてみたいとおもう。 そのためには下記のプラグインをインストールすることが必要だ。 Scala IDE for Eclipse http://www.scala-lang.org/node/94
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)
}
}
// 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
}
2問目の最後から2つ目にマッチ case e :: _ :: Nil => e 1問目の最後にマッチ case e :: Nil => e ::は配列の要素間の区切り文字である。
.length
.takeRight(2) ()はapplyメソッドと同等らしいです。
.head
回答に
(n, l) match {
}
という構文が使用されている
第一パラメータっぽい変数nがmatch内で使えるようになっている
配列変数(n)
例
def foo = {
def bar = {
}
bar
}
.tail
.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)
case h :: tail => reverse(tail) ::: List(h)
配列の書き方で 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 は /: とも書けるので次のようにより簡潔に書ける (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の扱いについて説明いたします。
その前にオーバライトの書き方を紹介します。
defの前にoverrideをつけます。
override def toString="(name:"+name+ " value:"+value+")"
case class FieldValue(name:String , value:String ) {
override def toString = "(name:"+name+ " value:"+value+")"
}
たったの3行です。おそらく1行でもいけるでしょう。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)
}
val field1 = new FieldValue("key1","data1")
val field2 = new FieldValue("key2","data2")
val field3 = new FieldValue("key2","data2")
val field4 = new FieldValue("key2","data2")
List(field1,field2,field3,field4)
答えは簡単に
def isPalindrome[T](l: List[T]): Boolean = l == l.reverse
だった。
配列以外でループさせたい場合につかうかもしれない
for (x <- (0 to 10)){
println(x)
}
ちなみにループ中にループを抜けたい場合は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
}
とりあえず、バクかなとおもって本家にバグ報告してみたところ下記の回答がかえってきました。
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 _)
とってもシンプルになりました。
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)
}
match処理内のcase処理部分でつかいます。
tail.dropWhile(_ == e)
普通に式を評価しようとするとvalue dropwhile is not a member of Listとエラーになります。
Haskellでいうところのgroupメソッドとかなり類似した関数のようですね。
最近、下記のようなところでもりあがっているのを知りました。
http://wordprogress.org/archives/366
それはさておき、ここの回答でつかわれているテクニックに焦点をあててみていきましょう。
ScalaもRubyのように多重代入ができるんですねぇ。
scala> val (aa,bb)=(11,22) aa: Int = 11 bb: Int = 22
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をつかいます。
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の記事でバッチファイル作りなど、いろいろ脱線した記事を書いているので、無視してもらっていいです。
前回で定義したメソッドと連携して最小限のメソッド実装で問題を解いていきます そのためには前回定義したメソッドをimportします。
import P09.pack
インポートするためには参照元の.scalaファイルがコンパイルされている必要があります。 さもなくば、下記のエラーが出ます。 P09 is not a member of problem
というわけで、
scalac P09.scala
でコンパイルすればいいんですが、 今後プロジェクトでこの99の問題をコンパイルし続けるとディレクトリ内はソースとクラスファイルでごちゃごちゃになってしまいます。
それを防ぐためには、ソースファイルとクラスファイルの出力先を分けたたほうがいいですし、 そして問題の回答としてできてくる99個のファイルは一つのパッケージにしなきゃつらいですね。
となると、package化の必要があります。と、なると
プロジェクト名
+---src
+---problem <---パッケージ名
+---target
+classes <----出力フォルダで、いちいちscalacのオプションを書くのは面倒なのでバッチファイルをつくりました。 拡張子の.scalaは省略してもいいようにしてあります。
@echo off set CP=. set BASE=. setlocal ENABLEDELAYEDEXPANSION for %%f in (%BASE%\*.jar) do set CP=!CP!;%%f echo %CP%
Java SE 6 では、クラスパスの指定に偽ワイルドカードが使えるようになるようです。 なんで偽かっていうと
設定箇所はプロジェクトのディレクトリです。
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をつかったほうがよさそうです。
ダウンロード http://maven.apache.org/download.html
仮に解凍したディレクトリの名称をmavenに変更して下記に設置したとします。 C:\maven
お決まりの操作なので バッチファイルとrubyでつくったスクリプトを用意しました。 なんで、rubyかって?単に簡潔に書けそうな気がしたからです。
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
cd プロジェクトの親ディレクトリ mkprj プロジェクト名 パッケージ名 合わせたいscalaバージョン
mkprj problem99 problem 2.7.7
#プロジェクト名を第一引数にとります。
#パッケージ名を第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
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)
}
}
}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))
}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
cd プロジェクトフォルダ\target\classes scala
今まで、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
scala> (1,2)._1 res11: Int = 1
scala> scala.Tuple2.apply[Int, Int](1, 2) == (1,2) res16: Boolean = true
引数の型の数だけTupleクラスが用意されていて、APIを見る限り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)
正常に解釈される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)
宣言されていないのでエラーとなった。問題文では、decode(List( (4, 'a), (1, 'b), (2, 'c), (2, 'a), (1, 'd), (4, 'e))) であったが、これを
で答えが出るようにするには、
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))
}val (packed, next) = l.span(_ == l.head)
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だったはずだ。
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)}
条件式に一致する要素を抽出します。
def drop[T](n: Int, l: List[T]): List[T] = l.zipWithIndex.filter(v => (v._2 + 1) % n != 0).map(_._1) }
def split[T](n: Int, l: List[T]): (List[T], List[T]) = l.splitAt(3)
def split[T](n: Int, l: List[T]): (List[T], List[T]) = (l.take(n), l.drop(n))
slice関数を使った回答が一番簡単な問題です。
def slice[T](start: Int, end: Int, l: List[T]): List[T] = l.slice(start, end)
dropとtakeを使って:::で連結することで、rotateを実現しています。
問17でも使った、spiltAtとmatchを使って解くと解ける問題
これも問17でも使った、spiltAtとmatchを使って解くと解ける問題
spiltAtを使ってからmatchを使うとpreとpostに分割されている。 そこで、
case (pre, post) =>
と、書きます。
組み込みの変数にRangeが用意されていますが、第二引数未満の整数のリストを作成するようです。
def range(start: Int, end: Int): List[Int] = List.range(start, end + 1)
乱数の使い方を知っていると解ける
(new util.Random).nextInt(l.length)
配列を生成するには下記のようにRangeを使うとよい
List.range(1, max + 1)
mutable array のupdateを使う
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))
}
}
すでに問題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))
}
}
scalaではfor文は配列の要素をループのカウントがわりに用いることができる。
for文のあとに記述することで、
for文のループ中の要素をyield文中のプログラムを実行することが可能です。
noA = l -- a
配列lから、配列aの要素を除いたモノを返す
slacaの配列のソートはsortメソッドが用意されています。
このsortメソッドに比較コードを書くことでsortが実施されます。
比較コードには、scala独特の _ で比較対象の要素を示すことが可能です。
問題bの方は、既に問題10で作成した、要素の長さの配列を得るメソッドを活用します。
既に作った関数をうまく使うことができるかどうかという問題でもあるわけですね。
素数を扱う問題で
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 })
となってしまいました。