秘密の本棚

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

「OK Google, 筋肉体操」で身体を徹底的に追い込む

今回やりたいこと

最近、NHKの番組の「筋肉体操」というのが話題ですね。特別な道具を使わずに短時間で筋トレをする方法を、ムキムキな先生と3人のモデルが教えてくれます。これを見ているだけで筋トレをした気分になれます。

ただ、本当は見ているだけではダメで、自分で実践していかないと意味がありません。しかし、番組でやっていたトレーニングの内容を自分でやろうと思っても、なにしろ負荷がキツいので途中で挫折してしまいます。やはり先生の厳しい声が必要です。したがって、この番組を録画しておいて再生しながら筋トレをするというのが現実的でしょう。
ただ、毎回毎回テレビをつけて、番組を選択して再生するというのは面倒です。できればワンタッチで始めたいですよね。そこで今回は、Raspberry PiとChromecastを用いて、OK Google, 筋肉体操」と言えば自動的にテレビで筋肉体操のプレイリストが再生されるようにしてみたいと思います。

用意するもの

以下のものを用意します。

必要なステップに分解する

OK Google, 筋肉体操」を実現するために必要なステップは以下のとおりです。

これらをそれぞれ実装します。

Google HomeからRaspberry Piが指示を受け取る

この部分は以下の記事と全く同じです。
nexusuica.hatenablog.jp
IFTTTにトリガーとしてGoogle Assistantを指定しておき、「筋肉体操」というフレーズに対してBeebotteのWebhookを投げるようにします。Python側ではこれを監視し、リアルタイムでその指示を受け取ることができます。

import paho.mqtt.client as mqtt
import json
import subprocess

TOKEN = "<Boobotteのトークン>"
HOSTNAME = "mqtt.beebotte.com"
PORT = 8883
TOPIC = "<Beebotteのチャンネル名>/<Beebotteのリソース名>"
CACERT = "mqtt.beebotte.com.pem"

def on_connect(client, userdata, flags, respons_code):
    print('status {0}'.format(respons_code))
    client.subscribe(TOPIC)

def on_message(client, userdata, msg):
    #print(msg.topic + " " + str(msg.payload))
    data = json.loads(msg.payload.decode("utf-8"))["data"][0]
    #data = {key:value.strip() for key, value in data.items()}
    print(data)
    for key, value in data.items():
        if key == "action" and value == "start":
            muscle_start()

client = mqtt.Client()
client.username_pw_set("token:%s"%TOKEN)
client.on_connect = on_connect
client.on_message = on_message
client.tls_set(CACERT)
client.connect(HOSTNAME, port=PORT, keepalive=60)
client.loop_forever()

Raspberry PiがChromecastに指示を送る

Chromecastをシェルから操作するには、専用のツールが必要です。今回はCattというPythonコマンドラインツールを利用します。
github.com
Cattの使い方は簡単で、例えば

catt cast "https://www.youtube.com/watch?v=PyJOEt2nsGQ"

とすればWiFiに接続されたChromecast上でYouTubeの動画が再生できます。やや動作に時間がかかるのは難点ですが、このツール以外でChromecastを操作できるものは少ないです。
Cattを用いて、Chromecastで筋肉体操の4本の動画を連続して再生させます。1本目はそのまま再生し、それ以降の動画はキューに追加しておけばよいです。たまに失敗するので、10回試行するようにしています。

VIDEO_IDs = ["PyJOEt2nsGQ","1GanGLmDt2I","IHYOhDe4FB8","WndOChZSjTk"]

def create_url(video_id):
    return "https://www.youtube.com/watch?v="+video_id

def muscle_start():
    for i in range(0,len(VIDEO_IDs)):
        for x in range(0,10):
            if(i==0):
                d = subprocess.call("catt cast "+"\""+create_url(VIDEO_IDs[0])+"\"",shell=True)
            else:
                d = subprocess.call("catt add "+"\""+create_url(VIDEO_IDs[i])+"\"",shell=True)
            if(not d):
                break

最後に動作している様子をご覧ください。これで快適な筋肉体操ライフが実現しますね!

メルボルンから行くエアーズロック②:エアーズロックに登頂!

前の記事:メルボルンから行くエアーズロック①:カタ・ジュタ散策とサンセット

極寒の夜を越す

サンセットを見終えてリゾートエリアに戻り、軽く夕食を食べたら星空観察に出かけました。Outback Pioneerの敷地内には少し高くなった展望台があり、ここでは暗闇の中で天体観測ができました。

ニュージーランドテカポでも南半球の天体は観察していましたが、南十字星と再会することができて嬉しかったです。テカポに劣らず綺麗な星空を見ることができたと思います。大掛かりな機材を持ち込んで撮影している人もいました。

そういえば、天体観測をしている最中に偶然にも突然施設が停電して周りの電気も全て消えて真っ暗になったので、より綺麗に星空を見ることができました(笑)

f:id:nexusuica:20180830222208j:plain

展望台に行くには少し坂を登る

f:id:nexusuica:20180830222151j:plain

天の川もくっきり見えた。流星もちらほらと

エアーズロック地方はこの季節は0℃近くまで気温が下がるので、星空観察も長時間は寒すぎて厳しいです。長くても30分くらいが我慢の限界でした。しかし、本当の戦いはこれからでした。

宿泊したOutback Pioneerはシャワー・トイレが共通で、別の棟になっています。寝る前にシャワーを使おうと思いシャワー棟に向かいましたが、出てきたお湯はお湯とも呼べないほどぬるく、浴びるだけで身体がどんどん冷えていきます。全身震えながら髪を洗って速攻でドミトリーに戻りました。しかし、ドミトリーに戻っても寒さは続きます。なぜなら、ドミトリーには暖房がついていないからです。毛布を一枚被ったところで寒さは到底しのげず、この晩は寒さで何回も目が覚めました。翌日フロントに言うと毛布をさらに何枚かもらえたので次の晩はいくぶんかマシになりました…。

早起きしてサンライズツアー

厳しい夜を越したところで、翌朝のツアーが始まりました。朝6:00集合というなんとも鬼畜なスケジュールです。あまりに寒くよく眠れていませんでしたが、寝起きの状態でバスに乗り込んで日の出を見に行きます

サンセットを見たところとは別の場所から、今度は太陽が昇ってくる様子を観察します。駐車場から10分ほど歩くと展望台があり、そこに登ると木々を避けて見やすくなります。太陽が登ってくるにつれてエアーズロックの凹凸に応じて光が当たり、サンセットとはまた別の姿を見ることができました。遠くには前日に散策したカタ・ジュタも見えていました。ちなみに、このときもかなり寒かったです…。

f:id:nexusuica:20180830222158j:plain

サンセットよりも凹凸がはっきり見える

f:id:nexusuica:20180830222159j:plain

遠くに小さく見えるのがカタ・ジュタ

エアーズロックに登る!

サンライズを見たら、いよいよエアーズロックの登山口へと向かいます。エアーズロックは登山道が1箇所しか無く、風や気温、天候によっては閉鎖されていることもあって、行ってみても登れないというケースも多々あるようですが、今回は幸運なことに登山口は開放されていました。

バスを降りると登山口はすぐそこにあり、いきなり急な直登が始まります。岩なので当然木などは生えておらず、あるのは鎖のみです。足を滑らせないように気をつけながら、慎重に登っていきます。最低限でも滑りにくいスニーカーは必要でした。最初からペースを上げすぎると確実にバテるので、ゆっくり登っていくのがコツです。

f:id:nexusuica:20180830222201j:plain
f:id:nexusuica:20180830222152j:plain
登山者の数は多く感じた。鎖があるのがとにかくありがたい

駐車場から鎖のあるゾーンをひたすら登りきると、後方にはカタ・ジュタが見えていました。そこから先は傾斜がやや緩やかになり、山頂を目指して歩いていきます。真っ赤な起伏のある岩の上を歩いていく感覚は、まるで火星の上を歩いているかのようでした。駐車場から山頂までは片道1時間ほどかかりました。

f:id:nexusuica:20180830222205j:plain
f:id:nexusuica:20180830222157j:plain
火星の上を散歩しているかのような感覚になる

f:id:nexusuica:20180830225719j:plain

地平線に浮かぶカタ・ジュタが目立つ

エアーズロックの地方はこの時期ほとんど雨が降らないのですが、ちょうど前々日に雨が降ったようで水たまりができている箇所がありました。ただ、生き物が生息している気配はありませんでした。

山頂には石碑のようなものが設置されていましたが、登ってしまえばほとんど平らなのでどこが山頂なのかはいまいちわかりにくかったです。

砂漠の上のイルミネーション「Field of Light」

エアーズロックに登頂した日の晩は、リゾートエリア近くの広大な敷地の中にある、無数のライトで作られたイルミネーションを見に行きました。「Field of Light」という名前のイルミネーションで、専用のバスで向かいます。

バスを降りるとあたりはほぼ真っ暗。ガイドの人が入り口まで案内してくれますが、そこから先は自分で歩いていきます。

f:id:nexusuica:20180830222154j:plain

通路を挟んで無数のライトが彩られている

このField of Lightのイルミネーションには光ファイバーが用いられていて、各ライトが発光しているのではなく各ライトに光ファイバーで光が分配されています。したがって、日本でよく見かけるようなLED電球を用いたイルミネーションのギラギラした感じとは違って、明るさは非常にほのかでした。しかし、その分全体としてかなり上品なテイストで、さらに目にも負担がないため夜空の星と同時に鑑賞ができました。この点はよく考えられていると思いました。一周40分ほどでコースを回ることができます。

f:id:nexusuica:20180830222155j:plain

天空に輝く星と大地に咲く光の花を同時に楽しめる

ちなみに、このField of Lightも当然かなり寒かったです。防寒具を多めに持っていくべきでした…。

次回は飛行機でメルボルンに戻り、一日でざっと観光します!(続く)

メルボルンから行くエアーズロック①:カタ・ジュタ散策とサンセット

登山禁止前にエアーズロックに登る!

f:id:nexusuica:20180827133848j:plain

オーストラリアの中でも行きにくい場所にあるエアーズロック

2018年8月上旬、オーストラリアのエアーズロックに行ってきました。2019年秋からは登山が禁止されるということで、その前の駆け込みです。

エアーズロックには公共交通機関がないため、レンタカーかツアーを利用することになります。今回はメルボルン発のツアーで回ることにしました。日本語ガイドもついているので安心です。

まずはツアーの出発地、メルボルンに向かいます。

カンタス航空ジェットスターエアーズロック

東京からメルボルンへは、カンタス航空80便を利用。成田20:05→メルボルン7:45で、ツアーの出発がメルボルン9:45なので接続がバッチリです。機内食は夕食と朝食が出ますが、これは結構満足。スパークリングワインも選べました。

f:id:nexusuica:20180827133843j:plain
f:id:nexusuica:20180827133846j:plain
夕食と朝食。朝食はほぼ果物でした。

到着は少し早まって7:00頃。オーストラリアは真冬で10℃くらいしかなかく、息が白くなりました。もう少し多めに防寒具を持ってくればよかった…と思った瞬間でした。メルボルン空港では入国審査がありますが、パスポートを機械に読み込ませることで完全自動で行うことができ、スムーズに通過できました。荷物を受け取ったら国内線ターミナルに移動しますが、徒歩5分もかからない距離にあり、便利でした。

メルボルンからの飛行機からはツアーのパッケージに含まれますが、宿泊地までは自力で移動する必要があります。まずはジェットスターエアーズロックに向かいました。乗降が完全に自力で、車椅子のお客さんとかはどうするものかと気になりました。

f:id:nexusuica:20180827134444j:plain
f:id:nexusuica:20180827134447j:plain
眼下には荒涼とした大地が広がる

エアーズロック空港についたら荷物を受け取り、シャトルバスで宿泊先に向かいます。宿泊先によって乗るバスが異なるので看板に注意して乗ります。今回の宿泊はOutback Pioneer Lodge」というところ。ドミトリーと個室があるのですが、個室が空いておらずドミトリーに。どこかで述べますが、いろいろと大変なところでした…。

f:id:nexusuica:20180827134612j:plain

ドミトリーもあるこのロッジ。もっと高級リゾートに泊まりたい

カタ・ジュタ散策

f:id:nexusuica:20180827135238j:plain

エアーズロックと対照的な、ゴツゴツのカタ・ジュタ

エアーズロックというと「大きな一枚岩」を思い浮かべますが、実はその近くにカタ・ジュタというたくさんの岩が突き出たようなところがあります。到着日の午後、ここをバスで訪れました。バスには日本語のガイドさんが乗っていて、移動中にはこれらの岩の成り立ちや、原住民の人の暮らしなどについて語ってくださいました。飛行機の疲れで寝てしまいましたが…。

f:id:nexusuica:20180827134439j:plain

大きな岩の間を進んでいく散策路

駐車場を降りると、目の前には大きな2つの岩が立ちはだかっていて、その間に散策道が続いています。往復1時間ほどの散策路で、左右を巨大な岩に挟まれる迫力を楽しむことができます。左右の岩から落ちてきたという大きな岩が無数に地面に転がっていますが、「ここ7000年は落ちていない」ということでした。

散策路の一番奥には小さな池があり、そこに写った岩と空が映えました。

f:id:nexusuica:20180827135233j:plain
f:id:nexusuica:20180827135513j:plain
樽2個分くらいの大きな岩が転がっていて、その跡が側面に残っている

夕日に映えるエアーズロックを鑑賞

カタ・ジュタ散策のあとは、日の入りで映えるエアーズロックを見に行きます。いわゆる「サンセット会場」にバスで到着すると、ツアー会社が用意している軽食と飲み物が机の上に並んでいて、スパークリングワインを飲みながらエアーズロックを鑑賞することができます。

エアーズロックと太陽の方向は正反対にあるため、夕日がちょうどエアーズロックに当たり、日が沈んでいくにつれて真っ赤に染まっていきます。

日が沈むと地面から濃い青色の領域が現れて、だんだんと夜へと移り変わっていきます。あまり地面付近の空の色に着目したことがなかったので、これは新鮮でした。

 

f:id:nexusuica:20180827133848j:plain

日没直前。真っ赤に染まる。

f:id:nexusuica:20180827134451j:plain

日没直後。

f:id:nexusuica:20180827134454j:plain

日没からしばらく経ったところ。下から夜が迫ってくる。

夜は寒さに耐えながら星空鑑賞をし、翌日はエアーズロック登頂を目指しました!(続く)

nexusuica.hatenablog.jp

AtCoder Beginner Contest 107 - Median of Medians

AtCoder Beginner Contest 107 - AtCoder

A - Train

問題

A - Train

考察

一列に並んだN個のものがあり、左から数えてi番目のものは右から数えて何番目か。だいたいN-iだが、テストケースからN-i+1だとわかる(雑)。

コード

int main(){
  ll n,i;
  cin >> n >> i;
  cout << n-i+1 << endl;
  return 0;
}

B - Grid Compression

問題

B - Grid Compression

考察

「.」または「#」が格子状に並んでおり、「.」のみからなる列や行を削除して表示する。どの列・行が「.」のみからなるかを調べ、それらを除いて表示すればよい。

コード

int main(){
  ll h,w;
  cin >> h >> w;
  char a[h][w];
  REP(i,h)REP(j,w) cin >> a[i][j];
  bool blank_row[h], blank_col[w];
  fill(blank_col,blank_col+w,true);
  fill(blank_row,blank_row+h,true);
  REP(i,h)REP(j,w){
    if(a[i][j]=='#') blank_row[i] = false;
  }
  REP(j,w)REP(i,h){
    if(a[i][j]=='#') blank_col[j] = false;
  }
  REP(i,h){
    REP(j,w){
      if(!blank_row[i]&&!blank_col[j]) cout << a[i][j];
    }
    if(!blank_row[i]) cout << endl;
  }
  return 0;
}

C - Candles

問題

C - Candles

考察

数直線上に並んだN個の点列のうちk個を廻り、その後原点に達する経路の最短経路の距離を求める。k個の点を廻る間に向きを変えるのは無駄なので、この間は同一方向に進み、この距離はiを座標が小さい方の番号として x _ {i+k-1}-x _ {i}である。iを固定したとき、左右どちらの頂点から原点に達するかを考えて答えは

{\displaystyle \min(x _ {i+k-1}-x _ i+|x _ i|,x _ {i+k-1}-x _ i+|x _ {i+k-1}|)}

で、さらにiについても動かして、求める値は

{\displaystyle
\min\{\min(x _ {i+k-1}-x _ i+|x _ i|,x _ {i+k-1}-x _ i+|x _ {i+k-1}|), i=1,2,\cdots,n-k+1\}}
である。

コード

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

D - Median of Medians

問題

D - Median of Medians

考察

長さNの数列aが与えられ、すべての部分列a\left[l,r\right]の中央値によって新たな数列を作るとき、その数列の中央値を求める。最終的な答えは元の数列aの要素であるので、aをソートしてから二分探索で答えを求めることを考える。ソート済みの数列ax番目(Xとする)が求める中央値よりも大きいかどうかは、以下のように判定できる。

いま、ある部分列a\left[l,r\right]の中央値がX以上であるということは、その部分列の中に含まれるXよりも小さい要素の個数が、X以上である要素の個数以下であることと同値である。さらに言い換えると、数列aの各要素がXよりも小さければ-1,X以上であれば1とした数列bを考えて、区間\left[l,r\right]の中にb[i]=-1なるiの個数がb[i]=1なるiの個数以下であることと言いかえられる。

これをそのまま解くのは難しいので、累積和を用いる。b[i]の累積和をs[i]とおくと(ただしs[0]=0とする)、上の条件はs[r]-s[l-1]\geq0と言いかえられる。これをみたす(l,r)の組の個数を求めるのはBIT(Binary Indexed Tree)を用いると実現できる。

いまsの値の範囲は-n以上n以下であることに注意し、2n+1個のBITを考える。蟻本162ページを参考にして個数を求めるが、sの要素数n+1個であることに注意が必要。中央値からなる数列の個数は、l=rの場合とl\lt rの場合を分けて足すことにより

\displaystyle n+\frac{n(n-1)}{2}=\frac{n(n+1)}{2}
なので、この半分と求めた個数を比較する。

コード(本問)

int main(){
  ll n;
  cin >> n;
  ll a[n];
  ll a_sort[n];
  REP(i,n){
    cin >> a[i];
    a_sort[i] = a[i];
  }
  sort(a_sort,a_sort+n);
  ll lb = 0, ub = n;
  while(ub-lb>1){
    ll x = (lb + ub)/2;
    ll b[n];
    REP(i,n) b[i] = (a[i]<a_sort[x]) ? -1 : 1;
    ll s[n];
    REP(i,n){
      s[i] = (i==0) ? b[i] : s[i-1]+b[i];
    }
    ll cnt = 0;
    BIT bit(2*n+1);//値の範囲は-n<=x<=n
    bit.add(n,1);
    REP(j,n){
      cnt += bit.sum(s[j]+n);
      bit.add(s[j]+n,1);
    }
    if(cnt>=ceil(n*(n+1)/2/2)){
      lb = x;
    }else{
      ub = x;
    }
  }
  cout << a_sort[lb] << endl;
  return 0;
}

コード(BIT部分)

struct BIT{
  ll M;
  std::vector<ll> bit;
  BIT(ll n){
    init(n);
  }
  void init(ll n){
    M = n;
    bit.resize(M+1);
    fill(bit.begin(),bit.end(),0);
  }
  ll sum(ll i){
    ll s = 0;
    while(i>0){
      s += bit[i];
      i -= i & -i;
    }
    return s;
  }
  void add(ll i, ll x){
    while(i<=M){
      bit[i] += x;
      i += i & -i;
    }
  }
};

雑感

完全にDはお手上げ状態でした。蟻本だと中級編の総合的な理解が必要とされるレベルで、さすがに厳しかったです。Cまでを14分で解き切って、その後はアイスを食べたりしていました(笑)。 f:id:nexusuica:20180826130914p:plain