ScalaでgroupBy
久々にScalaでコードを書いてみました。
これまではテキストエディタで描いていたのですが今回はIDEA 12環境で取り組んでおります。
今回のお題はコレクションクラスのgroupByになります。
値の分布レンジ毎に集計するという基本的な処理を書いてみました。
コードはいい加減にgithubにアップしようと思っていたら、IDEAから直接gistにアップ出来るのですな。
RPGなどで新しい魔法やアイテムを入手するような新鮮な感覚ですわ。
https://gist.github.com/4419753
ちなみに実行すると以下のように出力されます。
val = 8, count = 1
val = 17, count = 1
val = 29, count = 1
val = 35, count = 1
val = 36, count = 1
val = 46, count = 1
val = 53, count = 1
val = 77, count = 1
val = 78, count = 1
val = 89, count = 1
val = 1 - 10, count = 1
val = 11 - 20, count = 1
val = 21 - 30, count = 1
val = 31 - 40, count = 2
val = 41 - 50, count = 1
val = 51 - 60, count = 1
val = 71 - 80, count = 2
val = 81 - 90, count = 1
groupByの使い方についてはこちらのサイトを参考にさせていただきました。
プレースホルダーの使い方も非常に参考になりました、感謝。
Scalaでお釣りの最適化
関数型ぽく書けるようになるにはひたすらコードを書き続けるしかないという事で
まずは手ごろそうなお釣りの最適化(最小枚数化)に挑んでみました。
varで剰余を管理しているのが残念な感じですね・・・。
他の人がどのように書いているかこちらを覗いたら全く違うやり方で
衝撃を受けた次第でアリマス。
object ChangeCalculator { val MoneyUnit = List(5000, 1000, 500, 100, 50, 10, 5, 1) class ChangeUnit(moneyUnit: Int, number: Int) { def getUnit = moneyUnit def getNumber = number def getString = {moneyUnit.toString+"円 "+ number.toString+"枚"} } def main(args: Array[String]) = { calChange(49) calChange(512) calChange(8917) } def calChange(change: Int): Unit = { println("[%d円]".format(change)) getAssortmentOfChange(change).foreach(each => if (each.getNumber > 0) println(each.getString)) println("-------------------") } def getAssortmentOfChange(change: Int): List[ChangeUnit] = { var rest = change def getNumberByUnit(moneyUnit: Int): ChangeUnit = { val number = rest / moneyUnit rest = rest % moneyUnit new ChangeUnit(moneyUnit, number) } for (moneyUnit <- MoneyUnit) yield getNumberByUnit(moneyUnit) } }
結果はこのようになります。
[49円] 10円 4枚 5円 1枚 1円 4枚 ------------------- [512円] 500円 1枚 10円 1枚 1円 2枚 ------------------- [8917円] 5000円 1枚 1000円 3枚 500円 1枚 100円 4枚 10円 1枚 5円 1枚 1円 2枚 -------------------
Scalaで年月単位の期間計算
勤続年数や有効期限など世の中には何かとある日からどれだけの日数が
経過したかの計算することがあります。
特に年月単位での計算が多いのでScalaでどのように記述できるか試してみました。
Joda Timeを使えばいいじゃない、で実用面では終わってしまう話なのですが
関数型の記述で日付計算が書けるものなのか興味があったので挑戦してみました。
仕事の都合でJavaで書いた内容がベースになっているのでJava臭さが抜けませんな。
import java.util.Calendar import java.util.GregorianCalendar import java.text._ object PeriodCalculator { val fields = List(Calendar.DATE, Calendar.MONTH, Calendar.YEAR) // 年月単位での差分結果を格納するオブジェクト class Period(years: Int, months: Int) { def getYears() = years def getMonths() = months } def main(args: Array[String]): Unit = { val startDate = getCalendar(2010, 1, 16) calPeriod(startDate, getCalendar(2010,3,31)) calPeriod(startDate, getCalendar(2011,1,15)) calPeriod(startDate, getCalendar(2011,1,16)) calPeriod(startDate, getCalendar(2011,12,31)) calPeriod(getCalendar(2008,2,29), getCalendar(2009,2,28)) calPeriod(getCalendar(2008,2,29), getCalendar(2008,3,28)) } def getCalendar(year: Int, month: Int, day: Int) = { new GregorianCalendar(year, month-1, day) } def calPeriod(aFromDate: Calendar, toDate: Calendar): Unit = { // 閏日特別処理 val fromDate = if (isLeapDay(aFromDate)) getPreviousDay(aFromDate) else aFromDate val period = getPeriod(fromDate, toDate, 0, 0) println("%tY/%<tm/%<td".format(aFromDate)+ " - "+ "%tY/%<tm/%<td".format(toDate)+ " >>> years = %d".format(period.getYears())+ ", months = %d".format(period.getMonths())) } /* Y年M月D日は次月以降のD日を迎えるたびに+1月経過したとみなします ex. 2010年10月1日を起算日とした場合、2010年11月1日で1ヶ月、 2010年12月1日で2ヶ月経過になる */ def getPeriod(fromDate: Calendar, toDate: Calendar, fieldIndex: Int, previousDiff: Int): Period = { // 1つ前の要素でマイナスがある場合は切り崩し処理が必要 val loan = if (previousDiff >= 0) 0 else 1 val diff = toDate.get(fields(fieldIndex)) - fromDate.get(fields(fieldIndex)) - loan if (fieldIndex == fields.size - 1) { // 年要素まで計算が終わったら終了する val months = if (previousDiff >= 0) previousDiff else previousDiff + 12 new Period(diff, months) } else { // 現在の要素の差分を渡して次の要素の差分計算を行う getPeriod(fromDate, toDate, fieldIndex + 1, diff) } } // 閏日判定 def isLeapDay(date: Calendar) = { if (date.get(Calendar.MONTH) == Calendar.FEBRUARY && date.get(Calendar.DATE) == 29) true else false } // 閏日処理のための特例メソッド def getPreviousDay(date: Calendar) = { getCalendar(date.get(Calendar.YEAR), date.get(Calendar.MONTH)+1, date.get(Calendar.DATE)-1) } }
実行結果はこんな感じになります。
2010/01/16 - 2010/03/31 >>> years = 0, months = 2 2010/01/16 - 2011/01/15 >>> years = 0, months = 11 2010/01/16 - 2011/01/16 >>> years = 1, months = 0 2010/01/16 - 2011/12/31 >>> years = 1, months = 11 2008/02/29 - 2009/02/28 >>> years = 1, months = 0 2008/02/29 - 2008/03/28 >>> years = 0, months = 1
Scalaでformat
前回の書き込みから久方ぶりになります。
細かなことでも継続していないと駄目でありますな。
ScalaではJavaとは異なる書式形式で数値や日付の文字列出力が行えるようなので
試してみましたわ。書式指示子が複数ある場合にどの引数を割り当てるのかに
「<」が必要になるのが慣れないと分かりづらいですね。
なお、今回はこちらを参考にさせていただいております。
ScalaのStringLike.formatで日付フォーマット
java.util.Formatterによると月の部分はゼロ補填の2桁表記しかないようですが
可変で1~2桁表示はできないのかしら。
import java.util._ import java.text._ object DateFormatTest { def main(args: Array[String]): Unit = { val timeRecorder = new TimeRecorder() timeRecorder.start() writeByStringLike() writeByDateFormat() timeRecorder.end() } def writeByStringLike() { println("----------------------------------") // YYYY/MM/DD + day of the week println("%tY/%<tm/%<td %<tA(%<ta)" format new Date()) // YYYY/MM/D + day of the week println("%tY/%<tm/%<td %<tA(%<ta)" format getDate(2011,4,1)) // YYYY/MM/D + day of the week println("%tY/%<tm/%<td %<tA(%<ta)" format getDate(2012,2,29)) println("%tY/%<tm/%<td(%<ta) %tY/%<tm/%<td(%<ta)" format (getDate(2012,2,29), getDate(2012,3,1))) } def writeByDateFormat() { val df = new SimpleDateFormat("yyyy/M/d(E)") println("----------------------------------") println(df.format(new Date())) println(df.format(getDate(2012,1,1))) println(df.format(getDate(2012,12,31))) println(df.format(getDate(2012,7,20))+" "+ df.format(getDate(2012,9,15))) } def getDate(year: Int, month: Int, day: Int) = { new GregorianCalendar(year, month-1, day).getTime() } class TimeRecorder { private val startTime = new Date() def start() { println("Start >>> %tY/%<tm/%<td %<TH:%<TM:%<TS:%<TL" format startTime) } def end() { val endTime = new Date() val processTime = endTime.getTime()-startTime.getTime() println("----------------------------------") print("End >>> %tY/%<tm/%<td %<TH:%<TM:%<TS:%<TL" format (endTime, processTime)) println(" time=%.3f(sec)" format processTime / 1000.0) } } }
ちなみに実行結果はこんな感じになります。
Start >>> 2012/05/03 14:53:37:337 ---------------------------------- 2012/05/03 木曜日(木) 2011/04/1 金曜日(金) 2012/02/29 水曜日(水) 2012/02/29(水) 2012/03/01(木) ---------------------------------- 2012/5/3(木) 2012/1/1(日) 2012/12/31(月) 2012/7/20(金) 2012/9/15(土) ---------------------------------- End >>> 2012/05/03 14:53:37:491 time=0.154(sec)
Scalaでファイル出力
Scala人口拡大を目指し、実用的なサンプルコード作成に取り組んでいこうと考えております。
今回は複数のテキストファイルをマージして出力するコードに挑戦してみました。
ディレクトリ内の情報を取得するのにJavaのFileクラスを使わざるをえなかったのですが
scala.io以下にディレクトリ管理系のクラスはまだ実装されていないのですかね。
ちなみに改行コードの部分はやっつけでLinuxでしか動作テスト出来ておりませぬ。
なお、ScalaでのOS判定はこちら[Scala]OS判別を参考にさせていただきました。
import scala.io.Source import java.io._ object FileMerger { def main(args: Array[String]): Unit = { // 第1引数はマージ対象のファイルの存在するディレクトリ // 第2引数は出力するファイル名 mergeFile(fileList(args(0)),args(1),lineType()) } def lineType() = { System.getProperty("os.name") match { case "Linux" => "\n" case _ => "\r\n" } } def fileList(dir: String) = { new File(dir).listFiles } def mergeFile(fileList: Array[File], outFile: String, lineType: String) = { // テキストファイルをマージして指定ファイルに出力する val bw = new BufferedWriter(new FileWriter(new File(outFile))) try { for (fileName <- fileList.filter(_.getPath.endsWith(".txt"))) { println("target file => "+fileName) Source.fromFile(fileName).getLines().foreach( (line: String) => bw.write(line+lineType)) } } finally { bw.close() } } }
Scala始め
ブログなぞを始めてみた次第でアリマス。
とりあえず、Scalaで素数判定プログラムを書いてみましたわ。
returnを使わずに結果リストも生成するコードにするにはどうすればよいのかしら。
import scala.math.sqrt object PrimeNumber { val START_NUMBER = 2 def main(args: Array[String]): Unit = { checkArgs(args) val primeNumbers = getPrimeNumbers(args(0).toInt) println("Prime Number Count = "+primeNumbers.length) primeNumbers.foreach(println(_)) } def checkArgs(args: Array[String]): Unit = { if (args.length > 1) throw new IllegalArgumentException("usage 1 argument!!") println("argument = "+args(0)) if (!args(0).forall(_.isDigit)) throw new IllegalArgumentException("argument must be natural number") if (args(0).toInt < 2) throw new IllegalArgumentException("argument must be natural number") } def getPrimeNumbers(maxNumber: Int): List[Int] = { checkPrimeNumber(START_NUMBER+1, maxNumber, List(START_NUMBER)) } def checkPrimeNumber(number: Int, maxNumber: Int, primeNumbers: List[Int]): List[Int] = { if (number > maxNumber) return primeNumbers //判定値の平方根以下で最大の整数を求めておく(最大の約数はその数の平方根) val squareVal = sqrt(number).intValue() //既知の素数で割り切れるかの判定 for (primeNumber <- primeNumbers; if (primeNumber <= squareVal)) { if (number % primeNumber == 0) //割りきれる場合は次の数の判定に移る return checkPrimeNumber(number+1, maxNumber, primeNumbers) } //割りきれない場合は素数なのでリストに追加して次の数の判定を行う checkPrimeNumber(number+1, maxNumber, primeNumbers ::: List(number)) }