| @雑談

納豆味噌汁漬け物弁当

今年の目標はとにかく金を貯めることにしようと思った。 いい歳なのに恥ずかしいくらい金がなくてちょっと風が吹いただけで生活が破綻しそうな予感がある。贅沢はしてないつもりだけどとにかく金がない。とりあえず三ヶ月間は給料がなくても生活していけるくらいの金は貯めたい。

💸金がない原因

車を買ってからずっとこの調子で、車を買ったことで維持費や保険料がかかるようになったことに加え、車によって行動範囲が広がったことが問題だと思う。郊外に引っ越して飲みに行く機会は減ったが、しょうもない外食をする機会が増えた。幼稚園の支払いも高い。うちは子供一人しかいないのにとにかく園納金の支払いが厳しい(年間40万円くらい)。子どもが二人以上いる普通の家庭はどうやってやりくりしているのだろう。生命保険や固定資産税の支払いも厳しい。一戸建てに引っ越したことで電気代も高くなった。郊外に家を買ってしまった以上車を手放すのは無理だし、子どもを幼稚園に通わせないわけにもいかない。電気代に関しては嫁さんが一晩中暖房をつけたまま居間でテレビを見ながら寝るのでこれをやめてもらえれば多少は改善されるだろうが、寝室で寝て欲しいと言っても「お前の頭皮と息が臭いしあたしには長谷川博己や山田裕貴が出てるドラマを見ることくらいしか楽しみがないんだよ👿」と言われるので自分ではどうすることもできない。加えて車、家、保険、教育費などもライフステージに起因する出費なのである程度はしょうがないものなのかも知れない。

💰対策

とりあえず自分に出来る範囲で以下のことをやるようにしたい。

👀保険の見直し

生命保険、親が選んだやつに惰性で入り続けてるけど明らかに損している気がする。特に医療保険は無駄がでかい気がする。解約して県民共済とかにした方がよさそう。今年分の保険料は払ってしまったので今年中にデカめの手術して保険金もらったあと解約したい。そもそも金があれば医療保険は入る必要ないと思うし金貯めて保険とか入らなくても大丈夫な状況にしたい。

🚫🍻金がかかる飲み会には参加しない

会社持ちの無料の飲み会にしか行かないことにする。二次会にも行かない。酒飲みに行ったらその時は楽しいけど飲みすぎて翌日破滅するし凄まじい勢いで金がなくなる。友達が減るのはしょうがないとあきらめるしかない。

🚫🍺第三のビールの六缶パックを買わない

家に缶ビールがあることでついつい飲んでしまう。酒がなければないでなんとかなる。どうしても飲みたいときにその日飲む一、二本だけ買うようなスタイルに改める。第三のビールとはいえ金がかかることには変わりはない。宵越の酒は持たないようにする。

🍜弁当がない日はスーパーのカップ麺で済ませる

弁当を作ってもらえなかった日の昼食代は 100 円以下に抑える。不健康になるかも知れないが早死にしたら住宅ローンがチャラになるし保険料も払わなくて済むようになるのでむしろ好都合だと思うことにする。

🚫🏪コンビニで買い物しない

前の会社は給料高い人が多くて、みんな朝の出社時に 500 円くらいするスターバックスのコーヒー、昼飯食ったあとに 300 円くらいするスターバックスのコーヒー、三時のおやつに 400 円くらいするスターバックスのコーヒーを買ってた。自分はスターバックスのコーヒーは買えなかったけど周りに流されてコンビニコーヒーを買う癖がついてしまった。セブンイレブンのコーヒーは一杯 150 円でも量が少なくて二回くらい飲みたくなってしまい一日で 300 円使ってしまうことになる。毎日買ったら月 6000 円になる。一生懸命頑張る自分へのご褒美、みたいな感じで朝から高いコーヒー飲んだりよい昼飯食ったりするという考え方もあるとは思うが、自分はそんなに仕事頑張ってないし稼ぎもよくないのでこういう思考とは距離を置くべきだと思う。ときどき朝食にコンビニで総菜パン買って食べるのもやめる。

🚫💻GitHub の有料プラン解約

なんかうぇぶさーびすを作って一発当てようと自分にハッパかけるつもりで Developer Plan に加入してるけど大したコード書いてないし人様に見せられないコードの置き場所は GitHub から Bitbucket に変える。

🚫🛒コストコに行かない

一昨年の 9 月に会員になって去年の 9 月に失効した。余裕ができたら更新したいと思っていたけどコストコ行くと 2 万とか平気で使ってしまうので貯金が目標金額に到達するまでコストコ断ちすることにする。

🙅🏻‍♂️抵抗

逆に嫁さんにやめろといわれて抵抗しているのは以下。

🖥さくらVPSの解約、もしくは下位プランへの変更

プログラマーなので自由に使える Linux サーバーがないと厳しい。このブログはファイルシステムを結構使うようにしてあるので Heroku だと多分動かない。 AWS は逆に高くなる。

いまはメモリ 2GB プランを使っているので下位プランへ契約変更するという手もあるが、いま動かしてるアプリケーション( puma × 2)だけでも結構メモリが足りないと感じることがあるのでさらにスペックダウンするのは厳しい。実験の場を失うことで長期的には年収を下げることにもつながりかねない。

そもそもサーバー代は Google Adsense と Amazon Associate で 2/3 くらいはまかなえてるのでここは手を付けないことにする。

🎞Amazon Prime 解約

解約してしまうと 2000 円分買わないと送料無料にならないとかいろいろ厳しい。 350 円のものが欲しいだけなのに送料無料にするために 1650 円の大して必要ではない何かを買わざるを得ないシチュエーションはとてもストレスがたまる。せっかく Apple TV で見られるようになった Amazon Prime Video を見られなくなるのも残念。このくらいは死守したい。


正月早々汚らしく金の話をしてしまって最悪だけど、 @taketin さんや @kitak@t32k さんなどといった元同僚の富裕層の皆さんのように用事もないのにヒルトンに泊まったり一回買って売ったイヤフォンをもう一回買い直したりわざと虫歯になってセラミックの歯を自慢したりできるような身分になりたい。バカすぎてブロックチェーンとかはわからないので今年は金を貯めることだけを目的にやっていきます🤑

| @技術/プログラミング

cho45 さんの以下の記事を参考に関連記事を表示するようにしてみた。

ほとんど cho45 さんの記事に書いてある SQL を実行しているだけだけど、関連記事の表示用に Lokka 側に Similarity というモデルを追加して、以下のようなスキーマにしてる。

similar-entries-erd.png

Similarity テーブルの更新は cho45 さんの記事にあるように SQLite で行った計算の結果を反映することで行う。以下のような Rake タスクを定義した。

desc "Detect and update similar entries"
task similar_entries: %i[similar_entries:extract_term similar_entries:vector_normalize similar_entries:export]

namespace :similar_entries do
  require 'sqlite3'
  desc "Extract term"
  task :extract_term do
    require 'natto'
    nm = Natto::MeCab.new
    db = SQLite3::Database.new('db/tfidf.sqlite3')
    create_table_sql =<<~SQL
      DROP TABLE IF EXISTS tfidf;
      CREATE TABLE tfidf (
        `id` INTEGER PRIMARY KEY,
        `term` TEXT NOT NULL,
        `entry_id` INTEGER NOT NULL,
        `term_count` INTEGER NOT NULL DEFAULT 0, -- エントリ内でのターム出現回数
        `tfidf` FLOAT NOT NULL DEFAULT 0, -- 正規化前の TF-IDF
        `tfidf_n` FLOAT NOT NULL DEFAULT 0 -- ベクトル正規化した TF-IDF
      );
      CREATE UNIQUE INDEX index_tf_term ON tfidf (`term`, `entry_id`);
      CREATE INDEX index_tf_entry_id ON tfidf (`entry_id`);
    SQL
    db.execute_batch(create_table_sql)

    entries = Entry.published.all(fields: [:id, :body])
    entry_frequencies = {}
    entries.each do |entry|
      words = []
      body_cleansed = entry.body.
        gsub(/<.+?>/, '').
        gsub(/!?\[.+?\)/, '').
        gsub(/(```|<code>).+?(```|<\/code>)/m, '')
      begin
        nm.parse(body_cleansed) do |n|
          next if !n.feature.match(/名詞/)
          next if n.feature.match(/(サ変接続|数)/)
          next if n.surface.match(/\A([a-z][0-9]|\p{hiragana}|\p{katakana})\Z/i)
          next if %w[これ こと とき よう そう やつ とこ ところ 用 もの はず みたい たち いま 後 確か 中 気 方 頃 上 先 点 前 一 内 lt gt ここ なか どこ まま わけ ため 的 それ あと].include?(n.surface)
          words << n.surface
        end
      rescue ArgumentError
        next
      end
      frequency = words.inject(Hash.new(0)) {|sum, word| sum[word] += 1; sum }
      entry_frequencies[entry.id] = frequency
    end
    entry_frequencies.each do |entry_id, frequency|
      frequency.each do |word, count|
        db.execute("INSERT INTO tfidf (`term`, `entry_id`, `term_count`) VALUES (?, ?, ?)", [word, entry_id, count])
      end
    end
  end

  desc "Vector Normalize"
  task :vector_normalize do
    db = SQLite3::Database.new('db/tfidf.sqlite3')

    load_extension_sql =<<~SQL
      -- SQRT や LOG を使いたいので
      SELECT load_extension('/usr/local/Cellar/sqlite/3.21.0/lib/libsqlitefunctions.dylib');
    SQL
    db.enable_load_extension(true)
    db.execute(load_extension_sql)

    update_tfidf_column_sql = <<~SQL
      -- エントリ数をカウントしておきます
      -- SQLite には変数がないので一時テーブルにいれます
      CREATE TEMPORARY TABLE entry_total AS
          SELECT CAST(COUNT(DISTINCT entry_id) AS REAL) AS value FROM tfidf;

      -- ワード(ターム)が出てくるエントリ数を数えておきます
      -- term と entry_id でユニークなテーブルなのでこれでエントリ数になります
      CREATE TEMPORARY TABLE term_counts AS
          SELECT term, CAST(COUNT(*) AS REAL) AS cnt FROM tfidf GROUP BY term;
      CREATE INDEX temp.term_counts_term ON term_counts (term);

      -- エントリごとの合計ワード数を数えておきます
      CREATE TEMPORARY TABLE entry_term_counts AS
          SELECT entry_id, LOG(CAST(SUM(term_count) AS REAL)) AS cnt FROM tfidf GROUP BY entry_id;
      CREATE INDEX temp.entry_term_counts_entry_id ON entry_term_counts (entry_id);

      -- TF-IDF を計算して埋めます
      -- ここまでで作った一時テーブルからひいて計算しています。
      UPDATE tfidf SET tfidf = IFNULL(
          -- tf (normalized with Harman method)
          (
              LOG(CAST(term_count AS REAL) + 1) -- term_count in an entry
              /
              (SELECT cnt FROM entry_term_counts WHERE entry_term_counts.entry_id = tfidf.entry_id) -- total term count in an entry
          )
          *
          -- idf (normalized with Sparck Jones method)
          (1 + LOG(
              (SELECT value FROM entry_total) -- total
              /
              (SELECT cnt FROM term_counts WHERE term_counts.term = tfidf.term) -- term entry count
          ))
      , 0.0);
    SQL
    db.execute_batch(update_tfidf_column_sql)

    vector_normalize_sql = <<~SQL
      -- エントリごとのTF-IDFのベクトルの大きさを求めておきます
      CREATE TEMPORARY TABLE tfidf_size AS
          SELECT entry_id, SQRT(SUM(tfidf * tfidf)) AS size FROM tfidf
          GROUP BY entry_id;
      CREATE INDEX temp.tfidf_size_entry_id ON tfidf_size (entry_id);

      -- 計算済みの TF-IDF をベクトルの大きさで割って正規化します
      UPDATE tfidf SET tfidf_n = IFNULL(tfidf / (SELECT size FROM tfidf_size WHERE entry_id = tfidf.entry_id), 0.0);
    SQL
    db.execute_batch(vector_normalize_sql)
  end

  desc "Export calculation result to MySQL"
  task :export do
    db = SQLite3::Database.new('db/tfidf.sqlite3')
    create_similar_candidate_sql = <<~SQL
      DROP TABLE IF EXISTS similar_candidate;
      DROP INDEX IF EXISTS index_sc_parent_id;
      DROP INDEX IF EXISTS index_sc_entry_id;
      DROP INDEX IF EXISTS index_sc_cnt;
      CREATE TABLE similar_candidate (
        `id` INTEGER PRIMARY KEY,
        `parent_id` INTEGER NOT NULL,
        `entry_id` INTEGER NOT NULL,
        `cnt` INTEGER NOT NULL DEFAULT 0
      );
      CREATE INDEX index_sc_parent_id ON similar_candidate (parent_id);
      CREATE INDEX index_sc_entry_id ON similar_candidate (entry_id);
      CREATE INDEX index_sc_cnt ON similar_candidate (cnt);
    SQL
    db.execute_batch(create_similar_candidate_sql)

    extract_similar_entries_sql = <<~SQL
      -- 類似していそうなエントリを共通語ベースでまず100エントリほど出します
      INSERT INTO similar_candidate (`parent_id`, `entry_id`, `cnt`)
          SELECT ? as parent_id, entry_id, COUNT(*) as cnt FROM tfidf
          WHERE
              entry_id <> ? AND
              term IN (
                  SELECT term FROM tfidf WHERE entry_id = ?
                  ORDER BY tfidf DESC
                  LIMIT 50
              )
          GROUP BY entry_id
          HAVING cnt > 3
          ORDER BY cnt DESC
          LIMIT 100;
    SQL

    search_similar_entries_sql = <<~SQL
      -- 該当する100件に対してスコアを計算してソートします
      SELECT
          ? AS entry_id,
          entry_id AS similar_entry_id,
          SUM(a.tfidf_n * b.tfidf_n) AS score
      FROM (
          (SELECT term, tfidf_n FROM tfidf WHERE entry_id = ? ORDER BY tfidf DESC LIMIT 50) as a
          INNER JOIN
          (SELECT entry_id, term, tfidf_n FROM tfidf WHERE entry_id IN (SELECT entry_id FROM similar_candidate WHERE parent_id = ?)) as b
          ON
          a.term = b.term
      )
      WHERE similar_entry_id <> ?
      GROUP BY entry_id
      ORDER BY score DESC
      LIMIT 10;
    SQL

    results = {}
    Entry.published.all(fields: [:id]).each do |entry|
      db.execute(extract_similar_entries_sql, [entry.id, entry.id, entry.id])
      db.results_as_hash = true
      similarities = db.execute(search_similar_entries_sql, [entry.id, entry.id, entry.id, entry.id])
      results[entry.id] = similarities
    end

    Similarity.destroy

    results.each do |entry_id, similarities|
      if similarities.present?
        similarities.each do |s|
          conditions = { entry_id: s["entry_id"], similar_entry_id: s["similar_entry_id"] }
          similarity = Similarity.new(conditions)
          similarity.score = s["score"]
          similarity.save
        end
      end
    end
  end
end

やってることとしては、全エントリーを拾ってきて本文を MeCab で品詞分解して名詞だけを取り出し記事ごとの term 一覧を作り、そこから TF-IDF を求めてベクトル正規化し、最後に関連していそうなエントリを探し出して similarities テーブル(こちらは SQLite のテーブルではない)を更新している。詳しいアルゴリズムはバカなのでわからないが、 cho45 さんが書いているやり方を Lokka のスキーマに素直に適用した感じ。

結構この処理は遅いので parallel.gem を使って高速化できないか試してみたが、スレッドによる並行処理ではあまり速くできなかった。 4 コアある CPU のうち一つが 100% で処理を実行してもまだ 3 コアは余っている。プロセスを増やして並列処理するのがよさそうだが、分散をプロセスレベルで行おうとすると MySQL server has gone というエラーが出る。 DataMapper が MySQL とのコネクションをロストするようである。 ActiveRecord であれば reconnect するだとか回避方法があるようなのだけど DataMapper は情報が少なく、対応方法が見つけられなかったので一旦並列処理はあきらめた。

何回か動かしてみて大体正しく関連記事を表示できてそうなのでさくらの VPS で稼働させたいところなのだけど、関連記事の更新はいまのところ手動でやっている。本番 DB の entries テーブルを dump してきて Mac に取り込み、 similarities テーブルを更新して今度はローカルで similarities テーブルを dump して本番にインポートするという手順をとっている。

これにはいろいろ理由があって、一つには利用している mecab-ipadic-neologd (新語にも対応している MeCab の辞書)が空きメモリ 1.5GB 以上でないとインストールできずさくらの VPS にインストールできなかったから。もう一つには cho45 さんのブログにもあるけど SQLite で LOGSQRT を使うためには libsqlitefunction.so の読み込みが必要で、 load_extension() できるようにしないといけないが、そのためには sqlite3 をソースからビルドする必要があり若干面倒だった( Mac では Homebrew で sqlite を入れた)。

関連記事の更新は自分が記事を書いたときにしか発生しないのでいまの手動運用でもまぁ問題ないが、このブログは Docker でも動くようにしてあるので Docker イメージを作ればさくら VPS でも問題なく動かせそうな気はする。正月休みにでもチャレンジしたい。

感想

関連記事表示、結構面白くてちゃんと関連性の高いエントリーが表示される。例えば人吉に SL に乗り行った記事の関連記事にはちゃんと山口に SL に乗りに行ったときの記事 が表示される。いまのところ Google Adsense の関連コンテンツよりも精度が高いようである。

無限に自分の黒歴史を掘り返すことができるのでおすすめです。

| @Mac/iPhone

旅行や街歩きに出かけたとき、移動軌跡が地図上に表示されるとその時何をしていたのかが思い出せて便利になると思う。例えば知らない街に旅行に行った数日後にこんな状況になったことはないだろうか。「昼飯を食ったあとに食器屋に行って良い皿を見たんだけど迷って買わなかったんだよなぁ、あれはなんて店なんだろう、ネット通販やってるかな🤔」。自分はよくある。こういうときに位置情報付きの行動ログが残ってれば店名を探り当てやすくなるし、目当ての商品にたどり着ける可能性が高まる。

自分は今のところ Twitter 、 Instagram への投稿と Swarm へのチェックインを IFTTT 経由で Day One に自動投稿しているので、それらの情報が手がかりにならないでもないが、それでもそのときの自分が Twitter なり Instagram なり Swarm なりに何らかの情報を投稿していないと Day One には何も残らない。やはり何らかの行動ログ的なものが記録されている方がいい。

とはいえ旅行や町歩きのときに意識的に行動ログの取得を開始するのは難しいと思う。旅行中、街を散策する前に iPhone を取り出して何らかのアプリケーションを起動し「開始」ボタンを押したりできるだろうか? 気がついたときには随分行動してしまったあとで「今から記録をとってもね😔」という感じになるのが現実だろう。この手のツールはバックグラウンドで勝手にログをとっといてくれるのが重要だと思う。

何か良いアプリはないものかと App Store で調べてみたところ、 SilentLog というやつが見つかった。評価は良かったがバッテリーの減りが速いとある。インストールしてみたところ確かにバッテリーの減りが速くなる。よくよく考えたら Google Maps のタイムラインや Day One でもバックグラウンドでの位置情報の取得はやってるので似たようなことを複数のアプリでやるのは効率が悪い。

とはいえ移動したログを一番かっちょよく見られるのは SilentLog だった。こんな風に一日のタイムラインを手軽に振り返ることができる。朝家を出て電車に乗り、9時から18時まで職場で仕事をしてなどがすぐわかる。休みの日に出かけた場所も意識せずに記録されていく。その日 iPhone で撮った写真も表示される。

SilentLog

特に良いのが1日の行動ログをほかのアプリやサービスに書き出せること。 Google Maps のタイムラインはエクスポート機能がないし、 Day One の Activity Feed は一定期間たつと消えてしまうので残しておきたいなら意識して記事を作成しないといけない上に、記事にしたとしても位置情報の履歴は残らない。

Google Maps and Day One.png

その点、 SilentLog は行動ログが地図上に表示された画像を手軽に書き出せる。とても便利なのだが、この機能を使うには App 内課金で有料チケットを購入する必要があるようだった。 240円/30日で年払いだと 1900 円とのこと。

位置情報の収集や書き出し、気持ち悪いと感じる人には気持ち悪いこと極まりないだろう。またもし個人を特定できるかたちで流出して誰かに見られてしまったら非常にまずい(浮気中の人など)。加えて SilentLog を作っている会社 はユーザーの位置情報データを売っているようである。おそらく「年収 XXX 万円くらいの 30-40 代の男性が毎週何曜の何時に新宿駅に何人くらいいる」みたいなデータをデパートとかに売ったりするのだろう。 DMP の位置情報版といったところか。個人を特定できないようにはなってると思うがあまりよい気持ちはしない。

とはいえ自分でも忘れるような行動の履歴をスマートフォンアプリが取っておいてくれてあとで振り返られるというのは絶対に便利だと思う。日記は書いておくと便利だけど書くのが非常に面倒くさい。行動のログから自動で簡易的な日記にしてくれてバッテリーの消耗が穏やかなアプリがあったらうれしいなぁ。(結構個人のプライバシーを尊重する) Apple がそういうのつくってくれないかな。

| @Mac/iPhone

先週、 Alfred で通貨換算をやる方法(💵 Alfred で手軽に通貨換算 💴 )について書いたけどその続き。

Numi icon

Numi という Alfred に似たソフトがある。 Soulver と違い多言語対応はしておらず、自然言語による入力は日本語未対応なのだが、 Soulver もまともに使えるのは英語利用時だけなので実質的には機能に差がない。タブを使えたりする分、 Numi の方が高機能かも知れない。さらに Numi は今のところ無料である(ベータ版のため)。ベータ版とはいえほとんど困ることなく利用できている。

Numi うどん

このソフトがすごいのが、 Alfred Workflow を入れると Numi による計算を Alfred から n <式> と入力することでできるようになるということ(ただし Numi.app を起動しておく必要あり)。こんな感じ。

Numi Currency conversion

いちいち Numi.app に切り替えて新規ドキュメントを作成し、ということをしなくて済む。とても手軽に計算できる。通貨換算のほかに、単位の換算もできる。 Google Fincance の Converter サービスを使うやり方よりも高機能だ。

Numi Unit conversion

このソフトを知ってから Soulver を使う機会がめっきり減ってしまった。 Alfred からも計算できるし、結果を残しておきたいような複雑な計算をするときは Numi.app に切り替えて使うこともできる。文章を書いたり考え事をしているときにささっと計算できて頭の切り替えコストが低い。 AWS を利用しているプログラマー諸氏なら EC2 インスタンスの料金なんかを計算するときにもとても便利だと思う。おすすめです。


この記事は Alfred 3 Advent Calendar 2017 - Adventar 12 日目の記事でした。明日は(明日も) @tsurusuke さんです。

| @旅行/散歩

DSC_3608

子どもがてっちゃんなので春の山口に引き続き SL を見る&撮るという目的で人吉に行った。自分が子どもの頃に阿蘇を走っていた SLあそBOY が老朽化のため立野の急勾配を上れなくなり、熊本と人吉の間を走る人吉号として余生を過ごしているのだった。

DSC_3525

朝早起きして福岡の家を出て 9 時半頃に熊本駅に到着し、駐車場に車を止めて熊本駅から SL に乗った。熊本駅では写真撮影コーナーがあって、乗務員の人にシャッターを押してもらって家族写真を撮ったりできる。カツアゲに怯えながら熊本駅近くの塾に通っていた中学生の頃が懐かしかった。

DSC_3502

SL 人吉号は SL やまぐち号よりも内装がしゃれてて綺麗だった。やまぐち号の車内は何となくすすけていて、乗車後に鼻をかむと黒い鼻くそが出てきたりしたけど、人吉号の車内は清潔かつモダンで冷房も効いており快適そのものだった。鼻くそが黒くなることもなかった。しかし人吉号は車内販売が充実しておらず、ちょっと何か食べたいと思っても飲み物と甘い物くらいしかなくてその点は不満だった。やまぐち号は乗務員の人が回ってきてスナック菓子や酒のつまみっぽいやつなど割と豊富に食べ物があった。

DSC_3504

熊本県出身ながら熊本駅以南に列車で出かけたことはほとんどなかったので、宇城や八代の平野部を走る列車の車窓は新鮮だった。緑の絨毯を SL で進む感じだった。

DSC_3513

人吉に着くまではよかったけど、人吉駅に着いて駅前の定食屋で適当に昼飯を食べ、予約しておいたレンタカー屋に駅まで迎えに来てもらってから雲行きが怪しくなってきた。レンタカー屋が宿から遠い、借りたレンタカーに USB ポートが付いていない、シガーソケットに付ける USB 充電器を忘れてきた、 Anker のモバイルバッテリーがすっからかん状態だったため iPhone の順電ができないことを嫁さんにとがめられ大げんかになり、撮影に行く途中で車を降りて別行動をとった。

DSC_3565

結局自分の iPhone も充電切れになりそうだったので、猛暑の中荷物とカメラを抱えて 2km くらい歩いてたどり着いたナフコで 2000 円もする怪しい USB 充電器を買って充電できそうなスポットを探し、近くのマクドナルドに入店してコンセントを確保し狂ったように充電しまくった。ゴミみたいな 3 時間だった。会社から課題図書として指定されていた『顧客が熱狂するネット靴店 ザッポス伝説』を読んでいた。わお。

SL 撮影から戻ってきた家族と合流し、レンタカーを返して宿に向かった。三連休でどこも混んでいるなか、三人で 1 万円ちょいで泊まれる格安の宿だった。いつか自分も元同僚の taketin さんのように一泊 2000 円の宿なのに 34000 円使うような豪遊をしてみたい。

この宿は場所はよいけど温泉は付いておらず、かわりに近隣の温泉の入浴券がもらえた。近くの球磨川沿いのあゆの里というホテルの温泉に入らせてもらうことにした。ここがすごく眺めがよくて、しかも 19 時までに入れば脱衣所で生ビールが無料で飲めるという謎のサービスも付いていた。最高すぎてここに泊まりたいと値段を調べてみたら一泊 30000 円くらいして到底庶民が泊まれるような宿ではなかった。

IMG_3419IMG_3415


人吉温泉 清流山水花 あゆの里

風呂から上がってから夕食をとる店を探したがなかなかよい店が見つからず、思い切って入った店は味はよかったが少々お高い店でげんなりした気持ちで宿に帰った。

翌朝、朝食付きプランにしていたので食堂に朝食を食べに行ったが食堂は狭くまたテーブルもセッティングされておらず少々待った。うまいのかまずいのかよく分からないまま食事を終えてデザートにスイカを食べたようとしたところ異臭がする。悪くなっていたようだった。つらい。

海の日の三連休で異常に暑かったが人吉まで来て観光しないわけにはいかないので街を歩いて回った。とても寂れた感じがしたが、なかなか味わいのある街並みだった。少し前までストリップ劇場があったらしいスカイビルというビルが何とも言えない雰囲気を放っていた。

DSC_3567

IMG_3436

DSC_3606

DSC_3604

城下町の鍛冶屋町というところを歩いているとファイナルファンタジーとかに出てきそうな包丁屋があり嫁さんが包丁を買った。恐ろしくよく切れる包丁で「あぁ俺は多分この包丁で刺されて死ぬんだろうな」と思った。その先にはみそ・しょうゆ蔵があり、味噌と醤油も買った。中の方まで自由に見学できる蔵で真夏なのに中は涼しく不思議な感じがした。お茶と漬け物を出してもらって小休止した。

DSC_3580

DSC_3588

DSC_3596

その後タクシーで駅前まで移動して青井阿蘇神社にお参りし、 SL がターンテーブルで回転するところを見物しようとしたが時間をミスってそのシーンを見逃してしまう。これでまたアホみたいに怒られた。

DSC_3624

DSC_3637

DSC_3643

慌ただしくお土産を買い、弁当を買って再び熊本に戻る SL 人吉に乗ろうとするが、なんと時間が中途半端過ぎて駅弁が売り切れ状態であり、いなり寿司を二パックしか買えなかった。往路同様、人吉号の車内販売はしょぼしょぼなので結局家族全員ひもじい状態で熊本駅まで二時間堪えなければならなかった。途中の停車駅でサバ寿司の駅売りがありこれを買って食べたが 800 円もするのにちょびっとの量でひもじさに拍車がかかった。

DSC_3646

帰りも記念写真を撮ったり景色を眺めているうちに熊本駅に到着し、車に乗り換えて熊本ラーメン(黒亭)を食べてから福岡へ帰った。初めて金峰山の西側、有明海沿いを運転したが夕日がものすごく綺麗だった。道は狭いところがあるものの、迫力のある景色が見られてなかなかよいドライブコースだと思う。

DSC_3658

KPT

Keep

  • 出身県でも地元から遠い地方にはなかなか行かないので足を伸ばすことができてよかった。
  • 熊本は阿蘇以外にもいっぱいいい温泉地ある。

Problem

  • 予定がぐだぐだ
    前回の山口旅行のときは家庭内 Kibela を活用して綿密にスケジュールを組でいたが、前回とは違って転職したばかりで慣れないこともあり事前に十分な計画を練ることができなかった。若かったり子どもがいなかったりなら場当たり的な旅行でも面白いかもしれないけど、子連れだとそれなりに事前調査してから行かないとばたばたになってしまう。

  • 酒が飲めない
    途中まで車で行って列車に乗るタイプの旅行は運転をしないといけないので列車の中で酒が飲めないのがつらい。山口のときも今回も、嫁さんだけ列車の中でうまそうにビールを飲んでいて気が狂いそうになった。

Try

  • 旅行前はきちんと計画を練る。
  • Anker のモバイルバッテリーは前日寝る前に確実に充電しておく。
  • レンタカーに乗るときはシガーソケットから充電するやつを忘れない。

このどうしようもないチラシの裏のような日記は旅行 Advent Calendar 2017 - Adventar 8 日目の記事( 1 日遅れで書いています、すみません)でした。 9 日目は nayo74 さんです。

| @Mac/iPhone

Soulver と Calca というのを去年自分が作った Advent Calendar で書いた(ただし締め切りを二ヶ月過ぎてから)。数字混じりのテキストを入力するといい感じに内容を読み取って計算してくれるソフトの紹介記事だった。

Soulver は便利でよく使っているのだけど、何か計算をしたいときに一々新規ドキュメントを作成しなければならないのが煩わしい。結果を保存しなくていいような、本当にチラシの裏で筆算してあとは捨ててもいいような手軽な計算ができないものかと思っていた。例えば Alfred を起動してさっと計算するような。

実は Alfred にも計算機能はある。ランチャーウィンドウを呼び出して数字を入力すると計算してくれる。

Alfred Calculator

ただ Soulver にあるような 1 USD in JPY と入力して 113 JPY と通貨換算してくるような機能はない。ネットでデジタルコンテンツをコンシュームしたり海外の通販でブツをゲットしたりするデジタルネイティブの皆様におかれましては USD 決済が基本であり、お金を払う前に「日本円にしたらいくらくらいかな?」と考えることはよくあると思う。そういうときにさくっと Alfred で通貨換算してくれる方法があったら便利ですよね。あるんですよそういうのが。

Currency Converter\.alfredworkflow
http://cl.ly/MBJ8

ちょっと URL が怪しいけど CloudApp というサイトからダウンロードできる。中身はシェルスクリプトで、例えばドル円の換算だと次のようなシェルスクリプトが実行される。

AMOUNT={query};

RESULT=`curl -s "http://finance.google.com/finance/converter?a=$AMOUNT&from=USD&to=JPY" | awk '/<span/{print}' | sed -e 's/<[^>][^>]*>//g' -e '/^ *$/d'`

echo '<?xml version="1.0"?><items>
            <item uid="convert" arg="'$RESULT'">
                <title>'$RESULT'</title>
                <icon>icon.png</icon>
                <subtitle></subtitle>
            </item>
        </items>'

Google の Currency Converter サービスに curl で問い合わせて結果を Alfred で表示できるように XML に整形しているだけである。

たとえば USD 58 と入力すると以下のように日本円でいくらになるかを表示してくれる。

Currency Converter USD

ここで Enter を押すとクリップボードに結果を保存してくれる。

Notification

なお Google の Currency Converter の URL が最近変更されたため↑のリンクのやつをダウンロードしてインストールしただけでは動かないと思う。 curl する URL を変える必要がある。よくわからん人は僕が貼ってるシェルスクリプトをコピーして 「Alfred Preferences」 -> 「Workflows」 -> 「Currency Converter 」と進み、 Script Filter にペーストするとよい。

Alfred Workflow Preference

Alfred Workflow Script Filter

curl する URL のクエリパラメーターを調整することで EUR -> JPY や話題の BTC -> JPY の変換もできる。仮想通貨の相場が気になって気になって仕方がない方などは Alfred で効率的に計算してるふりしてレートが調べられて便利だと思うので是非お試し下さい。


この記事は Alfred 3 Advent Calendar 2017 - Adventar 7 日目の記事でしたが一日遅れで書いてます。 8 日目は誰も登録していません。


追記 2018-06-25

コメント欄で @gnrr さんにご指摘いただいてますが、 Google の通貨変換 API が機械的なアクセスを拒否するようになり、この記事に書いてあるやり方では通貨換算できなくなりました。 @gnrr さんが作成された以下の Alfred Extension だとこの機能が復活しますのでお困りの方はご利用下さい。

| @料理/食事

DSC_4302

Kaizen Platform, Inc. (現株式会社Kaizen Platform)を退職したときに、記念品として AeroPress をプレゼントしてもらった。この AeroPress がなかなかよくて気に入っているので紹介します。

AeroPress は Rebuild でも話題に上がったことがあるし、名前だけは知ってる人いるかもしれない。

自分はどういうわけか知らないけど存在を知っていた。おそらくスーパーウルトラ富裕なコーヒー好きの元同僚 @t32k さんのツイッターかブログで知ったのだろう。買いたいとは思っていたけど自分で買うには結構高くて躊躇していたところ、退職イベントが発生してプレゼントしてもらうことができた。カプ社の皆様ご恵贈ありがとうございました。

何がよいのか

味にぶれがないことがよい。ドリップでうまい味を再現するためにはお湯の量とか抽出時間とか一度に注ぐお湯の量とか気をつけなければならないことが多すぎると思う。 AeroPress を使う場合、豆の量とお湯の温度さえ気をつければよくて、あとはテキトーにやっても味が変わらない。毎回おいしくできるのがよい。

まずい豆でもそこそこおいしくなるのも不思議だけどよい。やっぱりコーヒーは豆の焙煎具合や鮮度が一番重要なファクターで、どんだけドリップを名人がやっても程度の低いコーヒー豆でおいしいコーヒーを抽出することは無理だと思う。 AeroPress を使ったとしてもその原則は崩れないのだけど、それでもスーパーの半額セールで買ったようなやつ(ドリップで飲むと思わず顔をしかめたくなるような味)でも「うん、まぁいけるね」という程度の味にはリカバリできる。本当に不思議。

さっと抽出できて片付けが楽なのもよい。ドリップは 3 分間くらい、ドリッパーの前につきっきりになってちょぼちょぼとドリップしていかなければならない。 AeroPress の場合、挽いた豆を容器の中に入れてお湯を注ぎ、付属のかき混ぜへらで混ぜてあとはカップの上に載せて抽出していくだけ。 30 秒くらいでできてしまう。片付けも楽で、プラスチックフィルターを外してぽんと押し出すだけで豆かすが捨てられる。洗うのもささっと流せば完了。めっちゃ楽。

オフィシャルな使い方

販売元的には AeroPress は空気で圧力をかけるエスプレッソみたいな存在なのだと思う。公式ページに置いてある動画でも少量を抽出して、お湯で薄める様子が紹介されている。

ただ自分的にはこの使い方はあまり合わなかった。コーヒー豆を沢山使わないとちょうどよい濃さのコーヒーにならない。自分は守銭奴なのでコーヒーを入れるときに使う豆の量はコップ一杯あたり 10g から豪遊しても 12g 程度に抑えたいと思ってる。 AeroPress 付属の計量スプーンは 15g 用で、一杯あたりのコーヒーにこんなに量を使えるのは @t32k さんのような富裕層だけだと思う。自分には無理。

いろいろやって試してみて、以下の入れ方が 10g のコーヒー豆で外れなしの抽出をできるという結論にたどり着いた。倒立法という逆さまにした方法で入れます。

自分の使い方

0. コーヒー豆を挽いてお湯を沸かす

1. 器具を組み立てて、本体の中に豆を入れる

DSC_4305 DSC_4306 DSC_4308

2. 沸かしておいたお湯を注ぐ。温度は 80 度にする。

DSC_4309 DSC_4310

3. ちょびっとだけ入れた状態でかき混ぜる

DSC_4311

4. お湯をぐーっと上まで注ぐ

5. 付属のプラスチックフィルターに円形ペーパーフィルターをセットして本体にくっつける

DSC_4312 DSC_4313 DSC_4314

6. ぐいっと本体をひっくり返してマグカップの上にセットする

DSC_4315

7. 注射をするようにじわじわ最後まで押し込んでいく

DSC_4316 DSC_4319

8. できあがり

DSC_4320

9. 片付けも楽勝です。プラスチックフィルターを外して豆かすをゴミ箱に押しだし捨てるだけ

DSC_4321 DSC_4322

10. 飲みましょう

DSC_4323

この入れ方だと一度に一人分しか抽出できない。 ArroPress は 4 人分まで抽出できるようになっているが、お湯で薄めることが前提となっている。上述の通りそれは豆の量が沢山必要になるので自分的にはこの入れ方に落ち着いている。

というわけで AeroPress: The Good Parts でした。コーヒー好きだけどなかなか自分ではおいしく入れられないという人は是非お試し下さい。


この記事は コーヒー Advent Calendar 2017 - Adventar 二日目の記事でした。明日は euxn23 さんです。