LoginSignup
4
0

More than 3 years have passed since last update.

数学ガールの秘密ノート/ビットとバイナリー 第2章を実装してみた(React版)

Last updated at Posted at 2019-08-26

はじめに

みなさん、数学ガールは読まれていますか?最新巻1のネタは「ビットとバイナリー」、つまり2進数に関する話題です。
数学ガールの秘密ノート/ビットとバイナリー

第2章は「変幻ピクセル」と題して16x16の画像をビット演算により変換する(フィルタをかける)話になっています。前巻と異なりプログラムは提示されており、すでにRubyで実装してみた例も公開されています。

ここまで前置き。
前回はPython(matplotlib)で実装したわけですがその後Twitterで「JavaScriptで実装してみました!」という方をちらほら見かけ、「インタラクティブええなぁ」と思ったわけで、
React使って実装してみました。

コードはここに置いてあります。
https://github.com/junjis0203/math_girls-secret_notebook_bits_and_binary

フィルタ

書籍では「受信する」・「送信する」という表現でフィルタへの入力・出力が行われており、先に紹介したRuby実装でもFiberを使って実装されています。
JavaScriptでもasync使って頑張れば実装できそうですが、本題から外れるし最終形のフィルタを考慮すると状態管理がめんどくさいので普通に入力配列を渡し出力配列を返すようにしました。

src/pixel-filter.js抜粋
const right = (input) => {
  const output = [];
  for (let i = 0; i < 16; i++) {
    let x = input[i];
    x = x >> 1;
    output.push(x);
  }
  return output;
};

Figureコンポーネント

この実装のメインとなる画像の表示。このためにFigureコンポーネントを作成しました(src/pixel-component.js)。使い方はこんな感じ。

src/pixel-main0.js
const world = <Figure lines={input1} clickable={true} />;

これで以下のように表示されます。ファイルとしてはpixel0.htmlです。

pixel0.jpg

clickableがtrueだとクリックに反応して白黒が反転するようになっているのですが、Edgeは問題ないもののChromeは一番下しか反応しません。line-heightを0にしているのが原因のようですが直し方がわからないのでそのままにしています。

コンポーネントの描画はrender()→lines()→pixels(y, line)という階層構造になっています。元々はFigure > PixelLine > Pixelという階層構造になっていたのですがイベントハンドリング考えると親にさかのぼるのが煩雑なのでFigureの中で全部処理するようにしました。

Store

この実装のメインその2、インタラクティブ性。入力画像をポチポチしたら出力が変わるようにしたいわけです。
Reactの仕組みも流儀もよくわかっていなかったので苦労しましたが最終的に以下のような感じにしました。

const in1 = <Figure lines={input} clickable={true} store={store} dst_tag={'INPUT1'} />;
store.addFilter('INPUT1', right, 'OUTPUT1');
const out1 = <Figure store={store} src_tag={'OUTPUT1'} />;
  1. in1のFigureはクリックされるとstoreに'INPUT1'というタグで画像全体を投げる(Figureクラスのdispatchメソッド)
  2. rightのフィルタは'INPUT1'が投げられると動作し結果を'OUTPUT1'として投げる(Storeクラスのdispatchメソッド)
  3. out1のFigureは'OUTPUT1'を受け取り表示する(FigureクラスのonDispatchメソッド)

pixel1.htmlが上記のコードに対応します(実際には次に説明するComposerを使っています)

pixel1.jpg

ちなみに、このaddFilterの実装(フィルタはlistenerの一部である)はなかなか美しいと自画自賛しているのですがいかがでしょうか。

src/composer.js抜粋
  addFilter(src_tag, filter, dst_tag) {
    // listen tag and execute filter
    const listener = {
      onDispatch: (input) => {
        const output = filter(input);
        this.dispatch(dst_tag, output);
      }
    };
    this.addListener(src_tag, listener);
  }

Composer

さてというわけでイベントに反応して出力が変わるようになりましたがもう一度見てみましょう。

const in1 = <Figure lines={input} clickable={true} store={store} dst_tag={'INPUT1'} />;
store.addFilter('INPUT1', right, 'OUTPUT1');
const out1 = <Figure store={store} src_tag={'OUTPUT1'} />;

ダサいですね。全然DRYじゃありません。というわけで、Composerというクラスを作り以下のように書けるようにしました。

pixel1.html抜粋
const composer = new Composer();

const in1 = composer.makeSource(input1);
const r_block1 = composer.addFilter(in1, right);
const world = r_block1.component;

仕組みは以下のようになっています。

  • tagは連番で勝手に割り振る。
  • makeSourceやaddFilterは{component: Reactコンポーネント, tag: 割り振られたタグ}を返す。他への入力に利用できる。

最終形:縁取りフィルタ

さてちょっと伏線してた最終形。入力画像の縁取りをするというフィルタについてテトラちゃんユーリが考え無理だった後、リサにより答えが示されます。

pixel3.jpg

右上にポツンとあるFは実際には最終段のAND入力の片割れでCSSを個別にいじればどうにかはできるのですが、まあ逆に目立つしいいかなと思ってます。

これを作るためのコードは以下になります。

pixel3.html抜粋
const out1_1 = composer.addFilter(in1, right);
const out1_2 = composer.addFilter(in1, left);
const out1_3 = composer.addFilter(in1, up);
const out1_4 = composer.addFilter(in1, down);

const out2_1 = composer.addFilter2(out1_1, out1_2, and);
const out2_2 = composer.addFilter2(out1_3, out1_4, and);

const out3 = composer.addFilter2(out2_1, out2_2, and);

const out4 = composer.addFilter(out3, complement);

const out5 = composer.addFilter2(in1, out4, and);

実際に動いている様子はこちら。左端の4つのFも入力なので同期させないといけないのですが分岐をちゃんと実装しようとするとコードが増えてしまうので今回は残バグということにしてます。

pixel3-movie.gif

おわりに

今回は秘密ノート/ビットとバイナリーで紹介されているフィルタプログラムをReactで実装してみました。ReactNativeは使ったことあるので2Figureコンポーネントはすぐに作れたのですが、入力→フィルタ実行&伝搬の仕組みは思いつくまでしばらくかかりました。暑くてやる気がしなかったというのもありますが(笑)

ところで

pixel3.html抜粋
  <script src="pixel-filter.js"></script>
  <script src="pixel-component.js"></script>
  <script src="pixel-composer.js"></script>
  <script src="pixel-main_common.js"></script>

何時代のJSだよって感じですね。importおよびWebpack使うのをちゃんとやろうと思いましたがめんどくさいので手抜きしました。

宣伝?

さて初めに書いたように数学ガールの秘密ノート最新巻は「ビットとバイナリー」ですが、なんと!すでに次の巻が決まっています。
数学ガールの秘密ノート/学ぶための対話

「数学が苦手な子にどのように教えるべきか」「わからないものをどう学んだらよいか」がテーマという連載当時から絶大な反響を呼んだシリーズが早くも書籍化!数学だけでなく「教えるということ」「学ぶということ」に関心のある方、ない方も必読です!

ちなみに、私にアフィリエイトは一銭も落ちませんので純粋に宣伝です。


  1. 2019/8/26現在 

  2. ReactNative使ったことあるのにReactは使ったことはないという「スーパーサイヤ人になれるのに舞空術は使えない」みたいな人だったのでReact使ってみようと思ったのも今回の動機です。 

4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0