Unyablog.

のにれんのブログ

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

norifyDataSetChanged でアニメーションする in RecyclerView

stackoverflow.com

  • RecyclerView.Adapter#getItemId()ユニークな ID を返す
  • RecyclerView.Adapter#setHasStableIds(true) を行う

これだけで RecyclerView.Adapter#notifyDataSetChanged() のみでもいい感じにアニメーションしてくれます。

パフォーマンスも良くなります。

ttps://www.reddit.com/r/androiddev/comments/2lr8bf/what_does_recyclerview_sethasstableids_do_and_why/*1

StackOverflow では supportsPredictiveItemAnimations()true を返すようにする必要があると書いてありますが、 Support Library 23.4.0 時点の LinearLayoutManager では必要ありませんでした。

実演

getItemId()ユニークなID(今回はデータの primary key) を返し、全て notifyDataSetChanged() で変更を伝えています。

毎回 notifyDataInserted() 等を呼ぶよりも簡単にできて便利。

*1:reddit リンク入れると投稿失敗するみたいなので h 抜いています

Haskell入門 14日目

十四日目

VBoxHeadlessTray を入れてみた。これだと一々 VM 起動してること気にしなくてよさそう。

SICP 読まないとなあとか思ってるこの頃。

Haskell, monad ぐらいまでやろうかなあとか思って目次見たけど一番最後だった。

すごいHaskellたのしく学ぼう!

すごいHaskellたのしく学ぼう!

第8章

入出力。結構難しいとか聞いたことある。

8.1

  • いい話

8.2

  • ついに ghci 以外から呼び出すようになるのか
  • コンパイルだ。。。!
    • まあ ghci からロードしてる時も呼び出してるのだけど
  • 返り値はあってアクション
  • C みたいに main がデフォで行われるみたいな感じかな

8.3

  • 急に命令形っぽくなってきた
    • main 自体がIOアクションを返す
  • getLine はIOアクションで、String を返す
    • printStrLn はIOアクション自体で、<- はそこから取ってくるみたいな感じ仮名
      • 見た感じリスト内包のアレとは違いそう。
  • do 構文自体はIOアクションを返すんかな
  • do の中で = 使ったら怒られたけどそういうもんなのか
    • let 使ったらできた。
  • haskell, 地味にインデント必要なのが難しいと思う*1
foo <-  putStrLn "hello"
putStrLn $ show $ foo == ()

としたら True と出てなるほどって感じ。

  • ghci でやっても do 内でやれって怒られた。

    • もしやと思って let 付けたら通った。
  • null 初めて見た気がする

    • Foldable を引数に取るので isEmpty みたいなもんっぽい
  • return もIOアクションなのかー

    • do 節で終わりに何もしない時とかに使う

8.4

  • それっぽい入出力群
  • 入力を反転して返すの、when 使えば
when (not $ null line) do putStrLn $ reverse line

に省略できて便利

  • forMmapM はIOアクションの結果を入れてくれる

感想

  • 一気に実用的になった感じで楽しかった。
  • なるほどいろいろ考えられてるなぁという感じ
  • これがよく聞くIOモナドかと思いながら読んでた。

*1:まあpythonもそうだけど