5.31.2015

Getting Started with AquaSKK

Aqua SKK を使う

 

日本語入力メソッドして、SKK を使いたい。(実は過去に一度使ったことがあるが挫折したので再挑戦)
Mac では Aqua SKK という実装があるのでそれを導入する。

 

環境

  • OS X Yosemite: Version 10.10.3
  • AquaSKK 4.2.5

SKK とは

Simple Kana to Kanji conversion program の略。形態素解析をユーザーが明示的に行うことで、変換ミスのストレスを軽減させることができるという非常にユニークな日本語入力メソッドだ。

詳しくは以下のリンク先を参照。

 

セットアップ方法

 

インストール

公式ページはこちら

だが、現在は GitHub 上で開発が進められている。 ここ

からダウンロード可能な最新版の dmg ファイルを実行するとよい。

インストールの完了にはログアウトが必要。

 

IME追加
  • System Preferences -> Keyboard -> Input Sources と選択
  • Japanese -> AquaSKK 統合を追加
  • Command + Space で IME AquaSKK統合を有効化

AquaSKK のカスタマイズ
  • メニューバー -> AquaSKK統合 -> 環境設定
  • 入力設定 -> 入力操作: 「Enter による確定で改行しない」にチェックを入れると
    より流れるようなタイピングができる
  • キーバインドの衝突の解消

 

Sticky shift と SandS

SKK はシフトキーを多用する入力メソッドである。
そこで実際の SKK ユーザの大半は、シフトキーを押さなくてもシフトを実現できるような工夫をしている。

Sticky shift

特定のキーを押すことで、直後のキーを Shift オンの状態で入力できるようにする機能。
例えば、セミコロン「;」をスティッキーキーに指定した場合、「;foo;bar」と打鍵すると「FooBar」と入力されるようになる。(セミコロンなのは英語キーボードを使っているため)

Mac では、Karabiner (旧名 KeyRemap4MacBook) でその機能を実現できる。

  • Karabiner - OS X用のソフトウェア
  • Change Key -> For Japanese -> Change Semicolon(;) Key
    -> Semicolon to Sticky Shift_L (effective only when input source is Japanese)
    にチェックを付ければよい

  • ただ、この場合 IME オンかつASCIIモードの場合にセミコロンを入力できなくなるのだが
    どうしたらいいものか。

 

SandS

Shift and Space。スペースキーと同時に他のキーを押した場合に Shift オンの状態にする。
親指でシフトを押せるようになるのがポイントだが、試したことはない。

 

基本的な入力方法

この資料が素晴らしい。

 

かな入力モード

  • 英数入力モード、全角英数入力モードで Ctrl-j
  • q キーで「ひらがな」「カタカナ」を切り替え
  • カタカナの入力自体は、読み入力モードで q を押すことでも対応可
英数入力モード
  • かな入力モードで l
全角英数入力モード
  • かな入力モードで Shift-L
読み入力(▽)モード
  • 変換が必要な場合
  • かな入力モードで Shift を押しながら単語の一打目を入力
  • かな入力モードで Shift-Q
  • かな入力モードで スラッシュ(/) を押すと英数字で読みを入力可能
  • Ctrl-g でキャンセル
  • Tab キーで見出し語の補完
  • Space キーで変換モードへ
変換(▼)モード
  • 候補送り: Space
  • 候補戻り: x
  • ユーザ辞書に登録した単語の削除: Shift-x
  • 次の文字を入力するか、Ctrl-j で確定
  • Ctrl-g で読み入力モードに戻る
単語登録
  • 読み入力モードで候補送りし続ける
  • クリップボードから貼り付け: Ctrl-y
  • Ctrl-g で変換モードに戻る
変換ルール
  • デフォルトのルール

    $ cat /Library/Input\ Methods/AquaSKK.app/Contents/Resources/kana-rule.conf |nkf
    # $Id$
    
    # このファイルは改行コードがLF、文字エンコーディングがEUC-JPでなければなりません。
    # 五つ目の項目は次状態です。無ければ項目自体を省略します。
    # エントリの順序には気を付けて下さい。例えば「ba」というエントリに出会うと、
    # AquaSKKはまず「b」という枝を探しますが、ここではまだ存在しないので
    # 「b」という空の枝を作成します。この後に「b」というエントリが現れると
    # そのエントリ「b」は既に定義されているのでどうなるか分かりません。
    
    # 最初の項目の,は半角カンマに置換されます。
    
    a,あ,ア,ア
    
    bb,っ,ッ,ッ,b
    ba,ば,バ,バ
    bi,び,ビ,ビ
    bu,ぶ,ブ,ブ
    be,べ,ベ,ベ
    bo,ぼ,ボ,ボ
    
    (snip)
    ...
  • ぁぃぅぇぉっ: xa xi xu xe xo xtu
  • ‥ … ・ 〜 『 』: z, z. z/ z- z[ z]
  • ← ↓ ↑ →: zh zj zk zl

 

辞書の拡張

このあたりを使うと便利そう。

5.28.2015

HyperLogLog Implementation in Redis, pt.1

Redis: HyperLogLog の実装について その1

だいぶ古い話だが、Redis 2.8.9 で新しいデータ構造 HyperLogLog が登場した。
その実装コードを読む。

(そして、モック作り に活かしたい)

2種類のデータ構造

Redis では、dense(密) と sparse(疎) の 2種類のデータ構造をシームレスに切り替えることで、最小限に近い空間計算量での高精度カーディナリティ推定を可能としている。

また、これらはいずれも STRING 型とよく似たデータ構造 (つまりバイト配列) をなしており、get コマンドを利用することでその実体を窺い知ることができる。

ヘッダの読み方

まずは実際に動かしてみる。

127.0.0.1:6379> get hll
(nil)
127.0.0.1:6379> pfadd hll A B C
(integer) 1
127.0.0.1:6379> pfcount hll
(integer) 3
127.0.0.1:6379> strlen hll
(integer) 27
127.0.0.1:6379> get hll
"HYLL\x01\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00Q|\x88^\xc1\x80Bb\x88MZ"

get で表示されたデータの先頭 16 bytes がヘッダとして定義されているもの。

struct hllhdr {
    char magic[4];      /* "HYLL" */
    uint8_t encoding;   /* HLL_DENSE or HLL_SPARSE. */
    uint8_t notused[3]; /* Reserved for future use, must be zero. */
    uint8_t card[8];    /* Cached cardinality, little endian. */
    uint8_t registers[]; /* Data bytes. */
};
  • magic: 最初の4文字は、データが HyperLogLog であることを示す固定値「HYLL」が入る。
  • encoding: 次の1バイトは、データ構造を示すフラグ。
    上記のように「\x01」であれば sparse(疎)、「\0x00」なら dense(密) を表す。
  • notused: 将来のために確保されている、未使用の領域が3バイトほど続く。
  • card: カーディナリティの値が64bitぶん、リトルエンディアンで保存されている。
    今回の場合は「0x03」「0x00」「0x00」「0x00」「0x00」「0x00」「0x00」「0x00」なので、
    カーディナリティ 3 を表す。

そして、registers の部分が HyperLogLog のデータそのもの。

今回の例では、「Q|\x88^\xc1\x80Bb\x88MZ」の部分 (11 bytes) がそれに該当する。

16進数表記をすれば、順に
「51, 7c, 88, 5e, c1, 80, 42, 62, 88, 4d, 5a」
である。

References

5.25.2015

Scala: Converting Tuple to Function Parameter using FunctionN#tupled

Scala: tupled メソッドを使ってタプルを関数の引数として渡す

 

タプルを直接関数のパラメータとして受け渡したい場合には、Function2, Function3, ... トレイトの tupled を使うと便利。

scala> def f(x: Int, y: Int, z: Int): Int = x + y + z
f: (x: Int, y: Int, z: Int)Int

scala> (f _).tupled((10, 20, 30))
res0: Int = 60

特に Case Class の組み立てに役立つ。

scala> case class C(x: Int, y: Int, z: Int)
defined class C

scala> Seq((1, 2, 3), (4, 5, 6), (7, 8, 9)).map(C.tupled)
res1: Seq[C] = List(C(1,2,3), C(4,5,6), C(7,8,9))

同様に、curried メソッドも備わっている。

scala> (f _).curried(10)(20)(30)
res2: Int = 60

5.24.2015

Scala: SeqLike#sortBy Never Compares in a Single Element Collection

Scala: sortBy は要素が一個だけの場合に比較を行わない

 

シーケンスの sortBy メソッドを利用し、要素を変換したあとにソートしたい。
変換処理で例外が発生する可能性がある場合の挙動。

シーケンスの要素が 1個だけの場合、コンペア自体が行われないため、期待した例外が送出されなかった。

scala> Seq.empty[String].sortBy(_.toInt)
res0: Seq[String] = List()

scala> Seq("a").sortBy(_.toInt)
res1: Seq[String] = List(a)

scala> Seq("a", "b").sortBy(_.toInt)
java.lang.NumberFormatException: For input string: "b"
  at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
  at java.lang.Integer.parseInt(Integer.java:492)
  at java.lang.Integer.parseInt(Integer.java:527)
  at scala.collection.immutable.StringLike$class.toInt(StringLike.scala:247)
  at scala.collection.immutable.StringOps.toInt(StringOps.scala:30)
  at $anonfun$1.apply(:8)
  at $anonfun$1.apply(:8)
  at scala.math.Ordering$$anon$5.compare(Ordering.scala:122)
  at java.util.TimSort.countRunAndMakeAscending(TimSort.java:324)
  at java.util.TimSort.sort(TimSort.java:189)
  at java.util.TimSort.sort(TimSort.java:173)
  at java.util.Arrays.sort(Arrays.java:659)
  at scala.collection.SeqLike$class.sorted(SeqLike.scala:618)
  at scala.collection.AbstractSeq.sorted(Seq.scala:41)
  at scala.collection.SeqLike$class.sortBy(SeqLike.scala:597)
  at scala.collection.AbstractSeq.sortBy(Seq.scala:41)
  ... 33 elided

Scala: Adding Two Long Values Safely

Scala: 2つのLong値を安全に足す

 

Long の足し算でオーバーフロー/アンダーフローが発生するかどうか検知したい。

BigInt に変換してから値を足し込み、有効な Long値かチェックする。

def safeAddition(a: Long, b: Long): Option[Long] =
  Some(BigInt(a) + BigInt(b)).withFilter(_.isValidLong).map(_.toLong)

実行例

scala> safeAddition(Long.MaxValue, 0)
res0: Option[Long] = Some(9223372036854775807)

scala> safeAddition(Long.MaxValue, 1)
res1: Option[Long] = None

scala> safeAddition(-1, Long.MinValue)
res2: Option[Long] = None

scala> safeAddition(Long.MaxValue, Long.MinValue)
res3: Option[Long] = Some(-1)

5.23.2015

Running Scala REPL in Chat Room with Hubot

Hubot で Scala REPL を動かす

 

HUBOT はヒューボットと発音するのが正しいらしい。

HipChat などのチャットツールの特定の部屋で、特定のメンションを付けたメッセージを Scala コードとして REPL 上で実行し、その結果をチャットに返すのが目的。 REPL環境はメッセージのたびに毎回初期化される。

今回初めて Coffee Script に触ってみた。

 

コード

以下のようなスクリプトを scripts ディレクトリ配下に書く。

外部プロセスとして scala コマンドを起動し、その標準入力にメッセージを流しこむだけ。

# Description:
#   ScalaのREPLを実行する
#
exec = require('child_process').exec

module.exports = (robot) ->
  robot.respond /((?:.|\n)+)/i, (res) ->

    if res.message.room == 'your_room'
      code = res.match[1]
      n = code.length
      res.reply n + ' Byte' + if n <= 1 then '' else 's'

      try
        scala = exec 'scala', (error, stdout, stderr) ->
          lines = ('' + stdout).split '\n'
          # 余計な行を省く
          lines = lines[4...-2]
          res.send '/code ' + lines.join('\n')

        scala.stdin.setEncoding = 'utf-8'
        scala.stdin.write(code + '\n')
        scala.stdin.end()
      catch e
        res.reply "command failed: " + e.message
  • line 7: 改行を含むメッセージを正しく処理できるように、少しトリッキーな正規表現を使っている。
    res から直接メッセージ文字列を取得できれば、こんな必要はなさそうだが……
  • line 12: コードゴルフ用にメッセージの長さを通知。
  • line 15: child_process.exec を使って実行。
    これが非同期で実行されるというところが最初理解できずハマった。
  • line 18: REPLの冒頭に表示されるバージョン表示などの冗長な出力部分をカット

たぶん、まだまだリファクタリングできるところは多そう。が、そもそも文法を学んでないので厳しい。

 

備考

  • OSのシステムコマンドや外部接続、リソース消費などに対するセキュリティは全くの無防備状態なので注意。
  • 将来的には conscript と連携して、各プロジェクトごとの sbt console に直接入って操作できるようにしたり、あるいはいくつか常駐プロセスを立ち上げておき、状態を永続化できるように拡張できたらよいと思う。

 

 

References

5.19.2015

Speed Up the Travis CI Build for Scala Projects

Travis CI: Scalaプロジェクトのビルドを高速化させる

 

sbt-assembly で使われていたテクニックを学ぶ。

 

1. コンテナベース・インフラの選択

現時点において、Travis CI におけるビルド環境は以下の3種類。

  • VMベース
  • コンテナベース
  • OS X

sudo に false を明示的に指定すると、コンテナベースのインフラが選択されるようになる。(未指定時はVMベース)
コンテナベースのほうが起動時間の短縮を見込める。

sudo: false

 

2. キャッシュの活用

Travis CI のキャッシュ機能を利用すれば、更新のないライブラリなどのダウンロード処理をスキップできる。

cache: 
  directories: 
    - $HOME/.ivy2/cache 
    - $HOME/.sbt/boot/scala-$TRAVIS_SCALA_VERSION

実際には Amazon S3 に対するダウンロード/アップロードが行われている。
容量にもよるが、ダウンロードで30秒、アップロードで1分程度はどうしてもかかってしまう。

 

3. 不要なキャッシュ更新を避ける

キャッシュに更新がなければ、Amazon S3 へのアップロード処理は省略される。

ビルド実行後に一時的なファイルを削除すれば、このアップロード処理の発生を必要最低限にできるはずだ。
これは before_cache ステージにコマンドを記述することで実現できる。

before_cache:
  - find $HOME/.sbt -name "*.lock" -delete
  - find $HOME/.ivy2 -name "ivydata-*.properties" -delete

これでももし、Travis CI のログに以下のようなメッセージが出ていたら、何か意図しない差分が存在しているということ。

store build cache
changes detected, packing new archive

このように表示されればOK。

store build cache
nothing changed, not updating cache

 

References

5.06.2015

Reading SJIS-Encoded Text File in Scala

Scala: SJIS形式のテキストファイルを読み込む方法

Scala IO での読み書き

ライブラリの Scala IO を利用するのがデファクトスタンダード。

sbt設定
libraryDependencies += "com.github.scala-incubator.io" %% "scala-io-core" % "0.4.3"
libraryDependencies += "com.github.scala-incubator.io" %% "scala-io-file" % "0.4.3"
Codec を定義

implicit value として Codec を定義すれば、その後発生する読み書きで、指定した Codec が使われるようになる。

scala> implicit val codec = scalax.io.Codec("Windows-31J")
codec: scalax.io.Codec = scalax.io.Codec@7f1ecf06

scala> scalax.file.Path("foo.txt").write("あいう")

scala> scalax.file.Path("foo.txt").string
res1: String = あいう

scala> implicit val codec = scalax.io.Codec.UTF8
codec: scalax.io.Codec = scalax.io.Codec@76783189

scala> scalax.file.Path("foo.txt").string    // SJIS形式のテキストファイルを
res2: String = ������                   // UTF8で読み込もうとしたので文字化け

scala> scalax.file.Path("foo.txt").write("あいう")

scala> scalax.file.Path("foo.txt").string
res4: String = あいう

 

InputStreamReader で読み込む

java.io.InputStreamReader を使いたい場合は、コンストラクトの第二引数に charset を指定すればよい。
これは Codec#name で得ることができる。

new InputStreamReader(new FileInputStream("foo.xml"), "windows-31j")

使用例

scala> implicit val codec = scalax.io.Codec("Windows-31J")
codec: scalax.io.Codec = scalax.io.Codec@69b68c37

scala> scalax.file.Path("foo.xml").write("<foo>bar</foo>")

scala> scala.xml.XML.load(new java.io.InputStreamReader(
     | new java.io.FileInputStream("foo.xml"), codec.name))
res1: scala.xml.Elem = <foo>bar</foo>

 

References

5.03.2015

Binary Indexed Trees in Scala

Scala で BIT を実装する

 

概要

 

コード

 

実行例

REPL でコードを貼り付ける場合は :paste モードでの実行が必要。
(コンパニオンオブジェクトを同時に定義するため)

scala> :paste
// Entering paste mode (ctrl-D to finish)

...snip...

// Exiting paste mode, now interpreting.

defined class BinaryIndexedTree
defined object BinaryIndexedTree

scala> val b = BinaryIndexedTree[Int](6).updated(0 -> 1, 2 -> 2, 3 -> 1, 4 -> 1, 5 -> 3)
b: BinaryIndexedTree[Int] = BinaryIndexedTree(6,Vector(0, 1, 1, 2, 4, 1, 4))

scala> b.sum(6)              // 1 + 2 + 1 + 1 + 3
res0: Int = 8

scala> b.sum(2, 5)           // 2 + 1 + 1
res1: Int = 4

 

Related Posts