Python: リスト内の True の個数を数える
sum を使えば、True: 1, False: 0 としてカウントされるので便利。
>>> [False, True, True, False, True].count(True) 3 >>> sum([False, True, True, False, True]) 3 >>> sum(x % 2 == 1 for x in range(5)) 2
Python: リスト内の True の個数を数える
sum を使えば、True: 1, False: 0 としてカウントされるので便利。
>>> [False, True, True, False, True].count(True) 3 >>> sum([False, True, True, False, True]) 3 >>> sum(x % 2 == 1 for x in range(5)) 2
Python アプリケーション内のメッセージの国際化 (i18n) について
思ったよりも面倒だったのでメモ。
import gettext
_ = gettext.translation('hello_i18n', 'locale', fallback=True).ugettext
print(_('hello i18n'))
print(_('hello %(world)s') % {'world': 'i18n'})
今回は、xgettext + msgfmt で実現する。
### Macの場合 $ brew install gettext ### バージョンは環境に合わせて適宜変更 $ /usr/local/Cellar/gettext/0.19.4/bin/xgettext --language=python --from-code=UTF-8 \ --keyword=_ --add-comments=NOTE -o hello_i18n.po hello_i18n.py $ vi hello_i18n.po
# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-06-21 23:48+0900\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: hello_i18n.py:5 msgid "hello i18n" msgstr "こんにちは i18n" #: hello_i18n.py:6 #, python-format msgid "hello %(world)s" msgstr "%(world)s、こんにちは"
$ mkdir -p locale/ja_JP/LC_MESSAGES $ /usr/local/Cellar/gettext/0.19.4/bin/msgfmt -o locale/ja_JP/LC_MESSAGES/hello_i18n.mo \ ./hello_i18n.po
$ python2 ./hello_i18n.py こんにちは i18n i18n、こんにちは $ LC_ALL=C python2 ./hello_i18n.py hello i18n hello i18n
GNU gettext との結び付きが強すぎるし、コンパイルが必須なのもあって、あまり使う気がしない設計という印象。
Redis: HyperLogLog の実装について その4
その他のトピック。
追加する要素のハッシュを求め、インデックスと base-2 rank を求める。
各インデックスごとにより大きい値をレジスタに保存するだけ。
$2^{14}$ 個の全てのレジスタを走査し、各位置に対して値の大きいほうを保持する。
非常に高速なマージ処理も HyperLogLog の特長である。
dense 表現では、sparse 表現と異なり、全部のインデックスの値が配列的に保持されている。
Redis では以下のような構造のバイト配列を定義することで、メモリ使用量を圧縮している。
(各レジスタの値は 0〜50 の範囲内であるため、それぞれ 6 ビットで表現可能)
* +--------+--------+--------+------// * |11000000|22221111|33333322|55444444 * +--------+--------+--------+------//
sparse から dense へ変換が行われるタイミングは以下。
Redis: HyperLogLog の実装について その3
前回は sparse 表現のバイナリについて中身をざっと見た。
今回は HyperLogLog の目的である、カーディナリティを求める処理 pfcount についてその実装を確かめてみる。
カウントのコア部分の処理は hllCount 関数に記述されている。
論文によれば、カーディナリティの期待値 $E$ は以下のように求められる。
これまで見てきたように、Redis の HyperLogLog 型に "A", "B", "C" という 3つの値を追加すると、レジスタの状態は以下のようになる。
この例で実際に $E$ を求めてみる。
実際のカーディナリティは 3 なので、大きなズレがあるように見える。
このように、HyperLogLog は小さいカーディナリティに対して大きな誤差が出るという性質がある。
この問題を解決するため、Redis では一旦期待値を算出した後で、以下のルールに従ってその値を補正している。
以下の式 (Redis のコメントには「LINEARCOUNTING アルゴリズム」と書かれている) によって期待値を計算し直す。値が 0 であるレジスタの個数を $z$ として $$E := m \cdot \text{log} \left( \frac{m}{z} \right)$$
上記の具体例では、$z = 16384 - 3 = 16381$ なので $$E := m \cdot \text{log} \left( \frac{m}{z} \right) = 16384 \cdot \text{log} \left( \frac{16384}{16381} \right) \approx 3.0003$$
正しいカーディナリティ 3 を得ることができた。
こうして得られた値 $E$ を 64ビット非負整数に変換したものが、HyperLogLog型のカウントとして得られる値である。
easy-scala-bench - 手軽に sbt-jmh を実行する
ワン・ライナーないしは数行の Scala スクリプトに対して簡単に JMH でマイクロ・ベンチマークを実行できるようにシェルを書いた。
$ cd your/work/dir $ git clone https://github.com/mogproject/easy-scala-bench.git $ cd easy-scala-bench
$ echo 'for (i <- 1 to 1000) for (j <- 1 to 1000) i + j' | ./easy-scala-bench
これだけ。
入力をもとに自動的に Scala コード (src/main/scala/Bench.scala) が生成され、
コマンド sbt 'run -i 3 -wi 3 -f 1 -t 1 easy_scala_bench.Bench' が実行される。
$ echo ' val xs = (1 to 1000000).toList ==== xs.length ' | ./easy-scala-bench
準備用コードと計測用コードを「====」(イコール 4個 完全一致) という行で区切れば、区切り以降のコードだけが計測対象になる。
たとえば
ln -s path/to/easy-scala-bench /usr/local/bin/
のようにパスの通る場所にリンクを作ってコマンド化してしまえば、実行ファイルのパスを意識する必要がなくなる。
$ echo 'for (i <- 1 to 1000) for (j <- 1 to 1000) i + j' | ./easy-scala-bench (snip) [info] Compiling 1 Scala source to /private/tmp/easy-scala-bench/target/scala-2.11/classes... [info] Generating JMH benchmark Java source files... Processing 3 classes from /private/tmp/easy-scala-bench/target/scala-2.11/classes with "reflection" generator Writing out Java source to /private/tmp/easy-scala-bench/target/scala-2.11/generated-sources/jmh and resources to /private/tmp/easy-scala-bench/target/scala-2.11/classes [info] Compiling generated JMH benchmarks... [info] Compiling 1 Scala source and 9 Java sources to /private/tmp/easy-scala-bench/target/scala-2.11/classes... [info] Running org.openjdk.jmh.Main -i 3 -wi 3 -f 1 -t 1 easy_scala_bench.Bench [info] # JMH 1.9.1 (released 43 days ago) [info] # VM invoker: /Library/Java/JavaVirtualMachines/jdk1.7.0_75.jdk/Contents/Home/jre/bin/java [info] # VM options: [info] # Warmup: 3 iterations, 1 s each [info] # Measurement: 3 iterations, 1 s each [info] # Timeout: 10 min per iteration [info] # Threads: 1 thread, will synchronize iterations [info] # Benchmark mode: Throughput, ops/time [info] # Benchmark: easy_scala_bench.Bench.bench [info] [info] # Run progress: 0.00% complete, ETA 00:00:06 [info] # Fork: 1 of 1 [info] # Warmup Iteration 1: 120.743 ops/s [info] # Warmup Iteration 2: 214.615 ops/s [info] # Warmup Iteration 3: 222.462 ops/s [info] Iteration 1: 226.371 ops/s [info] Iteration 2: 230.060 ops/s [info] Iteration 3: 226.733 ops/s [info] [info] [info] Result "bench": [info] 227.722 ±(99.9%) 37.095 ops/s [Average] [info] (min, avg, max) = (226.371, 227.722, 230.060), stdev = 2.033 [info] CI (99.9%): [190.626, 264.817] (assumes normal distribution) [info] [info] [info] # Run complete. Total time: 00:00:06 [info] [info] Benchmark Mode Cnt Score Error Units [info] Bench.bench thrpt 3 227.722 ± 37.095 ops/s [success] Total time: 17 s, completed Jun 6, 2015 11:08:17 PM
$ echo ' val xs = (1 to 1000000).toList xs.length ' | ./easy-scala-bench (snip) [info] # Run complete. Total time: 00:00:06 [info] [info] Benchmark Mode Cnt Score Error Units [info] Bench.bench thrpt 3 24.990 ± 71.849 ops/s (snip) $ echo ' val xs = (1 to 1000000).toList ==== xs.length ' | ./easy-scala-bench (snip) [info] # Run complete. Total time: 00:00:07 [info] [info] Benchmark Mode Cnt Score Error Units [info] Bench.bench thrpt 3 212.187 ± 35.787 ops/s (snip) $ echo ' val xs = (1 to 1000000).toVector ==== xs.length ' | ./easy-scala-bench (snip) [info] # Run complete. Total time: 00:00:06 [info] [info] Benchmark Mode Cnt Score Error Units [info] Bench.bench thrpt 3 1291414027.800 ± 568908317.371 ops/s (snip)
Bash: 入力された内容を行単位で配列に格納する
readarray は互換性が不十分なので使いたくない。
IFS を正しく扱うのがポイント。
IFS=$'\n' lines=($(cat))
declare -a lines
while IFS= read line; do
lines+=("$line")
done
bash-3.2$ IFS=$'\n' lines=($(cat))
a b c
d
e
f
g
bash-3.2$ printf '[%s]\n' "${lines[@]}"
[ a b c]
[d]
[e ]
[ f ]
[g ]
bash-3.2$ lines=()
bash-3.2$ while IFS= read line; do lines+=("$line"); done
a b c
d
e
f
g
bash-3.2$ printf '[%s]\n' "${lines[@]}"
[ a b c]
[d]
[e ]
[ f ]
[]
[g ]
Scala: MurmurHash を実装する
Redis の MurmurHash2 実装をそのまま Scala で書いてみる。
効率よりも分かりやすさを優先。
object MurmurHash {
private val defaultSeed = 0xadc83b19L
def murmurHash64A(data: Seq[Byte], seed: Long = defaultSeed): Long = {
val m = 0xc6a4a7935bd1e995L
val r = 47
val f: Long => Long = m.*
val g: Long => Long = x => x ^ (x >>> r)
val h = data.grouped(8).foldLeft(seed ^ f(data.length)) { case (y, xs) =>
val k = xs.foldRight(0L)((b, x) => (x << 8) + (b & 0xff))
val j: Long => Long = if (xs.length == 8) f compose g compose f else identity
f(y ^ j(k))
}
(g compose f compose g)(h)
}
}
13行目は、Redis のコードの big endian の場合の分岐に相当。
Byte 型を 0xff と論理積を取れば、非負の Int 値が手に入る。
scala> MurmurHash.murmurHash64A("A".getBytes).toHexString
res1: String = fc089b66b14af040
scala> MurmurHash.murmurHash64A("AB".getBytes).toHexString
res2: String = 24fb508dc42efb7f
scala> MurmurHash.murmurHash64A("ABCDEFGHabcdefgh".getBytes).toHexString
res3: String = 40055074d92b389f
scala> MurmurHash.murmurHash64A(Seq.fill[Byte](100)(-1)).toHexString
res4: String = 78be0c4f11cdc6d5
Redis の HyperLogLog で行われるカウント処理の結果も前回の調査内容と一致した。
scala> def count(x: Long): (Int, Int) = (
| (x & ((1L << 14) - 1)).toInt,
| (14 until 64).find(i => ((x >>> i) & 1L) != 0L).getOrElse(63) - 13
| )
count: (x: Long)(Int, Int)
scala> count(MurmurHash.murmurHash64A("A".getBytes))
res7: (Int, Int) = (12352,1)
scala> count(MurmurHash.murmurHash64A("B".getBytes))
res8: (Int, Int) = (12964,3)
scala> count(MurmurHash.murmurHash64A("C".getBytes))
res9: (Int, Int) = (4477,3)