タグ別アーカイブ: scala

ScalaでSeq(List)をソート

要素をそのまま比較する単純なソート (sorted)

val s1 = Seq(6,1,3,3)
val r1 = s1.sorted

r1: Seq[Int] = List(1, 3, 3, 6)

 

要素に演算とかを施して比較する場合 (sortBy)

// tupleの最初の要素を大文字にした結果でソート
val s2 = Seq(("C",3),("a",1),("B",2))
val r2 = s2.sortBy(_._1.toUpperCase) 


 r2: Seq[(String, Int)] = List((a,1), (B,2), (C,3))

インスタンスの特定要素で単純にソートするならこれが便利

 

関数を渡して比較するソート (sortWith)

val s3 = Seq("a","Z","C","e")
val r3 = s3.sortWith((a,b) => a.compareToIgnoreCase(b) < 0)

r3: Seq[String] = List(a, C, e, Z)

これは色んなケースに対応できて一番使う気がします。つかこれを覚えておけば大体いける。

 

Javaで言うComparator的な比較用インスタンスを渡してソート (sorted + Ordering )

val s4 = Seq("a","Z","C","e")
val r4 = s4.sorted(new Ordering[String]{
  def compare(a:String,b:String):Int = {
    a.compareToIgnoreCase(b)
  }
})

r4: Seq[String] = List(a, C, e, Z)

うん、面倒。
だけどOrderingクラスを作っておけば使いまわせるのがイイですね。
複雑な要件でのソートであればこれか。

Slick3.0 トランザクション内で、クエリした結果を使って更新処理を行う

クエリした結果(リスト)から別の更新処理を行う方法です。

トランザクション内で処理するために、DBActionにまとめないとダメなのですが、
このへん、ちょっとややこしいですよね。

以下はTableAから得た結果(リスト)からTableBの削除を行う例です

val action =
(for {
   // TableAをクエリ
   list <- TableA.filter(name === "tateo").result
   // その結果を使ってTableBを削除
   _ <- DBIO.seq( list.map(r => TableB.filter(id === r.id).delete  ) : _*  )
} yield () ).transactionally

db.run(action)

SlickでANDとORを混在させたクエリを発行する

例えばこんなSQLを発行したかったりします。

select * from cities
where (population > 100000) and (class = '都' or class = '府')

Slickではこんな感じで書けます

Cities.filter(row => row.population > 100000 && ( class === '都' || class === '府' ))


“AND” は “&&”、”OR” は “||” で記述し、必要な箇所でカッコを記述すれば目的のクエリとなってくれました。

これが動的になった場合にはどうするか?
例えば 下記のSQLで都道府県の抽出が可変の場合(inで書けってのは無しで・・)

select * from cities
where (population > 100000)
  and (class = '都' or class = '府' or class = '道' or class ='県')

試行錯誤した末にfunctionを作って対応しました。

def classConditions(row:Cities,t:Boolean,d:Boolean,f:Boolean,k:Boolean):Rep[Boolean] = {
  var condition:Rep[Boolean] = true
  if (t) condition = condition || (row.class === "都")
  if (d) condition = condition || (row.class === "道")
  if (f) condition = condition || (row.class === "府")
  if (k) condition = condition || (row.class === "県")
  condition
}

Cities.filter(row => row.population > 100000 && ( classConditions(row,true,true,false,false) ))

このへんの情報がなかなか見つからずに苦労します。

[Play] dist時に独自のリソースフォルダを加える

Play frameworkで作成したアプリケーションを公開する際に、”dist”コマンドを実行してデプロイ用のzipを作成すると思いますが、そのまま処理すると独自に追加したリソース用のディレクトリが含まれてくれません。

そういった場合にはbuild.sbtに追記します。

例えば プロジェクトフォルダ直下に”templates”というディレクトリを作成していたとしたら、
build.sbtに次のコードを追記します。

// templatesフォルダとその内容をdist対象に追加
mappings in Universal ++= {
  val templateDirectory = baseDirectory(_ / "templates").value
  val templateDirectoryLen = templateDirectory.getCanonicalPath.length
  (templateDirectory ** "*").get.map { f: File =>
    f -> ("templates/" + f.getCanonicalPath.substring(templateDirectoryLen))
  }
}

“AWS SDK for Java”を使ってCloudSearchから検索結果を取得する

CloudSearchの検索時に”AWS SDK for Java”を使ってみるサンプルです。
HttpClient等を使った例は良く見かけますが、SDKを使った例が見つからなかったので載せときます。

scalaでの記述例ですけど、javaでもほとんど同じですよ。

import com.amazonaws.auth.AWSCredentials
import com.amazonaws.services.cloudsearchdomain.AmazonCloudSearchDomainClient
import com.amazonaws.services.cloudsearchdomain.model.SearchRequest
import com.amazonaws.services.cloudsearchdomain.model.QueryParser
import com.amazonaws.services.cloudsearchdomain.model.SearchResult


// search用のエンドポイント
val endpoint = "search-testdb-xxxxxxxxx.ap-northeast-1.cloudsearch.amazonaws.com"

// クライアントを準備
val credentials = new BasicAWSCredentials("accessKey", "secretKey")
val client = new AmazonCloudSearchDomainClient(credentials)
client.setEndpoint(endpoint)


// リクエストを生成
// 各項目に設定する内容はhttpクライアント等で search apiを実行する時に
// 設定する内容とだいたい一緒です  
// 詳しくは↓
// http://docs.aws.amazon.com/cloudsearch/latest/developerguide/search-api.html
//
var request = (new SearchRequest())
	.withQueryParser(QueryParser.Simple)
	.withQuery("検索ワード")
	// titleとsubtitleに対して"AND"検索を行う	
	.withQueryOptions("{\"defaultOperator\":\"and\",\"fields\":[\"title\",\"subtitle\"]}")
	// 先頭から1000件まで取得	
	.withStart(0)
	.withSize(1000)
	// ソート条件を設定
    .withSort("sortkey1 asc,sortkey2 desc")
    // 結果として取得する項目
    .withReturn("_score,title,ranking")
    

try{
    // 検索処理
    val result = client.search(request)
	
	 
    val status  = result.getStatus()
    val hits = result.getHits()
    
    // インデックス全体でのトータル件数(startとsizeで指定した範囲外も含む)
    val total = hits.getFound();
    
    // 取得したレコードをマップ (javaな人は for-eachしていると思って下さい)
    hits.getHit().map{rec => 
    
      // ID項目
      val id = rec.getId()
      
      // Fieldを参照してみる
      val ranking = rec.getFields().get("ranking").get(0)
      
      Logger.info(" id:" + id + " ranking:" + ranking)
    }

} finally {
   client.shutdown()
}

なぜSDKを使う必要があったかと言うと、IAMロールでのアクセス制限を行いたかったからです。
署名済リクエストを作るのがやたらと面倒だったので・・・
SDKを使う事で簡単に署名済のリクエストが行えます。

“AWS SDK for Java”を使ってCloudSearchへデータを登録する

前回の検索に続いてこんどはドキュメントの更新処理です
これもSDKを使ったサンプルが見つからないんですよね。。。探し方が悪いのかな??

scalaでの記述になっていますけど、javaでもほとんど同じです。

import com.amazonaws.auth.AWSCredentials
import com.amazonaws.services.cloudsearchdomain.AmazonCloudSearchDomainClient
import com.amazonaws.services.cloudsearchdomain.model.UploadDocumentsRequest
import com.amazonaws.services.cloudsearchdomain.model.ContentType
import java.io.ByteArrayInputStream


// document用のエンドポイント
val endpoint = "document-testdb-xxxxxxxxx.ap-northeast-1.cloudsearch.amazonaws.com"

// クライアントを準備
val credentials = new BasicAWSCredentials("accessKey", "secretKey")
val client = new AmazonCloudSearchDomainClient(credentials)
client.setEndpoint(endpoint)

// データ投入用のjsonデータを作成する
val datajson = "[{ ...省略... }]"

// json文字列をbyte配列としてInputStreamを取得
val postdata = datajson.getBytes()
val data = new ByteArrayInputStream(postdata); 

// リクエストを生成
var request = (new UploadDocumentsRequest())
    .withContentType(ContentType.Applicationjson)
    .withContentLength(postdata.length)
    .withDocuments(data)
     
// uploadを実行
val response = client.uploadDocuments(request)

// 何件登録して、何件削除したのかを確認できます      
Logger.info(response.toString())

client.shutdown()

datajsonに入れる文字列はHttpクライアント等で “documents/batch” APIにpostする内容と同じです
詳しくは↓
http://docs.aws.amazon.com/cloudsearch/latest/developerguide/document-service-api.html

簡単なサンプルですとこんな感じです

[
  { 
  	"fields": { "title": "タイトル100","subtitle": "サブタイトル" }, 
	"id": "100", 
	"type": "add"
  }, 
  {
  	"fields": { "title": "タイトル101", "subtitle": "サブタイトル" }, 
	"id": "101", 
	"type": "add"
  },
  {
  	"id": "900",
  	"type": "delete"
  }
]

jsonはJacksonとかGsonとかで作ると楽ですね。

[Play] APIドキュメントの生成を抑制する

play stage とか play dist とかしたときに何故がAPIドキュメントとかが生成されちゃって、
ただでさえ遅いscalaのコンパイルがさらに遅くなってイライラしているそんな貴方、こちらをお試しあれ。

build.sbt(プロジェクトのルートにあると思う)の playScalaSettings にちょっと手を加えれば解決できます。

こんな感じ

name := "playapplication"

version := "1.0-SNAPSHOT"

libraryDependencies ++= Seq(
  jdbc,
  anorm,
  cache
)     

//play.Project.playScalaSettings
// ↓ こんな感じに書き換える (ver2.2以下)
play.Project.playScalaSettings ++ Seq(doc in Compile <<= target.map(_ / "none"))

// ↓ (追記) 2.3以降だと下記の行を加えるとOK
doc in Compile <<= target.map(_ / "none")

こうするとドキュメントの生成を止めてくれますよ。
※playframework 2.2.2で動作確認

[Play] Playframeworkのセッションでハマる!

表題の時点で分かる人は大体「あー、あれだな!」って気づかれてしまうネタですけど、
拡散の意味を含めて投稿しておきます。

セッションに値を設定する時にwithSessionを使いますよね。
んで、新規セッションとする場合にはwithNewSessionを使う。

コードだとこんな感じ

Ok(・・・).withSession("authKey" -> "eyewhale")
Ok(・・・).withNewSession

そもそもこのメソッド名が混乱の元なんですよね。
一方がNewSessionならもう一方は既存のセッションを引き継ぐって思ってしまうんですが、これが間違い!
withSessionは指定された内容で既存セッションを丸ごと上書きします!!(重要)
(with・・・なのでそうあるべきなのは分かりますが)

なのでセッションに追加したい場合には

Ok(・・・).withSession(session + ("authKey" -> "eyewhale"))

のように記述する必要があります。
複数の値をセットする場合にはこんな感じ。

Ok(・・・).withSession(
      session + ("authKey" -> "eyewhale") + ("authorName" -> "tateo"))

逆にセッションから値を消す場合にはマイナスすれば消えてくれます。

Ok(・・・).withSession(session - "authKey")

という事で、
気をつけなはれや!