memorandums

日々のメモです。

GASファイルの復旧作業メモ

なんか(新しいことを)やると失敗します。

失敗したら何とかしようとするわけです。

何とかしょうとしたときの行動記録は、同じような過ちをした人の助けになるはずです。。たぶん。少なくとも(将来の)自分には。

そういう意味ではこのブログは失敗データベースとも言えるような気がしてきます。。。😁

■まえおき

さて、本題。といいながらまえおき。

1週間ほど前にASUSから以下のキャンペーンが切れるお知らせメールを受け取りました。

getnews.jp

一般ユーザの割当容量は15GBです。

とりあえず100GBが無くなっても納まっていたのですが、将来的に考えるとちょっと不安になりました。

幸い、数年前に大学がG Suiteを採用したため、大学アカウントであれば無制限の容量が使えます。

それまでの経緯もあり、通常は個人のGoogleアカウントを使ってきたのですが、ことDriveに関しては大学の方がいいな。。。と思い、そちらに移行しました。

個人アカウントでログインしてひたすらファイルをダウンロードして、大学のアカウントに切り替えてアップロードする感じです。

で、問題が発覚したのが、GAS(Google App Script)ファイルでした。

アップロードしたファイルは通常のテキストファイルになっているようで、GASとして認識してくれませんでした。。。壊れたかとヒヤッとしましたがテキストを開いてみるとそれらしいデータが入っているので一安心でした。

このGASは、非常勤の授業でファイル提出用のWebページをGASで作ったものでした。

ちなみに以下です。Google Formsでもアップロードできなくはないのですが、Googleにログインさせる必要があって、非常勤先の学生さんにそれをさせるのは難しいだろうな。。。と思って、GASを使った感じでした。

■コード.gs

function doGet() {
  return HtmlService.createHtmlOutputFromFile('index');
}

function processForm(formObject) {
  var folder = DriveApp.getFolderById('★アップロードするフォルダのIDをいれます★');
  var formBlob = formObject.myFile;
  folder.createFile(formBlob);
}

function getFiles() {
  var result = [];
  var folder = DriveApp.getFolderById('★アップロードするフォルダのIDをいれます★');
  var files = folder.getFiles()
  while(files.hasNext()){
    var loadFile = files.next();   
    var fileName = loadFile.getName();
    result.push(fileName);
  }
  return result.sort();
}

■index.html

<html lang="ja">
  <head>
    <meta charset="utf-8">
    <base target="_top">
    <script>
      // Prevent forms from submitting.
      function preventFormSubmit() {
        var forms = document.querySelectorAll('form');
        for (var i = 0; i < forms.length; i++) {
          forms[i].addEventListener('submit', function(event) {
            event.preventDefault();
          });
        }
      }
      
      function loadFileList() {
        google.script.run.withSuccessHandler(updateFiles).getFiles();
      }
      
      window.addEventListener('load', preventFormSubmit);
      window.addEventListener('load', loadFileList);
      
      function handleFormSubmit(formObject) {
        var filepath = document.getElementById("myFile").value;
        console.debug(filepath);
        if (filepath == null || filepath == "") {
          alert("ファイルを選択してからアップロードボタンを押してください");
        } else {
          google.script.run.withSuccessHandler(updateMsg).processForm(formObject);
        }
      }
      
      function updateMsg(msg) {
        alert('アップロードが完了しました');
        //setTimeout(function() {
            loadFileList();
        //}, 1000);
      } 
      
      function updateFiles(msg) {
        var div = document.getElementById("files");
        var rst = "";
        for (var i = 0; i < msg.length; i++) {
          if (msg[i] == "upload") continue;
          rst += msg[i] + "<br/>";
        }
        div.innerHTML = rst;
      }
    </script>
    <title>課題提出用ページ</title>
  </head>
  
  
  <body>
    <h1>課題提出用ページ</h1>
    <span style="background-color:yellow;">「ファイルを選択」ボタンを押して、アップロードしたいファイルを選択し、「アップロード」ボタンを押してください。提出済みファイル一覧に表示されたら提出完了となります。</span>
    <div style="margin:20px; border-style: dotted; border-color:red; padding:20px;">
      <form id="myForm" onsubmit="handleFormSubmit(this)">
        <input id="myFile" name="myFile" type="file" value="" style=" font-size: 150%;"/>
        <input type="submit" value="アップロード" style=" font-size: 150%;" />
      </form>
    </div>
    
    <h2>提出済ファイル一覧</h2>
    <div id="files"></div>
  </body>
</html>

■本題

さて、復旧した手順を以下に示します。GASファイルをダウンロードしてしまい、そのファイルをGoogle Driveに再度アップロードして使う場合、という非常に特殊な状況からスタートです。

手元にダウンロードしたGASファイルがあるとします。

(1)GASファイルを開くとユニコード(\u0027など)らしきものが入っていることがわかりますので、これを変換します。変換するときには以下のページを使わせていただきました。

Unicode Escape Sequence | KWONLINE.ORG

(2)変換後の改行コードが\nと表示されていますので、これも改行文字に置き換えます。これにはvimを使いました。他にも手段があると思いますが。。。vimで以下の置換コマンドを実行すると改行されてコードが見やすくなります。

:s/\\n/\r/g

(3)あとは手作業になります。GASを新規作成し、上記の処理をしたファイルからそれらしい部分をコード.gsに転写します。さらに、index.htmlファイルを新規作成して、index.htmlの内容っぽいところをコピペします。

ただ、これだけですが。。。やったことがない作業だったので。。。壊れたと思っても何とかなるな。。。という感じです。

あと、本件とは違うGASアプリでFusion Tableもあったのですが、これはcsv形式になっていました。今年の授業ではもう使わないので。。。来年度までに時間をみつけて直したいと思います。

では。

Hack言語を少々(Facebookのやつじゃないからね)

春から2年生向け少人数ゼミで以下の書籍をもとに勉強会を開催しています。

コンピュータシステムの理論と実装 ―モダンなコンピュータの作り方

コンピュータシステムの理論と実装 ―モダンなコンピュータの作り方

大学生なら自分で勉強するのは当たり前でしょう?ということで、好き嫌いに関わらず教わる授業ではなく、自分で本を買って勉強しようという授業です。

週1コマなのでなかなか進まないのですが。。。やっと4章を終えようとしているところです。

4章は機械語です。

それまでの章で開発してきたCPUを、機械語(Hack言語)で動かそうという章です。

Hack言語というか、機械語をまともに授業で教えていないので、なかなか理解するのが難しいようですが、使えるリソースがRubyJavaなどに比べると圧倒的に少ないので新鮮なようです。

4章の章末問題が2つあるのですが、2つ目の問題を解説してみたいと思います。

といってもわかりやすい解説というわけではなく、あくまで自分のためのメモログ?になります。

今はとりあえず勉強して理解しているのですが、半年もすれば忘れているでしょう。一応、来年もこの本での勉強会を予定しているので、あとで思い出しやすいように自分の思考過程を残しておきたいと思う、それが目的になります。

問題:キーボードを無限ループで監視して、キーが何か押されていたら黒を、押されていなければ白を描画するプログラムを作成する。

ちなみに、Hack言語などの解説資料(英語)がここにあります。

まず、押下されたキーの値を読み取ります。

以下のように書けます。

@KBD
D=M

@0
0;JMP

@KBDは押下したキーの値が入るアドレス(0x6000番地)を指しています。次のD=MのMは前の行で指定したアドレス(0x6000)の内容を表していて、要は0x6000番地の内容を読んでDレジスタに書き込みなさい、ということを表しています。

あとは、@0は0番地を表していて、その次にくる0;JMPが無条件ジャンプなので、0番地に無条件でジャンプする。。。ということを表しています。

ここまでをCPU Emulatorで実行すると、キーを押すとDレジスタにそのキーの値が入り、キーを離すとDレジスタが0になります。

さて次です。

「キーが押されているときは」ということは、上記のプログラムで「Dレジスタの値が0以外だったとき」ということになります。これはちょうど命令があって「D;JNE」と書けばよいようです。

コードは以下のようになります。

まず赤字部。@ENDは(END)があるアドレスを表しています。D;JNEとありますので、キーが押されていなかったときはD=0になり、D;JNEはスルーします。スルーすると最初に戻るジャンプ命令(0;JMP)で繰り返されるわけです。

つまり、以下のコードでは、キーが押されていなければキー値を読み取ることを続け、キーが押されたら終了する(無限ループになる)のようです。ちなみに、青字の「(END) @END 0;JMP」は無限ループになっています。

@KBD
D=M

@END
D;JNE

@0
0;JMP

(END)
@END
0;JMP

キーが押されていなかったら画面に点を打ってみましょう。画面のRAMは@SCREENで定義されています。具体的には0x4000になります。そこに1を書き込む命令を追加すれば良さそうです。追加したのが以下です。

@KBD
D=M

@END
D;JNE

@SCREEN
M=1

@0
0;JMP

(END)
@END
0;JMP

では、画面の同じ場所に点を打ち続けても面白くないので、点を打つ場所を変えてみます。変えるためには@SCREENをメモリに入れて、その値に1足していけば良さそうです。

まず、@SCREENをメモリの0番地に入れてみます(以下の赤字)

本当はメモリの0番地をM[0]と指定できて、そこに@SCREENを代入するときにM[0] = @SCREEN と書けたらいいのでしょうけど、そんな命令はHackにはありません。そこで、@SCREENというアドレスをDレジスタに退避して、その値を0番地に書き込んでみます。面倒ですが。。。CPU上に設計された命令しか使えない。。。当たり前ですが、それが機械語です。しかたがないのです。

あと、キーボードを読み取るコードが先頭ではなくなったので無条件ジャンプの「@0 0;JMP」の行を書き換える必要があります。どこに戻せばいいかというと@KBDのところですね。。。ここが何番地かわからないので、上記の(END)と同じように、ここにラベルを置きます。

@SCREEN
D=A
@0
M=D

(LOOP)
@KBD
D=M

@END
D;JNE

@SCREEN
M=1

@LOOP
0;JMP

(END)
@END
0;JMP

次に0番地に格納したアドレスを1つずつ足していくコードを付け加えます。@0で0番地を指定し、その直後にMが来ていますので、右辺のM+1は、0番地のメモリの内容に1足し、その結果をさらに0番地にセットすることになります。結果的に0x4000が0x4001になり、0番地の内容が1つずつ増えていきます。

@SCREEN
D=A
@0
M=D

(LOOP)
@KBD
D=M

@END
D;JNE

@SCREEN
M=1

@0
M=M+1

@LOOP
0;JMP

(END)
@END
0;JMP

あともう少しです。

じゃ、上記で「@SCREEN M=1」という行を「0番地で指しているアドレスの内容の番地に1を書き込む」ように書き換えてみます。以下です。

0番地に入っている値をアドレスとして使用したいので、@0と書いたのちに、A=Mと書いて0番地に入っている値を参照してAレジスタに入れます。その次の行でM=1とありますが、このMはAレジスタに入っている値をアドレスとしてみて、その値を参照しますので、結果的に0番地に入っているアドレスに1を書き込むことができるわけです。。。わかってしまえばどうってことがないのですが。。。機械語を書き慣れないと辛いですね。。。

@SCREEN
D=A
@0
M=D

(LOOP)
@KBD
D=M

@END
D;JNE

@0
A=M
M=1

@0
M=M+1

@LOOP
0;JMP

(END)
@END
0;JMP

あと少し。

上記のコードは、キーを押すまで画面に点を描画しますが、押したら終了(無限ループに入る)してしまいます。これでは、この問題の要求とは違いますので、書き換えないといけません。説明の手順が悪かったかもしれませんが。。。面倒なので続けます。ごめんなさい。

キーが押されていなかったら、画面に点を打つ「@0 A=M M=1」を迂回したいと思います。キーが押されていなかったら、つまり、Dレジスタが0だったら、とするため、ジャンプ命令をJNEからJEQに書き換えました。で、ジャンプ先は迂回するためPSETというラベルを作り、そこにジャンプさせます。

@SCREEN
D=A
@0
M=D

(LOOP)
@KBD
D=M

@PSET
D;JEQ

@0
A=M
M=1

(PSET)
@0
M=M+1

@LOOP
0;JMP

もう最後ですね。

点では見にくいので、1バイトを塗りつぶすため「M=1」を「M=!M」に書き換えます。これで塗りつぶせます。

@SCREEN
D=A
@0
M=D

(LOOP)
@KBD
D=M

@PSET
D;JEQ

@0
A=M
M=!M

(PSET)
@0
M=M+1

@LOOP
0;JMP

やっと完成です。Hackコンピュータは十分に遅いので、全画面が塗りつぶされるまで待つことはしないでしょうけど、画面最後尾に行っても戻すコードはないので。。。テキトウなところで止めるようにしましょう。

実行結果は以下のようになります。

youtu.be

最初からこういうのオモシロイと思う人はなかなかいないよな。。。と思うのですが、会社に入ってからこういうの勉強する機会はなかなかないと思うので、教養として経験してもらいたいな。。。と思っています。

/dev/randomをProcessingで可視化してみた件

自作CPUをテーマにしている学生さんと話していたときに、何か独自命令を実装してみようよという話になり、身近なところで乱数生成命令はどうかいな?という話になりました。

ちなみに私は計算機工学は専門外です。

研究室としては成果が出ませんのでいいことではないのですが、就職する子であれば僕が専門外でも自分の興味の持てるテーマを積極的に考えてやってもらってきました。

で、調べてみると、インテルもそうした命令を実装したというエントリーを見つけました。全く的はずれな目標ではなさそう。

本の虫: ハードウェア乱数生成器は信頼できるか

そのエントリーを読むと、/dev/randomという疑似デバイスがあるとか。

Wikipediaによると、以下のような実装になっているそうで。

デバイスドライバその他の情報源から集めた環境ノイズを利用して、真の乱数性を得るのが目的である。

/dev/random - Wikipedia

情報理論を専門にしている先生と共同研究をしたこともあり(といっても私は理論本体はさらっと勉強した程度)、興味がムクムクと。

ランダムさを評価するためdieharderというツールがあるそうで。macOSの/dev/randomをcatすると使えそう。

Robert G. Brown's General Tools Page

ちょっとやってみました。

dieharderをbrewでいれたあと、以下のコマンドをターミナルに打ち込みます。ちなみに-g 200は標準入力から乱数を受け取ることを意味しています。-aは乱数の全てのテストを実行することを指定しています。使い方はこちらを参考にしました。

cat /dev/random | dieharder -g 200 -a

実行結果は以下でした。

#=============================================================================#
#            dieharder version 3.31.1 Copyright 2003 Robert G. Brown          #
#=============================================================================#
   rng_name    |rands/second|   Seed   |
stdin_input_raw|  7.50e+06  | 209043854|
#=============================================================================#
        test_name   |ntup| tsamples |psamples|  p-value |Assessment
#=============================================================================#
   diehard_birthdays|   0|       100|     100|0.84010314|  PASSED  
      diehard_operm5|   0|   1000000|     100|0.88184702|  PASSED  
  diehard_rank_32x32|   0|     40000|     100|0.80116887|  PASSED  
    diehard_rank_6x8|   0|    100000|     100|0.32797592|  PASSED  
   diehard_bitstream|   0|   2097152|     100|0.71336935|  PASSED  
        diehard_opso|   0|   2097152|     100|0.32036284|  PASSED  
        diehard_oqso|   0|   2097152|     100|0.56588344|  PASSED  
         diehard_dna|   0|   2097152|     100|0.16104878|  PASSED  
diehard_count_1s_str|   0|    256000|     100|0.02587503|  PASSED  
diehard_count_1s_byt|   0|    256000|     100|0.99975997|   WEAK   
 diehard_parking_lot|   0|     12000|     100|0.76205443|  PASSED  
    diehard_2dsphere|   2|      8000|     100|0.35372258|  PASSED  
    diehard_3dsphere|   3|      4000|     100|0.52628048|  PASSED  
     diehard_squeeze|   0|    100000|     100|0.99795234|   WEAK   
        diehard_sums|   0|       100|     100|0.10828469|  PASSED  
        diehard_runs|   0|    100000|     100|0.22786568|  PASSED  
        diehard_runs|   0|    100000|     100|0.73520191|  PASSED  
       diehard_craps|   0|    200000|     100|0.99817327|   WEAK   
       diehard_craps|   0|    200000|     100|0.53775024|  PASSED  
 marsaglia_tsang_gcd|   0|  10000000|     100|0.96242186|  PASSED  
 marsaglia_tsang_gcd|   0|  10000000|     100|0.49693013|  PASSED  
         sts_monobit|   1|    100000|     100|0.97466667|  PASSED  
            sts_runs|   2|    100000|     100|0.23166568|  PASSED  
          sts_serial|   1|    100000|     100|0.34808416|  PASSED  
          sts_serial|   2|    100000|     100|0.99265438|  PASSED  
          sts_serial|   3|    100000|     100|0.51311140|  PASSED  
          sts_serial|   3|    100000|     100|0.53917861|  PASSED  
          sts_serial|   4|    100000|     100|0.69049546|  PASSED  
          sts_serial|   4|    100000|     100|0.99828593|   WEAK   
          sts_serial|   5|    100000|     100|0.90872696|  PASSED  
          sts_serial|   5|    100000|     100|0.87589480|  PASSED  
          sts_serial|   6|    100000|     100|0.85664976|  PASSED  
          sts_serial|   6|    100000|     100|0.76773811|  PASSED  
          sts_serial|   7|    100000|     100|0.88820883|  PASSED  
          sts_serial|   7|    100000|     100|0.90635794|  PASSED  
          sts_serial|   8|    100000|     100|0.25447163|  PASSED  
          sts_serial|   8|    100000|     100|0.34641270|  PASSED  
          sts_serial|   9|    100000|     100|0.84809016|  PASSED  
          sts_serial|   9|    100000|     100|0.54602730|  PASSED  
          sts_serial|  10|    100000|     100|0.38435104|  PASSED  
          sts_serial|  10|    100000|     100|0.31579546|  PASSED  
          sts_serial|  11|    100000|     100|0.70558193|  PASSED  
          sts_serial|  11|    100000|     100|0.11063842|  PASSED  
          sts_serial|  12|    100000|     100|0.63108657|  PASSED  
          sts_serial|  12|    100000|     100|0.46233543|  PASSED  
          sts_serial|  13|    100000|     100|0.32157881|  PASSED  
          sts_serial|  13|    100000|     100|0.65954542|  PASSED  
          sts_serial|  14|    100000|     100|0.96746596|  PASSED  
          sts_serial|  14|    100000|     100|0.77409273|  PASSED  
          sts_serial|  15|    100000|     100|0.89417935|  PASSED  
          sts_serial|  15|    100000|     100|0.86407150|  PASSED  
          sts_serial|  16|    100000|     100|0.89478924|  PASSED  
          sts_serial|  16|    100000|     100|0.67017732|  PASSED  
         rgb_bitdist|   1|    100000|     100|0.18252442|  PASSED  
         rgb_bitdist|   2|    100000|     100|0.19992269|  PASSED  
         rgb_bitdist|   3|    100000|     100|0.80715854|  PASSED  
         rgb_bitdist|   4|    100000|     100|0.78238484|  PASSED  
         rgb_bitdist|   5|    100000|     100|0.98778718|  PASSED  
         rgb_bitdist|   6|    100000|     100|0.55393736|  PASSED  
         rgb_bitdist|   7|    100000|     100|0.24144382|  PASSED  
         rgb_bitdist|   8|    100000|     100|0.22933685|  PASSED  
         rgb_bitdist|   9|    100000|     100|0.44936032|  PASSED  
         rgb_bitdist|  10|    100000|     100|0.47427858|  PASSED  
         rgb_bitdist|  11|    100000|     100|0.48019771|  PASSED  
         rgb_bitdist|  12|    100000|     100|0.32258149|  PASSED  
rgb_minimum_distance|   2|     10000|    1000|0.35669936|  PASSED  
rgb_minimum_distance|   3|     10000|    1000|0.32627779|  PASSED  
rgb_minimum_distance|   4|     10000|    1000|0.25836237|  PASSED  
rgb_minimum_distance|   5|     10000|    1000|0.36578020|  PASSED  
    rgb_permutations|   2|    100000|     100|0.84772302|  PASSED  
    rgb_permutations|   3|    100000|     100|0.60586989|  PASSED  
    rgb_permutations|   4|    100000|     100|0.03945497|  PASSED  
    rgb_permutations|   5|    100000|     100|0.36162649|  PASSED  
      rgb_lagged_sum|   0|   1000000|     100|0.90713644|  PASSED  
      rgb_lagged_sum|   1|   1000000|     100|0.69476062|  PASSED  
      rgb_lagged_sum|   2|   1000000|     100|0.55459730|  PASSED  
      rgb_lagged_sum|   3|   1000000|     100|0.61342206|  PASSED  
      rgb_lagged_sum|   4|   1000000|     100|0.16390450|  PASSED  
      rgb_lagged_sum|   5|   1000000|     100|0.20790596|  PASSED  
      rgb_lagged_sum|   6|   1000000|     100|0.52135294|  PASSED  
      rgb_lagged_sum|   7|   1000000|     100|0.40424151|  PASSED  
      rgb_lagged_sum|   8|   1000000|     100|0.17133571|  PASSED  
      rgb_lagged_sum|   9|   1000000|     100|0.16488017|  PASSED  
      rgb_lagged_sum|  10|   1000000|     100|0.04872171|  PASSED  
      rgb_lagged_sum|  11|   1000000|     100|0.14221327|  PASSED  
      rgb_lagged_sum|  12|   1000000|     100|0.73422080|  PASSED  
      rgb_lagged_sum|  13|   1000000|     100|0.52321745|  PASSED  
      rgb_lagged_sum|  14|   1000000|     100|0.92157772|  PASSED  
      rgb_lagged_sum|  15|   1000000|     100|0.46483494|  PASSED  
      rgb_lagged_sum|  16|   1000000|     100|0.77249270|  PASSED  
      rgb_lagged_sum|  17|   1000000|     100|0.90706117|  PASSED  
      rgb_lagged_sum|  18|   1000000|     100|0.07863041|  PASSED  
      rgb_lagged_sum|  19|   1000000|     100|0.57804548|  PASSED  
      rgb_lagged_sum|  20|   1000000|     100|0.62148226|  PASSED  
      rgb_lagged_sum|  21|   1000000|     100|0.43169864|  PASSED  
      rgb_lagged_sum|  22|   1000000|     100|0.55355207|  PASSED  
      rgb_lagged_sum|  23|   1000000|     100|0.37528172|  PASSED  
      rgb_lagged_sum|  24|   1000000|     100|0.89652958|  PASSED  
      rgb_lagged_sum|  25|   1000000|     100|0.56031126|  PASSED  
      rgb_lagged_sum|  26|   1000000|     100|0.06051842|  PASSED  
      rgb_lagged_sum|  27|   1000000|     100|0.48204716|  PASSED  
      rgb_lagged_sum|  28|   1000000|     100|0.78433150|  PASSED  
      rgb_lagged_sum|  29|   1000000|     100|0.46256405|  PASSED  
      rgb_lagged_sum|  30|   1000000|     100|0.65675002|  PASSED  
      rgb_lagged_sum|  31|   1000000|     100|0.40871416|  PASSED  
      rgb_lagged_sum|  32|   1000000|     100|0.63328571|  PASSED  
     rgb_kstest_test|   0|     10000|    1000|0.04705398|  PASSED  
     dab_bytedistrib|   0|  51200000|       1|0.29698159|  PASSED  
             dab_dct| 256|     50000|       1|0.97199683|  PASSED  
Preparing to run test 207.  ntuple = 0
        dab_filltree|  32|  15000000|       1|0.06271944|  PASSED  
        dab_filltree|  32|  15000000|       1|0.08685594|  PASSED  
Preparing to run test 208.  ntuple = 0
       dab_filltree2|   0|   5000000|       1|0.07147807|  PASSED  
       dab_filltree2|   1|   5000000|       1|0.74653162|  PASSED  
Preparing to run test 209.  ntuple = 0
        dab_monobit2|  12|  65000000|       1|0.86530447|  PASSED  

あまり深い興味はないので、とりあえずPASSEDが並んでいることから、様々なランダムさが基準を満たしているのかな。。。と思います<テキトウですいません。

で、本題のProcessingでの可視化。

まず、ヒストグラムを描いてみることにしました。/dev/randomから1バイトずつ読み取り、そのデータをfreqという2次元配列にぶっこみます。普通に走らせ続けると画面からすぐにオーバーフローしそうだったので平均値を差し引くことで中央に稜線?を書くような感じにしました。ばらつきが小さければそれぞれの値(0−255)の頻度が大きくなっても中央付近に線が1本描かれるだけになるはずです。

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;

FileInputStream fs;
InputStreamReader isr;

int freq[][];
int gen = 0;

void setup() {
  size(256, 256);
  clearFreq();
  try {
    fs = new FileInputStream("/dev/random");
  }
  catch (IOException e) {
    e.printStackTrace();
  }
  
  frameRate(3000);
}

void clearFreq() {
  freq = new int [2][256];
}

int getRandomNumber() {
  int data = 0;
  try {
    data = fs.read();
  }
  catch (IOException e) {
    e.printStackTrace();
  }
  return (data & 0xff);
}

void draw() {
  freq[0][(int)random(256)]++;
  freq[1][getRandomNumber()]++;
  
  if (++gen % 1000 != 0) return;

  fill(255);
  background(0);
  text(gen, 0, 20);

//  if (gen % 1000 == 0) clearFreq();

  noFill();
  for (int i = 0; i < 2; i++) {
    if (i == 0) stroke(255, 0, 0); 
    else stroke(0, 255, 0); 
    beginShape();
    int y_base = avg(freq[i]);
    for (int j = 0; j < 256; j++) {
      vertex(j, height / 2 + y_base - freq[i][j]);
      //if (freq[i][j] > height) clearFreq();
    }  
    endShape();
  }
}

int avg(int[] v) {
  int sum = 0;
  for (int i = 0; i < v.length; i++) {
    sum += v[i];
  }
  return sum / v.length;
}

100万回繰り返した(100万バイトを取り出して、それぞれのバイト値の頻度を集計した)結果は以下の通りです。縦横256ピクセルのウィンドウです。ちなみに赤線がProcessingのrandomメソッドで、緑線が/dev/randomでした。

f:id:ke_takahashi:20181112184544p:plain

発生頻度に256回程度の差があるように見えますが100万回の試行に対してなので。。。ほぼ均一と言えるような気はします。でも、randomメソッドの分布もいい感じですね。。。あまり差がありません。実は両方とも同じ仕組みで動いていたりして。。。ちょっとそこまでは追っていません。

分布はほぼ一様として、順列に法則性があるのかないのか?

dieharderじゃないですが、ランダムさを測る指標があるのでしょうけど。。。ま、思いつくところで。

ランダムウォークさせてみました。1バイトなので360度を256等分して。。。移動した経路がどうなるか?

コードは以下です。

ベタですが、前述のプログラムように実行速度がなかなか出なかったので、なるべく参照する深さを減らしたつもりですが。。。あまり効きませんでした。

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;

FileInputStream fs;
InputStreamReader isr;

final int BUFF_SIZE = 1000;

int gen = 0;

float p1x, p1y;
float p2x, p2y;

float p1x_[] = new float [BUFF_SIZE];
float p1y_[] = new float [BUFF_SIZE];
float p2x_[] = new float [BUFF_SIZE];
float p2y_[] = new float [BUFF_SIZE];

float tbl_x[] = new float [256];
float tbl_y[] = new float [256];

void setup() {
  fullScreen();

  try {
    fs = new FileInputStream("/dev/random");
  }
  catch (IOException e) {
    e.printStackTrace();
  }
  
  p1x = p2x = width / 2.0;
  p1y = p2y = height / 2.0;
  
  makeTbl();
  
  frameRate(3000);
  
  background(0);
}

void makeTbl() {
  for (int i = 0; i < 256; i++) {
    tbl_x[i] = cos(TWO_PI / 255 * i);
    tbl_y[i] = sin(TWO_PI / 255 * i);
  }
}

int getRandomNumber() {
  int data = 0;
  try {
    data = fs.read();
  }
  catch (IOException e) {
    e.printStackTrace();
  }
  return (data & 0xff);
}

void draw() {
  int th = (int)random(256);
  int th2 = getRandomNumber();
  int g = gen++ % BUFF_SIZE;
  
  p1x += tbl_x[th];
  p1y += tbl_y[th];
  p1x_[g] = p1x;
  p1y_[g] = p1y;

  p2x += tbl_x[th2];
  p2y += tbl_y[th2];
  p2x_[g] = p2x;
  p2y_[g] = p2y;

  if (g != BUFF_SIZE - 1) return;

  noStroke();
  fill(0);
  rect(0, 0, width, 30);
  
  fill(255);
  text(gen, 0, 20);

  beginShape(POINTS);

  stroke(255, 0, 0);
  for (int i = 0; i < BUFF_SIZE; i++) {
    vertex(p1x_[i], p1y_[i]);
  }

  stroke(0, 255, 0);
  for (int i = 0; i < BUFF_SIZE; i++) {
    vertex(p2x_[i], p2y_[i]);
  }
  endShape();
}

とりあえず15万回(歩)試行させた経路が以下です(なお、以下の画像は1920x1200ピクセルで、1歩の最大値は1ピクセルです)。上記と同じく、赤線がProcessingのrandomメソッド。緑線が/dev/randomです。

f:id:ke_takahashi:20181112201657p:plain

緑の方が方向性を持ってるような気がしますが。。。プログラムコードの誤りのような気もします。

大きな差があるようには思えませんでした。

/dev/randomは遅いとかで。。。であればrandomメソッドで十分じゃないかな。。。という結論です。

とりあえず。

ああ。。。また脱線してしまった。やることやらなきゃ。

早とちりから始まった文献管理移行作業(Mendeley⇒Zotero)。。。ムダの極み>反省

追記:結局、Mendeleyに戻しました。。。クライアント使えなさすぎる。。。


昨日、たまたま電車内でスマホでMendeleyアプリを立ち上げたときに以下のメッセージが表示されまして。同期時間が制限されるようになるのか。。。と勘違いしたんですね。。。よく読めばそんなことは書いていませんでした。

f:id:ke_takahashi:20181109085055p:plain

で、とりあえず勘違いのまま昨日からあれこれやり始めてしまったんです。

文献管理を別のサービスでやろうと。。。です。

で、昨日に書いたエントリーのような試行をして失敗し。

ReadCubeも試してみましたがイマイチ。Mendeleyのライブラリをインポートできるにはできるのですが、フォルダー情報は移行されません。これじゃダメです。

Zoteroがいいな、と思って昨夜から始めた移行作業メモが以下。

(1)こちらのページにアクセスしてZoteroのアカウントを作成します。

(2)ZoteroのトップページにDownloadボタンがあるのでデスクトップアプリ(私はMac版)をダウンロードしてインストールします。

(3)インストールが完了したら、Zoteroのデスクトップアプリを起動しZoteroにサインインします。

(4)Zoteroのデスクトップアプリにインポート機能があるのでMendeleyからインポートしようとするとエラーがでます。ぐぐるといろいろと情報があって原因は、Mendeleyのデスクトップアプリ1.19のローカルのDBが暗号化されているためインポートできなくなったとのこと。なるほど。で、解決方法としては「暗号化前のバージョン1.18を入れ直して同期すればいいよ」とのこと。

(5)でも、1.18はMendeleyでは公開されておらず、ぐぐって出てくるリンクはいかにも怪しそう。で、あれこれやっているうちに以下のリンクから公式のdmgファイルをダウンロードできました。ちなみに、1.17とか指定するとそのバージョンもダウンロードできます。不思議ですね。。。しばらくこのdmgファイルはとっておこうっと。

https://www.mendeley.com/autoupdates/installer/Mac-x64/1.18

(6)で、Mendeleyのデスクトップアプリを1.19から1.18に変えて同期して。。。ながい。。。同期完了したらZoteroのデスクトップアプリを起動して、以下のインポート作業を進めます。

(6−1)Importを選択
f:id:ke_takahashi:20181109091143p:plain

(6−2)Mendeleyを選択
f:id:ke_takahashi:20181109091205p:plain

(6−3)アカウントを選択
f:id:ke_takahashi:20181109091220p:plain

(6−4)インポート先を選択。ここでチェックをいれると「Mnedeleyからインポートされたコレクション」が作成されてそこに文献情報がはります。僕はZeteroの標準のコレクションに入れたかったのでチェックをせずにインポートしました。
f:id:ke_takahashi:20181109091232p:plain


ここまでスムーズにいっても30分以上はかかると思います。なかなかの作業です。


で、問題はここからです。。。

Mendeleyと比較してZoteroの弱点は自社のモバイルアプリを持っていないところです。。。これが非常に惜しい。。。

サードパーティ製もあるんですが(安いけど)有料だったり、そのアプリもなんか作りが悪そう。。。

まともそうなアプリが以下です(と思います)。この系統のアプリは複数あるのですがAndroid Storeでトップに出てくるのでそれなりなのかと。

https://play.google.com/store/apps/details?id=net.ezbio.zotez&hl=ja

で、ZoteroがWeb APIを持っていないようで、ローカルDBで同期するしかないようです。そこで、DropBoxなどのファイル共有サービスを利用するしかないようです。

以下、そのセットアップです。上記の作業が終了したあとですね(Mendeleyからのインポートが完了した後という意味です)。

ZoteroのデスクトップアプリのPreferencesを選択して、「Advanced」ー「Files and Folders」を選択し、DropBox内にZoteroというフォルダを作成します。DropBox以外の選択肢(Google Driveとか)もあります。

f:id:ke_takahashi:20181109100414p:plain

ZoteroのデータがDropBoxに同期したあと、スマホZoteroアプリを起動して、Dropboxのアイコンを押して許可すれば同期が開始します。

使用感はまずまずです。文献を選択してダウンロードボタンを押せばPDFビュワーを選択して開くことができます。PDFに注釈はできないようですが。。。

Mendeleyほど統一感はありませんが。。。代替えの候補として。。。

文献管理サービスcolwizのアカウント削除手順

Mendeleyのサーバーが更新中とかでアプリで利用が制限(サーバーとの同期ができない)されている状況のようです。

不便なので、他のサービスがないかと探して以下のサービスをとりあえず試してみました。

colwiz - Wikipedia

試そうとしたときは通勤中だったのでスマホで登録していたんです。ここがミソです。デスクトップから登録しようとした人はこのエントリーは無益なので退室?してください

ちなみにデスクトップでwww.colwiz.comにアクセスすると、wizdom.aiに転送されます。サービスがここに統合されたんだとか。スマホだと転送されないんですね。。。たぶん、リダイレクト忘れているんでしょう。。。

登録できたけど、イマイチなのでアカウント削除しようとしたらモバイルページではメニューがない。。。おいおい。

乗り換え後に座れたのでPCでアクセスすると、上記の通りでwizdom.aiに転送されちゃいます。どーずりゃいいんだ?

そこでやったのが以下の手順です。

(1)Chromeでメニューから「その他のツール」ー「デベロッパーツール」を選択します。

(2)以下のアイコンをタップすると、モバイルページとして開くことができます。この状態でアドレスバーにwww.colwiz.comと入力すると、転送されずにwww.colwiz.comにログインすることができます。

f:id:ke_takahashi:20181108103814p:plain

(3)ログイン後、さきほどタップしたアイコンを押してデスクトップ版に戻します。すると。。。アカウント設定のメニューが表示されます。

f:id:ke_takahashi:20181108104044p:plain

(4)上記メニューで、Account Settingを選択し、さらに次の画面でMoreを選択すると、やっとDelete Accountのボタンが表示されます。

f:id:ke_takahashi:20181108104243p:plain

ということでした。

〈追記〉

ちなみにcolwizでアカウント削除したあとにwizdom.aiにsign upしようとするとエラーになりました。実は削除しなくてもsign up画面でcolwizユーザは引き継ぎができる画面が出てくるんですね。。。orz 統合したなら前のアカウント名が使えると思って試してはみたんです。駄目だったので上記のように削除したんですね。。。楽天トラベルで確か似たような経験があって、あちらは最初から旧ユーザーのログイン画面が出てたので迷わなかったのですが。。。配慮不足ですね。この設計は。

Cloud9 IDEをローカルで動かす

発端は以下。

Cloud9を知ったときに、確かローカルでも使えたよな。。。という記憶が。ちょっと探したときには見つからずこのようなつぶやきをしていました。

本日、探してみたらすぐに見つかって。。。

とりあえず、VirtualBox上にUbuntuいれて、その中で動かしてみました。

ソースは以下で公開されています。

github.com

手順はQiitaにもあります。

qiita.com

確かにCloud9 IDEが使えます。でも。。。ターミナルが使えない。当然ですが。。。これではフロント開発には使えてもRoR開発には使えません。

ちなみに、Cloud9 IDEがどんな感じになるのか以下にデモページがありますのでご参考まで。

Cloud9

SuperCollider起動時にSuperDirtを自動起動

SuperCollider 3.9.3(macOS版)の話です。それ以外は未確認です。

昨日、初めて触ったTidalCyclesですが、音源サーバとしてSuperColliderを予め起動してその中で「SuperDirt.start」を実行しなければならないようです。ちょっとしたことですが面倒ですね。DRY。

で、ググってみましたら、以下の記事が。

SuperCollider起動時に自動的に「SuperDirt」をスタート(SuperDirt.start;)させる方法!SuperDirtがスタートしないエラー対応にも - Creative Plus

真似してみましたが。。。メニューが違う。

で、以下でできましたのでご報告。もっと簡単でした。

SuperColliderを起動して、以下のメニュー(Open startup file)を開きます。

f:id:ke_takahashi:20181105083127p:plain

あとはそこに「SuperDirt.start」と入力してセーブします。

f:id:ke_takahashi:20181105083321p:plain

起動できるか確認します。SuperColliderを再起動します。ログに赤下線部が表示されていればOKと思います。

f:id:ke_takahashi:20181105083617p:plain

あとはAtomで拡張子 .tidal でファイル保存して、ガンガンスクリプトを書いては、Shift+Enter、していけばいいですね。

通勤電車が楽しくなりそう。

<2018/11/5追記>

起動ログ?を見ると「sc3-pluginsが見つからない」とか。うーん。音は鳴っているけど気持ち悪いので調べてgithubからrelease版をダウンロードして、展開して ~/Library/Application Support/SuperCollider/Extensionsの中に入れたらエラーが消えた。とりあえず。メモしておこう。