秘密の本棚

気になることをなんでも書きます

Touch Barでメールの未読を通知する

Dockを消したい

MacOSには下部にはDockという、起動中のアプリケーションを表示しておく場所があります。

f:id:nexusuica:20180912143915p:plain
Dock。通知のバッジも表示される
各アプリケーションのアイコンの右上には、通知がある場合にその数が赤い丸で表示されます。一目で通知があるかを確認できるので、結構便利です。
ただ、Dockは下部のスペースを占領してしまって邪魔なので、隠したいというのは本当のところ。しかしそうすると、この通知のバッジも見られなくなってしまうのでメールが来ているかどうかを一目で確認することができません。そこで今回は、Touch Bar上に未読メールがあるかどうかを表示させるようにしてみたいと思います。

未読メールがあるかどうかをチェックする

MacOSで未読メールがあるかどうかを判断する方法として、以下の2つがあります。

  • デフォルトのメールアプリMail.appから未読メール数を取得する
  • メールサーバから未読メール数を取得する

前者はオフラインでも実行できるところが優れています。ただ、Mail.appから直接未読数を取得することができないため、メールを一通ずつ取得して未読かどうかを判断し、それをカウントすることになります。コードは以下のとおりです。

tell application "Mail"
	set unread to 0
	repeat with msg in messages in inbox
		if msg's read status is not true then
			set unread to unread + 1
		end if
	end repeat
end tell
return unread

やっていることは単純で、メールボックスのメールをすべて取り出して、ループで回しています。想像はつくと思いますが、これを実行するのにはかなり時間がかかり、システムの負担も大きいです。これでは常駐させるにはあまりにも向いていないため、メールサーバにアクセスする方法を取ることにしました。
僕はメインでGmailを使っているのですが、Gmailには未読メールを簡単に取得できるURLが存在します。URLにアクセスし、ユーザ名とパスワードを入力することで、未読メール数と各メールの概要をXML形式で取得することができます。fullcountというところを読めば、未読メールがいくつあるかを取得できます。

https://mail.google.com/gmail/feed/atom

<feed xmlns="http://purl.org/atom/ns#" version="0.3">
<title>Gmail - Inbox for <メールアドレス></title>
<tagline>New messages in your Gmail Inbox</tagline>
<fullcount>1</fullcount>
<link rel="alternate" href="https://mail.google.com/mail" type="text/html"/>
<modified>2018-09-12T05:54:12Z</modified>
<entry>
<title>テスト</title>
<summary>本文です。</summary>
...
</entry>
</feed>

これを利用して、以下のスクリプトを書きました。

set xml_data to do shell script "curl -u \"<ユーザ名>\":\"<パスワード>\" --silent \"https://mail.google.com/mail/feed/atom\""
tell application "System Events"
	set xml_data to make new XML data with data xml_data
	set unread to xml_data's XML element "feed"'s XML element "fullcount"'s value
end tell
return unread

まずシェルスクリプトで先ほどのURLにアクセスしてXMLを受け取り、それをMacOSのSystem Eventsでパースし、タグを読むことで未読数を取得します。これで準備は完了です。

BetterTouchToolでウィジェットを作成

あとはこのスクリプトを組み込んだウィジェットBetterTouchToolで作成します。
nexusuica.hatenablog.jp
定期的に上のスクリプトを実行し、実行結果が1以上であればアイコンと背景色を変更するようにしておきます(「Run Apple Script and Shown Return Value」)。また、アイコンをタッチするとMail.appが起動するようにしておきます(Action)。作成したものがこちら。

f:id:nexusuica:20180912150209j:plainf:id:nexusuica:20180912150212j:plain
未読メールの有無に応じて色とアイコンが変わる。必要に応じて未読数を表示することも容易。
これでDockを隠しておいたとしてもメールに気付かないということがなくなります。Touch Barも便利なものですね。

1,2,...,nからm個をランダムに取ってきたときの最小値の平均値

事の発端

事の発端は、塾で教えている生徒から来た、ある問題に関する質問でした。

 100個の数字1,2,\cdots\cdots,100から3個の数を任意に選んだとき、最小の数の平均値はいくつか。
僕がすぐに考えた解法は、期待値の定義どおり、ある数kが最小の数となる確率を求めてkとの積を取り、和を取る方法です。ただ、これをやってみると…

いま、1から100のうち、最小の数となりうるのは1から98である。この範囲のある整数をkとおき、最小の数がkとなる確率を求めると、最小でない残り2枚をk+1以上100以下の数から選ぶ確率なので\displaystyle \frac{{}_{100-k}C_2}{{}_{100}C_3}である。よって求める期待値は
  \displaystyle E[k]=\frac1{{}_{100}C_3} \displaystyle \sum_{k=1}^{98}k\ {}_{100-k}C_2
となる。

\sumの中はkの3次式なので手計算は一応可能で、実際に計算すると\displaystyle \frac{101}4となります。ただ、この問題がもともと穴埋め式の問題だったことと、最後の答えが綺麗に\displaystyle \frac{100+1}{3+1}になっていたので、何かもっと綺麗な解法があるのではないかとTwitterに助けを求めました。すると、非常に様々な解法が寄せられました。 togetter.com

最終的にこれだ、というものがあったので、その解法と地道に計算する方法、2通りの解法を紹介します。

せっかくなので一般化

先ほどの問題だと具体的すぎるので、ここから先は一般化して次の問題を考えます。

 n個の数字1,2,\cdots\cdots,nからm個の数を任意に選んだとき、最小の数の平均値はいくつか(ただし、n\geq mとする)。

コンビネーションをガリガリ計算する方法

まずは先ほどと同様に定義どおり計算します。最小の数となりうる整数kについて、最小の数がkとなる確率は、最小でない残りm-1枚をk+1以上n以下のn-k枚から選ぶ確率なので\displaystyle \frac{{}_{n-k}C_{m-1}}{{}_{n}C_{m}}となります。これを用いて期待値は
 { \begin{align} E[k]&=\frac1{{}_nC_m}\sum_{k=1}^{n-m+1}k\ {}_{n-k}C_{m-1}\\ &=\frac1{{}_nC_m}\sum_{k=1}^{n-m+1}\{(n+1)-(n-k+1)\}{}_{n-k}C_{m-1}\\ \end{align} }
と立式できます。ここで、
  { \begin{align} (n-k+1){}_{n-k}C_{m-1}&=(n-k+1)\ \frac{(n-k)!}{(m-1)!(n-k-m+1)!}\\ &=m\ \frac{(n-k+1)!}{m!(n-k-m+1)!}\\ &=m\ {}_{n-k+1}C_{m} \end{align} }
を用いることで、上の期待値は
  \displaystyle E[k]=\frac1{{}_nC_m} \sum_{k=1}^{n-m+1}\{ (n+1)\ {}_{n-k}C_{m-1}-m\ {}_{n-k+1}C_m \}
と書き直すことができます。 さてここでいったん、 \displaystyle \sum_{i=k}^l{}_iC_kを考えます。二項係数の性質
  {}_iC_k+{}_iC_{k+1}={}_{i+1}C_{k+1},\ {}_kC_k={}_{k+1}C_{k+1}=1
を繰り返し用いれば、
  \displaystyle{
\begin{align}
\sum_{i=k}^l{}_iC_k &= {}_kC_k+{}_{k+1}C_k+{}_{k+2}C_k+{}_{k+3}C_k+\cdots+{}_lC_k\\
&= {}_{k+1}C_{k+1}+{}_{k+1}C_k+{}_{k+2}C_k+{}_{k+3}C_k+\cdots+{}_lC_k\\
&= {}_{k+2}C_{k+1}+{}_{k+2}C_k+{}_{k+3}C_k+\cdots+{}_lC_k\\
&= {}_{k+3}C_{k+1}+{}_{k+3}C_k+\cdots+{}_lC_k\\
&= {}_lC_{k+1}+{}_lC_k\\
&= {}_{l+1}C_{k+1}
\end{align}
}
となることがわかります。これをE[k]\sumの第1項にはk=m-1,\,l=n-1として、第2項には k=m,\,l=nとして代入することで、
  \displaystyle {
\begin{align}
E[k]&=\frac1{{}_nC_m}\{(n+1)\,{}_nC_m-m\,{}_{n+1}C_{m+1}\}\\
&=n+1-m\,\frac{{}_{n+1}C_{m+1}}{{}_nC_m}\\
&=(n+1)\left(1-\frac{m}{m+1}\right)\\
&=\frac{n+1}{m+1}
\end{align}}
が導出できました(ふう……)。

もっとエレガントに解く方法

さすがに二項係数の計算は疲れます。もっと直感的な解法はないのでしょうか。もう一度問題を見てみましょう。

 n個の数字1,2,\cdots\cdots,nからm個の数を任意に選んだとき、最小の数の平均値はいくつか(ただし、n\geq mとする)。
いま、適当に選ばれたm個の数字を小さい順にx_1,x_2,\cdots\cdots,x_mとします(ただし、 x_1\geq 1,\,x_{i+1}\geq x_i+1,\, x_m\leq n)。
いま、これらの差に着目してm+1個の数字yを、
  y_1= x_1,\ y_i=x_i-x_{i-1}\,(2\leq i\leq m),\ y_{m+1}=n+1-x_m
によって定めます。このとき、(x_1,x_2,\cdots\cdots,x_m)(y_1,y_2\cdots\cdots,y_{m+1})は1対1に対応します。
  y_1+y_2+\cdots\cdots+y_{m+1}=n+1
が常に成立すること、および最小値x_1=y_1に注意すると、以下のように問題が言い換えられることがわかります。
  y_1+y_2+\cdots\cdots+y_{m+1}=n+1が成り立つようにm+1個の自然数y_1,y_2,\cdots\cdots,y_{m+1}を任意に選んだとき、y_1の平均値はいくつか。
すると、すべてのy_iは対称的な条件なので平均値は等しくなり、
  \displaystyle E[y_1]=\frac{n+1}{m+1}
となることが容易にわかります。なんと……。
このように、適当な1対1対応を作ることで期待値が簡単に求まってしまう場合があるんですね。

Macを超カスタマイズできる「BetterTouchTool」

何ができるの?

以前の記事では、MacBookを無料のソフトウェアのみを用いてカスタマイズしてみました。
nexusuica.hatenablog.jp
今回は「BetterTouchTool」という有料アプリでカスタマイズを一歩進めてみたというお話です。
「BetterTouchTool」は トラックパッドやマウス、キーボード、トラックパッドの動作をカスタマイズできるソフトウェアで、「ある動作をしたら(トリガー)、この操作を実行する(アクション)」というような設定を非常に細かく行えます。例えば、「Commandキー+Optionキー+Cを押したら、Google Chromeを起動する」といったようなものです。

f:id:nexusuica:20180910222205p:plain
BetterTouchToolの設定画面。素っ気ないが詳細な設定が可能
BetterTouchToolは、トリガーの入力パターンが非常に多彩な上、アクションの種類も非常に豊富であるのが特長で、無料ソフトウェアにこれを追随するものはありません。特に、アクションとしてシェルスクリプトApple Scriptの実行ができるのでかなり自由度が高いです(かなりコアなユーザー向けですが)。
僕はこのBetterTouchToolを用いて、Touch Barのヘビーカスタマイズをしてみました。
f:id:nexusuica:20180910221021j:plain
Touch Barのカスタマイズ例。想像力次第で何でもできる
このTouch Barのカスタマイズに関しては別の記事で紹介したいと思います。

購入方法

このBetterTouchToolですが、まずは購入する前に試用することが出来ます。公式サイトからダウンロードして起動すれば、45日間は無料ですべての機能を使用できます。その後、ライセンスを購入するかどうか判断できます。
https://folivora.ai
ライセンスを購入する場合、2年間のみアップデートが提供されるものと永久にアップデートを受けられるものがあります。記事執筆時点で前者が$6.50、後者が$20です。さすがに$20はちょっと高いので、2年間のアップデート付きライセンスですかね…。ただ実はこのライセンス、もっと安く買う方法が存在します。
App Storeで同じ開発者が販売しているアプリ「BetterSnapTool」(記事執筆時点で360円)を購入すると、なんと「BetterTouchTool」のライセンスをもらうことが出来ます。

BetterSnapTool

BetterSnapTool

  • folivora.AI GmbH
  • Productivity
  • $2.99
というわけで、BetterSnapToolをApp Storeで購入すれば360円でBetterTouchToolが使えるというわけです。ちなみに、BetterSnapToolの機能であるウィンドウスナップ(ウィンドウを縦横に綺麗に分割して配置する機能)はBetterTouchToolに内蔵されているので、BetterSnapToolは購入後即アンインストールしても構いません。
Mac生活が確実に豊かになるBetterTouchTool、まずは試用してみて気に入ったら購入してみてはいかがでしょうか。

AtCoder Beginner Contest 109

AtCoder Beginner Contest 109 - AtCoder

A - ABC333

問題

A - ABC333

考察

整数a,bが与えられるとき、a\times b\times cが奇数となるような整数cが存在するかを判定する。a\times bが偶数であるときcをどのように選んでもa\times b\times cは偶数になる。一方でa\times bが奇数であるときcを奇数とすればa\times b\times cは奇数になる。

コード

int main(){
  ll a,b;
  cin >> a >> b;
  if(a*b%2==0){
    cout << "No" << endl;
  }else{
    cout << "Yes" << endl;
  }
  return 0;
}

B - Shiritori

問題

B - Shiritori

考察

N個の単語が与えられるとき、しりとりのルールを守っているかを判定する。まだ発言していない単語かどうかは、連想配列を利用して各文字列の頻度をカウントすることで判定できる。前の単語の末尾と次の単語の先頭が一致しているかは、配列にすべての単語を記憶しておくことで容易に判定できる。

コード

int main(){
  ll n;
  cin >> n;
  str w[n];
  std::map<str, ll> mp;
  REP(i,n){
    cin >> w[i];
    if(i!=0&&(w[i-1].back()!=w[i].front()||mp[w[i]]>0)){
      cout << "No" << endl;
      return 0;
    }
    mp[w[i]]++;
  }
  cout << "Yes" << endl;
  return 0;
}

C - Skip

問題

C - Skip

考察

数直線上にあるN個の点\{x_i\}を、座標Xから\pm D移動することを繰り返してすべて訪れるとき、最大のDを求める。
まず、Xから出発して最初の点x_jを訪れるためには|x_j-X|Dの倍数である必要がある。さらに順次他の点を訪れていくためには|x_k-x_j|Dの倍数である必要がある。これらをまとめると、結局すべてのx_iについて|x_i-X|Dの倍数であることが必要で、これをみたす最大のDは最大公約数である。最大公約数はユークリッドの互除法を用いて効率的に計算することができる。

コード

ll gcd(ll a, ll b){
  if(b==0) return a;
  return gcd(b,a%b);
}

int main(){
  ll n,X;
  cin >> n >> X;
  ll x[n];
  REP(i,n){
    cin >> x[i];
    x[i]-=X;
    x[i]=abs(x[i]);
  }
  ll ans = x[0];
  REP(i,n-1) ans = gcd(ans,x[i+1]);
  cout << ans << endl;
  return 0;
}

D - Make Them Even

考察

h\times wマスに各々数枚ずつ置かれたコインを、1枚ずつ移動させていってできるだけ多くのマスのコインの枚数を偶数枚にするための操作の仕方を求める。制約として同じマスのコインを複数回選んではいけないので、マスを一筆書きの順番で移動させていけばよい。あるマスの枚数が奇数であれば移動を行い、偶数であれば移動を行わないということを繰り返す。

コード

struct move_coin{int from_y, from_x, to_y, to_x;};

std::vector<move_coin> ans;
int a[500][500];

void move(int from_y, int from_x, int to_y, int to_x){
  if(a[from_y][from_x]%2==0) return;
  move_coin m;
  m.from_y = from_y+1, m.from_x = from_x+1, m.to_y = to_y+1, m.to_x = to_x+1;
  ans.push_back(m);
  a[from_y][from_x]--;
  a[to_y][to_x]++;
}

int main(){
  int h,w;
  cin >> h >> w;
  REP(i,h)REP(j,w){
    cin >> a[i][j];
  }
  REP(i,h){
    if(i%2==0){
      REP(j,w-1) move(i,j,i,j+1);
      if(i!=h-1) move(i,w-1,i+1,w-1);
    }else{
      REP(j,w-1) move(i,w-j-1,i,w-j-2);
      if(i!=h-1) move(i,0,i+1,0);
    }
  }
  cout << ans.size() << endl;
  for(auto m : ans){
    printf("%d %d %d %d\n", m.from_y, m.from_x, m.to_y, m.to_x);
  }
  return 0;
}

雑感

今回も用事で不参加で、後日解きました。Dのルールを最初読み違えていて「??」となりました。ちょっと実装が冗長になってしまった気がします。