20201108のJavaScriptに関する記事は30件です。

娘に会えない日も自分の声で絵本を読み聞かせられるようにした。機械学習入門。

娘も私も絵本の読み聞かせは毎日欠かせない

もうすぐ2歳になる娘は絵本を読んでもらうことが好きで、寝る前に必ず自分で選んだ絵本を持ってくる。仕事で娘に会えない日も、自分の声で読み聞かせてあげることができれば、明日も私を指名して絵本を持ってきてくれるかもしれない。娘のお気に入りの絵本を機械学習が判断し、読み聞かせの音声を再生するWebアプリケーションを作った。

娘が使う目的なので、画面はできるだけシンプルにして、カメラに絵本をかざせば、すぐに読み聞かせが開始されるようにした。↓が実際の画面。
image.png

また、娘が使ってくれたことに気づけるように、LINE Notifyで通知する機能も追加した。(※「あ、今日も読んでくれたんだー」と仕事帰りの電車でニヤニヤするための自己満足)
図1.png

構成

1.Teachable Machineを使い、娘のお気に入りの絵本を学習させ、モデルの埋め込み用リンクを発行する。
image.png
2.学習モデルを、ml5を使って読み込み、フロントエンドで動くようにする。

sample.html
<script src="https://unpkg.com/ml5@latest/dist/ml5.min.js"></script>
sample.js
// 作成したモデルのURL
const imageModelURL = 'https://teachablemachine.withgoogle.com/models/XXXXX/';

// 自作モデルのロード
classifier = ml5.imageClassifier(imageModelURL + 'model.json', video, () => {
// ロード完了
console.log('Model Loaded!');
});

3.収録した読み聞かせ音源が、学習モデルの判断結果によって再生されるように実装する。

sample.html
<audio id="sound-file1" preload="auto">
  <source src="https://dotup.org/uploda/dotup.org2302152.mp3" type="audio/mp3" controls>
</audio>
sample.js
function storytelling1(){
  //音声ファイルを再生する
  document.getElementById('sound-file1').play();
  //Webhookにアクセス
  sendWebhook('「Do you want a Hug?」を読んだよ');
}

4.axiosを使い、音源が再生された際にIntegromatのWebhookURLにアクセスされるようにする。

sample.html
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
sample.js
// 引数に送りたいメッセージを入れる
async function sendWebhook(message) {
// Integromatに送る
try {
// 取得したIntegromatのWebhookURL
const res = await axios.get(`https://hook.integromat.com/XXXXXXXXXXXXXXXXXXXXXXXXXX?message=${message}`);
console.log(res.data);
} catch (err) {
console.error(err);
}
}

5.Integromatを使ってWebhookURLとLINE Notificationを連携し、「読み聞かせ」が行われたことを通知する。
image.png

ソース

storytelling.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Storytelling</title>
  </head>

  <body>
    <h1>Storytelling</h1>
    <div id="console_log"></div>
    <video id="myvideo" width="640" height="480" muted autoplay playsinline></video>
    <audio id="sound-file1" preload="auto">
        <source src="https://dotup.org/uploda/dotup.org2302152.mp3" type="audio/mp3" controls>
    </audio>
    <audio id="sound-file2" preload="auto">
        <source src="https://dotup.org/uploda/dotup.org2302153.mp3" type="audio/mp3" controls>
    </audio>
    <audio id="sound-file3" preload="auto">
        <source src="https://dotup.org/uploda/dotup.org2302151.mp3" type="audio/mp3" controls>
    </audio>

    <script src="https://unpkg.com/ml5@latest/dist/ml5.min.js"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>

    <script>
      // 作成したモデルのURL
      const imageModelURL = 'https://teachablemachine.withgoogle.com/models/XXXXXXX/';

      console.log = function (log) {
      document.getElementById('console_log').innerHTML = log;
      }

      async function main() {
        // カメラからの映像取得
        const stream = await navigator.mediaDevices.getUserMedia({
          audio: false,
          video: true,
        });

        // IDが"myvideo"であるDOMを取得
        const video = document.getElementById('myvideo');

        // videoにカメラ映像をセット
        video.srcObject = stream;

        // 自作モデルのロード
        classifier = ml5.imageClassifier(imageModelURL + 'model.json', video, () => {
          // ロード完了
          console.log('Model Loaded!');
        });

        // 分類処理を連続的に行う
        function onDetect(err, results) {
          if (results[0]) {
              console.log(results[0].label);
              //読み聞かせ音を出す
              if (results[0].label === 'Do you want a Hug?') {
              // storytelling 関数実行
                 storytelling1();
              }
              if (results[0].label === 'おつきさまこんばんは') {
                 storytelling2();
              }
              if (results[0].label === 'だるまさんと') {
                 storytelling3();
              }
            }
          classifier.classify(onDetect);
        }
        classifier.classify(onDetect);
      }

      // 引数に送りたいメッセージを入れる
      async function sendWebhook(message) {
      // Integromatに送る
      try {
      // 取得したIntegromatのWebhookURL
      const res = await axios.get(`https://hook.integromat.com/XXXXXXXXXXXXXXXXXXXXXXXXXX?message=${message}`);
      console.log(res.data);
        } catch (err) {
      console.error(err);
       }
      }

      function storytelling1(){
            //音声ファイルを再生する
            document.getElementById('sound-file1').play();
            sendWebhook('「Do you want a Hug?」を読んだよ');
      }

      function storytelling2(){
            //音声ファイルを再生する
            document.getElementById('sound-file2').play();
            sendWebhook('「おつきさまこんばんは」を読んだよ');
      }

      function storytelling3(){
            //音声ファイルを再生する
            document.getElementById('sound-file3').play();
            sendWebhook('「だるまさんと」を読んだよ');
      }
      // 実行
      main();

    </script>
  </body>
</html>

機械学習入門として作ってみた感想

機械学習ってキーワードから入ると、勉強しなければならないことも多いと思うが、TeachableMachineなどを使えば簡単に学習モデルを作ることができた。また、ml5などのライブラリからモデルを「使う」ことも、そこまで難しくなくできる、という感覚も得られた。ただ、モデルのタイプによっては、色々と制約などもありそうなので、使いながら覚えていきたいと思った。

おまけ

・絵本の各ページを撮影し、声を吹き込んだ動画を流す案も考えましたが、娘が絵本を読むのは、寝る前であることが多いので、明るい画面を見続けて眠れなくならないように、「音声」での読み聞かせを選びました。
・私にとって、絵本の読み聞かせは、娘と過ごす時間の中でもトップ3に入る好きな時間です。より良い時間にできるアイデアを、また何か考えてみたいものです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React (JavaScript) で湯婆婆を実装してみる

はじめに

原作者様: Javaで湯婆婆を実装してみる - Qiita

この壮大なビッグウェーブならぬビッグ湯婆婆に乗るっきゃない!

コード

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>React 湯婆婆</title>
  <script src="https://unpkg.com/react/umd/react.development.js"></script>
  <script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
  <script src="https://unpkg.com/babel-standalone/babel.min.js"></script>
</head>
<body>
  <div id="root"></div>
  <script type="text/babel">
    (() => {
      'use strict';

      const {useState} = React;

      function Yubaba() {
        const [name, setName] = useState('');
        const newName = name.substr(Math.floor(Math.random() * name.length), 1);

        return (
          <div>
            <p>契約書だよそこに名前を書きな</p>
            <input type='text' value={name} onChange={e => setName(e.target.value)}/>
            <p>フン{name}というのかい贅沢な名だねぇ</p>
            <p>今からお前の名前は{newName}いいかい{newName}だよ分かったら返事をするんだ{newName}!!</p>
          </div>
        );
      }

      ReactDOM.render(
        <Yubaba/>,
        document.getElementById('root')
      );

    })();
  </script>
</body>
</html>

こちらがコードになります。これを textfile とかにコピペしてブラウザ起動すれば動きます。

コードの解説

湯婆婆の部屋に入る準備

React と babel を CDN で読み込む。

  <script src="https://unpkg.com/react/umd/react.development.js"></script>
  <script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
  <script src="https://unpkg.com/babel-standalone/babel.min.js"></script>

レンダー用の div 要素 (root) を記述して、あとは JS のお作法を記述する。

  <div id="root"></div>
  <script type="text/babel">
    (() => {
      'use strict';

      ...

    })();
  </script>

契約書と名前を奪う Yubaba コンポーネントを作成

Yubaba コンポーネントをつくる!
フック (useState) も使う!

      const {useState} = React;

      function Yubaba() {
        const [name, setName] = useState('');
        const newName = name.substr(Math.floor(Math.random() * name.length), 1);

        return (
          <div>
            <p>契約書だよそこに名前を書きな</p>
            <input type='text' value={name} onChange={e => setName(e.target.value)}/>
            <p>フン{name}というのかい贅沢な名だねぇ</p>
            <p>今からお前の名前は{newName}いいかい{newName}だよ分かったら返事をするんだ{newName}!!</p>
          </div>
        );
      }

      ReactDOM.render(
        <Yubaba/>,
        document.getElementById('root')
      );


実行結果

react-yubaba.png

おまけ

名前の入力が空白だった場合のクラッシュ湯婆婆は入室しませんでした!
SPA なので名前を言い終える前に湯婆婆は名前を奪い始めるという。早とちり湯婆婆降臨。

最後に

ここまで読んでいただきありがとうございました!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【webpack】webpack設定を一からまとめてみる①

webpackとは

フロントエンド開発におけるオープンソースのモジュールバンドラーである。
ここで言うモジュールとは、JavascriptやCSS、画像などのファイルを示し、モジュールバンドラーとはこれらのファイルをまとめる役割を持つ。現在の最新はバージョン5となっている。
スクリーンショット 2020-11-08 11.20.06.png
webpackを利用するメリット
・自動でファイルの依存関係を解決してくれる
・リソースをコンパクトにしてくれる
・ローダーや各種プラグインを導入する事でデフォルトの設定を上書きし、機能を拡張する事が出来る

導入方法

①Node.jsをインストールする

webpackはNode.jsで動かす為、最初にインストールしておく。

②フロントエンドパッケージを初期化

Node.js上で動かすモジュールの管理には今回npmを使用する。

npm init -y 

上記コマンドを実行すると、対象プロジェクトを管理するpackage.jsonが作成される。

③webpackをインストールする

npm install --save-dev webpack webpack-cli

上記は対象プロジェクト内に適用する場合である。
PC全体に適用した場合はグローバルでインストールすれば良い。

④webpackを実行する

npx webpack

上記コマンドでwebpackを実行し、バンドルを開始出来る。

基本設定

開発環境、本番環境で実行結果を分岐させたりなど、
設定ファイルにオプションを指定してやるのが一般的な使い方となっている。
また設定ファイルは、CommonJSの規格でモジュールをインポート・エクスポートする仕様となっている。

webpack.config.js
module.exports = {
    mode: 'development', // 開発モードか本番モードかを設定出来る
    entry: '.src/app.js' // バンドルの起点となるファイル
}

デフォルトでdevelopmentモードとproductionモードが存在し、
バンドルされるファイルの圧縮についての設定が変わるなど、モード設定だけで使用が可能。
参考:Mode | webpack

entryはバンドルの起点となるファイルを設定する事が出来て、
ファイルの依存関係を解決しながらバンドルされる。

エントリーポイントは配列で複数指定する事も可能であり、
それぞれ依存関係がないファイルも1つのファイルにバンドルする事が出来る。

webpack.config.js
module.exports = {
    mode: 'development',
    entry: ['.src/app.js', '.src/foo.js'] // エントリーポイントは複数設定可
}

また、依存関係のないファイルをそれぞれ別ファイルで出力する場合はオブジェクト形式で設定すれば良い。

webpack.config.js
module.exports = {
  mode: 'development',
  entry: {
       app: '.src/app.js',
       foo:  '.src/foo.js'
  }
}

出力先のディレクト、ファイル名を指定する時はoutputプロパティを使用する。
特に指定しなければ、distディレクトにmain.jsが出力される初期設定になっている。

webpack.config.js
const path = require('path'); // pathモジュールを使用する

module.exports = {
  mode: 'development',
  entry: {app: './src/app.js'}, 
  output: {
    path: path.resolve(__dirname, 'public'), // 出力されるディレクトリの指定
    filename: 'bundle.js' // 出力されるファイル名の指定
  },
}

ディレクトリを絶対パスで指定する必要がある為、pathモジュールをrequireしている。

webpack.config.js
const path = require('path'); 

module.exports = {
  mode: 'development',
  entry: {app: './src/app.js'}, 
  output: {
    path: path.resolve(__dirname, 'public'), 
    filename: '[name].bundle.js' // name、idなど変数を含める事が出来る
  },
}

[name]を指定すると、entryをオブジェクト指定した際のキーが入る仕様となっている。

ローダーの導入

webpackでjavascriptを扱う分には初期設定でも問題ないが、
違う拡張子のファイルも扱う場合にはローダーを導入する必要が出てくる。

sassファイルをバンドルする

以下のコマンドで必要なローダーをnpmでインストールする。

npm add --dev sass sass-loader css-loader style-loader

configファイルの設定。余談だが、\はmacではoption + ¥で入力可能。

webpack.config.js
const path = require('path');

module.exports = {
  mode: 'development',
  entry: {app: './src/app.js'}, 
  output: {
    path: path.resolve(__dirname, 'public'),
    filename: '[name].bundle.js'
  },
  module: { // モジュールプロパティの中に
    rules: [ // ルールズを配列で指定
      {
        test: /\.scss$/, // 対象の拡張子を正規表現で指定。ちなみに$は末尾を表している
        use: [// どのローダーを使うのか(下から実行されていく)
          'style-loader', //cssをhtmlにstyleタグとして出力
          'css-loader', // jsにバンドルされる
          'sass-loader' // cssに変換、コンパイルする
        ]
      }
    ]
  }
}

使用するローダーは下から順番に実行されると言う点に注意する。

画像ファイルをバンドルする

cssからbackground-imageなどで画像を読み込む場合、これもバンドルの対象となるが、
このようなケースでも、別途ローダーを導入する必要がある。

npm add --dev file-loader

configファイルの設定。
WEBフォントも画像と同様にfile-loaderを介す必要がある。

webpack.config.js
const path = require('path');

module.exports = {
  mode: 'production',
  entry: {app: './src/app.js'}, 
  output: {
    path: path.resolve(__dirname, 'public'),
    filename: '[name].bundle.js'
  },
  module: {
    rules: [
      {
        test: /\.(jpe?g|gif|png|svg|woff2?|ttf|eot)$/, // 拡張子毎に設定
        use: [
          { // ローダーオプションを使用する際はオブジェクト形式
            loader: 'file-loader',
            options: {
              name: '[contenthash].[ext]', // 拡張子エクステンションのext
              outputPath: 'images', // 格納するディレクトリ
              publicPath: 'images', // サーバー上のパス情報。CDNなどで利用する
            }
          }
        ]
      }
    ]
  }
}

nameに画像ファイル名を指定出来て、ここでも[name]、[id]などが指定可能。
[contenthash]とする事で本番でのキャッシュ対策にファイル名をハッシュ値で指定する事が出来る。

ハッシュ値に設定についてはいくつか選択肢がある
[hash]・・・ビルド単位で発行されるハッシュ。あまり使わない。
[contenthash]・・・コンテンツ単位で発行されるハッシュ。画像ファイルで良く使う。
[chunkhash]・・・チャンクファイル単位で発行されるハッシュ。具体的には1つのoutputのバンドル単位でチャンクが構成される。

参考:Output | webpack

babelを導入する

ES2015以降で書かれたコードをES5以前の環境でも動作するようにする為、babelと連携させる。以下のコマンドで必要なローダーと合わせてインストールする。

npm add --dev babel-loader @babel/core @babel/preset-env   

configファイルの設定。babel-loaderを噛ます記述を追加する。

webpack.config.js
const path = require('path');

module.exports = {
  mode: 'production',
  entry: {app: './src/app.js', sub: './src/sub.js'}, 
  output: {
    path: path.resolve(__dirname, 'public'),
    filename: '[name].bundle.js'
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/, // node_modulesはbabelを通す必要がない
        use: [
          'babel-loader',
        ]
      },
    ]
  }
}

babel用の設定ファイルも作成する。

.babelrc
{
  "presets": ["@babel/preset-env"]
}

これでES2015以降で書かれたコードもES5以前のコードへ変換される。

ESLintの導入

ESLintは、JavaScriptのコードで見つかった問題パターンを識別する為の、静的コード分析ツールである。ルール設定が柔軟であり、Google社が公開しているeslint-config-googleなどが有名である。

以下のコマンドで必要なローダーと合わせてインストールする。

npm add eslint eslint-loader babel-eslint --dev   

babel-loaderの前にeslint-loaderを使用するよう記述を追加する。

webpack.config.js
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/, // babelを通す必要がない
        use: [
          'babel-loader',
          'eslint-loader'
        ]
      },
    ]
  }

ESlint設定ファイルを作成する。

.eslintrc
{
  "env": {
    "browser": true,
    "es2017": true,
  },
  "extends": "eslint:recommended",
  "parser": "babel-eslint"
}

取り敢えず、基本的な設定はここまで。
ESlintのオプションやルール設定の詳細などはまた別の記事でまとめる事とする。
SplitChunksなど応用的な使用方法などについても、長くなってしまったので、次回以降で。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DBSCANのJavaScriptによる実装

はじめに

色々な機械学習処理をブラウザ上で試せるサイトを作った」中で実装したモデルの解説の五回目です。

今回はDBSCANの実装についてです。

デモはこちらから。(TaskをClusteringにして、ModelのDBSCANを選択)
実際のコードはdbscan.jsにあります。

なお、可視化部分については一切触れません。

概説

Wikipediaの擬似コードをそのままコードに起こしただけとなります。
なので、解説することがありません。

コード中のgetNeighborsが、上記の擬似コードのregionQueryとなります。

アルゴリズム

日本語で書いただけになります。

  1. クラスタ番号$C$を$0$に初期化する
  2. 各データ間の距離を計算する
  3. 各データ$P$に対して
    1. $P$にすでに訪問していた場合は、次のデータの処理に移る
    2. $P$を訪問したものとする
    3. $P$の近傍データ$P_{near}$を取得する
    4. $P_{near}$のデータ数が一定値に満たない場合、$P$はノイズとし、次のデータの処理に移る
    5. $P$のクラスタを$C$とする
    6. $P_{near}$の各データ$P'$に対して
      1. $P'$に訪問していない場合
        1. $P'$を訪問したものとする
        2. $P'$の近傍データ$P_{near}'$を取得する
        3. $P_{near}'$のデータ数が一定値以上の場合は、$P_{near}$に$P_{near}'$を追加する
      2. $P'$のクラスタが決定していない場合は、$P'$のクラスタを$C$とする
    7. $C$に$1$を加える

コード

ノイズはカテゴリ-1に対応します。

class DBSCAN {
    // https://ja.wikipedia.org/wiki/DBSCAN
    constructor(eps = 0.5, minPts = 5, metric = 'euclid') {
        this._eps = eps;
        this._minPts = minPts;

        this._metric = metric
        switch (this._metric) {
        case 'euclid':
            this._d = (a, b) => Math.sqrt(a.reduce((s, v, i) => s + (v - b[i]) ** 2, 0));
            break
        case 'manhattan':
            this._d = (a, b) => a.reduce((s, v, i) => s + Math.abs(v - b[i]), 0)
            break
        case 'chebyshev':
            this._d = (a, b) => Math.max(...a.map((v, i) => Math.abs(v - b[i])))
            break;
        }
    }

    predict(datas) {
        let c = 0;
        const n = datas.length;
        const visited = Array(n).fill(false);
        const cluster = Array(n);
        const d = Array(n);
        for (let i = 0; i < n; d[i++] = Array(n));
        for (let i = 0; i < n; i++) {
            for (let j = 0; j < i; j++) {
                const v = this._d(datas[i], datas[j]);
                d[i][j] = d[j][i] = v;
            }
        }
        const getNeighbors = (i) => {
            const neighbors = [];
            for (let k = 0; k < n; k++) {
                if (d[i][k] < this._eps) neighbors.push(k);
            }
            return neighbors
        }
        for (let i = 0; i < n; i++) {
            if (visited[i]) continue;
            visited[i] = true;
            const neighbors = getNeighbors(i);
            if (neighbors.length < this._minPts) {
                cluster[i] = -1;
                continue;
            }
            const clst = c++;
            cluster[i] = clst;
            while(neighbors.length > 0) {
                const k = neighbors.pop();
                if (!visited[k]) {
                    visited[k] = true
                    const ns = getNeighbors(k);
                    if (ns.length >= this._minPts) {
                        neighbors.push(...ns);
                    }
                }
                if (!cluster[k]) cluster[k] = clst;
            }
        }
        return cluster;
    }
}

考慮点

この記事を書いているときに、一か所うまく動かない可能性のある場所を見つけたので記載しておきます。

クラスタ番号clst0から始まるため、最後の場所

                if (!cluster[k]) cluster[k] = clst;

が正常に動作しないと思われます。

修正方針としては、次のどちらか

  • if (!cluster[k])if (cluster[k] === undefined)に変更する
  • clst = c++clst = ++cに変更する

さいごに

配列について、Array(n)などとして初期容量を指定していますが、不要、あるいはむしろ効率が悪くなる、という考え方もあるようです。
特にこの程度のコードであれば、単に[]としたほうが分かりやすいのではないでしょうか。

また、fillも不要です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

reduce関数完全に理解した

reduce 関数って使ってますか?

JavaScript の数ある配列操作の中で最もよくわからん奴・・・じゃないですか?

私はなんとなく、単に合計を求めたりする為のものだと思ってました

でも実際使ってみるとかなり便利だったので、皆さんにも完全に理解してもらうべく立ち上がりました

気分はスーパーヒーローです

※ この記事はレジェンドオブトゥモローシーズン 2 を見ながら書いたので少しだけヒーロー成分が入っています

reduce 関数って?

MDN の例文を少しわかりやすく書いています

array.reduce( ( previousValue, currentValue[, currentIndex[, originalArray]] ) { //callbackFunction
  // return result from executing something for accumulator or currentValue
}
[, initialValue]);

いやわかりにくい!

1 つ 1 つ説明します

第一引数 : callbackfunction

他の配列関数(forEach とか map とか)と同じで、配列の 1 レコードずつに対して行う処理を記述します

最後のループの時に return する値が reduce メソッドの戻り値になります

1 つ 1 つ引数を見ていきましょう

引数

previousValue

前回のループの return 値です

currentValue

こちらは任意

元の配列の、現在のループの要素です

currentIndex

元の配列の、現在のループのインデックスです

originalArray

元の配列が渡されます

第二引数 : initialValue

reduce 関数の第二引数である initialValue は

後述の callbackfunction の引数に影響します

initialValue を渡すかは任意です

initialValue を渡した場合、渡さなかった場合の previousValue と currentValue の1ループ目の値の比較

引数 渡した場合 渡さなかった場合
previousValue initialValue 元の配列の最初の要素
currentValue 元の配列の最初の要素 元の配列の2番目の要素

実際どう使うの?

結局 reduce 関数、どう使えばいいの?

と思ったそこのあなた

安心してください

用意してますよ(実際のソースを)

超定番 値の合計を求める

const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

const sum = array.reduce((previousValue, currentValue) => previousValue + currentValue)

console.log(sum) // 55

どうですか?素晴らしいでしょう

何が素晴らしいか、reduce を使わない

よくあるやり方を見てみましょう

let foolishSum = 0
for (const i = 0; i < array.length; i++) {
    foolishSum += array[i]
}

何がダメかは一目瞭然ですよね

let

こいつです

再代入可能なせいで可読性を悪くする悪の根源ですね(言い過ぎ)

reduce は ループした末の結果が戻り値になるので再代入をする必要がありません

でもこの程度の計算しか出来ないなら別に for で良くない?

と思うかもしれません

確かに大体の記事の reduce の説明は(他の配列操作関数も) このような計算が多いです

でもこの関数は実はなんでもできるんです

次は実務で良く使えそうなやり方をお見せします

オブジェクト配列を操作する

const list = [
    {
        name: "Flash",
        power: "The fastest man in the world",
    },
    {
        name: "Arrow",
        power: "Master of sniper",
    },
    {
        name: "Super Girl",
        power: "Alien of Crypton",
    },
    {
        name: "Legends of Tommorow",
        power: "Overhanging heroes",
    }
];

const object = list.reduce((previousValue, currentValue) => {
    const name = currentValue.name;
    const power = currentValue.power;
    previousValue[name] = power;
    return previousValue;
}, {});

console.log(object);
/*
{
  Arrow: "Master of sniper",
  Flash: "The fastest man in the world",
  Legends of Tommorow: "Overhanging heroes",
  Super Girl: "Alien of Crypton"
}
*/

アローバースのヒーローの情報を取得する API のレスポンスを操作する

と言うイメージです
アローバース最高!!

initialValue に空のオブジェクト渡しています

空のオブジェクトに、各ループで

key=name,value=power のプロパティを push しています

最後に戻り値を const で宣言した変数に入れている為、それ以降操作する必要がなくなります

これを普通にやろうとするとこうなります

const uglyObject = {}

for (let i = 0; i < list.length; i++) {
    const object = list[i]
    const name = object.name
    const power = object.power
    uglyObject[name] = power
}

いやまあ確かに const になっていますが

JavaScript のオブジェクトや配列が const なのに後から追加できちゃうのはどうなんだ・・・

できるからと言って const で宣言しているオブジェクトに追加するのは良くないですね

このように reduce 関数であれば可読性を失わずにオブジェクトを操作したりできます

reduce 関数さえあれば、他の配列操作関数で出来る大概のことはできます

他の関数の模倣

reduce 関数でいろんな配列操作関数の模倣をしてみます

その前に・・・

配列関数にはそれぞれの意図があります

reduce 関数や forEach 関数は割となんでもできます

でもなるべくそれぞれの処理にあった配列を関数を使った方が、何をしているかが一目瞭然です

それに、実際のソースをみてもらえばわかりますが各関数を使うよりステップ数が多くなります

可読性が悪いですね・・・

じゃあなんで模倣のやり方を書くのかって?

実際に何かを作る時、いつかはゴリ押ししないといけない時が必ずあります・・・

そんなどうしようもない時の技の一つとして

必要な時にあんなやり方書いてあったな・・・

と思い出してもらえれば良いなと思います

以上を理解した上でこの先を読んでください

find

対象の配列から条件に合う要素を抽出する奴です

const findResult = list.find((object) => object.name === "Flash");
const findAlternative = list.reduce((previousValue, currentValue) => {
    const isFlash = currentValue.name === "Flash"
    const alreadyFound = previousValue.name !== "Flash";
    return isFlash && alreadyFound ? currentValue : previousValue;
});

find 関数は一つ該当する要素を見つけたらその時点でループが終了します
配列関数は break 等ができず、reduce 関数は最後までループが続いてしまいます

そのため上の例では既に該当する要素を見つけている場合

以降は結果を取得しないようにしています

かなりゴリ押しですが、find 関数と同じ事ができました

filter

対象の配列から条件に合う要素全てを抽出して、新しい配列を生成する奴です

const list = [
    {
        name: "Barry Allen",
        team: "Team Flash",
    },
    {
        name: "Oliver Queen",
        team: "Team Arrow",
    },
    {
        name: "John Diggle",
        team: "Team Arrow",
    },
    {
        name: "Caitlin Snow",
        team: "Team Flash",
    },
    {
        name: "Cisco Ramon",
        team: "Team Flash",
    },
    {
        name: "Felicity Smoak",
        team: "Team Arrow",
    },
];

const filterResult = list.filter((object) => object.team === "Team Flash");

const filterAlternative = list.reduce((previousValue, currentValue) => {
    if (currentValue.team === "Team Flash") {
        previousValue.push(currentValue);
    }
    return previousValue;
}, []);

map

対象の配列から新しい配列を生成できるやつです

うまい例が思いつきませんでした・・・悔しい

const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

const mapResult = array.map((value) => value * 2);

const mapAlternative = array.reduce((previousValue, currentValue) => {
    previousValue.push(currentValue * 2);
    return previousValue;
}, []);

第三、第四引数の使い道

お気付きかと思います

これまでの例は全て第一、第二引数しか使ってませんね

じゃあ第三、第四引数はどう使うの?と思うかもしれません

正直いまいち使い道を見つけたことがあまりありません・・・

ただ、各ループでの previousValue と currentValue 以外の要素を使う事が出来るんじゃないかと思います

const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

const result = array.reduce((previousValue, currentValue, currentIndex, originalArray) => {
    const nextIndex = currentIndex + 1;
    if (originalArray[nextIndex]) {
        previousValue.push(String(currentValue) + String(originalArray[nextIndex]));
    }
    return previousValue;
}, []);
// ["12", "23", "34", "45", "56", "67", "78", "89", "910"]

これまたうまい例が思いつかなかったので普通に数字だけの配列です

originalArray と currentIndex を利用して現在のループの次の要素を取得しています

何かうまい使い道があれば是非教えて欲しいです

ちなみに originalArray は元の array と同一の参照になるので、下手に操作するとぶっ壊れます(下記参照)

基本的に originalArray に対して手を加えることはしない方が良さそうです

const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

const result = array.reduce((previousValue, currentValue, currentIndex, originalArray) => {
        originalArray[currentIndex] = 100
        previousValue.push(currentValue)
        return previousValue
}, []);
console.log(array) // [100, 100, 100, 100, 100, 100, 100, 100, 100, 100]
console.log(result) // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

後書き

reduce 関数はただ計算結果を出力するだけでなく、ループ間で操作した配列やオブジェクトを返却する使い勝手の良い関数です

ちゃんと理解して使えばかなり便利(ゴリ押しも効く)なので

要法要領を守って正しく使用し、より良い配列操作ライフを送ってください

また、DC ドラマシリーズはネトフリやアマプラで見れるので是非みてください

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JSエコシステムぶらり探訪(5): CommonJSモジュールバンドラー

←前 目次

モジュールバンドラーとは

前3回までで説明したNode.jsのモジュールシステムとnpmのパッケージシステムは、再利用可能なJavaScriptプログラムを作り、配布し、利用する仕組みとして非常によくできたものでした。そのため、このNode.jsのモジュールシステムをブラウザでも利用したいという需要が生じることになりました。正確には以下の2つの需要がありました。

  • Node.js用に書いたプログラムをブラウザでも再利用したい。
  • 純粋にブラウザ向けのJavaScriptコードでも、モジュールシステムやパッケージシステムを使いたい。

これらの願いを同時に叶えるべく、Browserifyをはじめとしたモジュールバンドラーというツールが開発されました。モジュールバンドラーは複数のJavaScriptモジュールを結合し、ひとつのJavaScriptファイルを生成します。

モジュールバンドラーの機能を持つツールは一つではなく、比較的有名なものでもBrowserify, Webpack, rollup.js, Parcel, FuseBox, esbuild など多くの実装がありますが、本稿では現在最もメジャーと思われる多機能バンドラー Webpack を代表例として扱います。

Webpackでバンドルしてみる

CommonJS modulesの説明に使った例を再掲します。

src/main.js
var util = require('./util');
console.log(util.square(2)); // => 4
console.log(util.private_value); // => undefined
src/util.js
var private_value = 42;
// exports.square = ... でもよい
module.exports.square = function(x) {
  return x * x;
};

これをWebpackでバンドリングしてみます。

package.json
{
  "scripts": {
    "build": "webpack --mode none --entry ./src/main.js"
  },
  "devDependencies": {
    "webpack": "^5.4.0",
    "webpack-cli": "^4.2.0"
  }
}
npm install
npm run build

これで dist/main.js が生成されます。多くの場合はこれを <script> から呼び出しますが、今回はブラウザの機能を使っていないので、そのままNode.jsで動かすことができます。

node dist/main.js

CommonJSモジュールバンドラーの制約

CommonJSモジュールバンドラーは、あらゆるCommonJSモジュールに対応しているわけではありません。原則として、以下のように定数インポートのみを行っているモジュールのみがバンドルできます。

  • require は必ず関数呼び出しの形で出現する。 require(...) はOKだが f(require)let x = require のような形は駄目。
  • require(...) の引数は必ず文字列リテラルである。require("./hoge") はOKだが require(addExtension("./hoge")) は駄目。

ただし、モジュールバンドラーによっては上記を満たさないコードも何とかしてバンドルできる場合があります (例: Webpackのrequire context)。

また、モジュールバンドルという機能自体はNode.jsとブラウザの環境差異を吸収しない1ため、環境差異については別途考慮する必要があります。本稿ではモジュールバンドル自体の性質のみを扱います。

条件つきrequire

この定数インポート条件は冠頭形モジュールよりも制約が緩やかで、条件つき requireを行うことができます。たとえばReactのエントリポイントは以下のように記述されています。

index.js
'use strict';

if (process.env.NODE_ENV === 'production') {
  module.exports = require('./cjs/react.production.min.js');
} else {
  module.exports = require('./cjs/react.development.js');
}

これは冠頭形モジュールの条件は満たしていませんが、定数インポートの条件は満たしています。

CommonJSバンドリングの実現方法

上記のように、CommonJSバンドリングでは条件つきrequireを扱う必要があります。また、Node.jsには「同一モジュールは1度しか評価されない」という挙動があり、一部のライブラリはこの性質に依存しているため、この挙動の再現もほぼ必須です (パフォーマンス上も重要)。

そのため、CommonJSのバンドルは原則として「Node.jsの require の挙動を実行時に再現する」というアプローチになります。 2

  1. バンドル時には以下を行います。
    • 全ての require 呼び出しを再帰的に収集し、収録するべきモジュールの一覧を確定する。 (このために定数インポートである必要がある)
    • モジュールにIDを振る (絶対パス3や番号などが使われる)
    • 全ての require 呼び出しを相対パスからIDに置き換える
    • モジュールのソースコードを、関数のリスト (オブジェクト) として1つのJavaScriptにまとめる。
  2. 実行時には以下を行います。
    • 読み込み済みモジュールを管理するためのオブジェクトを用意する。
    • バンドル済みのモジュール (関数) を実行する require 関数を作る。
    • require を使って、最初のモジュールを読み込む。

例として、以下のようなプログラムを考えます。

src/index.js
console.log("Loading: index.js");
require("./utils/module2");
require("./utils/module3");
console.log("Loaded: index.js");
src/utils/module1.js
console.log("Loading: module1.js");
console.log("Loaded: module1.js");
src/utils/module2.js
console.log("Loading: module2.js");
require("./module1");
console.log("Loaded: module2.js");
src/utils/module3.js
console.log("Loading: module3.js");
require("./module1");
console.log("Loaded: module3.js");

これをバンドルすると、以下のようなJavaScriptになります。 (Webpackの出力を参考に手書きし、説明を付与したものです)

bundled.js
// IIFEでスコープを作って実行
(function () {
  // 必要なモジュールのソースがそのまま入ったオブジェクト。
  // この例ではCWDからの相対パスをそのままモジュールIDとして使っている
  var moduleDefs = {
    // 渡されるexports, require, moduleはNode.jsで使われているものと互換
    "src/index.js": function(exports, require, module) {
      console.log("Loading: index.js");
      // この部分がモジュール相対ではなく、CWD相対に変換されていることがポイント!
      require("src/utils/module2.js");
      require("src/utils/module3.js");
      console.log("Loaded: index.js");
    },
    "src/utils/module1.js": function(exports, require, module) {
      console.log("Loading: module1.js");
      console.log("Loaded: module1.js");
    },
    "src/utils/module2.js": function(exports, require, module) {
      console.log("Loading: module2.js");
      require("src/utils/module1.js");
      console.log("Loaded: module2.js");
    },
    "src/utils/module3.js": function(exports, require, module) {
      console.log("Loading: module3.js");
      require("src/utils/module1.js");
      console.log("Loaded: module3.js");
    },
  };

  // ロード済みモジュールの一覧を入れるオブジェクト
  var modules = {};
  // Node.jsのrequireのかわりに使う関数。
  // ここではわかりやすさのためにpathという変数名にしているが、
  // Node.jsのrequireと異なり、モジュールIDなら何でもよい。
  // (あらかじめバンドラーによってrequire()の呼び出しが変換されているため)
  function require(path) {
    // 読み込み済み、または読み込み中 (循環参照) のときはキャッシュされたモジュールを返す
    if (modules[path]) return modules[path].exports;

    // Node.js互換のCommonJS呼び出しロジック
    var module = modules[path] = { exports: {} };
    moduleDefs[path](module.exports, require, module);

    return module.exports;
  }

  // エントリポイントの読み込みを開始
  require("src/index.js");
})();

まとめ

  • Node.jsのモジュールシステムをブラウザでも使うための仕組みとして、モジュールバンドラーが生まれた。
  • CommonJSモジュールバンドラーは、ビルド時にはファイルの収集と最低限の変換だけ行い、実行時にNode.jsのインポート処理を模倣するという方法で実装されている。

本稿で説明したものはCommonJSモジュールバンドラーの中核となる機能にすぎません。本シリーズの後半で、高機能アセットパイプラインとしてのWebpackを詳しく見ていきますが、その前にCommonJS以外のモジュールシステムに触れる予定です。

←前 目次


  1. 実際にはWebpackのnode shimのように、そういった機能が同梱されているものも多い 

  2. モジュール結合という形で、より積極的な変換が可能な場合もある。 

  3. 正確には絶対パスよりもCWDからの相対パスなどが使われることが多いと思われます。重要なのは、インポート元のモジュールからの相対ではないということです。 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ハッカーっぽいやつ作ってみた ~ コーディングを記録して動画風に再生する

はじめに

いつもの通り作りっぱなしなので、バグってるかもしれません。

デモ用

See the Pen CodingDemo by kob58im (@kob58im) on CodePen.

※デモのサンプルコードがミスっております。1

配列作成用

CodePen上で開いたほうが使いやすいです。(リロードすれば消去できます())

保存機能とかは無いので、ガチなコーディングはちゃんとしたエディタなりでやってくださいまし。

See the Pen CodeRecorder by kob58im (@kob58im) on CodePen.

textareaに入力された文字列について、更新イベントの前後の差分を強引に抽出して、編集履歴を出力するようにしてます。

お試し用

とりあえずC#とJavaScriptを指定してあります。

See the Pen CodePlayer - for - C# by kob58im (@kob58im) on CodePen.

参考 - highlight.js

参考 - 作成時にお世話になったところ


  1. Mainのところ、staticが抜けている。 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[初心者向け] 図で覚えるデータ構造:配列 Array編

前置き

データ構造ってたくさんあって覚えるの大変ですよね.僕も勉強した直後はいいんですがある程度日数が経つと忘れてしまい -> また勉強し直す,の繰り返しでした.

その過程のうち効果的だったのが,タイトルにもある通り絵や図で特徴を覚える方法でした.

データ構造ってかなり抽象的な内容だと思います.コンピューターのメモリの中身を実際に見る機会なんて少ないので学んだデータ構造が本当にどのように振る舞っているのかなかなか確認しづらいのが,定着率が低い原因だと思いました.

そこで,この記事では簡単かつ具体的な図で各構造を表す(つもり)ことで大学で情報工学専攻じゃ無い人も,プログラミング初心者でも事前知識無しでデータ構造の特徴を覚えることができるのでは,と期待しつつ,自分へのメモ書きも含めて記事を書きました.

データ構造を学ぶ目的としては完全に構造を暗記するのではなく,それぞれの構造の良いところと欠点を知ることで,自分の開発目的にあった効率の良い構造を選べるようになることだと思います.というわけでこの記事の内容も丸暗記するというよりは,配列ってこんな良いところ,欠点があるよねって話せるようになるキッカケになれば,と思っています.

Big O

各データ構造を比べるには,その性能を定量的に測る仕組みが必要ですよね.データ構造の性能を測るものさしとしてよく使われる考え方がBig Oです.以下にその定義を示しますが少し複雑に感じるかもしれません.

Big O notation is a mathematical notation that describes the limiting behavior of a function when the argument tends towards a particular value or infinity. Big O is a member of a family of notations invented by Paul Bachmann,[1] Edmund Landau,[2] and others, collectively called Bachmann–Landau notation or asymptotic notation.

(wikipedia)

僕はBig Oはデータの要素がたくさん増えたとき,その処理時間(Time Complexiy),とメモリ消費量(Space Complexiy)がどれくらい増えやすいかを表す指標,と考えると理解しやすかったです.以降では時間に関するBig OO()Time,メモリ消費に関してはO()Spaceと表記します.

BigOが大きいほど処理時間やメモリ消費が増えてしまうのでよくない,という感じです.

以下によく使うBig Oの表記とその意味を示しておきます.

  • O(1): どれだけデータが増えても(nが大きくなっても)関係なしに一瞬で動作が終わる状態.超早い!これを目指そう!
  • O(log n): ループ処理などで,各ループで要素の半分を切り捨てることができる状態.要素全てを見なくてもOKなので,かなり良い状態!
  • O(n): データが増えた量だけ(nの増加分だけ)処理が比例して増える状態.普通.

特によく使うのは上の3つです.もっと詳しくBig Oについて知りたい人はBig-O Cheat Sheetなどが参考になると思いますし,Google画像検索をしても良いイメージが掴めるかと思います.

Big O Notation

Big-O Cheat Sheetより

特にO(1), O(log n) が早いことが分かりますね

ArrayのBigOまとめ

復習しやすいように配列,ArrayのBig Oを表にまとめました.この記事を読んだ後にまた見返して下さい.

目標はなぜArrayがこの表の特性を持つのか,そしてどのような場合に使うべきかを説明できるようになることです.

methods time
lookup O(1)
insert O(n)
delete O(n)
push (to the end) O(1) / sometime O(n)
memory use continues
loop every element O(n)

ただしnは配列の要素数を示します

ではなぜこのような特性になるのか詳しく見ていきましょう.

Arrayの構造

memory use

Arrayはメモリを連続に使っていきます.これによって配列を簡単にループできます.でも配列を作成するには連続で空いているメモリを見つける必要がある,とも言えますね.

const myArray = ['apple', 'blueberry', 'chocolate']

array-memory-use.png

lookup: O(1)

何番目の要素か知っている場合はアクセスするのは一瞬です.これは配列がメモリを連続で使う,という縛りがあるからですね.

// lookup: O(1)Time
myArray[2] // chocolate

array-lookup.png

insert to the end (push): O(1)

新しい要素をArrayの最後に追加するのも一瞬です.

Arrayのメモリでの状況をイメージできていれば予想できますね.

// add item to the end (push): O(1)Time
myArray.push('donut')

console.log(myArray) // [ 'apple', 'blueberry', 'chocolate', 'donut' ]

array-push.png

insert: O(n)

要素を配列の真ん中に挿入のは遅いです.挿入する場所以降の要素を一つずらす必要があるからですね.先頭に追加する場合が最悪(全要素をずらす必要がある),最後から2番目への挿入は1つしか要素をずらさずに済みます.

// insert item to the middle or the bigining: O(n)Time
myArray.splice(2, 0, 'cookie')

console.log(myArray) // [ 'apple', 'blueberry', 'cookie', 'chocolate', 'donut' ]

array-insert.png

delete: O(n)

要素を削除するのも挿入と同じように,処理後の配列がメモリを順番に使う状態を保つため幾つかの要素をずらす必要があります.これが原因で遅くなってしまうのですね.

せっかく削除したい要素を見つける(lookup)は早いのに,なんだかもったいない気がしますね.

// delete: O(n)Time
myArray.splice(1, 1) 

console.log(myArray) // [ 'apple', 'cookie', 'chocolate', 'donut' ]

array-delete.png

最後の要素を削除するBig Oはどうなると思いますか?

答えはO(1)Timeです.最後の要素を消した後に配列を調整する必要がないからですね.これもイメージできていれば簡単に導けますね.

後書き

図を作って説明するのって結構大変なので省略しがちですが,図を書くのが大変な抽象的な内容ほど必要なことだと思います,逆に図で説明できないということは理解していない証拠ですね.

もう一度ArrayのBigOまとめの表を見て下さい.Arrayの構造をイメージできていれば左の内容から右のBigOを導けるはずです.ポイントは表を丸暗記するのではなく,構造のイメージを掴み,長所,短所を導き出せることです.

この図で説明する記事が特にプログラミング初学者に役立つことを期待して,次はHash Tableについて記事を書こうと思います.

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[初心者向け] イメージで覚えるデータ構造:配列 Array編

前書き

データ構造ってたくさんあって覚えるの大変ですよね.僕も勉強した直後はいいんですがある程度日数が経つと忘れてしまい -> また勉強し直す,の繰り返しでした.

その過程のうち効果的だったのが,タイトルにもある通り絵や図で特徴を覚える方法でした.

データ構造ってかなり抽象的な内容だと思います.コンピューターのメモリの中身を実際に見る機会なんて少ないので学んだデータ構造が本当にどのように振る舞っているのかなかなか確認しづらいのが,定着率が低い原因だと思いました.

そこで,この記事では簡単かつ具体的な図で各構造を表す(つもり)ことで大学で情報工学専攻じゃ無い人も,プログラミング初心者でも事前知識無しでデータ構造の特徴を覚えることができるのでは,と期待しつつ,自分へのメモ書きも含めて記事を書きました.

データ構造を学ぶ目的としては完全に構造を暗記するのではなく,それぞれの構造の良いところと欠点を知ることで,自分の開発目的にあった効率の良い構造を選べるようになることだと思います.というわけでこの記事の内容も丸暗記するというよりは,配列ってこんな良いところ,欠点があるよねって話せるようになるキッカケになれば,と思っています.

Big O

各データ構造を比べるには,その性能を定量的に測る仕組みが必要ですよね.データ構造の性能を測るものさしとしてよく使われる考え方がBig Oです.以下にその定義を示しますが少し複雑に感じるかもしれません.

Big O notation is a mathematical notation that describes the limiting behavior of a function when the argument tends towards a particular value or infinity. Big O is a member of a family of notations invented by Paul Bachmann,[1] Edmund Landau,[2] and others, collectively called Bachmann–Landau notation or asymptotic notation.

(wikipedia)

僕はBig Oはデータの要素がたくさん増えたとき,その処理時間(Time Complexiy),とメモリ消費量(Space Complexiy)がどれくらい増えやすいかを表す指標,と考えると理解しやすかったです.以降では時間に関するBig OO()Time,メモリ消費に関してはO()Spaceと表記します.

BigOが大きいほど処理時間やメモリ消費が増えてしまうのでよくない,という感じです.

以下によく使うBig Oの表記とその意味を示しておきます.

  • O(1): どれだけデータが増えても(nが大きくなっても)関係なしに一瞬で動作が終わる状態.超早い!これを目指そう!
  • O(log n): ループ処理などで,各ループで要素の半分を切り捨てることができる状態.要素全てを見なくてもOKなので,かなり良い状態!
  • O(n): データが増えた量だけ(nの増加分だけ)処理が比例して増える状態.普通.

特によく使うのは上の3つです.もっと詳しくBig Oについて知りたい人はBig-O Cheat Sheetなどが参考になると思いますし,Google画像検索をしても良いイメージが掴めるかと思います.

Big O Notation

Big-O Cheat Sheetより

特にO(1), O(log n) が早いことが分かりますね

ArrayのBigOまとめ

復習しやすいように配列,ArrayのBig Oを表にまとめました.この記事を読んだ後にまた見返して下さい.

目標はなぜArrayがこの表の特性を持つのか,そしてどのような場合に使うべきかを説明できるようになることです.

methods time
lookup O(1)
insert O(n)
delete O(n)
push (to the end) O(1) / sometime O(n)
memory use continues
loop every element O(n)

ただしnは配列の要素数を示します

ではなぜこのような特性になるのか詳しく見ていきましょう.

Arrayの構造

memory use

Arrayはメモリを連続に使っていきます.これによって配列を簡単にループできます.でも配列を作成するには連続で空いているメモリを見つける必要がある,とも言えますね.

const myArray = ['apple', 'blueberry', 'chocolate']

array-memory-use.png

lookup: O(1)

何番目の要素か知っている場合はアクセスするのは一瞬です.これは配列がメモリを連続で使う,という縛りがあるからですね.

// lookup: O(1)Time
myArray[2] // chocolate

array-lookup.png

insert to the end (push): O(1)

新しい要素をArrayの最後に追加するのも一瞬です.

Arrayのメモリでの状況をイメージできていれば予想できますね.

// add item to the end (push): O(1)Time
myArray.push('donut')

console.log(myArray) // [ 'apple', 'blueberry', 'chocolate', 'donut' ]

array-push.png

insert: O(n)

要素を配列の真ん中に挿入のは遅いです.挿入する場所以降の要素を一つずらす必要があるからですね.先頭に追加する場合が最悪(全要素をずらす必要がある),最後から2番目への挿入は1つしか要素をずらさずに済みます.

// insert item to the middle or the bigining: O(n)Time
myArray.splice(2, 0, 'cookie')

console.log(myArray) // [ 'apple', 'blueberry', 'cookie', 'chocolate', 'donut' ]

array-insert.png

delete: O(n)

要素を削除するのも挿入と同じように,処理後の配列がメモリを順番に使う状態を保つため幾つかの要素をずらす必要があります.これが原因で遅くなってしまうのですね.

せっかく削除したい要素を見つける(lookup)は早いのに,なんだかもったいない気がしますね.

// delete: O(n)Time
myArray.splice(1, 1) 

console.log(myArray) // [ 'apple', 'cookie', 'chocolate', 'donut' ]

array-delete.png

最後の要素を削除するBig Oはどうなると思いますか?

答えはO(1)Timeです.最後の要素を消した後に配列を調整する必要がないからですね.これもイメージできていれば簡単に導けますね.

後書き

図を作って説明するのって結構大変なので省略しがちですが,図を書くのが大変な抽象的な内容ほど必要なことだと思います,逆に図で説明できないということは理解していない証拠ですね.

もう一度ArrayのBigOまとめの表を見て下さい.Arrayの構造をイメージできていれば左の内容から右のBigOを導けるはずです.ポイントは表を丸暗記するのではなく,構造のイメージを掴み,長所,短所を導き出せることです.

この図で説明する記事が特にプログラミング初学者に役立つことを期待して,次はHash Tableについて記事を書こうと思います.

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript】日時の表示

プログラミング勉強日記

2020年11月8日
日時表示の方法をコードでまとめる。

コード

script.js
//データオブジェクトの初期化
const now = new Date();

//現在の年を取得
const year = now.getFullYear();

//現在の月を取得(月は0-11で表す)
const month = now.getMonth();

//現在の日付を取得
const date = now.getDate();

//現在の時間を取得
const hour = now.getHours();

//現在の分を取得
const min = now.getMinutes();

//『年/月/日 時間:分』の形で文字列を定数outputに代入
const output = `${year + 1900}/${month + 1 }/${date} ${hour}:${min}`;

//id属性がtimeのコンテンツをoutputに書き換える
document.getElementById('time').textContent = output;

注意点

  • 日時を取得するときのメソッドはほとんどget〜()だが、年を取得するときだけ、getFullyear()で取得。
  • getyear()を調べたところ、地方時に基づき、与えられた日付の「年」を表す数値から1900を引いたものを返します。とMDNに記載されており、現在は非推奨のメソッドであるとわかった。

  • また、getMonth()メソッドは、0~11までの整数で値を返すため、実際の月を表示したい時は、 +1する必要がある。0は1月、1は2月、11は12月に対応している。

参考資料

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初学者向け JavaScript 問題集

JavasSriptの問題作ってみました

わかりずらい可能性大?‍♂️

問題1

初期値としてJapaneseを設定const word = wordFactory('Japanese');
createメソッドを実行すると以下のようにコンソールに出力されます。

word.create('English');
出力結果 "Japanese + English = Japanglish"

以上のようなメソッドを持ったオブジェクトをクロージャーを用いて作成してください。

実行したいときはindex.htmlを作成してください

index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script src="main.js"></script>
</body>
</html>

解答

main.js
function wordFactory(some) {
    return {
        create: function(target) {
            const newWord = 'Japanglish';
            console.log(some + ' + ' + target + ' = ' + newWord);
        }
    }
}

const word = wordFactory('Japanese');
word.create('English')

問題2

先ほどの発展バージョン
四則演算を行うメソッドを持ったオブジェクトをクロージャーを用いて作成してください。
四則演算を行うメソッド(plus, minus, multiply, divide)を実行すると以下のようにコンソールに出力されます。

const calc = calcFactory(10); 初期値を10として設定
calc.plus(8); 出力結果 "10 + 8 = 18"
calc.minus(3); 出力結果 "18 - 3 = 15"
calc.multiply(5); 出力結果 "15 x 5 = 75"
calc.divide(5); 出力結果 "75 / 5 = 15"

※前に行った計算結果をもとに足し算を行います。

解答

main.js
function calcFactory(val) {
  return {
    plus: function (target) {
      const newVal = val + target;
      console.log(`${val} + ${target} = ${newVal}`);
      val = newVal;
    },
    minus: function (target) {
        const newVal = val - target;
        console.log(`${val} - ${target} = ${newVal}`);
        val = newVal;
      },
    multiply: function(target) {
        const newVal = val * target;
        console.log(`${val} * ${target} = ${newVal}`);
        val = newVal;
    },
    divide: function(target) {
        const newVal = val / target;
        console.log(`${val} / ${target} = ${newVal}`);
        val = newVal
    }
  };
}

const calc = calcFactory(10);
calc.plus(8);
calc.minus(3);
calc.multiply(5);
calc.divide(5);

問題3

次のコードを即時関数で書き直してください

main.js
function a() {
    console.log('kewl');
}

a();

またクロージャーの問題で作成したcalcFactory即時関数で書き直してみてください。

【JavaScript】無名関数とは/即時関数とは

解答

main.js
(function() {
    console.log('kewl');
})();
main.js
const calc = (function(val) {
    return {
      plus: function (target) {
        const newVal = val + target;
        console.log(`${val} + ${target} = ${newVal}`);
        val = newVal;
      },
      minus: function (target) {
          const newVal = val - target;
          console.log(`${val} - ${target} = ${newVal}`);
          val = newVal;
      },
      multiply: function(target) {
          const newVal = val * target;
          console.log(`${val} * ${target} = ${newVal}`);
          val = newVal;
      },
      divide: function(target) {
          const newVal = val / target;
          console.log(`${val} / ${target} = ${newVal}`);
          val = newVal
      }
    }
})(10);


calc.plus(8);
calc.minus(3);
calc.multiply(5);
calc.divide(5);

問題にしてまとめるとすごく頭に残りました!

お付き合いありがとうございました。

参考

[JS]初級者から中級者になるためのJavaScriptメカニズム

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LaravelとjQueryで非同期処理で実装するいいね機能を世界一わかりやすく解説してみたい

やりたいこと

ジャズライブの口コミを投稿できるサイトをチームで制作しています。
今回は、ユーザーの投稿にいいねをできる機能を実装したいです。結構苦労したので、初心者だけど、いや初心者だからこそ日本一わかりやすくメモを残しておきたいと思います。ご指摘などありましたら、ぜひ容赦無くよろしくお願いします。

Laravel 7です。

仕様

  1. ハートボタンとカウンターで構成する
  2. 各投稿が合計いくつのいいねを集めているかが表示される
  3. 下記行動ができるのはログインしているユーザーのみ
  4. いいねボタンが押されたらスタイルをピンクにし、いいねカウントの数字を1増やす
  5. もう一度押されたらスタイルと数字を元に戻す
  6. あるユーザーが1つの投稿に対していいねが押せるのは1回のみ
  7. 自分がいいねした投稿は常にピンクになっている

実装前のプログラムの挙動に関する疑問...

  • どの時点でサーバー側にリクエストを出せばいい?(非同期処理だとしたら、イベントが発生した直後?)
  • 非同期処理だとしたら、何回も押されたらその都度リクエストを出してサーバーと通信するのか??
  • 誰がリクエストを出す?(JavaScriptのEventListener?)
  • どんなリクエストを出せばいい?(review_id, いいね押されたよっていう何らかのサイン...でもどんな??)
  • 投稿が削除されたら、それに付されたいいねもDBから全て削除する(リレーションで設定可能)

こんなこともわからない状態からでもなんとか実装に至りました!

材料

僕はお恥ずかしながら、まずどんな材料が必要なのか?そこから想像するのが難しかったです。
で、結局はこんなのが必要でした↓

材料
View これは誰でもわかりますよね。
CSS いいねされたハートマークは色を変えたいところです。
Frontのスクリプト この辺は僕でも、サイトを動的にするには必要だってわかりました。非同期処理はajaxでjQueryがそれできると知っていたので、jQueryを採用。
DB いいね専用のテーブルを作ります。ある投稿がいくついいねを集めているかカウントするために必要です。今回はEloquentでModelを作成しusersとreviewsと1対多でリレーションする。
migration DBバージョン管理ファイルで、各開発環境でテーブルの自動生成をしてくれます。
Model 今回はEloquentを利用します。ModelはDBテーブルの内容を定義したり操作するクラスです。
Route ルートも要るんですね。ページが遷移しない機能は初めてで、ページが遷移しなくてもルートって要るんだ!と知りました...。
Controller コントローラも要るんですね。でも確かにコントローラなきゃ誰がDBと通信するの?て話ですよね。

では、実際にどう料理するか見てきましょう!

実装!

DB データベース設計

テーブル構造(必要なフィールドのみ抜粋)

users
    id
    created_at, updated_at
reviews
    id
    created_at, updated_at
    user_id
likes
    id
    created_at, updated_at
    user_id
    review_id

usersとreviewsが既に作成済という前提で、likesテーブルとリレーションを作っていきましょう。
ターミナルで下記コマンドで、モデルを生成します。-mもしくは--migrationオプションでマイグレーションファイルも同時に作成できます。

$php artisan make:model Like -m

Migration マイグレーション

DBのバージョン管理機能、マイグレーションファイルで、先のテーブル構造を作っていきます。

database/migrations/yyyy_mm_dd_xxxxxx_create_likes_table.php
class CreateLikesTable extends Migration
{
    public function up()
    {
        Schema::create('likes', function (Blueprint $table) {
            $table->id(); //bigIncrements('id')と同じ
            $table->timestamps(); //s複数形でcreated_atとupdated_atを生成

            $table->foreignId('user_id') //usersテーブルの外部キー設定
                ->constrained() //userテーブルのidカラムを参照するconstrainedメソッド
                ->onDelete('cascade'); //削除時のオプション

            $table->foreignId('review_id') //同じことをreviewsテーブルとも
                ->constrained()
                ->onDelete('cascade');
        });
    }

マイグレーションファイルが準備できたら、マイグレートを実行します↓

terminal
$php artisan migrate

Model モデル

app/Like.php
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Like extends Model
{
    public function user()
    {   //usersテーブルとのリレーションを定義するuserメソッド
        return $this->belongsTo(User::class);
    }

    public function review()
    {   //reviewsテーブルとのリレーションを定義するreviewメソッド
        return $this->belongsTo(Review::class);
    }
}

  • belongsToの中身User::class。::スコープ定義演算子。static、定数、オーバーライドされたクラスのプロパティやメソッドにアクセスできる。::classはclassキーワードと呼ばれ、クラスの完全修飾名を文字列で取得できる。つまり'App\Model名'が返ってくる。つまり'App\Model名'をパラメータにするのと同じことです。(むしろそれが今は標準?)

view ビュー

ビューを作る前に仕様の再確認です。
いいねをできるのはログインユーザーのみ。なので、①ログインユーザーと②ゲストでの条件分岐が必要。
さらに自分が一度いいねした投稿は、いつ来てもピンクなままでないとわからなくなってしまうので、①の中でもいいね済の投稿とそうでない投稿を条件分岐しないとなりません。
まとめると、3つの条件分岐が必要になります→(①-1 ログインユーザーでいいねの押されていない投稿、①-2 ログインユーザーでいいねの既に押された投稿、② ゲストユーザー)

<!-- head内 -->
<meta name="csrf-token" content="{{ csrf_token() }}">

<!-- body内 -->
<!-- 参考:$itemにはReviewControllerから渡した投稿のレコード$itemsをforeachで展開してます -->
@auth
  @if (!App\Like::where('user_id', Auth::user()->id)->where('review_id', $item->id)->first())
    <span class="likes">
        <i class="fas fa-music like-toggle" data-review-id="{{ $item->id }}"></i>
      <span class="like-counter">{{$item->likes_count}}</span>
    </span><!-- /.likes -->
  @else
    <span class="likes">
        <i class="fas fa-music heart like-toggle liked" data-review-id="{{ $item->id }}"></i>
      <span class="like-counter">{{$item->likes_count}}</span>
    </span><!-- /.likes -->
  @endif
@endauth
@guest
  <span class="likes">
      <i class="fas fa-music heart"></i>
    <span class="like-counter">{{$item->likes_count}}</span>
  </span><!-- /.likes -->
@endguest
  • auth, endauth, guest, endguestは認証ディレクティブと呼ばれるbladeの機能で、アクセスしているユーザがログインしているのかゲストかを判別できます。
  • iタグはfont-awesomeです。(音楽サイトなので、ハートではなく♫を使ってます)
  • 巷にはなぜかaタグを使用するコーディング例が多いですが不要です。aタグを入れると余計なページ遷移が起きるので、逆にうざかったのですが、aタグを使うメリットをぜひ教えてください。
  • iタグのdata-の属性はカスタムデータ属性と呼ばれるグローバル属性(HTMLのどのタグにも設定可能な属性)の一種。これを介して、HTMLとJavaScript、jQuery間などでデータをやりとりできます。ここではいいねをする対象の投稿のid(review_id)をjQueryに伝える役割を担います。

CSS

いいねマークにlikedクラスが付いた時のスタイルを適宜設定します。

styles.css
.liked {
  color: pink;
}

jQuery

like.js
$(function () {
  let like = $('.like-toggle'); //like-toggleのついたiタグを取得し代入。
  let likeReviewId; //変数を宣言(なんでここで?)
  like.on('click', function () { //onはイベントハンドラー
    let $this = $(this); //this=イベントの発火した要素=iタグを代入
    likeReviewId = $this.data('review-id'); //iタグに仕込んだdata-review-idの値を取得
    //ajax処理スタート
    $.ajax({
      headers: { //HTTPヘッダ情報をヘッダ名と値のマップで記述
        'X-CSRF-TOKEN' : $('meta[name="csrf-token"]').attr('content')
      },  //↑name属性がcsrf-tokenのmetaタグのcontent属性の値を取得
      url: '/like', //通信先アドレスで、このURLをあとでルートで設定します
      method: 'POST', //HTTPメソッドの種別を指定します。1.9.0以前の場合はtype:を使用。
      data: { //サーバーに送信するデータ
        'review_id': likeReviewId //いいねされた投稿のidを送る
      },
    })
    //通信成功した時の処理
    .done(function (data) {
      $this.toggleClass('liked'); //likedクラスのON/OFF切り替え。
      $this.next('.like-counter').html(data.review_likes_count);
    })
    //通信失敗した時の処理
    .fail(function () {
      console.log('fail'); 
    });
  });
  });
  • $()はjQueryのセレクターの書き方。$はjQueryの略です。
  • thisは変数の一種で、特殊な変数。プログラムが自動的に値を代入するもの。今回はイベントが発火した変数like=iタグが代入されています。
  • なぜ$this$が付くのか?jQueryにおいて、変数を宣言する際、$はjQueryオブジェクト(=$()でセレクトした要素)を入れるための変数宣言で、 $は必須ではないが、通常の変数と区別するのに通常使います。
  • .onはイベントハンドラー。第一パラメータにイベントの種類、第二パラメータにハンドラとして無名関数を取っています。
  • .dataはjQueryのメソッド。HTML内に仕込んだカスタムdata属性の値を取得することができます。ここでは投稿のid(review_id)を受け取ります。取得したい要素.data('カスタムdata属性の名前')という文法になります。
  • $.ajax()は、非同期処理を行うイベントハンドラ。$.はこれまたjQueryのこと。中のパラメータはコード内の説明ご参照。ここの大きな構文は、$.ajax().done().fail()です。通信に成功したら、doneメソッドを、失敗したらfailメソッドを実行します。
  • attr()は、attributeの略で日本語訳すると「属性」です。つまり、なんてことはない、指定された属性の値を取ってくるメソッドです。(日本人プログラマの不利なところは一回英単語の意味を知らないといけないとこだなと最近思ってます)
  • .next()は、セレクター。Sibling(兄弟)の後ろの要素を全て、つまり全ての弟を返します。その中から特定の要素を指定する場合は、パラメータで指定します。
  • obj.html(htmlString)は、obj(=英語でいうところの目的語)にhtmlStringをセットします。.done()のパラメータdataにはコントローラからreview_likes_countという名前の「新規いいね後の総いいね数」が(←あとでコントローラで見ていきましょう)、{review_likes_count : 1}というJSONの形で渡ってきます。それをdata.review_likes_countとい文法でプロパティにアクセスします。JavaScriptの記法では「変数名.プロパティ名(=key)」でそのオブジェクトの対応する値が取得できます。この.をドット演算子と呼びます。このサイトがわかりやすいです。
  • .fail()には、処理が失敗したときの指示をコールバック関数で書いておきます。コールバック関数とは、他の関数(ここではfail)に引数として渡される関数です。ここでは、単純にconsoleに'fail'という文字列をログを出力するというだけの実装にしています。

Route ルート

routes/web.php
Route::post('/like', 'ReviewController@like')->name('reviews.like');

Ajaxで指定したurlと整合性を取ります。

Controller コントローラ

LikeControllerを作った方が良かったのかもしれませんが、私は投稿を制御するReviewController.phpに作りました。

app/Http/ReviewContoller
public function like(Request $request)
{
    $user_id = Auth::user()->id; //1.ログインユーザーのid取得
    $review_id = $request->review_id; //2.投稿idの取得
    $already_liked = Like::where('user_id', $user_id)->where('review_id', $review_id)->first(); //3.

    if (!$already_liked) { //もしこのユーザーがこの投稿にまだいいねしてなかったら
        $like = new Like; //4.Likeクラスのインスタンスを作成
        $like->review_id = $review_id; //Likeインスタンスにreview_id,user_idをセット
        $like->user_id = $user_id;
        $like->save();
    } else { //もしこのユーザーがこの投稿に既にいいねしてたらdelete
        Like::where('review_id', $review_id)->where('user_id', $user_id)->delete();
    }
    //5.この投稿の最新の総いいね数を取得
    $review_likes_count = Review::withCount('likes')->findOrFail($review_id)->likes_count;
    $param = [
        'review_likes_count' => $review_likes_count,
    ];
    return response()->json($param); //6.JSONデータをjQueryに返す
}
  1. Authクラスに対してuserメソッドでログインしているユーザーのモデルインスタンスを返す。
  2. 投稿のidが、ビューのカスタムdata属性→jQuery Ajax経由で$requestで渡ってきているので、それをキャッチします。
  3. このユーザーがこの投稿に既にいいねをしていれば、そのlikeレコードを取得します。
  4. モデルから新しいレコードをDBに挿入するには、まず新しいインスタンス(記入用紙)を準備して、save()メソッドで実行します。いいねの新規レコードをDBに挿入する準備として、newキーワードでLikeクラスのインスタンスを生成しておきます。参考→公式ドキュメント「クラスの基礎」
  5. 投稿テーブルを管理するReviewモデルに対してwithCountメソッドを使用することでリレーションされている別テーブルの数をカウントすることができます。今回はパラメータにいいねテーブルとのリレーション名likesを渡すことで、いいねの数を数えます。投稿idをキーにしています。likes_countはwithCountメソッドとセットの関係にあります。Laravelでは、withCount('hoge')とすると自動的にhoge_countというフィールドが生成されるのです。参考→関連するモデルのカウント
  6. jQueryの.done(data)に$paramという変数名で最新の総いいね数を返します。

実装は以上になります。

エラーと対処の例

私が直面したエラーを参考にメモしておきます。

responseJSON: {message: "CSRF token mismatch.", responseText: "{↵ "message": "CSRF token mismatch.",

csrftokenmismatch.png
<meta name="csrf-token" content="{{ csrf_token() }}">をビューのheadタグに追記して解決

responseJSON: {message: "Unauthenticated."} status: 401 statusText: "Unauthorized"

unauthenticated.png
原因:ゲストのまま一生懸命いいねボタン押していたから(汗)

responseJSON: message:"Property [likes_count] does not exist on the Eloquent builder instance."

スクリーンショット 2020-10-26 21.12.13.png
→なんだったっけ...すみません。忘れました。思い出したら追記します。

あとがき

自分が実装したことをきちんと理解したく、あやふやなところを全て言語化しました。もしご指摘などあれば、ぜひコメントください。

Thanks to:
【jQuery】$(this)の意味&使い方について

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

小規模なテキストエディタを実装しながらプログラミングとUnit testを学んでみよう

kilo-editorとは

これはなに

これは、kiloをベースにして
非常に小規模なテキストエディタを実装しながら、個人的にテキストエディタの実装を学んだり、
jestを使ったテスト駆動開発の勉強のために作りました。

また、本家はc言語で開発されていましたが、私が慣れているnodejsで実装しました。

なんでこんなものをつくったの

きっかけとしては Build Your Own Text Editorというサイトを
たまたま見つけ
プログラミングを始めようと思ってJavaScriptの本を読んだり、
私のようにテスト駆動開発を学ぼうとしている人には十分に理解できるサイズでかつ面白い題材だと思ったからです。
みなさんも普段テキストエディタを使ってプログラミングしていると思いますが、
実際に実装してみると裏でこんな苦労があるのかと発見もあり面白いですよ

demo

インストール方法

npm -g i kilo-editor

使い方は?

もともとのkilo.cとは少しアレンジを加えています
少しでもvimっぽい動きをさせようと頑張りましたが、実装できたのは下記です。

画面移動

h j k l

vimのキーバインドに似せようと実現しました。
- h: 左
- j: 下
- k: 上
- l: 右

0 ^ $

ホームやエンドボタンでも同じ挙動になりますが、行の先頭/行の終わりにジャンプします。
hjklで移動するよりも早く移動できます。

G

ファイルの最後に直接ジャンプ

gg

ファイルの先頭に直接ジャンプ

編集モード

kilo-editorでは、カーソル移動用の「NORMAL」モードと
テキストを追加したり変更したりする「INSERT」モードがあります。

i/a

INSERTモード(カーソルで挿入/カーソルの後に追加)に遷移します、Esc キーを押すとINSERTモードを終了し、NORMALモードに戻ります。

o/O

ラインブレイク(現在のラインより下/現在のラインより上)挿入します。

dd

行を削除します。行を削除して新しい場所に移動し、後述する「p」と組み合わせてで貼り付けができます。

yy

行をコピーします。"y "は "yank(やんちゃ)"という意味らしいですよ。

p

カーソルの行に最後に削除またはコピーしたテキストを貼り付けます。

u

一つ元に戻します undoの意味

SEARCH モード

"/"を押すと検索モードになります。
最初に見つかった単語に移動します。
次の検索結果に移動するには、<-または->を使って移動できます

COMMAND

コマンドモードに遷移するには ":" を使用します。
- w -> 保存
- q -> 保存せずに終了
- wq -> 保存して終了

テストの走らせ方

npm t

サポート環境

os

下記はkilo-editorの開発者用の情報です

テスト情報

Coverage report

class document

Kilo class

最後に

本来なら本家同様に
チュートリアルにしてもいいかなと思ったのですが
結構これまでで、体力を使い切ってしまい、とても手が出せませんでした。
こんな素晴らしいコンテンツを提供してくれたBuild Your Own Text Editor はとてもありがたいですね
でわでわ

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

0から始めるPromise(javascript)

Promiseとかthenが何をやっているのか全くわからない人向けの記事。
イラストを使って説明しているのでこれを読めば「こういう事やっているのか!」と理解できるレベルになると思います。
本題に入る前に同期処理と非同期処理の違いについて説明します。

同期処理ついて

超ざっくり説明すると上から順番に処理してくれるのが同期処理
例えば来店から完食までをconsole.logで表す同期処理は下記になる

同期処理の例

console.log("来店");
console.log("ラーメン注文");
console.log("待機中");
console.log("ラーメンお待ち!");
console.log("食事中");
console.log("御馳走様でした");

非同期処理ついて

例えばタイムラグを発生して来店から完食までをconsole.log表したいとする。
今回の例は下記のように表示させたいとする。

・画面読み込みから3秒後に「来店」
・上記の2秒後に「ラーメン注文」
・上記の1秒後に「待機中」
・上記の6秒後に「ラーメンお待ち!」
・上記の1秒後に「食事中」
・上記の5秒後に「ご馳走様でした!」

function action(action, time){
    setTimeout(() => {
      console.log(action);
    },time);
}

action("来店", 3000);
action("ラーメン注文", 2000);
action("待機中", 1000);
action("ラーメンお待ち!", 6000);
action("食事中", 1000);
action("御馳走様でした", 5000);

順番バラバラやん・・・
スクリーンショット 2020-11-08 15.15.54(3).png

なぜバラバラ表示に?

なぜこんな事が起こるかというとsetTimeout関数が非同期処理、つまり前の処理の完了を待たずに、次の処理を実行してしまうのだ。
スクリーンショット 2020-11-08 15.28.38(2).png

どうすればいいの?

最初のsetTimeout()関数の中に次に処理したい関数を書けば順番に処理される。
ただ、これだと無駄に長すぎる・・・これが俗にいうコールバック地獄です。

action("来店", 3000);

function action(come, time){
    setTimeout(() => {
      console.log(come); // 来店
      action2("ラーメン注文", 2000);
    },time);
  function action2(order, time){
    setTimeout(() => {
      console.log(order); // ラーメン注文
      action3("待機中", 1000);
    },time);
  }
  function action3(wait, time){
    setTimeout(() => {
      console.log(wait);// 待機中
      action4("ラーメンお待ち!", 5000);
    },time);
  }
  function action4(offer, time){
    setTimeout(() => {
      console.log(offer);// ラーメンお待ち!
      action5("食事中", 1000);
    },time);
  }
  function action5(meal, time){
    setTimeout(() => {
      console.log(meal); // 食事中
      action6("御馳走様でした", 4000);
    },time);
  }
  function action6(treat, time){
    setTimeout(() => {
      console.log(treat);// 御馳走様でした!
    },time);
  }
}

ここで登場するのがPromise

Promiseは非同期処理を操作できる。〇〇関数の処理が終わったら△△関数実行してね!という感じ。
実行した関数に成功した場合の処理と失敗した時の処理を書くことができます。
Promiseをnewでインスタンス(設計図)を作ります。
作ったインスタンス(設計図)はそのままreturnさせて「成功(失敗)したかどうか」を関数に返します。
返した関数はthenの中で
(
第一引数:成功した時の処理,
第二引数:失敗した時の処理
)

で実行されます。
言葉だけじゃ難しいと思うので実際に描いてみましょう。

function check(age){
  return new Promise((resolve,reject) => {
    if(age >= 20){
      resolve('成年')
    } else {
      reject('未成年');
    }
  })
}

check(21).then(
  yes => {
    console.log(yes+'。大人だね!');
  },
  no =>{
    console.log(no+'。学生かな?');
  }
)

成功(resolve)した場合のサンプル

スクリーンショット 2020-11-08 15.52.20(2).png

スクリーンショット 2020-11-08 16.25.17(3).png

失敗(reject)した場合のサンプル

スクリーンショット 2020-11-08 15.52.40(2).png

スクリーンショット 2020-11-08 16.26.20(3).png

エラー発生のサンプル(catch)

下記のコードは80点未満の場合はエラーを出す処理。
エラーの場合はcatchが読まれ、thenは処理されない。

function check(age){
  return new Promise((resolve,reject) => {
    if(age >= 80){
      resolve(age)
    } else {
      reject(new Error('エラーが発生しました'));
    }
  })
}

check(75).then(
  success => {
    console.log('あなたの点数は'+success+'点です');
  }).catch(
    error => {
    console.log(error);
  }
)

スクリーンショット 2020-11-08 16.06.07(3).png

実演

先程のラーメン店のconsole.logをスマートに書けないのか?その答えは下記の通りです。
最初のaction("来店", 3000)処理が終わらない限り、then以降の処理が実行される事はありません。

function action(action, time){
  return new Promise(resolve => {
    setTimeout(() => {
        console.log(action);
        resolve();
    }, time)
  })
}

action("来店", 3000)
.then(() => action("ラーメン注文", 2000))
.then(() => action("食事中", 1000))
.then(() => action("ラーメンお待ち!", 4000))
.then(() => action("待機中", 1000))
.then(() => action("ご馳走様でした", 5000))

スクリーンショット 2020-11-08 16.55.40(2).png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript: 配列の直積を返すジェネレータを作ってみた

前の記事のついでにやってみた。
多重ループとかの時に便利そう。

// 直積を返すジェネレータの内部再帰関数
const innerProdG = selected => (xs, ...xss) => function*(){
  if (xs === undefined ) {
      yield selected
      return
  }
  for (const x of xs) yield* innerProdG( [...selected, x] )( ...xss )
}()

// 複数の配列をとって直積を返すジェネレータ
const prodG = 
  innerProdG([])

// 使用例:
const a = [0, 1]
const b = [2, 3]
const c = [4, 5]

for(const e of prodG(a, b, c)) console.log(e)
/* 
[ 0, 2, 4 ]
[ 0, 2, 5 ]
[ 0, 3, 4 ]
[ 0, 3, 5 ]
[ 1, 2, 4 ]
[ 1, 2, 5 ]
[ 1, 3, 4 ]
[ 1, 3, 5 ]
 */
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Vue.js】他で並列処理(axios)させていたものをgetするまで待ちたい

はじめに

とりあえず力技で実装しました。
※もっと良い書き方があれば教えてください!

やりたいこと

  • templateを読み込むとき、axiosで値Aを取得
  • 取得したAを画面に表示(つまりAの取得は下記のボタンを押す前から開始していたい)
  • button @clickで発火したとき、Aをstoreに保管

問題

  • @click発火時、Aを取得しているとは限らない

参考コード

vue
<template>
<div>
  <p>{{ a }}</p>
  <button @click="storeAction">登録</button>  
</div>
</template>

<script>
import axios from 'axios'
export default {
  data() {
    return {
      a: ""
    }
  },
  created() {
    axios(param).then(el => {//param: header,urlなどの情報モジュール
      this.a = el
    })
  },
  methods: {
    storeAction() {
      this.$store.dispatch('module/set', this.a)//aをstore.stateにセットする
    }
  },
}
</script>

解決方針

  • axiosが完了するまでdispatchを待たせる
  • setIntervalを利用し、値がAに入っているか、定期的に確認する。

参考コード

vue
    storeAction() {
      //(略)「読み込み中」のモーダルを表示する処理
      const id = setInterval(() => {
        if(!isEmpty(a)) {//aが空ではない場合
          this.$store.dispatch('module/set', this.a)//aをstore.stateにセットする
          // (略)モーダル解除処理
          clearInterval(id)
        }
      }, 500);
    }

課題

値を取得するまで繰り返し処理させるの・・・カッコ悪い・・・
他で並列処理させているものが完了したことを良い感じに教えてくれるものがないか色々ググってみたんだけど、良い方法が見つからなくてこんな力技になりました。
もっと良い書き方、そもそもやりたいことがメソッドで提供されている場合は教えてくださると嬉しいです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

highlight.jsを動的に使ってみた - CodePen

まえがき

最終的にやりたいこと:コーディングの様子を動画っぽく表示したい。コードはシンタックスハイライトしたい。(先は長そう(?))
できたっ → ハッカーっぽいやつ作ってみた ~ コーディングを記録して動画風に再生する - Qiita

今回やったこと:ユーザが入力しているコードにシンタックスハイライトを適用して表示する。(入力用の領域とは別の領域に表示する。)

やってみた

See the Pen DynamicCodeHighlight by kob58im (@kob58im) on CodePen.

version 9.15.10を使用しましたが、現時点(2020.11.8)ではversion 10.1.2が最新のようです。

スタイルを変えたい場合

スタイルを変更したい場合は、参考サイトの2(※バージョンは適宜選択して変更ください)から探して、
image.png
「Copy Link Tag」をクリックして得られたHTMLのタグ(linkタグ)を埋め込む(置き換える)と変更できます。

参考サイト

  1. コードのハイライト表示用 JS ライブラリ highlight.js の使い方 - Qiita
  2. highlight.js - Libraries - cdnjs - The #1 free and open source CDN built to make life easier for developers
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初学者向け Javascript 問題集

Javascriptの問題作ってみました

問題1

以下のコードではエラーが発生します。(ReferenceError: a is not defined)
if文は削除せずにコンソールで"So cooool!"と表示されるようにfn内のコードを変更してください。

main.js
function fn() {
    if(true) {
        let a = 'So cooool!';
    }
    return a;
}

const result = fn();
console.log(result);

実行したいときはindex.htmlを作成してください

index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script src="main.js"></script>
</body>
</html>

解答

main.js
function fn() {
    let a;
    if(true) {
        a = 'So cooool!';
    }
    return a; 
}

const result = fn();
console.log(result);

ブロックスコープの外でも変数を使いたい場合は変数を使用している部分と変数宣言のスコープを同じにしてあげる必要があります。

問題2

各コンソールで期待値を出力してください。

main.js
var name = 'Hikakin';
function fn2() {
    console.log(name); // 期待値->'Hikakin'

    if(true) {
        var name = 'Seikin';
        console.log(name); // 期待値->'Seikin'
    }

    console.log(name); // 期待値->'Hikakin'
}
fn2();

解答

main.js
var name = 'Hikakin';
function fn2() {
    console.log(name); 

    if(true) {
        let name = 'Seikin';
        console.log(name); 
    }

    console.log(name); 
}
fn2();

letを使ってブロックスコープを有効にする。

if文のブロックの中でvarを使って宣言してもブロックスコープは有効にならない。

varを使った場合はこのような記載と変わらない。

main.js
var name = 'Hikakin';
function fn2() {
    console.log(name); 

    var name = 'Seikin';
    console.log(name); 

    console.log(name); 
}
fn2();

問題3

increment関数をブロックスコープとクロージャーを利用して
各コンソールで期待値を出力してください。

main.js
increment(); // 期待値->4
increment(); // 期待値->8
increment(); // 期待値->16
increment(); // 期待値->32

解答

main.js
{
  let num = 2;
  function increment() {
    num *= 2;
    console.log(num);
  }
}

increment(); 
increment(); 
increment(); 
increment(); 

JavaScriptでクロージャ入門。関数はすべてクロージャ?

問題にしてまとめるとすごく頭に残りました!

お付き合いありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初学者向け JavaScript 問題集

JavaScriptの問題作ってみました

問題1

以下のコードではエラーが発生します。(ReferenceError: a is not defined)
if文は削除せずにコンソールで"So cooool!"と表示されるようにfn内のコードを変更してください。

main.js
function fn() {
    if(true) {
        let a = 'So cooool!';
    }
    return a;
}

const result = fn();
console.log(result);

実行したいときはindex.htmlを作成してください

index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script src="main.js"></script>
</body>
</html>

解答

main.js
function fn() {
    let a;
    if(true) {
        a = 'So cooool!';
    }
    return a; 
}

const result = fn();
console.log(result);

ブロックスコープの外でも変数を使いたい場合は変数を使用している部分と変数宣言のスコープを同じにしてあげる必要があります。

問題2

各コンソールで期待値を出力してください。

main.js
var name = 'Hikakin';
function fn2() {
    console.log(name); // 期待値->'Hikakin'

    if(true) {
        var name = 'Seikin';
        console.log(name); // 期待値->'Seikin'
    }

    console.log(name); // 期待値->'Hikakin'
}
fn2();

解答

main.js
var name = 'Hikakin';
function fn2() {
    console.log(name); 

    if(true) {
        let name = 'Seikin';
        console.log(name); 
    }

    console.log(name); 
}
fn2();

letを使ってブロックスコープを有効にする。

if文のブロックの中でvarを使って宣言してもブロックスコープは有効にならない。

varを使った場合はこのような記載と変わらない。

main.js
var name = 'Hikakin';
function fn2() {
    console.log(name); 

    var name = 'Seikin';
    console.log(name); 

    console.log(name); 
}
fn2();

問題3

increment関数をブロックスコープとクロージャーを利用して
各コンソールで期待値を出力してください。

main.js
increment(); // 期待値->4
increment(); // 期待値->8
increment(); // 期待値->16
increment(); // 期待値->32

解答

main.js
{
  let num = 2;
  function increment() {
    num *= 2;
    console.log(num);
  }
}

increment(); 
increment(); 
increment(); 
increment(); 

JavaScriptでクロージャ入門。関数はすべてクロージャ?

問題にしてまとめるとすごく頭に残りました!

お付き合いありがとうございました。

参考

[JS]初級者から中級者になるためのJavaScriptメカニズム

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails(5.0)】 japanMapリンク使用時 js.erbファイル内のパラメータ書き方

初投稿になります。プログラミング始めて4ヶ月の初心者です。よろしくお願いします。

ポートフォリオ作成の際にjapanMapを使用して8地方ごとにイベントを検索できるようにしました。

その際、地方ごとのリンクに任意のパラメータを渡すのに少し苦労したので、自分で確認用として投稿しています。

今回は application.jsファイルにjapanMapのプログラムを記述しました。

以下が自分の書いたコードになります。

application.js
//検索ページ日本地図
$(function(){
  //8地方でリンク作成
  var areaLinks = {
    1:"/user/index?sort=hokkaido",
    2:"/user/index?sort=tohoku",
    3:"/user/index?sort=kanto",
    4:"/user/index?sort=chubu",
    5:"/user/index?sort=kinki",
    6:"/user/index?sort=chugoku_shikoku",
    7:"/user/index?sort=kyusyu_okinawa",
  };

  //8地方エリア指定
  var areas = [
    {code : 1, name: "北海道", color: "#ab86c4", hoverColor: "#dfcceb", prefectures: [1]},
    {code : 2, name: "東北",   color: "#6d93d1", hoverColor: "#91b0e3", prefectures: [2,3,4,5,6,7]},
    {code : 3, name: "関東",   color: "#f5a164", hoverColor: "#f5c09a", prefectures: [8,9,10,11,12,13,14]},
    {code : 4, name: "中部",   color: "#77e077", hoverColor: "#adedad", prefectures: [15,16,17,18,19,20,21,22,23]},
    {code : 5, name: "近畿",   color: "#ffe966", hoverColor: "#fff2a3", prefectures: [24,25,26,27,28,29,30]},
    {code : 6, name: "中国・四国",   color: "#e68ccc", hoverColor: "#f0b9e0", prefectures: [31,32,33,34,35,36,37,38,39]},
    {code : 7, name: "九州・沖縄",   color: "#de6474", hoverColor: "#f29da9", prefectures: [40,41,42,43,44,45,46,47]},
  ];

  //地図表示設定
  $("#map-container").japanMap({
    width: 600,
    areas  : areas,
    selection : "area",
    borderLineWidth: 0.25,
    drawsBoxLine : false,
    movesIslands : true,
    showsAreaName : true,
    font : "MS Mincho",
    fontSize : 13,
    fontColor :"#777",
    fontShadowColor : "white",
    onSelect : function(data){
    location.href = areaLinks[data.area.code];
  };
});

上記のこの部分がリンクになります。

var areaLinks = {
    1:"/user/index?sort=hokkaido",
    2:"/user/index?sort=tohoku",
    3:"/user/index?sort=kanto",
    4:"/user/index?sort=chubu",
    5:"/user/index?sort=kinki",
    6:"/user/index?sort=chugoku_shikoku",
    7:"/user/index?sort=kyusyu_okinawa",
};

html.erbファイルでは下記のように記述するところを

○○.html.erb
<%= link_to '◯◯', ○◯_path(:sort => 'hokkaido') %>

js.erbファイルでのlink_to の書き方がわからなかった為、

○○.js.erb
1:"/user/index?sort=hokkaido"

のように記述しました。こうすることで任意のparams[:sort]のパラメータを渡すことができました。
[?sort=hokkaido] の部分がパラメーターになります。

○○_controller.rb
def index
    if params[:sort] == 'hokkaido'
        @events = Event.where(prefecture_code: "北海道")
        @events = @events.page(params[:page]).per(6).order("id DESC")

上記がコントローラーの一部になります。
[if params[:sort] == 'hokkaido']の記述でパラメーターを区別し表示するイベントを変更しています。

初めての投稿で見にくいところ間違いなどあるかもしれませんが最後まで見ていただきありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

jQueryを使って選択した画像をプレビュー表示させる

はじめに

画像投稿する際、投稿画像選択時に選択した画像(今回は1枚のみ)をプレビュー表示するまでを解説したいと思います。

完成イメージ

imageupload.gif

環境

MacOS 10.15.7
ruby 2.6.5
Ruby on Rails 6.0.0

前提条件

jQueryの導入が完了済みであること。

作業していきましょう!

①画像投稿フォームを作成

はじめに、画像の投稿フォームを作成していきます。
▼フォームの完成イメージ
imageinput 1.png

html.erb
<div class='image'>
  <label class="labelName" for="imageSpace">Photo </label>
  <input  class='imageInput' placeholder='写真を選択してください' type='file'>
</div>
<div class="prevImageBox">
</div>
scss
.image {
  color: #ffffff;
  padding: 10px 16px 8px;
  margin-top: 8px;
  display: flex;
}

.imageInput {
  color: #000000;
  background-color: #fff;
  border-radius: 5px;
  border: 1px solid #ccc;
  margin-left: 10px;
}

上記の記述で出来上がりです。
(フォームの背景の設定については今回省略しております)

htmlの

<div class="prevImageBox">
</div>

の部分については、選択した画像をプレビュー表示させるための要素(箱)になります。

②jQueryで実装していく

まずはjsファイルを作成します。ファイル名はなんでも良いですが今回は「image.js」というファイルを作成して、jQueryの記述をしていきます。

image.js
$(function(){

  // <div class='image'>の直後の兄弟要素(今回の場合は<div class='prevImageBox'>がそれに該当)を取得。
  const prevImage = $('.image').next();

  // プレビュー表示部分のhtml
  function buildHTML(id,image) {
    let html = `<div class="prevImageBox">
                  <div class="upper-box">
                    <img src=${image} alt="preview">
                  </div>
                  <div class="lower-box">
                    <div class="delete-box">
                      <div class="delete-btn" data-delete-id= ${id}>削除</div>
                    </div>
                  </div>
                </div>`
    return html;
  }


  // ===========================================
  // imageInputの値が変更したとき(画像が選択された時)の処理
  // ===========================================
  $(document).on('change', '.imageInput', function() {
    //imageInputのidを取得
    let id = $(this).attr('id');
    //選択したfile(画像)のオブジェクトを取得
    let file = this.files[0];
    // PC内にあるファイルをアプリケーションに非同期で読み込む
    let reader = new FileReader();
    //readAsDataURLで指定したFileオブジェクトを読み込む
    reader.readAsDataURL(file);
    //読み込み時に発火するイベント
    reader.onload = function() {
      // 直前に実行されたイベント(imageファイルの読み込み)を変数imageに代入
      let image = this.result;
      //htmlを作成
      let html = buildHTML(id,image);
      //<div class='prevImageBox>にプレビュー画像を追加
      $(prevImage).append(html);
      //画像が選択された状態(プレビュー画像表示中)だと、画像選択フォームを隠す
      $('.imageInput').hide();
    }
  });


  // ==================================
  //   プレビュー画像を削除する場合の処理
  // ==================================
  $(document).on('click', '.delete-btn', function() {
    let id = $(this).attr('data-delete-id')
    //画像を消去
    $(this).parent().parent().parent().remove();
    //画像選択フォームの中身を空にする
    $('.imageInput').val("");
    //画像選択フォームを表示
    $('.imageInput').show();
  });
});

以上が、jQueryの記述となります。

プレビュー画像部分のCSSも載せておきます。

プレビュー画像部分のcss
.prevImageBox {
  display: block;
  width: 10vw;
  margin: 0 15px 10px 0;
  padding-left: 45px;
  .upper-box {
    height: 100%;
    width: 100%;
    img {
      width: 100%;
      height: 100%;
    }
  }
  .lower-box {
    display: flex;
    text-align: center;
    .delete-box {
      color: #00b0ff;
      width: 100%;
      height: 50px;
      line-height: 50px;
      border: 1px solid #eee;
      background: #f5f5f5;
      display: flex;
      justify-content: center;
      align-items: center;
      cursor: pointer;
      .delete-btn {
        background-color: #f5f5f5;
        line-height: 40px;
        width: 60px;
      }
    }
  }
}

プレビュー画像のサイズについては.prevImageBoxのwidthで調整してみてください。

簡単に解説

4行目で

const prevImage = $('.image').next();

として、「nextメソッド」を使って、imageクラスの兄弟要素にあたるprevImageBoxを取得して、それを変数prevImageに格納しています。

nextメソッドについては以下が参考になります。
http://js.studio-kingdom.com/jquery/traversing/next

6〜19行目の

function buildHTML(id,image) {
    let html = `<div class="prevImageBox">
                  <div class="upper-box">
                    <img src=${image} alt="preview">
                  </div>
                  <div class="lower-box">
                    <div class="delete-box">
                      <div class="delete-btn" data-delete-id= ${id}>削除</div>
                    </div>
                  </div>
                </div>`
    return html;
  }

に関しては、実際に表示させるプレビュー画像部分のhtmlとなります。
構造としては、PrevImageBoxという大枠の中に、upper-boxとlower-boxの2段に分けた構造になります。
upper-boxにはイメージ画像を導入し、lower-boxには画像削除用ボタンを搭載する形となっております。

あとは、
画像を選択する時の処理

// ===========================================
  // imageInputの値が変更したとき(画像が選択された時)の処理
  // ===========================================
  $(document).on('change', '.imageInput', function() {
    //imageInputのidを取得
    let id = $(this).attr('id');
    //選択したfile(画像)のオブジェクトを取得
    let file = this.files[0];
    // PC内にあるファイルをアプリケーションに非同期で読み込む
    let reader = new FileReader();
    //readAsDataURLで指定したFileオブジェクトを読み込む
    reader.readAsDataURL(file);
    //読み込み時に発火するイベント
    reader.onload = function() {
      // 直前に実行されたイベント(imageファイルの読み込み)を変数imageに代入
      let image = this.result;
      //htmlを作成
      let html = buildHTML(id,image);
      //<div class='prevImageBox>にプレビュー画像を追加
      $(prevImage).append(html);
      //画像が選択された状態(プレビュー画像表示中)だと、画像選択フォームを隠す
      $('.imageInput').hide();
    }
  });

プレビュー画像を削除する時の処理

// ==================================
  //   プレビュー画像を削除する場合の処理
  // ==================================
  $(document).on('click', '.delete-btn', function() {
    let id = $(this).attr('data-delete-id')
    //画像を消去
    $(this).parent().parent().parent().remove();
    //画像選択フォームの中身を空にする
    $('.imageInput').val("");
    //画像選択フォームを表示
    $('.imageInput').show();
  });

を分けて記述しております。

最後までご覧いただきありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript製汎用テーブル(第2回:表データ定義)

JavaScript勉強の目的で、小規模のアプリをコーディングしています。
今回はその1つ「汎用テーブル」を制作したので紹介します。
第1回 https://qiita.com/suwanishi77/items/02e2ddeb26f55928ee54

資材は
https://github.com/nnnmu24/generaltable
よりDLしてください。

表データの定義

定義するデータは表データと表オプションが存在します。

サンプルの表データは以下の通りです。

const tableDataSource = [
["SAMPLE01","映像作品リスト"],
["タイトル","ジャンル","鑑賞方法","鑑賞日","評価"],
["text","text","text","date.YYYY年MM月DD日","num"],
["","option01","","","option02"],
["アメリカのヒーローが大集合","アクション","","",""],
["余命少ない恋人と過ごす","恋愛","テレビ","2017年03月05日","3"],
["銀河を支配する帝国を打倒する","SF","DVD購入","2018年01月15日","5"],
["剣と魔法で闘う勇者","ファンタジー","動画配信サイト","2018年09月16日","1"],
["伝説の龍を育てる","ファンタジー","レンタルDVD","2019年02月05日","3"],
["王子様系の転校生に恋をする","恋愛","テレビ","2019年08月21日","2"],
["ドタバタ家族の海外ドラマ","コメディ","動画配信サイト","2019年05月05日","5"],
["とある王朝の栄枯","歴史","DVD購入","2018年11月03日","5"],
["続・とある王朝の栄枯","歴史","DVD購入","2019年08月21日","4"],
["学校一の不良を決める喧嘩祭","アクション","レンタルDVD","2017年07月11日","3"],
["豪華客船での恋物語","恋愛","映画館","2017年08月01日","5"],
["天下無双の剣豪","アクション","映画館","2020年01月04日","1"],
["伝説の傭兵が立ち上がる","アクション","映画館","2019年12月09日","4"],
["銀河を支配する帝国を打倒する・前日譚","SF","","",""],
["美大生全員が片想い","恋愛","映画館","2017年10月20日","5"],
];

サンプルの表オプションは以下の通りです。

const tableColumnOption = {
option01: ["<div style='color:red'>", "</div>"],
option02: ["★"],
};

表データの定義内容を説明します。

1行目から4行目は表の定義で、5行目以降が表の実データになります。

  • 1行目、1要素目:表コード
     表を識別するコード値を記述します。任意の文字を指定可能です。
     設定を保存する際のキーに使用します。
  • 1行目、2要素目:表名
     表の名前を記述します。任意の文字を指定可能です。
     表のタイトルとして表示します。
  • 2行目:列名
     各列の名称を記述します。任意の文字を指定可能です。
     表のヘッダとして表示します。
  • 3行目:列の型
     各列がどの値であるかを示す型を記述します。文字列、数値、日付のいずれかとします。
     型名はそれぞれ「text」「num」「date」です。
     dateは"."を付与し日時書式を追記します(例「date.YYYY/MM/DD」)日時書式は後述のいずれかとしてください。
     列の型は検索、ソート処理に影響します。
     配列要素は列名と同数としてください。要素超過分は無視されます。
     数値型、日付型を1つ以上定義した場合、表示変更エリアに数値絞込、日付絞込が表示されます。
  • 4行目:列オプション
     特定列の表データを装飾したい場合に、表オプションで定義したオプション名を記述します。装飾しない場合は空文字。
     オプション名を記述した場合、該当列の表データの前後に表オプションの文字列を付与します。この文字列はHTMLとして解釈されます。
     配列要素は列名と同数としてください。要素超過分は無視されます。
     サンプルの場合、2列目にoption1が、5列目にoption2が適用されます。
     これにより2列目は文字色が赤色に、5列目は先頭に"★"が追加されます。
  • 5行目以降:表データ
     表データの実値を記述します。
     行の要素数に上限はありません。
     値は任意ですが、数値型、日付型を指定した列はその定義に従った値にしてください。空文字は可能です。
     列の要素数は列名と同数としてください。要素超過分は無視されます。

表の解析、異常ケース

初回表示時に表データの定義値が正当かをチェックし、不備があった場合その内容をダイアログで表示します。
チェック内容と、不正時のデフォルト値は以下の通りです。
- 表コード:未定義または空文字→デフォルト値「T0001」
- 表名:未定義または空文字→デフォルト値「デフォルト表」
- カラム名:空文字→デフォルト値「カラム1」「カラム2」・・・
- カラム型
 カラム型不正→デフォルト値「text」
 日付書式不正→デフォルト値「text」
- カラムオプション:不正なキー→デフォルト値 オプションなし
- 表レコード
 カラム数に足りない→デフォルト値 空文字
 数値型、日付型で不正→デフォルト値 空文字

列の型「日付型」の日時書式について

列の型に日付型を指定した場合、型名「date」の後に"."を付与し日時書式を追記します。

日時書式は以下に示す「日付要素」「時刻要素」または「日付要素 時刻要素」のいずれかとしてください。

例.
- date.YYYY/MM/DD
- date.HH:mm :ss
- date.YYYY年MM月DD日 HH時mm分ss秒
"."以降を省略した場合はデフォルトで「YYYY/MM/DD HH:mm :ss」が適用されます。

  • 日付要素
    • YYYYMMDD
    • YYYY/MM/DD
    • YYYY-MM-DD
    • YYYY年MM月DD日
    • MMDD
    • MM/DD
    • MM-DD
    • MM月DD日
  • 時刻要素
    • HHmmss
    • HH:mm :ss(記載時はmm後の空白なしで)
    • HH時mm分ss秒
    • HHmm
    • HH:mm
    • HH時mm分
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

多次元配列を1重のループで扱う【JavaScript】

このあいだ4次元美術館プレーヤーというのを書きました。美術館というのはパズルの名前で、もとは2次元上の盤面にやたら指向性の高い明かりを置いて、すべてのマスを照らすパズルです。こんな感じ。

akari.png

で、何を思ったかこれを4次元に拡張したパズルを制作する人がいて、2次元でもデジタルの助けが欲しいパズルなのに4次元ともなるとさすがに紙とペンで解くには限界があり、それならということでプレーヤーを作りました。こんな感じです。なんかヤバそうですね。

2020-11-08 12.35.33 4次元美術館プレーヤー.png

って、そんな話はどうでもいいのでした。とにかく、4次元の盤面を生成したり操作したりするのには4次元配列が必要で、すなわち4重ループを回す必要に迫られたのです。

問題:深すぎるネスト

愚直に書くとまーこんな感じです。

for(let x = 0; x < boardX; x++){
    for(let y = 0;y < boardY; y++){
        for(let z = 0; z < boardZ; z++){
            for(let w = 0; w < boardW; w++){
                board[x][y][z][w] = hogehoge()
            }
        }
    }
}

いやこれはさすがにダサい。筆者は別にfor文廃止論者ではありませんが、こんなん誰だって間違うしネストは深過ぎで見づらいし、いいことはなにもない。というわけで、解決策を考えましょう。

解決策①: 余りを用いる

なるべくネストは1重にしたいので、まずはそういうふうに書いてみます。

for(let index=0; index < boardX * boardY * boardZ * boardW; index++){
    let x,y,z,w // どうやって求める?
}

indexからx,y,z,wを求めるには、割り算の余りを使えそうです。

function calcQandR(n,m){
    return [Math.floor(n/m), n%m]
}

function index2pos(i){
    var [i,w] = calcQandR(i,boardW)
    var [i,z] = calcQandR(i,boardZ)
    var [x,y] = calcQandR(i,boardY)
    return [x,y,z,w]
}

for(let index=0; index < boardX * boardY * boardZ * boardW; index++){
    let [x,y,z,w] = index2pos(index)
    //処理
}

行数は増えましたが、大分すっきりしました。ただ、index2posの中に定数が残っていたりして、まだ使い勝手が向上できそうです。

解決策②:ジェネレータを使う

ジェネレータについて詳しいことは他の記事を参照してもらうとして、ここではfor-ofで取り出し可能な座標リスト(正確には「イテラブルなオブジェクト」)を生成するものと思ってもらえれば十分です。

function* product(...sizes){
    sizes = sizes.reverse()
    let all = sizes.reduce((a,c) => a*c, 1)
    for(let index=0; index<all; index++){
        let result = [], i = index
        sizes.forEach((size) => {
            result.push(i%size)
            i = Math.floor(i/size) 
        })
        yield result.reverse()
    }
}

for(let [x,y,z,w] of product(boardX,boardY,boardZ,boardW)){
    //処理
}

すっきりしました。何次元でも対応できるように書いたので、5次元や6次元になっても最小限の書き換えで対応できます。やったぜ。

pythonでは

itertoolsというパッケージがあり、その中のproductを使えば同じことが簡単に実現できます。jsでもこれが使えたらハッピーだねというだけの記事でした。

参考

JavaScript の ジェネレータ を極める!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GitHubの使い方やマナーなど。

勉強の記録

  • 初心者のプログラミング5 2020/11/08

勉強した内容

  • GitHubのアカウント作成と運用の仕方。

何をベースに勉強してるか

内容の詳細

わかったことについて

  • コードをオープンにすることで皆で技術向上してるんだなって感じました。
  • SNSに投稿する用に、OGPの設定の仕方。

むずかしかったよ

  • 今回は特になし。

次回やる予定のこと

  • 次回から第2章に入ります、不安です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

methodでよくない?coumputedとmethodの違いを調べてみた

はじめに

この記事ではVue.js2を使っています。
初学者のため、至らない部分や間違い等あればご教授よろしくお願いします。
Vue.jsを勉強しているとcoumputedプロバティが出てきてあれ、これmethodプロバティでいいんじゃない?ってなったので違いはどこにあるのか調べてみました。

結論

coumputedプロバティは、依存関係にもとづきキャッシュされる
methodプロバティは、描写が変わるたびに実行される

動きを確認してみる

とは言っても、文字だけだと難しいという方は続きをご覧ください。

プログラムの仕様

coumputedプロバティとmethodプロバティの挙動を確認します。

・number が3以下の時に"3以下"を表す

・number が3以上の場合は"3より上"を表す

index.html
<!-- Vue.jsのインストール -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

<div id="app">
  <p>{{ number }}</p>
  <button @click="number += 1">+1</button>

<!-- coumputed -->
  <p>coumputedプロパティ->{{ Coumputed }}</p>
<!-- method -->
  <p>methodプロパティ->{{ Method() }}</p>
</div>

new Vue({
 el: "#app",
   data: {
     number: 0,
     },
   //coumputedプロパティ
   computed: {
     Coumputed: function() {
       return this.number > 3 ? "3より上" : "3以下"
     }
   },
   //methodsプロパティ
   methods: {
     Method: function() {
       return this.number > 3 ? "3より上" : "3以下"
     }
   }
})

結果

結果は同じ挙動となりました。
methodプロパティでもcoumputedプロパティと同じ挙動となりました。
スクリーンショット 2020-11-08 12.02.04.png

ソースの追加

関数がいつ実行されているかを確認してみます。
<追加点>

・index.htmlにohternumberを追加

・console.logを追加

index.html
<!-- Vue.jsのインストール -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

<div id="app">
  <p>{{ number }}</p>
  <button @click="number += 1">+1</button>
 <!-- 追加 -->
 <p>{{ othernumber }}</p>
  <button @click="othernumber += 1">+1</button>

<!-- coumputed -->
  <p>coumputedプロパティ->{{ Coumputed }}</p>
<!-- method -->
  <p>methodプロパティ->{{ Method() }}</p>
</div>

new Vue({
 el: "#app",
   data: {
     number: 0,
     //追記
     othernumber: 0,
     },
   //coumputedプロパティ
   computed: {
     Coumputed: function() {
       //追記
       console.log("Computed");
       return this.number > 3 ? "3より上" : "3以下"
     }
   },
   //methodsプロパティ
   methods: {
     Method: function() {
       //追記  
       console.log("Methods");
       return this.number > 3 ? "3より上" : "3以下"
     }
   }
})

結果

coumputedプロバティは、依存関係にもとづきキャッシュされ、methodプロバティは、依存関係にない場合でも描写が変わるたびに実行されていました。

numberを押した場合

スクリーンショット 2020-11-08 12.49.19.png

coumputedプロバティもmethodプロバティも挙動を確認できました。

othernumberを押した場合

スクリーンショット 2020-11-08 12.49.58.png
methodプロパティが実行されており、依存関係にない場合でも描写が変わるたびに実行されてることが分かりました。

最後に

coumputedプロバティ
インスタンスに持たせて参照できるようにしたいときに用いる
methodプロバティ
クリック時、マウスオーバー時等の何かアクションが起きた時に処理を行うような場合に用いることがいいようです。

参考記事
https://dev83.com/vue-computed-methods/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

face-api(ml5.js)は感情ではなく、顔の位置検出のためのAPIであるというメモ

やろうとしてたこと

画像から感情分析を行って感情に応じたアクションを取ろうとしていた
(ml5.jsを使ってやろうとした)

face-apiは顔の部位の位置検出のためのものでした

表情の読み取りとどこかに記載があったから、感情に近いものが取れるのかと考えたが違った。
Happyなどの感情をとれるものではない。

faceapiは画像から顔の口や目などの部位の位置を、点の配列で返してくれるものでした。
口は、たぶん6点の情報をくれて、描画で点と点を線でつなぐと口が検出できる。
他もいくつかの点の情報を順番に線でつなぐといったもの。

何の用途で使うの?とおもったが、たぶん、
 1.位置検出→位置を直すようメッセージを出す(証明写真のときに真ん中に寄ってもらうみたいなの)
 2.位置検出→デジカメか何かに組み込んで、その位置にピントを合わせる
といったところか。

ちなみに、目をつぶっても目の輪郭の大きさは変わらなかったから寝ているかは判断できなさそう。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript ~イベント②~】勉強メモ11

JavaScriptちゃんと学習中。
今回はイベントを使ってフォームの入力内容を取得する。
ほぼ自分の勉強メモです。
過度な期待はしないでください。

フォームの入力内容を読み取り出力する

 - 先ずは、HTMLでフォーム部分の作成

HTML
<form action="#" id="form">
  <input type="text" name="word">
  <input type="submit" value="検索">
</form>
<p id="result"></p>
  • action属性は、データを送信する先のURLを指定するものだが、何処にも送信しない場合は、URLの代わりに「#」を指定する(「#」はページの最上部を指す)  
  • <p id="output"></p>は、入力内容を読み取ったものを出力する場所
  • name属性の値は、入力されたデータがサーバーに送信される時に、そのデータにつく「名前」となる。フォーム部分にname属性の名前が付いていないと、受信したサーバー側のプログラムで処理されない。


 - 続いて、name属性を使って送信ボタンがクリックされたら入力内容を読み取るプログラムを作成

JavaScript
'use strict';

document.getElementById('form').onsubmit = function(event) {
  event.preventDefault();
  const search = document.getElementById('form').word.value;
  document.getElementById('result').textContent = `取得結果:「${search}`;
};

 -ここで先ず重要なのが、preventDefault( )
 preventDefault( ) は、ブラウザが最初から持っているアクションをキャンセルするメソッドになる
 つまり、ここでこれがないと、送信ボタンがクリックされたらデータを送ると同時に、action属性で指定した
 ページに遷移してしまう為、取得結果が表示されない。
 なので、preventDefault( )を使って、データが送信される、次のページに遷移するという
 フォームの基本動作自体をキャンセルさせる。

 -書き方について
 関数の引数にeventと追記、イベント内の関数の第一引数に自動でイベントオブジェクトが渡される。
 イベントオブジェクトには、イベントの発生元の要素や押されたキーの情報などが入っている。
 ここで取得したイベントオブジェクトに対して、event.preventDefault( )を使用することで
 要素のイベントをキャンセルすることが出来る。

 -入力内容を読み取る部分、document.getElementById('form').word.valueについて
 書き方は、取得した要素.読み取りたいフォーム部分のname属性.valueとなる。
 要素の取得は、今迄使用して来たgetElementByIdメソッドを使用して取得する。
 そして、今回読み取りたいname属性は、<input type="text" name="word">のwordを指定する。
 
 これでテキストフィールドの取得が出来たので、次にそこに入力された内容を調べる為にvalueプロパティを使用する。
 valueプロパティには、フォーム部分に入力された内容が保存されている。
 
 これで、フォームの入力内容を読み取り出力させる事が出来る。
 こんな感じで。
ファイル名

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript ~イベント(入力内容を取得)とDateオブジェクト~】勉強メモ11

JavaScriptちゃんと学習中。
今回はイベントを使ってフォームの入力内容を取得する。
ほぼ自分の勉強メモです。
過度な期待はしないでください。

フォームの入力内容を読み取り出力する

 - 先ずは、HTMLでフォーム部分の作成

HTML
<form action="#" id="form">
  <input type="text" name="word">
  <input type="submit" value="検索">
</form>
<p id="result"></p>
  • action属性は、データを送信する先のURLを指定するものだが、何処にも送信しない場合は、URLの代わりに「#」を指定する(「#」はページの最上部を指す)  
  • <p id="output"></p>は、入力内容を読み取ったものを出力する場所
  • name属性の値は、入力されたデータがサーバーに送信される時に、そのデータにつく「名前」となる。フォーム部分にname属性の名前が付いていないと、受信したサーバー側のプログラムで処理されない。


 - 続いて、name属性を使って送信ボタンがクリックされたら入力内容を読み取るプログラムを作成

JavaScript
'use strict';

document.getElementById('form').onsubmit = function(event) {
  event.preventDefault();
  const search = document.getElementById('form').word.value;
  document.getElementById('result').textContent = `取得結果:「${search}`;
};

 -ここで先ず重要なのが、preventDefault( )
 preventDefault( ) は、ブラウザが最初から持っているアクションをキャンセルするメソッドになる
 つまり、ここでこれがないと、送信ボタンがクリックされたらデータを送ると同時に、action属性で指定した
 ページに遷移してしまう為、取得結果が表示されない。
 なので、preventDefault( )を使って、データが送信される、次のページに遷移するという
 フォームの基本動作自体をキャンセルさせる。

 -書き方について
 関数の引数にeventと追記、イベント内の関数の第一引数に自動でイベントオブジェクトが渡される。
 イベントオブジェクトには、イベントの発生元の要素や押されたキーの情報などが入っている。
 ここで取得したイベントオブジェクトに対して、event.preventDefault( )を使用することで
 要素のイベントをキャンセルすることが出来る。

 -入力内容を読み取る部分、document.getElementById('form').word.valueについて
 書き方は、取得した要素.読み取りたいフォーム部分のname属性.valueとなる。
 要素の取得は、今迄使用して来たgetElementByIdメソッドを使用して取得する。
 そして、今回読み取りたいname属性は、<input type="text" name="word">のwordを指定する。
 
 これでテキストフィールドの取得が出来たので、次にそこに入力された内容を調べる為にvalueプロパティを使用する。
 valueプロパティには、フォーム部分に入力された内容が保存されている。
 
 これで、フォームの入力内容を読み取り出力させる事が出来る。
 こんな感じで。
ファイル名

Dateオブジェクト

  • Dateオブジェクトとは

 日時をを扱う為のオブジェクトで、現在の日時を取得する事が出来る。
 
 コード例がこちら。

HTML
<p>最終アクセス日時:<span id="time"></span></p>
JavaScript
'use strict';

const now = new Date();
const year = now.getFullYear();
const month = now.getMonth();
const date = now.getDate();
const hour = now.getHours();
const min = now.getMinutes();

const output = `${year}/${month + 1}/${date} ${hour}:${min}`;
document.getElementById('time').textContent = output;

 -Dateオブジェクトを使いたい時、先ず最初にインスタンスの生成する必要がある。そのコードがnew Date();
 newはオブジェクトをインスタンスの生成の為のキーワードで、これをつける事でオブジェクトはインスタンス出来る。
 インスタンスについてはこちらを参照
 【JavaScript ~クラスやインスタンス、メソッドについて~】勉強メモ④

 これで、Dateオブジェクトを使い日時の出力や計算が出来るようになる。そして、その計算の基準日と
 なっているのが現在日時です。なので現在の日時を記憶したDateオブジェクトを使い日時の出力や計算が出来るようになる。

 -続いて、現在の日時を記憶したDateオブジェクトから年月日を取得する為のメソッドが、
 getFullYear( )、getMonth( )、getDate( )、getHour( )、getMinutes( )

メソッド 説明
getFullYear( ) 年を取得する
getMonth( ) 月を0〜11の数値で取得する(なので0が1月になる)
getDate( ) 日を取得する
getHour( ) 時を取得する
getMinutes( ) 分を取得する

その他にも、

メソッド 説明
getSeconds( ) 秒を取得する
getDay( ) 曜日を0〜6の数値で取得する(なので0が日曜日になる)
getTimezoneOffset( ) 時差を取得する

などがある。

 そして注意しなければいけないのは、getMonth( )メソッド。
 このメソッドを使うと「実際に月-1」の数字が取得される。つまり、1月なら「0」、2月なら「1」、12月なら「11」
 といった具合に取得されるので、所得出来た数字に1を足す必要がある。
 ${month + 1}という書き方になる。

 後は、それぞれ定数に代入し、getElementByIdメソッドとtextContentプロパティを使い
 取得した要素のコンテンツを書き換えれば完了。
 こんな感じで出力される。

ファイル名

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

テキストエリアに書かれたコードをJSとして実行する

OpenCV.jsをブラウザで実行できるPlaygroundを作っていたのですが、その際にエディターで記載したコードをJSとして実行する必要がありました。

いろんなサイトでこういう機能結構見ますが、どうやっているのか調べてみると意外と簡単だったのでメモを残しておきます。

Functionを使う

テキストとして記載されたコードをJSとして実行する、つまり関数を実行する際にはFunctionオブジェクトを作成します。

new Function([arg1 [, arg2 [, ...argN]] ,] functionBody)

外から引数を渡す必要があるときはコンストラクタで順番に渡してやり、最後に関数本体の文を渡してあげればよい。

const sum = new Function('a', 'b', 'return a + b')
sum(1, 2) // 3

上のように1文だけとかそういうことはなく、渡した文字列が関数の体をなしていれば、きちんと実行される。

let src = cv.imread("preview")
let dst = new cv.Mat()
cv.cvtColor(src, dst, cv.COLOR_RGBA2GRAY)
cv.imshow("outputCanvas", dst)
src.delete()
dst.delete()

変なJSコードを実行される恐れもあるので少し考えようだが、簡単に実行出来て便利ではあります。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PlotlyのLayoutに書くdomain属性って何?

このテーマに関して、英語のドキュメントにはコードと実行結果しか載っておらず、
ちょっとわかりにくかったのでその解説を残しておきます。

domain属性の使いどころ

  1. 1つの画像に複数のグラフを配置したい時の位置指定
  2. (x軸またはy軸を共有)同じグラフ内に複数のデータを重ねたい時の位置指定。これにはdomainの他にも軸の設定とか調整が必要なので説明は省略します。
  3. 他にもこういう時に使うよ、というのがあれば教えてください!

参考ドキュメント

https://plotly.com/javascript/subplots/#multiple-custom-sized-subplots

これはJavaScriptのドキュメントですが、Pythonで書くときの参考にできます。

この multiple-custom-sized-subplots という項目では
上記の用途1の、与えられた1つのエリア内に複数のグラフを表示するときの、
配置とサイズを設定しています。

JSとHTMLのコードをCodePenですぐ描画確認できるので、試してみてください。

実際のコード(JS)

var plot1 = {
  x: [1, 2],
  y: [1, 2],
  type: 'scatter',
  name: '(plot1)'
};

var plot2 = {
  x: [1, 2],
  y: [1, 2],
  type: 'scatter',
  name: '(plot2)',
  xaxis: 'x2',
  yaxis: 'y2'
};

var plot3 = {
  x: [1, 2],
  y: [1, 2],
  type: 'scatter',
  name: '(plot3)',
  xaxis: 'x3',
  yaxis: 'y3'
};

var data = [plot1, plot2, plot3];

var layout = {
  title: '複数プロットのサイズをカスタマイズ',
  xaxis: {
    domain: [0, 0.45],
    anchor: 'y1'
  },
  yaxis: {
    domain: [0, 1],
    anchor: 'x1'
  },
  xaxis2: {
    domain: [0.55, 1],
    anchor: 'y2'
  },
  yaxis2: {
    domain: [0.55, 1],
    anchor: 'x2'
  },
  xaxis3: {
    domain: [0.75, 1],
    anchor: 'y3'
  },
  yaxis3: {
    domain: [0, 0.45],
    anchor: 'x3'
  },
};

Plotly.newPlot('myDiv', data, layout);

実際のコード(HTML)

<head>
    <!-- Load plotly.js into the DOM -->
    <script src='https://cdn.plot.ly/plotly-latest.min.js'></script>
</head>

<body>
    <div id='myDiv'><!-- Plotly chart will be drawn inside this DIV --></div>
</body>

描画結果

このような配置になっています。
image.png

domainで指定する値の解説

domain は 領域という意味です。
xaxis(x軸): と yaxis(y軸): 両方に domain属性があるのが分かります。
このそれぞれのdomainで座標の範囲を設定します。

以下Layout内の記載

  xaxis: {
    domain: [0, 0.45],
    anchor: 'y1'
  },
  yaxis: {
    domain: [0, 1],
    anchor: 'x1'
  },

与えられた描画面積の、横(x)と縦(y)のそれぞれの最長をとしたときの
小数の値で指定します。

上のplot1の例では、
x軸の domain:[0, 0.45] は 0〜0.45 の範囲
y軸の domain:[0, 1] は  0〜1 の範囲 ということになっています。
スクリーンショット 2020-11-08 7.54.13.png
※描画されたウィンドウのサイズが変わったので、この画像が少し横に伸びたように見えてしまっています。

ちなみに

私が最初にドキュメントをみたとき、0.55やと0.75のような数がややこしかったので0.5と1で設定してみました。
すると下のように、グラフの終端ともう一つの軸の開始がかぶってしまいました。
グラフ間は10%くらい開けておくのがスタンダードのようです。

image.png

最後に

コードと描画結果をみただけで理解できた方も結構いたかもしれませんが、
私は、初見で[0.55, 1]がマジックナンバーにしか見えず混乱しました。数学の基礎って大事ですね!

補足:複数グラフの扱いについて疑問のある方は、こちらのPlotlyのJavaScriptドキュメントの[Try It On CodePen]で動作確認するのがおすすめです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む