starwish’s diary

GASで日曜プログラミング-天気予報配信-

GASが非常に便利だということが分かったので、今回は天気予報を自分に配信してみた。
こんなコードをGASに登録し、GASのトリガで毎日午後6時に実行させると、翌日の天気予報が配信されるという寸法である。

function myFunction() {
  var url = "http://www.jma.go.jp/jp/yoho/320.html";
  var response = UrlFetchApp.fetch(url);
  var data = response.getContentText();
  var ps1 = Parser.data(data)
  var ps2 = ps1.from('<div style="float: left">東部</div>').to('<th class="weather">\n明後日').build()
  var ps3 = Parser.data(ps2).from('<th class="weather">\n明日').to('</table></div></td>').build()
  var ps4 = Parser.data(ps3)
  var info = ps4.from('<td class="info">').to('</td>').build();
  var prob = ps4.from('<td align="right">').to('</td>').iterate()
  var result = "神奈川県東部 " + info+prob
  MailApp.sendEmail('xxxx@yyyy', '天気予報', result);
}

ParserというのはIvan Kutilという方が公開してくれているGASのライブラリで、面倒なテキスト抽出作業を簡単にやってくれる。
ライブラリを使うにはスクリプトIDが必要なのだが、他の人が公開しているIDを知るすべはないので、googleさんに探してもらった。Parserの場合はM1lugvAXKKtUxn_vdAG9JZleS6DrsjUUVである。

(GAS)Google Apps Scriptやってみた

体重や睡眠時間などの個人的なデータをGoogleSpreadsheetで管理しているが、いちいち入力するのが面倒だった。
GAS(Google Apps Script)を使えば簡単になると聞きやってみた。入力のデフォルト値はシートから取得したかったので、プロジェクト内にindex.htmlを作り、gs側で

function doGet(){
  var output = HtmlService.createTemplateFromFile('index');
  output.dflt = 58
  return output.evaluate()
}

とやると、html側で

  <form method='post' action='https://script.google.com/macros/s/XXXXXXXXXXXXXXXXXXXXXXXXXX/exec'>
    <input type="number" name="weight" step="0.1" value="<?= dflt?>" max="65.0" />

のように変数が受け取れる。入力した値をdoPost()内でスプレッドシートを取得して

function doPost(postdata){
  var spreadsheet = SpreadsheetApp.openById('XXXXXXXXXXXXXXXXXXXXXXXXXXXX')
  var sheet = spreadsheet.getSheetByName(today.getFullYear())
  sheet.getRange(days+2,2).setValue(weight);
}

で値が書き込まれる。シートを見ているとポコッと増えるのでわかりやすい。引数2つでgetRange()を呼ぶと、そのセルが対象になる。基数が1になることに注意。

コイン両替問題を解く

趣味でやっているCheckIOというプログラミングゲームサイトでコイン両替問題が出ていたので解いてみた。この問題は、決まった種類のコインがあったとき、ある目標金額を両替するときに最小となるコインの数を求めるというものだ。
例えば623円をコインで両替するとしてコインの数が最小となる組み合わせは[500,100,50,10,5,1]=[1,1,0,2,0,3]なので7枚ということになる。単純に思いつく解法は、すべてのコインの組み合わせについて合計してみて、目標金額に達した組み合わせのうち要素数が最小となるものを選ぶことである。
目標金額をprice(int型),コインの種類をdenominations(list型)とし、コインの最小数を得る関数を書いてみる。両替できない場合(10円玉だけで3円とか)もあるので、その場合はNoneを返すこととする。

def check(price, denominations):
    combination_list = itertools.product(*[[x for x in range(price//x+1)] for r in denominations])
    sum_list = [sum(combination) for combination in combination_list if sum([a*b for a,b in zip(combination,denominations)])==price]
    s = min(sum_list) if len(sum_list)>0 else None
    return s

これでcheck(123, [1, 5,10, 20])を計算すると9となった。正解である。ちなみにここでitertools.product()は複数の集合の直積集合をiteratorとして得るツールである。これを使って各コインの最悪必要となる数までの全コインの組み合わせを求めている。また"*"は外側のリストから中身を取り出すpython演算子であり、例えば*[1,2,3]=1,2,3となって、今回のようにリストの中身を関数の複数の引数に展開したいときに重宝する。付け加えておくとpythoniteratorは使い捨てオブジェクトなので、print()して覗いただけでも中身が空になってしまい、要注意である。

さてできた、と思ってチェックしてみると、数が大きいときにえらい時間がかかる。check(1234, [1, 5,10, 20])をやってみると30分かかった、123だと0.2秒で終わったのにである。考えてみると、この手順ではコイン毎の最悪必要となる枚数を目標金額//コイン金額としているので、全部1円玉にする場合とか明らかに無駄な計算もやってしまうことになる。コイン毎最悪必要となる枚数をr、コイン種類数をnとすると、処理量はr^nに比例する。

一般的に考えてみると少額コインと高額コインのペアについて、少額コインはペアの最小公倍数となる金額に必要な枚数より多くは不要なように思われる。例えば5円玉と3円玉があったとして、5,10は5円玉だけ、3,6,9,12は3円玉だけでしか作れないが、15以上になると15の倍数分は5円玉を使った方が明らかにコイン枚数は少なくなるので、3円玉は4枚より多くは不要ということになる。なので任意のペアについて探索するコイン数の上限を設定して計算させてみる。

任意の整数(a,b)の最小公倍数はa*b/最大公約数なのでユークリッドの互除法を用いて最大公約数を求めてから最小公倍数を計算する。これを実装したのが関数lcmで、これを使ってcheck関数の1行目でコインごとに、そのコインより高額なコインの金額との最小公倍数を計算して辞書型に落とし込んでいる。さらに2行目でコイン金額で割って枚数に変換したうえで最小限必要となる枚数に変換している。3行目で全組み合わせをリスト化するときに、必要枚数以上の組み合わせはリストしないようにしている。

import itertools

def gcd(a,b):
    big = max(a,b)
    sml = min(a,b)
    while(big%sml!=0):
        bih = big
        big = sml
        sml = bih % sml
    return sml

def lcm(a,b):
    return ( a*b//gcd(a,b))

def check(price, denominations):
    lc = {x:[lcm(x,y) for y in denominations if x<y] for x in denominations}
    md = {x:min(lc[x])//x if len(lc[x])>0 else price//x+1 for x in denominations}
    combination_list = itertools.product(*[[x for x in range(md[r])] for r in denominations])
    sum_list = [sum(combination) for combination in combination_list if sum([a*b for a,b in zip(combination,denominations)])==price]
    s = min(sum_list) if len(sum_list)>0 else None
    return s

再度check(1234, [1, 5,10, 20])をやってみると0.001秒で終わった。

にんにくの芽の正体

庭で育てているにんにくがそろそろ収穫に近いらしく、先っぽにネギ坊主みたく丸い葉が出てきた。人に聞くと取った方がよいらしく、引けば抜けるというので引き抜いてみた。(写真は抜いたあとの状態)

f:id:starwish:20190506224510j:plain
抜いたあとの状態
ネギ坊主みたいな部分をつまんで力を入れると、すっぽりと抜けた
f:id:starwish:20190506224630j:plain
これがにんにくの芽だ!
これは何だろうとググると、なんとこれがにんにくの芽らしい。正確に言うと真ん中の少し膨らんでいる部分が芽なので、その下はにんにくの芽の茎というべきか。

docker on WSL

WSLでdocker動かそうとしたらエラーになる
バージョンはWin10 Home Edition version 1809&ubuntu 18.04LTS

docker version
Client:
 Version:           18.09.2
 API version:       1.39
 Go version:        go1.10.4
 Git commit:        6247962
 Built:             Tue Feb 26 23:52:23 2019
 OS/Arch:           linux/amd64
 Experimental:      false

Server:
 Engine:
  Version:          18.09.2
  API version:      1.39 (minimum version 1.12)
  Go version:       go1.10.4
  Git commit:       6247962
  Built:            Wed Feb 13 00:24:14 2019
  OS/Arch:          linux/amd64
  Experimental:     false

ihaskellのdockerイメージを動かそうとすると

sudo docker run -it --volume $(pwd):/notebooks --publish 8888:8888 gibiansky/ihaskell:latest
docker: Error response from daemon: OCI runtime create failed: container_linux.go:344: starting container process caused "process_linux.go:297: getting the final child's pid from pipe caused \"EOF\"": unknown.

atomでgoの開発環境を作る

atomでgoの開発環境を作った。goimportというモジュールをインストールすると足りないimportを自動補完してくれるというのでmain.goを作って保存してみたが普通にエラーになる。どうもファイル単独で開いた場合とフォルダごと開いた場合で動作が違うようだ。
ファイル単独で開くとこう
f:id:starwish:20190501130250p:plain
フォルダごと開こうとするとこんなダイアログが出て
f:id:starwish:20190501125607p:plain
左を選ぶと新規ウィンドウが立ち上がり、右を選ぶと現ウィンドウが更新される。どちらかを選んでも上のファイルに対する動作は同じで、保存すると期待通り補完された
f:id:starwish:20190501125801p:plain