Unyablog.

のにれんのブログ

Kotlin の String#split で正規表現を用いる

Kotlin の String#split は、 String を引数に取った場合 Regex を使用しません。 正規表現を用いて split を行うには、Regex を引数に取る必要があります。

java

"hogehoge piyo  foo".split("\\s+")
// => ["hogehoge", "piyo", "foo"]

kotlin

  • Bad
"hogehoge piyo  foo".split("\\s+")
// => ["hogehoge piyo  foo"]
  • Good
"hogehoge piyo  foo".split(Regex("\\s+"))
// => ["hogehoge", "piyo", "foo"]

Android の df コマンドについて

最近 Android 用の mackerel-agent を作ろうとしていて、Android/proc/meminfo を読んだりしていたのですが、 filesystem の metrics は df -P コマンドを使っていました。*1

Android にも df コマンドは存在していたのでパースしようとしたのですが、バージョンごとの違いなどがあったのでメモ。

バージョンによる挙動

Andorid 6.0.1 (Marshmallow) まで

$ adb shell
shell@SO-02H:/ $ df
Filesystem               Size     Used     Free   Blksize
/dev                   889.4M   104.0K   889.3M   4096
/sys/fs/cgroup         889.4M     0.0K   889.4M   4096
/sys/fs/cgroup/memory: Permission denied
/mnt                   889.4M     0.0K   889.4M   4096
/tmp                   889.4M     4.0K   889.4M   4096
/system                  4.9G     3.9G     1.1G   4096
/data                   21.9G    12.1G     9.8G   4096
/oem                   503.7M   340.1M   163.6M   4096
/cache                 341.0M   304.0K   340.7M   4096
/rca                     8.4M     6.4M     1.9M   1024
/idd                    14.0M     4.9M     9.0M   1024
/persist                27.5M   804.0K    26.7M   4096
/firmware               80.0M    56.5M    23.5M   16384
/lta-label              11.7M     1.1M    10.6M   4096
/storage               889.4M     0.0K   889.4M   4096
/mnt/runtime/default/emulated: Permission denied
/storage/emulated       21.9G    12.1G     9.8G   4096
/mnt/runtime/read/emulated: Permission denied
/mnt/runtime/write/emulated: Permission denied

Android 6.0 までの df コマンドは coreutilsdf とは大きく異なります。

マウントされた先しか見ることが出来ない他、 -h などのオプションは一切使えません。

$ adb shell
shell@SO-02H:/ $ df -h
Filesystem               Size     Used     Free   Blksize
-h: No such file or directory

Android 7.0 (Nougat) 以降? *2

$ adb shell
bullhead:/ $ df
Filesystem                                             1K-blocks     Used Available Use% Mounted on
tmpfs                                                     922680      428    922252   1% /dev
tmpfs                                                     922680        0    922680   0% /mnt
/dev/block/dm-0                                          2999516  2430552    552580  82% /system
/dev/block/dm-1                                           241908   184160     52752  78% /vendor
/dev/block/platform/soc.0/f9824900.sdhci/by-name/cache     92656     4504     86188   5% /cache
/dev/block/platform/soc.0/f9824900.sdhci/by-name/modem     88016    56720     31296  65% /firmware
/dev/block/dm-2                                         26094648 12222956  13855308  47% /data
/dev/fuse                                               26094648 12222956  13855308  47% /storage/emulated

Android 7.0 では coreutils と同じ形式になりました。

-h-P などのオプションも使えるようになり便利。

$ adb shell
bullhead:/ $ df -h
Filesystem                                             Size  Used Avail Use% Mounted on
tmpfs                                                  901M  428K  901M   1% /dev
tmpfs                                                  901M     0  901M   0% /mnt
/dev/block/dm-0                                        2.8G  2.3G  540M  82% /system
/dev/block/dm-1                                        236M  180M   52M  78% /vendor
/dev/block/platform/soc.0/f9824900.sdhci/by-name/cache  90M  4.3M   84M   5% /cache
/dev/block/platform/soc.0/f9824900.sdhci/by-name/modem  86M   55M   31M  65% /firmware
tmpfs                                                  901M     0  901M   0% /storage
/dev/block/dm-2                                         25G   12G   13G  47% /data
/dev/fuse                                               25G   12G   13G  47% /storage/emulated

細かい話

  • Android 6.0 までの実装は以下にあり、Android toolbox に含まれていました。/proc/mounts から読み取っていますが特にオプションを受け取るなどは行っていません。

platform_system_core/df.c at marshmallow-release · android/platform_system_core · GitHub

  • Android 7.0 からは df の実装が toybox に置き換わったので、POSIX に準拠するようになりました。

Lose df to toybox. · android/platform_system_core@cca6019 · GitHub

toybox とは linux コマンド群の BSD ライセンス実装です*3

おまけ

toybox 自体は 6.0 から Android に取り込まれているので、toybox を使えば 6.0 でも新しい df を使えました。

$ adb shell
shell@SO-02H:/ $ toybox df
Filesystem      1K-blocks       Used  Available Use% Mounted on
tmpfs              910748        104     910644   1% /dev
none               910748          0     910748   0% /sys/fs/cgroup
tmpfs              910748          0     910748   0% /mnt
tmpfs              910748          4     910744   1% /tmp
/dev/block/dm-0   5174428    4054116    1103928  79% /system
/dev/block/bootdevice/by-name/userdata 22994780   12698312   10280084  56% /data
/dev/block/bootdevice/by-name/oem 515776     348284     156844  69% /oem
/dev/block/bootdevice/by-name/cache 349136        304     341624   1% /cache
/dev/block/bootdevice/by-name/diag 14327       5165       8835  37% /idd
/dev/block/bootdevice/by-name/persist 28144        804      26688   3% /persist
/dev/block/bootdevice/by-name/modem 81872      57824      24048  71% /firmware
/dev/block/bootdevice/by-name/LTALabel 12016       1168      10524  10% /lta-label
/dev/fuse        22994780   12698312   10280084  56% /storage/emulated

ただ、toybox のバージョンが少し古いので長い filesystem に対応できずはみ出しています。*4

ということで、今回は Android 6.0 以前と Android 7.0 以降でパースを分けるという対応になりそうです。toybox のおかげで色々コマンド打てるようになって便利。

*1:https://github.com/mackerelio/mackerel-agent/blob/master/util/filesystem.go

*2:未来のことはわからない

*3:busybox は GPL2 なので Android のライセンス上含められなかったそう

*4:ここで修正されている https://github.com/landley/toybox/commit/c10638d3b16d065c1efd97ae17c1a8bf417be706

cookpad のインターンに行ってきた話

8/10 から今日まで一ヶ月間、恵比寿にあるクックパッドインターンに行っていました。忘れないうちにヒュヒュっと書きます。

インターンについて

僕が行ったインターンは技術インターンシップです。

このインターンは前半一週間と後半二週間に分かれており、前半が講義、後半が実際に業務を行うというもの。

後半は相談可能と書いてある通り割りと柔軟に期日の設定が可能で、僕は時間があったので後半を一週間伸ばして一ヶ月となっています。

参加

初めはインターンではなく一ヶ月ほど夏にアルバイトとして行こうと思っていました。

しかし、インターンも薦められて迷っていると 「昼ごはんも出るよ?」 との声。

昼ごはんが出るなら選択肢は一つしか無いですね。インターンにしておいてとても良かったと思います。

前半

前半は社員の方が講師となり、6分野についての講義を受けます。

  • 1日目は Rails や TDD。Rails は初めて書いたのですが、一日ガッツリやることで軽くは書けるようになった気がします。*1
  • 2日目は Android。せっかくなので Kotlin で実装したり Realm 入れたりしました。Kotlin, やはり DataBinding (apt) 周りが厳しい。
  • 3日目は iOS。この日は台風が来たので夕方にシュッと帰ったのですが、終わった後 @tyage とハンバーガー屋に行ったら他のインターン生もいて面白かった。
  • 4日目はサービス開発。詳しくは下にありますが、コードを書かずにプロトタイプなどの作成を行いました。普段全くしないことで刺激的だった。

  • 5日目は機械学習。TensorFlow で画像分類だけは軽くしたことがあるのですが、この講義では sklearn を使いました。 mecab で自分で教師用データをいい感じに作るのが面白かった。
  • そして最終日は JS のコンパイラのコードジェネレータ。興味はあるけど普段はなかなか行わないことで、非常に面白かったです。親切なスライドのおかげでやったことなくても案外できました。(という煽り返し)

こうしてみると盛りだくさんな内容ですが、講師の方がわかりやすく解説してくださったので楽しくこなしていくことができました。

最終日に後半に進む人が発表されます。僕は事前に希望していたとおり、インフラの部署に所属することになりました。

後半

後半は実際に業務をこなしていくというもので、配属される部署によって内容は異なります。

僕はインフラ部署で、メンターの星さんの元で色々やりました。例えば構成図書いたり、サーバー増やしたり移行したり。

自分はインフラっぽいことはしたことがなくて、せいぜい家の RasPi を ansible で管理したり GCE で一万溶かした程度なので少し不安でしたが、みなさん優しく教えてくださっていい感じに業務を進めることができました。

サーバーの管理、大規模サービスでのロードバランサやキャッシュetc構成、DBのレプリ構成などなど様々なことを知ることができましたし、最後の方には大掛かりなこともやらせていただいてとても楽しかったです。

これだけ AWS でいろいろやってる会社は少ないと思うし、興味がある人はぜひ行ってみるべきだと思う。

昼ごはん

インターンに行くきっかけとなった昼ごはんですが、さすが cookpad なだけあって(?)非常に美味しかったです。みなさん行きましょう。

詳しくは下の pocke くん*2の記事を見たら良いと思います!!!!

ゆゆ式

完璧に対応してた。

http://alice345.hatenablog.com/entry/2016/09/04/100000

その他

  • オフィス、恵比寿ガーデンプレイスにあるのだけど、恵比寿ガーデンプレイスは丘の上にあるので cookpad 社内からの景色が非常に良かった。良く窓際の人を駄目にするソファーでコード書いてました。
  • オフィスにたまにバナナが置いてあって食べれて良かった。バナナ美味しい。
  • 社内ツールがいろいろとそろっていて、環境を感じた。
    • ほぼ任意の場所で ruby が書かれていてさすがという感じ

まとめ

とにかく毎日が新鮮という感じで楽しかったです。インターン生も面白い人が沢山いたし、社員もすごい人ばっかりだった。

ruby はほとんど読んだことも書いたこともなかったし、AWS も EC2 ポチポチ立てたことがあるだけだった僕が ruby を好きになり route53 のマークを見ずに頭に浮かばせれるようになったのは完全に進捗だし、最高と言えるでしょう。ありがとうございました。

*1:実は bundle exec を初めて使った...

*2:彼はすごくて、rubyPython 書いたりしてた

RecyclerView で Drag と Swipe をサクッと実装

RecyclerView で並び換えや項目の消去をする UI を作成するには、ItemTouchHelper を用いると非常に楽です。

これを使うと、

  • 長押しからの移動で並び替え
  • スワイプで消去をする

といった処理を行えます。

Kotlin での実装

val helper = ItemTouchHelper(object : ItemTouchHelper.Callback() {
    // どのような動きを許可するか
    // ViewHolder ごとに分ける等の場合はここで制御する
    override fun getMovementFlags(recyclerView: RecyclerView?, viewHolder: RecyclerView.ViewHolder?): Int {
        return makeMovementFlags(ItemTouchHelper.UP or ItemTouchHelper.DOWN, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT)
    }

    // 動いた場合
    override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
        // アイテムの位置を変更
        val button = adapter.buttonList.removeAt(viewHolder.adapterPosition)
        adapter.buttonList.add(target.adapterPosition, button)
        adapter.notifyDataSetChanged()
        return true
    }

    // スワイプされた場合
    override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
        // 項目を消去
        adapter.buttonList.removeAt(viewHolder.adapterPosition)
        adapter.notifyDataSetChanged()
    }

    // 選択状態が変化した時に呼ばれる
    // 選択が解除された場合 viewHolder は null になるので #clearView で操作する
    override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
        super.onSelectedChanged(viewHolder, actionState)
        // e.g. 半透明にする
        when (actionState) {
            ItemTouchHelper.ACTION_STATE_DRAG, ItemTouchHelper.ACTION_STATE_SWIPE -> {
                (viewHolder as? OptionButtonsPreferenceRecyclerAdapter.ViewHolder)?.let {
                    it.binding.itemBaseView.alpha = 0.5f
                }
            }
        }
    }

    // アニメーションが終了する時に呼ばれる
    override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
        super.clearView(recyclerView, viewHolder)
        // e.g. 反透明にしていたのを元に戻す
        (viewHolder as OptionButtonsPreferenceRecyclerAdapter.ViewHolder).binding.itemBaseView.alpha = 1.0f
    }
})

helper.attachToRecyclerView(recyclerView)

PhotoLinkViewer-Core/PLVOptionButtonPreferenceActivity.kt at 63fb7a73f7eaded1d8801e5a63bcc65965829b28 · nonylene/PhotoLinkViewer-Core · GitHub

こうするだけで下のように良い感じに移動・消去できるようになります。すごく便利。

補足

  • OptionButtonsPreferenceRecyclerAdapter.ViewHolder に無理矢理キャストしているのはこの ViewHolder しか存在しないため
  • notifyDataSetChagned() でもいい感じにアニメーションしているのは、以下の記事のように特定の ID を返しているため

Android Layout XML の名前空間はルート要素に定義する

たまに DataBinding を使うのですが、例えば TextViewtools:text を指定してレイアウトのプレビューを試みても上手くできないことがあって困っていました。

例えば、適当に

<?xml version="1.0" encoding="utf-8"?>
<layout>
    <TextView
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        tools:text="hogehoge"
        >
    </TextView>
</layout>

と言ったレイアウトを作成しても、プレビューには hogehoge と表示されません。

f:id:nonylene:20160815235046p:plain

仕方がないので今までは android:text="@{text; default=`hogehoge`}" という記法を使ってデフォルト値を設定するようにしていました。

しかし、今日アプリを作っているとプレビューできているレイアウトがあるのを発見。調べてみると、 layout で tools と android を定義しているとプレビューできる ということが分かりました。

なので、今回はこう置き換えます。

<?xml version="1.0" encoding="utf-8"?>
<layout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    >
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        tools:text="hogehoge"
        >
    </TextView>
</layout>

こうすると、しっかり表示されるようになりました。

f:id:nonylene:20160815235606p:plain

レイアウトの名前空間はルート要素に定義する

他のレイアウトでも調べた結果、DataBinding とは関係なく ルート要素で tools と android を定義していないとプレビューに失敗する みたいです。

普通はルート要素に定義していたので気づかなかった。これからは <layout> でもルート要素に名前空間を置こう(自戒)。

画像取得ライブラリの Glide 使ってみた

Android で画像を表示する際、 URL からいい感じにキャッシュしていい感じに表示してくれるライブラリはいくつかあります。

有名所だと

  • Picasso
    • Square のライブラリ
  • Glide
    • 元BumpTech のライブラリ (Google のプロダクトでも使われているらしい)
  • Fresco*1

日本だとなんとなく Picasso をよく見かける気がする。

元々 PhotoLinkViewer では Picasso を用いていて、アニメーション GIF は webView で表示していたのですが、 Glide がアニメーション GIF に対応しているとのことで Glide を使ってみました。

使い方の説明は以下に詳しいです。

上に書いてあるとおりカスタムがしやすい Picasso と言った感じ*2で、インターフェースもそっくりなので適当に Picasso を Glide に置き換えるだけで動きます。すごい。

move to glide from picasso · nonylene/PhotoLinkViewer-Core@a11fe73 · GitHub

OkHttp や Volley にも対応していて、さらに GlideModule を使えば自分の OkHttpClient も使えて便利。

Integration Libraries · bumptech/glide Wiki · GitHub

アニメーションGIFについて

さて、目的はアニメーションGIFな訳ですが一筋縄には行きませんでした。

軽いアニメーションGIF(500*500程度) では軽快に動くのですが、 1980 * 1080 ぐらいになるとよくOOMで落ちてしまいます。

.asBitmap() を指定すると細かい画像の縮小ロードの設定ができるのですが、GIF だとできないので BitmapOptions.inSampleSize レベルではあまり縮小してくれない。*3

ttps://www.reddit.com/r/androiddev/comments/2tpwub/glide_35_released/co1hmsx*4

そもそもまだ縮小してデコードするのに完全に対応していないらしい。

OOM loading huge gifs · Issue #83 · bumptech/glide · GitHub

メモリキャッシュをスキップしたり、 AndoridManifest.xmlandroid:largeHeap="true" を設定すると落ちにくくはなったのですが、そこまでしても結局GIFの拡大・縮小操作やアニメーション自体が非常に重くなってしまいました。

その後結局諦めて webView で表示するようにしました。 webView だと 3000px ぐらいでも落ちることがなく、比較的滑らかです。

小さい GIF の表示にでは簡単に GIF を入れることができて非常に便利そうなので、また機会があれば使おうという感じでした。*5

その他

  • Fresco という Facebook の画像ライブラリもアニメーションGIFに対応していて多機能そうだったのですが、ダウンサンプリングにはまだあまり対応しておらず、コードも大きい等いろいろ面倒そうだったので使っていません。
  • Ion というライブラリも対応していますが、これはもうちょっと汎用的ななんでもライブラリっぽい。

*1:京都には同名のスーパーがある

*2:内部のコードは全然違いますが

*3:inSampleSize が小さい方の辺が優先されるため、横に大きい画像だと縦に表示した際に縦が 3000px ぐらいになるまで inSampleSize が 1 のまま

*4:reddit のNG避け

*5:戻すのめんどくさくて Picasso 使っていた場所はそのまま Glide にしてますが

SQLクエリチューニングメモ

  • table: "table_name"
A B
"a" 1
"a" 2
"a" 4
"b" 1
"b" 4
"c" 1
"c" 3

のようなデータが 100k レコードほど存在するデータベースにおいて、指定のB (b_list) が存在するAランダムに一つ抽出 する。

例:

  • blist: [1] -> mathced A: ["a", "b", "c"] -> ランダムに取り出し -> "b"
  • blist: [1, 2] -> mathced A: ["a"] -> ランダムに取り出し -> "a"
  • blist: [1, 4] -> matched A: ["a", "b"] -> ランダムに取り出し -> "b"

このとき、

select A from table_name
    where B in ({ b_list })
    group by A
    having count(distinct B) > { b_list_length - 1 }
    order by random()
    limit 1
;

としていたが、1.5 秒ほどかかって困っていた。

order by random() があまり良くなく、 count して自前でランダムしたほうが良いという話を見る *1 が、そもそも limit 1 を消し、得られた結果を count しても同程度に遅いのであんまり関係無さそう。

count(distinct B) が悪いのではということで、はじめから B でも group by して count() はグループ化された A の個数を数えるだけにした。

そして、

select A from (
    select A from table_name
        where B in ({b_list})
        group by A, B
    )
    group by A
    having count() > { b_list_length - 1 }
    order by random()
    limit 1
;

とすると 0.8 秒ほどになり、そこから A,B 間に index を貼ることで 0.6 秒ほどになった。

蛇足

その後 group by は暗黙のソートがあるので exists を使おう!という数年前の記事を見かけた。

実際にやってみたら 0.8秒と遅くなってしまったので、 group by に戻した。

参考になったサイト

Use Subqueries to Count Distinct 50X Faster