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

月齢と月の満ち欠けを表示

JavaScriptで簡易的な月齢の計算及び月の満ち欠け表示を作成してみました。

image.jpg

動作デモ

日時及び平均朔望月からおおよその月齢を出し、月齢に応じた満ち欠けをcanvasを使用して描画しています。

満ち欠けの状態に応じた形をその都度描いているわけではなく、新月(暗 真円)、上弦及び下弦(明 半円)、リサイズ用真円(明暗切替)の3枚を重ね合わせて、CSSで反転や変形させることで表現しています。
言葉では伝わりにくいかと思いますが、こちらの満ち欠けテストページで適当に操作していただければ分かりやすいかもしれません。

index.html
<!doctype html>
<html lang='ja'>
<head>
<meta name="viewport" content="user-scalable=no,width=device-width,initial-scale=1">
<meta charset='utf-8'>
<title>月齢表示</title>
<link rel='stylesheet' href='./style.css' type='text/css'>
<script src='./script.js'></script>
</head>
<body>
<div class='d'>
    <div class='moonAge'>
        <canvas id='a0'></canvas>
        <canvas id='a1'></canvas>
        <canvas id='a2'></canvas>
    </div>
    <div id='disp'>
    </div>
</div>
<input type='date' id='c' onchange='chg(this.value)'>
</body>
</html>
style.css
.d {
    background: black;
    width: 150px;
}
#disp {
    color: #ffff00;
}
.moonAge {
    position: relative;
    height: 200px;
}
canvas[id*='a'] {
    position: absolute;
    top: 0px;
    left: 0px;
}
script.js
'use strict';
const size = 200;

const topAngle = Math.PI + (Math.PI / 2) * 3;
const bottomAngle = Math.PI + (Math.PI / 2);
const pi2 = Math.PI * 2;

const c = [];
const ctx = [];
const start = [0, topAngle, 0];
const end = [pi2, bottomAngle, pi2];

window.addEventListener('DOMContentLoaded', function(){
    document.querySelector('.moonAge').style.height = size + 'px';
    document.querySelector('.d').style.width = size + 'px';
    for(let i = 0; i < 3; i++) {
        c[i] = document.getElementById('a' + i);
        c[i].style.width = size + 'px';
        c[i].style.height = size + 'px';
        c[i].width = size;
        c[i].height = size;
        ctx[i] = c[i].getContext('2d');
        ctx[i].fillStyle = i === 0 ? '#444444' : '#ffff00';
        ctx[i].arc(size / 2, size / 2, (size / 2) * .95, start[i], end[i]);
        ctx[i].fill();
    }
    const e = document.querySelector('#c');
    e.value = new Date().toLocaleDateString('sv');
    chg(e.value);
}, false);

function chg(d){
    const date = new Date(d),
          day = date.getTime() / 1000 / 86400 - 6.975,
          // 平均朔望月
          r = 29.530588853 + 
                0.000000002162 * (new Date(date).getFullYear() - 2000),
          age = day > 0 ? day % r : r + day % r;
    document.querySelector('#disp').innerHTML =
        `${date.toLocaleDateString()}<br>月齢:${age.toFixed(1)}`;
    appearance(age, r);
}

function appearance(age, m = 29) {
    const s  = Math.cos(pi2 * (age / m)),
          s2 = Math.sin(pi2 * (age / m)),
          r  = Math.abs((size / 2) * s);
    ctx[2].clearRect(0, 0, size, size);
    ctx[2].fillStyle = s > 0 ? '#444444' : '#ffff00';
    c[1].style.transform = 'rotate(' + (s2 > 0 ? 180 : 0) + 'deg)';
    ctx[2].arc(size / 2, size / 2, (size / 2) * .95, 0, pi2);
    ctx[2].fill();
    c[2].style.width = (r * 2) + 'px';
    c[2].style.left = (size / 2 - r) + 'px';
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

12月なのでクリスマスソングにノるテンアゲなキーボードliteを実装!【WebHID API】 #iotlt

この記事はIoTLTアドベントカレンダー1日目です!(先に断っておきますが自作キーボード話ではないです) 先日のIoTLT vol69で話したネタをアップデートした感じです。
関係ないけどついに、この季節がやってきましたか〜 2020年めち

IoTLT vol69 https://iotlt.connpass.com/event/192582/

テンション爆上がりなキーボードがこちら

毎年アドベントカレンダーネタでクリスマスっぽい話をやろうかと思ってたけど、全然やれてなかったので今回はクリスマス曲に合わせてみました。

記事の後半に実際に試せる(MacBookProでしか試してませんが)ページのURLを記載してますので是非手元でも試してみてください!

ちなみに再生してる曲はこちらから

https://www.youtube.com/watch?v=eYhkSC3gRno

なんとブラウザでキーボードバックライトの制御

Webブラウザからキーボードバックライトを制御出来ています。

ブラウザからハードウェアが制御できるAPIは色々と試して来てますが、今回は特に面白いですね。

WebHID API - Nintendo Switchも動かせるよ

HIDはhuman interface devicesの略です。様々なヒューマンインターフェイスデバイスをJavaSciprtで制御出来るAPIです。

参考記事から引用: ヒューマン インターフェース デバイス(HID)には、新しすぎる、古すぎる、あまりにも一般的でないなどの理由で、システムのデバイス ドライバーからアクセスできないロングテールなものがあります。WebHID API は、デバイス固有のロジックを JavaScript で実装する方法を提供することで、この問題を解決します。

ヒューマンインターフェイスデバイスと言われてもよく分からないと思いますが、 Nintendo Switchのコントローラー(Joy-Con)や冒頭のデモ動画のような キーボードのバックライトの制御が出来ます。

詳細は参考記事を読んでみてください。

利用はChrome v86以上かつ、現状オリジントライアル

WebHID APIは現時点だと、Google Chrome v86以上で対応しています。

他のブラウザの対応状況はCan I Useを見て確認しましょう。


2020/12/1現在 まだ全然対応してない......
https://caniuse.com/?search=webhid

見てみると全然対応してないですね、最新版のChromeでもアップデートしただけでは使えず、オリジントライアルという利用するオリジン(利用するサイト的な)の登録作業をし、利用するユーザーのChromeのフラグ設定を変えてやっと利用出来ます。

Gamepad APIとの違いは?

Gamepad APIというものが既存で存在しましたが、Nintendo SwitchのJoy-Conが使えるとなると、このAPIとの違いは?と思う人もいるかもしれません。(この辺のハードウェア制御系のブラウザAPIをウォッチしてる人が少なさそうですが苦笑)

間違ってるかもですが僕が見ている印象だと、Gamepad APIはインプットのみでWebHID APIの方がやれることは多そうな印象でした。

  • Gamepad API
    • デバイス -> ブラウザ のインプットのみ
    • ゲームパッドのみ
  • WebHID API
    • デバイス <-> ブラウザ でインプットとアウトプット両方
    • ゲームパッド以外も対応

上位互換というよりかは、Gamepad APIはゲームパッドからのインプットに特化してる気がします。ちゃんと調べ切れてないので、認識間違ってたらコメント下さい。

WebHID APIを利用してみよう

では実際にWebHID APIを利用してみましょう。

事前準備

最初にちょっと準備が必要です。

クライアント(ブラウザ)側

フラグを確認する必要があります。chrome://flags/にアドレスバーからアクセスしま、#experimental-web-platform-featuresのフラグをenableにしましょう。

サーバー側、オリジンの申請

WebHID APIは、現状オリジントライアルというステータスで、利用するオリジンをGoogleに申請する必要があります。

こちらからオリジントライアル申請します。

ドメインだけでなく、オリジンなのでちゃんとhttps://も入れましょう。

  • パスは不要です。
    • 例えばhttps://hogehoge.com/huga/index.htmlで利用したい場合はhttps://hogehoge.comを登録すれば大丈夫です。
  • SSLがマストです。
    • httpは使えません、httpsがマストです。

無事に登録できると次のページでトークンが発行されます。

利用するページのHTMLファイルのmetaタグに以下のような指定をし、発行されたトークンを指定することでWebHID APIが利用出来ます。

<meta http-equiv="origin-trial" content="TOKEN_GOES_HERE">
補足: npxで気軽にサーバー起動とトンネリング

オリジンの登録ということは、基本ホスティング環境ということになりますが、毎回デプロイしなおすのは手間なので、最近はserveとngrokの二つのコマンドをnpx経由でインストールせずに実行して手軽に試してます。

  • ローカルサーバー起動
$ npx serve -l 4444
  • トンネリングサーバー起動
$ npx ngrok http 4444

ngrokで発行されたオリジンをオリジントライアルに登録するとデプロイなどせずに手軽に試せます

コードの実装

index.htmlscript.jsを作成しました。

HTML側 - トークンの指定

先ほども紹介したように、index.htmlのhead内に以下を記載しましょう。

<meta http-equiv="origin-trial" content="TOKEN_GOES_HERE">

TOKEN_GOES_HEREの箇所は登録して発行されたトークンです。

JavaScript側 - navigator.hid

script.jsは以下のコードで実行してみます。

利用できる状態だと、navigator.hidが利用出来ます。

if ("hid" in navigator) {
    // The WebHID API is supported.
    console.log(`HIDが使えるよ`);
} else {
    console.log(`HIDが使えません`);
}

コンソールにHIDが使えるよと表示されればOKです。

https://~~で登録してるのにブラウザで確認するときにhttp://~~になってるなど登録した条件と違う場合は動作しないので注意しましょう。

Nintendo Switchのコントローラーで試す

そのまま実行すると怒られるのでボタンを追加して、ボタンクリックからスタートするようにします。

Uncaught (in promise) DOMException: Failed to execute 'requestDevice' on 'HID': Must be handling a user gesture to show a permission request.

web.devのサンプルコードからそのままという感じですが、Nintendo SwitchのJoy-ConはvendorIdproductIdが↓のような値なので指定しましょう。

参考: https://web.dev/hid/

// Filter on devices with the Nintendo Switch Joy-Con USB Vendor/Product IDs.
const filters = [
    {
      vendorId: 0x057e, // Nintendo Co., Ltd
      productId: 0x2006 // Joy-Con Left
    },
    {
      vendorId: 0x057e, // Nintendo Co., Ltd
      productId: 0x2007 // Joy-Con Right
    }
];

const main = async () => {
    // Prompt user to select a Joy-Con device.
    const [device] = await navigator.hid.requestDevice({ filters });
};


if ("hid" in navigator) {
    // The WebHID API is supported.
    console.log(`HIDが使えるよ`);
    const btn = document.querySelector(`button`);
    btn.addEventListener('click', main);
}else{
    console.log(`HIDが使えません`);
}

Webサイトにアクセスし、スタートボタンを押すと接続済みのJoy-ConがHIDとして認識されます。

また、このような実装まで繋げてあげると、 Joy-Conのバイブレーション機能を利用出来ます。

// First, send a command to enable vibration.
// Magical bytes come from https://github.com/mzyy94/joycon-toolweb
const enableVibrationData = [1, 0, 1, 64, 64, 0, 1, 64, 64, 0x48, 0x01];
await device.sendReport(0x01, new Uint8Array(enableVibrationData));

// Then, send a command to make the Joy-Con device rumble.
// Actual bytes are available in the sample below.
const rumbleData = [ /* ... */ ];
await device.sendReport(0x10, new Uint8Array(rumbleData));

参考: https://web.dev/hid/#send-output-reports

device.sendReport()でデータ配列を送ってバイブを制御しています。

デモはこちらのIoTLT vol69の様子を

ここのデータを変えて試してみたいですね。

キーボードバックライトで試す

同じようなアクセスの仕方で、キーボードのバックライトをJavaScriptから制御できます。

スクリーンショット 2020-12-01 20.52.28.png

vendorIdなどのパラメータも参考記事を元に指定します。

const [device] = await navigator.hid.requestDevice({
  filters: [{ vendorId: 0x05ac, usage: 0x0f, usagePage: 0xff00 }]
});

リクエストするとKeyboard Backlightの許可を求められます。

// Blink!
const reportId = 1;
for (let i = 0; i < 10; i++) {
  // Turn off
  await device.sendFeatureReport(reportId, Uint32Array.from([0, 0]));
  await waitFor(100);
  // Turn on
  await device.sendFeatureReport(reportId, Uint32Array.from([512, 0]));
  await waitFor(100);
}

音楽に合わせてキーボード光らせるのはこちらで試せます

最後ですが、ここまで試して、キーボードを光らせることが出来たので、音楽にノらせてパリピ感のある演出をしてみたいなと突発的に思いました。これが最初のデモになります。

Izm Logさんのブログを参考に、ビート検出のコードを利用させてもらい、 ビート検出時にキーボードを光らせる というプログラムを作ってみましたが、けっこういい感じの反応になりました。

P5.jsでAudio Visualizerを作ってみた!【ビート検知】- Izm Log

デモ動画の最後にも載せてますが、指パッチンでの反応もいい感じです。

MacBookProでしか試せてませんが、こちら↓で試せます。

https://mystifying-newton-aedc2e.netlify.app/

  • マイクの利用を許可する
  • リクエストのボタンを押す
  • デバイス利用の許可を求められるので、Keyboard Backlightを選択
    • (MacBookPro以外の人はここでデバイスが表示されないかも)
  • オープンのボタンを押す
  • このページを開きながら、何か音楽を流す
    • この時、別タブでYoutubeを長そうとすると、このページの処理が止まってしまうので、一つのPC内で試す際は別タブではなく別ウィンドウで開くようにしましょう。このページがアクティブになってる状態にして下さい。

まとめ

色々と書きましたが、Chrome v86から利用できるようになったWebHID APIを利用してみたという記事です。

まだGoogle公式のサンプルを少しいじったくらいしか試せてませんが、キーボードのバックライトをいじれるのはけっこう楽しいです。

Nintendo Switchのコントローラーもまだまだ遊びきれてないのでまた少し触ってみたいなぁ

前回のIoTLT vol69の時の様子はこちらのYoutubeでも観れるのでぜひご覧ください!そしてチャンネル登録も是非!

https://www.youtube.com/watch?v=Jynrd0VbIig&t=11s

次回は12/15にIoTLTを実施しますのでこちらもよろしくお願いします!

明日は@3yaka4さんによるねこIoT的な何かの記事です!
楽しみですね〜 :)

少し早いですが、今年もありがとうございました!来年もよろしくお願いします!

それでは!

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

地理院タイルとdeck.glで3D地図をつくろう

この記事はMIERUNEアドベントカレンダー2日目の記事です!

1日目は最近弊社にジョインした@igarashiさんがFOSS4GHokkaidoでの登壇から引き続き、とてもアツい記事を投稿されましたのでMIERUNE的なインパクトは十分、2日目はゆるふわに富士山でも見て涼んでいきましょう。

TL;DR

国土地理院の標高タイルで3D表現をするためのGsiTerrainLayerをつくりました。
https://github.com/Kanahiro/deckgl-gsi-terrain-layer

npmモジュールとして公開済みです、以下でインストール出来ます。

npm install deckgl-gsi-terrain-layer

はじめに

QGISやウェブ問わず、GISの使い始めは、まずはOSM地図や地理院タイルを表示するところから始める方が多いのではないでしょうか。今回は地理院タイルにフォーカスしていく訳ですが、国土地理院は空中写真だけではなく、様々なデータをラスタータイルとして配信しています。

https://maps.gsi.go.jp/development/ichiran.html
こちらがその一覧です。ベースマップなどは定番ですね。

この中に標高タイルなるデータがあります。標高データ、DEMと言えば標高値をグレースケール画像に変換した白黒のアレをイメージしますが、標高タイルでは何やらカラフルな画像が配信されているようです(下記画像)。
スクリーンショット 2020-11-08 17.51.36.png
※地理院タイルより10mDEM、東京都と富士山のまわり

10mレベルのデータが既に配信されていて無料で使える訳ですから、これを用いてウェブ地図で3D表示出来たら便利ですよね。ということで色々やったのが本記事です。

RGB値の標高換算

先ほども言ったとおりDEMと言えばグレイスケールですが、標高値をRGB画像に変換するというのは、標高タイル以外にもやっている例があります。

https://docs.mapbox.com/help/troubleshooting/access-elevation-data/
たとえばMapbox GL JSではterrain-RGBとして、以下の計算式によりRGB値を標高値に換算しています(もともとはMapzenという会社が定義した規格のよう)。

height = -10000 + ((R * 256 * 256 + G * 256 + B) * 0.1)

何やら難しく見えますが、RGBそれぞれが0-255の値をとるので、RGB値を3桁の256進数として見立ててRGBと標高をやり取りしている訳です(傾き0.1の単調増加関数と言えますね)。

先ほどの標高タイルと同じエリアの標高を、この式から得られるterrain-RGB画像にすると以下のような画像になります。見た目がずいぶん違う事がわかりますね。
スクリーンショット 2020-11-19 20.12.27.png

地理院の標高タイルも、ベースの考え方は全く同じで、計算式が異なり以下です(仕様を解釈して式になおしています)。

height = (R * 256 * 256 + G * 256 + B) * 0.01
# ただし[R,G,B] = [128,0,0]を無効値とする
# ただし[128,0,1]以上の場合2^24を引き去る

ここでサラッと書いた但し書きがミソで、これらのせいで、RGB値→標高値の関係が、単調増加と言えなくなっており、Mapboxなどが採用するterrain-RGBの仕組みに乗っかれなくなってしまっています。

参考:国土地理院-標高タイルの詳細仕様

Mapzen Terrain-RGBと地理院標高タイルの考え方の違い

(こきたない落書きですみません…)
※とくに標高タイルの方、間違いがありましたらご指摘ください

スケッチ.png

それでも標高タイルを使いたい理由

既にホスティングされていて、自由に使えることに尽きます。

terrain-RGBを配信しているサービスはいくつかありますが、API-keyが必要だったり、アクセス数がそれなりにあれば当然費用が発生したりと、ライトに使えるものはありません。
また、標高タイルの元データである10mDEMは、基盤地図情報から取得可能なので自前で変換してホスティングする事も不可能ではありませんが、それを全国分やるのは骨です。

標高タイルをdeck.glで3D表示してみた

Mapbox GL JSでは陰影図は表示出来ますが3D表示は出来ません。terrain-RGBの表示には、deck.glのTerrainLayerが良い感じです。deck.glのTerrainLayerをベースに、標高タイルに準拠するよう拡張したGsiTerrainLayerをつくってみました。

ちなみにTerrainLayerでは任意の標高画像に対応していて、グレースケール画像でも標高表現が可能ですが、RGB値と標高値の関係が単調増加である必要があり、標高タイルはどうやっても読ませる事ができません。

サンプルページ
スクリーンショット 2020-11-10 23.06.43.png
※羊蹄山

deckgl-gsi-terrain-layer

で、GsiTerrainLayerを使うためのモジュールはnpmで既に公開済みです。
https://github.com/Kanahiro/deckgl-gsi-terrain-layer

npm install deckgl-gsi-terrain-layer

これでインストール可能です。詳細な使い方は本記事では割愛します、上記リポジトリのREADMEを参照してください。

終わりに

いかがだったでしょうか?
グレースケールPNGでは分解能は16bitが最大でしょうか。その点、24bitの分解能を持つRGB-PNGが標高表現で有利なのは明らかでしょう。本記事をきっかけにterrain-RGBの利用が少しでも広がったらうれしいですね。
ちなみに、過去にMapbox GL JSで同じ様な事をされた方がいらっしゃったみたいですね。

そんな訳でMIERUNEアドベントカレンダー2日目でした。3日目は@mits003さんです、ぜひご覧ください:)

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

Vue.jsで超簡単なタイピングゲームを作ります

完成品

demo


App.vue
<template>
  <div id="app">
    <div v-if="playing">
      <span>{{ pressed }}</span>{{ word }}
      <br>
      <br>
      miss:{{ miss }}
    </div>
    <div v-else>Spaceでスタート</div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      words: ['apple', 'banana', 'grape'],
      word: '',
      pressed: '',
      miss: 0,
      playing: false,
    };
  },
  created() {
    addEventListener('keydown', (e) => {
      if (e.key !== ' ' || this.playing) {
        return;
      }
      this.playing = true;
      this.setWord();
      this.keyDown();
    });
  },
  methods: {
    setWord() {
      this.word = this.words.splice(Math.floor(Math.random() * this.words.length), 1)[0];
    },
    keyDown() {
      addEventListener('keydown', (e) => {
        if (e.key !== this.word[0]) {
          this.miss++;
          return;
        }
        this.pressed += e.key;
        this.word = this.word.slice(1);
        if (this.word.length === 0) {
          this.pressed = '';
          if (this.words.length === 0) {
            this.word = 'おしまい';
            return;
          }
          this.setWord();
        }
      });
    },
  },
};
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}

span {
  opacity: 0.5;
}
</style>

配列からランダムな値を取り出す

setWordメソッドを作り、createdで呼び出しています。
その際wordsの中からランダムな値をwordに格納。

App.vue
<template>
  <div id="app">
    <div>
      {{ word }}
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      words: ['apple', 'banana', 'grape'],
      word: '',
    }
  },
  created() {
    this.setWord();
  },
  methods: {
    setWord() {
      this.word = this.words[Math.floor(Math.random() * this.words.length)];
    }
  }
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}

タイピングの処理

keyDownメソッドを作り、createdで呼び出しています。
入力されたKeyと現在のwordの頭文字が一致しない場合はmissが加算。

pressedには入力されたkeyを格納。
wordにはkeyが押されるごとに頭文字を削除し再代入します。
これで入力した文字と未入力の文字を仕分けしています。
wordが全て無くなったらpressedをリセットし、新しいwordを表示させます。

App.vue
<script>
export default {
  data() {
    return {
      words: ['apple', 'banana', 'grape'],
      word: '',
      pressed: '',
      miss: 0,
    }
  },
  created() {
    this.setWord();
    this.keyDown()
  },
  methods: {
    setWord() {
      this.word = this.words[Math.floor(Math.random() * this.words.length)];
    },
    keyDown() {
      addEventListener('keydown', (e) => {
        if (e.key !== this.word[0]) {
          this.miss++;
          return;
        }
        this.pressed += e.key;
        this.word = this.word.slice(1);
        if (this.word.length === 0) {
          this.pressed = '';
          this.setWord();
        }
      });
    },
  }
}
</script>

Viewでpressedword連結させます。
入力されていることが分かりやすいようにpressedspanタグで囲みスタイルをつけます。

あとmissのカウントをここで表示させます。

App.vue
<template>
  <div id="app">
    <div>
      <span>{{ pressed }}</span>{{ word }}
      <br>
      <br>
      miss:{{ miss }}
    </div>
  </div>
</template>

<style>
span {
  opacity: 0.5;
}
</style>

スクリーンショット 2020-12-01 22.33.49.png


単語の重複を防ぐ

現時点だと単語が重複して無限に遊べるので一度出た単語は消してしまいます。

App.vue
<script>

    setWord() {
      this.word = this.words.splice(Math.floor(Math.random() * this.words.length), 1)[0];
    },

</script>

ゲームの開始と終了の処理

dataplayingを追加しSpaceキーを押すことでfalseからtrueにします。

App.vue
<template>
  <div id="app">
    <div v-if="playing">
      <span>{{ pressed }}</span>{{ word }}
      <br>
      <br>
      miss:{{ miss }}
    </div>
    <div v-else>Spaceでスタート</div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      words: ['apple', 'banana', 'grape'],
      word: '',
      pressed: '',
      miss: 0,
      playing: false,
    }

</script>

Spaceキーが押されたらplayingtrueになり、ゲームスタートです。
|| this.playingで再スタートを防ぎます。

App.vue

<script>

  created() {
    addEventListener('keydown', (e) => {
      if (e.key !== ' ' || this.playing) {
        return;
      }
      this.playing = true;
      this.setWord();
      this.keyDown();
    });
  },

</script>

wordsが空になったらおしまいと表示させゲーム終了です。

App.vue
<script>

    keyDown() {
      addEventListener('keydown', (e) => {
        if (e.key !== this.word[0]) {
          this.miss++;
          return;
        }
        this.pressed += e.key;
        this.word = this.word.slice(1);
        if (this.word.length === 0) {
          this.pressed = '';
          if (this.words.length === 0) {
            this.word = 'おしまい';
            return;
          }
          this.setWord();
        }
      });
    },
  }
}
</script>

おしまい

以上となります。
もっとこうした方が良いんじゃない?
というのがあれば是非教えて下さいm(_ _)m

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

PythonでJavaScriptの分割代入のようなことをする

やりたいこと

JavaScriptの分割代入のようなことがPythonでしたいです。

destructuringAssignment.js
let a, b, c;
[a, b, c] = ["x", "y", "z"];
console.log(a, b, c); // x y z

結論

Pythonのシーケンスのアンパックによって実現できます。

unpack.py
a, b, c = ("x", "y", "z")
print(a, b, c) # x y z

余談

Pythonのテキストデータはテキストシーケンス型であり、シーケンスのアンパックが行えます。

textunpack.py
a, b, c = "xyz"
print(a, b, c) # x y z

このPythonの挙動を見て、JavaScriptではどうなるのか試したところ。。。

textDestructuringAssignment.js
let a, b, c;
[a, b, c] = "xyz";
console.log(a, b, c); // x y z

いけました!ただし、色々調べましたがこちらの仕様がECMAScriptのどこで定義されているのかはよくわかりませんでした。。。

さらにここから、見かけ上Pythonと同様の記法も試してみました!

textUnpack.js
let a, b, c;
a, b, c = "xyz";
console.log(a, b, c); // undefined undefined xyz

どうやら左辺の最後の変数に右辺の値が代入され、左辺の最後以外の変数はundefinedとなるようです。
このあたりの仕様も一旦この記事にメモ書きして、興味が湧いたらまた調べます!

参考URL

分割代入 - JavaScript | MDN
5. データ構造 — Python 3.9.1rc1 ドキュメント
組み込み型 — Python 3.9.1rc1 ドキュメント

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

異業種から転職して感じたwebエンジニアに必要な素養

とりあえず箇条書き

・分かりづらいことを分かりやすく説明する能力
・急な内容修正などに対する対応力と柔軟性
・チームの人に対する思いやりと気遣い(コードの工夫やコミュニケーションなどで)
・自分の書くコードに過度な愛着やこだわりを持たない
・自分の思い通りにいかなくても許容する広い心
・ファイル内容やDB構造など広く記憶する記憶力
・どんなバグにもへこたれない強いメンタル
・行き詰ったらすぐに質問をする
・手遅れになる前に相談する
・小まめに進捗報告をする(実は気付いていない内容がよくある)
・どんな状況においてもポジティブシンキング
・自分の間違いを認められること
・相手の間違いを許せること
・意見が合わなくてもレスバしないでうまく妥協点を見つける、その場を丸く収める
・納期を守る、約束を守る
・ダブルチェック、確認をしっかりとする
・あいさつをする
・遅刻しない
・整理整頓
・清潔感
・笑顔

もうエンジニア関係なくなっちゃった!

今日の名言

If today were the last day of my life, would I want to do what I am about to do today?
(もし今日が人生最後の日だとしたら、今しようとしていることが、本当にしたいことだろうか?)

-スティーブ・ジョブズ

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

配列の重複するオブジェクトを取り除きつつ数える unique関数 group 関数 の実装

この記事は、下記記事のメント欄に書こうと思ったのですが、長すぎるコードになったので単独記事にしました。

配列の重複するオブジェクトを取り除きつつ数えたい - Qiita
https://qiita.com/torajiro1220/items/caac851ae3a3c4eb7fad

上記記事のデータ加工の1つめのものだけ対象です。

const data = [
  {name: 'aaa', age: 18,},
  {name: 'bbb', age: 20,},
  {name: 'bbb', age: 21,},
  {name: 'ccc', age: 21,},
  {name: 'bbb', age: 20,},
];

上記のものを、下記のように加工したいということになります。

[
  { name: 'aaa', age: 18, count: 1 },
  { name: 'bbb', age: 20, count: 2 },
  { name: 'bbb', age: 21, count: 1 },
  { name: 'ccc', age: 21, count: 1 },
],

元記事では reduce の例が載っています。

私は、reduceはあまり可読性がよくないと思っているので使わないようにしています。紹介するユニーク化やグループ化できる関数を作っておき再利用したりすると便利かと思います。

unique関数

こんな感じの unique 関数作っています。外部関数で動作制御でき、detailフラグで結果だけを得るか詳細情報を得るかを分岐させています。

const unique = (
  array, func = v => v, detail = false,
) => {
  const index = [];
  const result = [];
  const count = [];
  array.forEach(v => {
    const funcResult = func(v);
    const indexResult = index.indexOf(funcResult);
    if (indexResult === -1) {
      index.push(funcResult);
      result.push(v);
      count.push(1);
    } else {
      count[indexResult] += 1;
    }
  });
  if (detail) {
    return { index, result, count };
  }
  return result;
};

const data = [
  {name: 'aaa', age: 18,},
  {name: 'bbb', age: 20,},
  {name: 'bbb', age: 21,},
  {name: 'ccc', age: 21,},
  {name: 'bbb', age: 20,},
];

console.log(
  unique(data, d => d.name, {detail: true}),
);
// {
//   index: ['aaa', 'bbb', 'ccc'],
//   result: [
//     { name: 'aaa', age: 18 },
//     { name: 'bbb', age: 20 },
//     { name: 'ccc', age: 21 }
//   ],
//   count: [1, 3, 1]
// }

var result = unique(data, d => d.name, {detail: true});
console.log(
  result.result.map((e, i) => ({ name: e.name, count: result.count[i]})),
);
// [
//   { name: 'aaa', count: 1 },
//   { name: 'bbb', count: 3 },
//   { name: 'ccc', count: 1 },
// ],

console.log(
  unique(data, d => d.name + d.age.toString(), {detail: true}),
);
// {
//   index: ['aaa18', 'bbb20', 'bbb21', 'ccc21'],
//   result: [
//     { name: 'aaa', age: 18 },
//     { name: 'bbb', age: 20 },
//     { name: 'bbb', age: 21 },
//     { name: 'ccc', age: 21 }
//   ],
//   count: [1, 2, 1, 1]
// }

var result = unique(
  data, d => d.name + d.age.toString(), {detail: true}
);
console.log(
  result.result.map((e, i) => ({ name: e.name, age: e.age, count: result.count[i] })),
);
// [
//   { name: 'aaa', age: 18, count: 1 },
//   { name: 'bbb', age: 20, count: 2 },
//   { name: 'bbb', age: 21, count: 1 },
//   { name: 'ccc', age: 21, count: 1 },
// ],

こちらで動作確認可能です。
https://jsbin.com/vazijipiqe/edit?html,js,console

group関数

また、このようなgroup関数を使っても同じデータ加工ができます。

const group = (
  array, func = v => v, detail = false
) => {
  const index = [];
  const result = [];
  array.forEach(v => {
    const funcResult = func(v);
    const i = index.indexOf(funcResult);
    if (i === -1) {
      index.push(funcResult);
      result.push([v]);
    } else {
      result[i].push(v);
    }
  });

  if (detail) {
    return { index, result };
  }
  return result;
};

const data = [
  {name: 'aaa', age: 18,},
  {name: 'bbb', age: 20,},
  {name: 'bbb', age: 21,},
  {name: 'ccc', age: 21,},
  {name: 'bbb', age: 20,},
];

console.log(
  group(data, d => d.name, {detail: true}),
);
// {
//   index: ['aaa', 'bbb', 'ccc'],
//   result: [
//     [{ name: 'aaa', age: 18 }],
//     [
//       { name: 'bbb', age: 20 },
//       { name: 'bbb', age: 21 },
//       { name: 'bbb', age: 20 },
//     ],
//     [{ name: 'ccc', age: 21 }]
//   ]
// }

console.log(
  group(data, d => d.name, {detail: true})
    .result.map(e => ({ name: e[0].name, count: e.length })),
);
// [
//   { name: 'aaa', count: 1 },
//   { name: 'bbb', count: 3 },
//   { name: 'ccc', count: 1 },
// ]

console.log(
  group(data, d => d.name + d.age.toString(), {detail: true}),
);
// {
//   index: ['aaa18', 'bbb20', 'bbb21', 'ccc21'],
//   result: [
//     [{ name: 'aaa', age: 18 }],
//     [
//       { name: 'bbb', age: 20 },
//       { name: 'bbb', age: 20 },
//     ],
//     [
//       { name: 'bbb', age: 21 },
//     ],
//     [{ name: 'ccc', age: 21 }],
//   ],
// }

console.log(
  group(data, d => d.name + d.age.toString(), {detail: true})
    .result.map(e => ({ name: e[0].name, age: e[0].age, count: e.length })),
);
// [
//   { name: 'aaa', age: 18, count: 1 },
//   { name: 'bbb', age: 20, count: 2 },
//   { name: 'bbb', age: 21, count: 1 },
//   { name: 'ccc', age: 21, count: 1 },
// ],

こちらで動作確認可能です。
https://jsbin.com/zikeniteva/edit?html,js,console

まとめ

unique と group は、用途が違うものですが、汎用的に使えるのでこのようなデータ加工を簡単に行うことができました。

unique も group も、自作ライブラリの Parts.js に搭載しています。プロジェクトの開発を楽にしたい方向けの便利関数を多く用意しているので、よかったら使ってみてください。マニュアル作れて無いのでテストコードみないと動きがわからないので、結構上級者用かもですが、直感的に理解できる感じの動作をする関数群を用意しています。

関数実装のサンプルなどや、WebPackのビルド設定参考などにも、どうぞです。

standard-software/partsjs
https://github.com/standard-software/partsjs

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

いまこそ知ってほしい!JavaScriptのconsoleの魅力 2020

こんにちは!
ウェブクルー Advent Calendar 2020 の3日目の記事です。
昨日は @kouchanne さんの「Anyダメ絶対! axios編」でした!
TypeScriptでAPIからのresponseデータにも型をセットしたいときに参考にしたいですね。

アドベントカレンダーのお祭り気分を楽しみながら書いていきます!

JavaScriptのconsoleオブジェクトのメソッド

普段使うわりに意外と検索する対象に入らないのではないでしょうか?
知ってみたら、面白いところも便利なところもあるなぁーって感じました。
いろいろ試したことを書いていきます。

JavaScriptのconsole.log()でフォーマット

CSSのフォーマット

ログを出力するのに使うconsole.log()の出力をCSSでフォーマットできるようです。
強調したいログに適用できて便利ですね。

試してみましょう。

const criminal = "御主人"
console.log(`犯人は・・・%c${criminal} %c、あなたです!!!`, "color: red;", "");

console_log_color.gif
第一引数でCSSを適用したい文字の前に%cディレクティブをつけて、
第二引数以降で宣言したCSSを適用させることができましたね。

console.log()は画像も出せる(Chromiumのブラウザのみ)

CSSで画像設定してもコンソールに出力できるようで、Chromeでちょっと試してみました。

console.log("%cHAPPY HOLIDAYS!%c ",
  'color: #FFFFFF; font-size:64px; font-family:"Georgia"; background: -webkit-linear-gradient(0deg, #00BB00, #FFA500, #FF0000); padding: 8px;',
  'background: url(https://1.bp.blogspot.com/-xRE8fggFEAs/X8BV4cGFpYI/AAAAAAABcgE/sgoe7drgu0kgTkUweELLBeGZK0tT7osKgCNcBGAsYHQ/s1103/christmas_mask_santa_tonakai.png); background-size: 100% 100%; padding: 256px 256px');

chrome_holidays.gif
画像も出せましたね!

画像は以下の「いらすとや」さんのものを使っています。
マスクを付けたサンタとトナカイのイラスト(クリスマス) | かわいいフリー素材集 いらすとや

console.log()のCSSのブラウザでの違い

console.log()のCSSですが、ブラウザによって挙動が違いました。
上記のはAppleのサイトっぽく、文字をくりぬいた形にしたかったのですが、
Chromeだと-webkit-background-clip: text;を適用できなかったですね。

cssに-webkit-background-clip使ったconsole.log()
console.log("%cHAPPY HOLIDAYS!",
  'color: #FFFFFF; font-size:64px; font-family:"Georgia"; background: -webkit-linear-gradient(0deg, #00BB00, #FFA500, #FF0000); -webkit-background-clip: text; color: transparent; padding: 8px;');

chrome_clipできない.gif

Firefoxだと可能でした!
firefox_holidays.gif

逆に、Firefoxだと画像が出せなかったです。
なのでサンタさんの画像を出力するところではChromeで実行したGIF画像を貼りました。

CSSは以下を参考:
CSS3でテキストにグラデーションをかける方法
[CSS]background-clipで文字の形に背景を切り取る
background-clip - CSS: カスケーディングスタイルシート | MDN

いろいろなフォーマット

他にも以下のようなフォーマットがあります!

置換文字列 変換用途 備考
%s 文字列
%dまたは%i 整数
%f 浮動小数点 ※Chromeだと動作しない
%oまたは%O オブジェクト

これは「console - Web API | MDN」や「Console Standard #formatting-specifiers」に書いてある通りで、
全部を一気にFirefoxで試してみるとこんな感じですね。

console.log("%s, %.3f, %i, %o", "a", 1.1, 10,{id: 1}); 

firefox_float.gif

JavaScriptのconsoleのメソッドをログレベル別で出力

info()、warn()、error()

ログレベルを変えて出力することも出来ます。
Chromeでの実行結果です。

console.log("log()");
console.info("info()");
console.warn("warn()");
console.error("error()");

chrome_info.gif

log()info() 同じじゃん」って感じなんですが、
次はFirefoxでの実行結果です。
Firefox_info.gif
info()の出力にはlog()の出力にはない「ⓘ」が出てきました!
Firefoxでなら差が出るようです。

warn()error()は、コンソール開くとよく見かけるやつですね。

これらのほかにdebug()というメソッドもあって、
ChromeだとログレベルがVerboseで、Firefoxだとログレベルがデバッグです。

assert()

error()を条件によって出しわけたいなって時に役立つと思います。

console.assert(true, "エラーは出ない");
console.assert(false, "エラーが出る!");

assert.gif
assert()だと、error()に出なかった「Assertion failed: 」がエラーメッセージと一緒に出るようですね。

info()、warn()、error()、assert()もフォーマット可能

Firefoxで試してみた実行結果です。
設定できる置換文字列を全部設定してます(CSSはFirefoxっぽく色を変えてみました笑)。

console.info('%c %s, %.3f, %i, %o',
  'color: #FFFFFF; font-size:64px; font-family:"Georgia"; background: -webkit-linear-gradient(0deg, #9758F5, #E9207A, #FF8552); -webkit-background-clip: text; color: transparent; padding: 8px;', 
  'a', 1.1, 10,{id: 1}
);

firefox_css.gif
こんな感じで、置換文字列がすべてフォーマットされたものが出力されました。

log()、info()、warn()、error()、assert()のグループ分け

Console Standard #loglevel-severity」に書いてあるのですが、
一般的なグループ分けは以下ですね。

グループ分け メソッド 説明
log log() 一般的なログ
info info() 参考になるログ
warn warn() ユーザーになにか警告するログ
error error(), assert() ユーザーにエラーを示すログ

console.time()で時間を計測

time()

console.time()でタイマーを開始し、
console.timeLog()で途中経過時間を出力、
console.timeEnd()でタイマー終了とかかった時間を出力できます。

測定するときに使えそうですね。

time()で測定してみる

試すのにちょうどいいの探して、フィボナッチ数列求める再帰処理がちょうどいいかもと思って
以下の中で使われていたのを参考にさせてもらっています。
Haskellをかける少女
Haskellをかけない中年

(「昨日のコード」)

const fibo = n => (n === 0 || n === 1)? n: fibo(n - 1) + fibo(n - 2);

(「今のコード」)

const rec = (f2, f1, n) => {
  if (n === 1) return f1;
  return rec(f1, f2+f1, n-1);
};
const fibo = n => (n < 2)? n: rec(0, 1, n);

ハスケル子「昨日のコードだと42番目のフィボナッチ数を求めたくらいでSafariが悲鳴を上げ始めますけど、」
ハスケル子「今のコードなら、1000番目のフィボナッチ数が」
ハスケル子「1ミリ秒以下で返ってきます」

どのくらい違うのか、time()を使ってちょっと測ってみました!
上記の「昨日のコード」の処理をChromeで計測してみたものです。

const fibo = n => (n === 0 || n === 1)? n: fibo(n - 1) + fibo(n - 2);
console.time("MyTimer");
console.log(fibo(30));
console.timeLog("MyTimer");
console.log(fibo(35));
console.timeLog("MyTimer");
console.log(fibo(40));
console.timeEnd("MyTimer");

console_time1.gif
40くらいまで行くと時間がかかっているのが数字に表れていますね。

次に、上記の「今のコード」の処理をChromeで計測してみたものです。

const rec = (f2, f1, n) => {
  if (n === 1) return f1;
  return rec(f1, f2+f1, n-1);
};
const fibo = n => (n < 2)? n: rec(0, 1, n);
console.time("MyTimer");
console.log(fibo(30));
console.timeLog("MyTimer");
console.log(fibo(35));
console.timeLog("MyTimer");
console.log(fibo(40));
console.timeLog("MyTimer");
console.log(fibo(1000));
console.timeEnd("MyTimer");

console_time2.gif
今のChromeは末尾呼出しの最適化されているようで、
他の処理の後で1000番目まで計算しても余裕で1ミリ秒以下で返ってきてるのがわかりますね。

time()で複数のタイマー

time()は第一引数に文字列や数字やオブジェクトをラベルとして入れて、
複数のタイマーを用意することもできます。

timelog()はフォーマット可能?

通常のconsole.log()のように複数の引数をとることはできます。
第一引数に%cの文字を入れた文字列をラベルとして設定して、Chromeで試してみましょう。

const time1 = {label: "%ctimer_1", color: "font-size: 16px; color: red;"};
const time2 = {label: "%ctimer_2", color: "font-size: 16px; color: blue;"};

console.time(time1.label, time1.color, "1スタート", "出力されないです");
console.timeLog(time1.label, time1.color);

console.time(time2.label, time2.color, "2スタート", "出力されないです");
console.timeLog(time2.label, time2.color);

console.timeLog(time1.label, time1.color);
console.timeEnd(time1.label, time1.color, "1エンド", "出力されないです");

console.timeLog(time2.label, time2.color);
console.timeEnd(time2.label, time2.color,  "2エンド", "出力されないです");

timelog_color.gif
複数のタイマーを設定したときに、CSSを適用させるのというのは使えるかもしれないですね。

ところで、CSSがラベルだけじゃなくて経過時間にも適用されてる感じですね?
経過時間には別のCSSを適用できそうですね。
こちらもChromeで試してみましょう。

const timerLabel = "%ctimer%c";
const color_2e2623 = "font-size:32px; color: #fff; text-shadow: 1px 1px 1px #2e2623, -1px 1px 1px #2e2623, 1px -1px 1px #2e2623, -1px -1px 1px #2e2623, 1px 1px 1px #2e2623, -1px 1px 1px #2e2623, 1px -1px 1px #2e2623, -1px -1px 1px #2e2623; background: #332826; padding: 16px 4px;"
const color_53b793 = "font-size:32px; color: #fff; text-shadow: 1px 1px 1px #53b793, -1px 1px 1px #53b793, 1px -1px 1px #53b793, -1px -1px 1px #53b793, 1px 1px 1px #53b793, -1px 1px 1px #53b793, 1px -1px 1px #53b793, -1px -1px 1px #53b793; background: #47b58e; padding: 16px 4px;"

console.time(timerLabel);
console.timeLog(timerLabel, color_53b793, color_2e2623);
console.timeLog(timerLabel, color_2e2623 + "text-transform: uppercase;", color_53b793);
console.timeLog(timerLabel, color_53b793, color_2e2623 + "text-transform: uppercase;");
console.timeLog(timerLabel, color_2e2623 + "text-transform: uppercase;", color_53b793 + "text-transform: uppercase;");
console.timeEnd(timerLabel);

timelog_color_one.gif

ラベルと別で経過時間にCSSでフォーマットすることができましたね。
ただ、FirefoxだとCSS適用できなかったです。

CSSは以下を参考:
HTMLとCSSで作れる!見出しに使えるおしゃれな文字装飾サンプル | CJコラム

console.trace()で呼び出し元を追う

trace()

console.trace()を呼び出した場所に至るまでの呼び出し経路を出力してくれるらしいので、
試してみます。

const a = (s) =>  b(s);
const b = (s) =>  c(s);
const c = (s) =>  console.trace(s);
a("trace()");

chrome_trace_.gif

呼び出し元を追えているのがわかりますね!

trace()はフォーマット可能(Chromium)

trace()のログ、もうちょっと賑やかにしてみたくなりませんか?
Chromeでフォーマットできるか試してみました!

const a = (s) => {
  console.trace(`%c a: %s`, 
   "color: red; font-size: 30px;  background: linear-gradient(transparent 60%, #ffff66 0%); ", s);
  b(s);
};
const b = (s) => {
  console.trace(`%c b: %s`, 
   "color: blue; font-size: 30px;  background: linear-gradient(transparent 60%, #ffff66 0%); ", s);
  c(s);
};
const c = (s) => {
  console.trace(`%c c: %s`,
   "color: green; font-size: 30px;  background: linear-gradient(transparent 60%, #ffff66 0%); ", s);
};
a("trace()");

chrome_trace_color.gif
フォーマットできましたね。
ただ、Firefoxだとフォーマットできないです。残念です!

CSSは以下を参考:
CSSでおしゃれな装飾!コピペで見出しをデザインしよう! | みゆ何でもブログ

counsole.count()でカウント

count()

呼び出された回数を出力してくれるようです。
第一引数に文字列や数値、オブジェクトなどラベルを渡して複数管理でき、
不必要に複数回呼ばれてそうな処理がありそうなときとか確認するのに便利かなと思います。

console.count("a");
console.count("b");
console.count("a");

count.gif

count()だとフォーマットはできませんでした

count()は第一引数しか見ていないようで複数の引数を渡しても意味ないです、
フォーマットはできません。

countReset()でカウンターリセット

countReset()でカウンターをリセットできます。これはブラウザですこし違いがありました。
Chromeの実行してみた結果です。

console.count("a");
console.count("a", "b");
console.count("a", "b", "c");

console.log("リセット!");
console.countReset("a");

console.count("a");

chrome_countreset.gif

Firefoxでも実行してみたものです。
firefox_count.gif
countReset()のときに0を出力してくれてて、Firefoxのほうがちょっと親切です笑

console.group()でコンソールのログに階層をつける

複雑な時のログに使えるかもしれないです。

group()、groupCollapsed()

group() 以降の出力を別レベルにインデントして、インライングループを作ってくれるメソッドです。

console.group("1st");
console.group("2nd");
console.group("3rd");

group.gif
group()で出力される文字はすこし大きめに表示されるみたいですね。
わかりやすいですね。

groupCollapsedは最初から折りたたまれた状態で出力します。

console.groupCollapsed("1st");
console.groupCollapsed("2nd");
console.groupCollapsed("3rd");

groupCollapsed.gif

group()の引数

group()groupCollapsed() は引数にとる文字列はログとして出力するために使うようで、
引数の文字が同じでも変えても特に結果に影響はありませんでした。
引数を複数指定することも出来ます。

group()はフォーマット可能

引数がログに出力するために使われ、複数指定可能なところ、
ちょっと気になりませんか?
group()もフォーマットできるかなと試してみました。

for(let n = 1; n < 10; n++){ 
    console.group(`%c Group:%s `, 
    `font-size: ${n*10}px; font-style: italic; letter-spacing: .1em; color: white; text-shadow: -4px 3px 0 #fa4141, -8px 6px 0 black;background:#fa4141; padding: ${n}px;`,n);
}

group_9.gif
やったね!

groupEnd()

groupEnd()1回あたり1階層上に戻れます。
group()と組み合わせて試してみました。

for(let n = 1; n < 7; n++){ 
    console.group(`%c Group:%s `, 
    `font-size: ${n*10}px; font-style: italic; letter-spacing: .1em; color: white; text-shadow: -4px 3px 0 #fa4141, -8px 6px 0 black;background:#fa4141; padding: ${n}px;`,(n + 9).toString(36).toUpperCase());
}
for(let n = 6; n > 0; n--){ 
    console.groupEnd();
    console.log(`%c Group:%s `, 
    `font-size: ${n*10}px; font-style: italic; letter-spacing: .1em; color: white; text-shadow: -4px 3px 0 #fa4141, -8px 6px 0 black;background:#fa4141; padding: ${n}px;`,(n + 9).toString(36).toUpperCase());
}

group_end.gif

groupEnd()で上の階層に戻れてますね。
ちなみに、これはFirefoxでも同じように動きました!

CSSは以下を参考:
CSS見出しデザイン参考100選!コピペ可!どこよりも詳しく解説! | JAJAAAN

console.table()でデータを表で出力

table()で配列を表に出力

普通の配列で試すとこんな感じですね。
比較のためにlog()table()の2つで出力してみます。

const arr = [1,2,3,4,5];
console.log("log()", arr);
console.table(arr);

table_arr.gif
log()だと配列のオブジェクトが表示されるだけですが、
table()だと表が出力されて並べ替えも出来ます。
表の下には配列のオブジェクトも表示されているので多めに情報をもらえる感じですね。

table()でオブジェクトの配列を表に出力

こちらも、
比較のためにlog()table()の2つで出力してみます!
この Console Standard の「Summary of formatting specifiers」の表をtable()を使って再度表にしてます。

const descriptions = []
descriptions.push({"Specifier":"%s","Purpose":"Element which substitutes is converted to a string","Type Conversion":"%String%(element)"});
descriptions.push({"Specifier":"%d or %i","Purpose":"Element which substitutes is converted to an integer","Type Conversion":"%parseInt%(element, 10)"});
descriptions.push({"Specifier":"%f","Purpose":"Element which substitutes is converted to a float","Type Conversion":"%parseFloat%(element, 10)"});
descriptions.push({"Specifier":"%o","Purpose":"Element is displayed with optimally useful formatting","Type Conversion":"n/a"});
descriptions.push({"Specifier":"%O","Purpose":"Element is displayed with generic JavaScript object formatting","Type Conversion":"n/a"});
descriptions.push({"Specifier":"%c","Purpose":"Applies provided CSS","Type Conversion":"n/a"});
console.log("log()", descriptions);
console.table(descriptions);

table_obj.gif
オブジェクトの配列だと見やすいですね!
使える用途はありそうです。

table()でクラスの配列を表に出力

オブジェクトのKeyが違っていても、
クラスがサブクラスでも、別のクラスでも平気なのか?
平気だろうなと想像できるのですが試してみました。

class Data {
  constructor(id, name) {
    this.id = id;
    this.name = name;
  }
}
class DummyData {
  constructor(id, name) {
    this.id = id;
    this.name = name;
  }
}
class SubData extends Data {
  constructor(id, name, property, boss) {
    super(id, name);
    this.property = property;
    this.boss = boss;
  }
}
const data      = new Data(1, "Data");
const dummyData = new DummyData(2, "DummyData");
const subData   = new SubData(3,"SubData", 5000, data);
console.log("log()", [data, dummyData, subData]);
console.table([data, dummyData, subData]);
console.table([data, dummyData, subData],["id", "name"]);

table_class.gif

見ればわかるように、
オブジェクトのkeyと階層が一致していれば同じ列に入れてくれるみたいです。
subdataのbossのDataは文字列でないのでクラス名が出力されているようですね。

またtable()の第一引数のオブジェクトのkeyを
第二引数で配列で指定すれば表に出力する列を設定できるようです。

table()で文字列を表に出力は出来ません

表になりません。
log()で出力したようになります。

table()はフォーマットできるかな?

log()のようになるということは文字列をフォーマットできる?
Chromeで試してみました。

console.table(`%cDOLCE%c&%cGABBANA`,
"font-size: 64px; font-family:Roboto,Helvetica Neue,sans-serif;",
"font-size: 32px; font-family:Roboto,Helvetica Neue,sans-serif; padding: 4px;",
"font-size: 64px; font-family:Roboto,Helvetica Neue,sans-serif;");

table_css.gif

CSS行けましたね。
ただ、table()の出力する文字列にCSSを適用させられても、表にCSSを適用させることはできなかったです笑

ちなみに、Firefoxはフォーマットができなくて文字が出力されるだけでした。
Chromiumのブラウザであればフォーマットが可能です。

最後に

楽しんで書いたのですが、
読んで楽しんでいもらえたり、役立つ情報がみつかったなら嬉しいです。
全部紹介したわけじゃないので、参考のページも見てもらえたらより情報が手に入ると思います。

もうすぐ年末ですね。
紅白歌合戦でどうなるのかなって気になってるアーティストさんがいるのでちょっと楽しみです。

明日は@shintaro_ike さんになります。
よろしくお願いします!

ウェブクルーでは一緒に働いてくれる方を絶賛募集中です!
興味のある方はぜひお問い合わせください。
開発エンジニア | 株式会社ウェブクルー

参考

動作環境

PC : Windows 10 Pro (64 ビット)
Chormeバージョン: 86.0.4240.198(Official Build) (64 ビット)
Edgeバージョン: 87.0.664.47 (公式ビルド) (64 ビット)
Firefox : 83.0 (64 ビット)

参考情報

console - Web API | MDN
https://developer.mozilla.org/ja/docs/Web/API/console
Console Standard
https://console.spec.whatwg.org/

[JavaScript]使い分けるだけで今すぐデバッグ効率を上げる、consoleオブジェクトの関数
https://qiita.com/kashira2339/items/874f95aaaa59f4a17d3d
console.logまとめ 2016年夏
https://qiita.com/ykyk1218/items/0f5858d077d43a49cfe2
Node.js v10.0.0でconsole.table()追加&console.log()アップデートに感動したので早速試してみる
https://qiita.com/n0bisuke/items/60e52cde73343bbe7703
console.log()系メソッドまとめ
https://qiita.com/mooriii/items/afad70b19f8150f4f1b1
便利なconsole.xxx
https://qiita.com/ryo2132/items/64c616a4cf0ae8770a9f
【2019年4月版】JavaScriptのconsoleがすごいことになってた。
https://qiita.com/koinori/items/83f119cb2d82c0ca2c1e
JavaScriptでの賢いconsole.log( )の使い方 & その他便利なconsole.xxx( )使い方まとめ (dir・table・warn・groupとか)
https://qiita.com/mtoyopet/items/7274761af5424cee342a
【consoleオブジェクト完全版】ログ出力を極める!
https://qiita.com/sa9ra4ma/items/bc6c12044582126c1be9

マスクを付けたサンタとトナカイのイラスト(クリスマス) | かわいいフリー素材集 いらすとや
https://www.irasutoya.com/2020/11/blog-post_552.html

CSS3でテキストにグラデーションをかける方法
https://marie-web.design/blog/text-gradation/
[CSS]background-clipで文字の形に背景を切り取る
https://qiita.com/teinen_qiita/items/9c23ef3befcb1119bce2
background-clip - CSS: カスケーディングスタイルシート | MDN
https://developer.mozilla.org/ja/docs/Web/CSS/background-clip
CSSでおしゃれな装飾!コピペで見出しをデザインしよう! | みゆ何でもブログ
https://miyu-info.com/midashi/
HTMLとCSSで作れる!見出しに使えるおしゃれな文字装飾サンプル | CJコラム
https://citrusjapan.co.jp/column/cj-column/w006_201803.html
CSS見出しデザイン参考100選!コピペ可!どこよりも詳しく解説! | JAJAAAN
https://jajaaan.co.jp/css/css-headline/
font-familyで指定できるフォント名一覧
https://w3g.jp/sample/css/font-family

Haskellをかける少女
https://qiita.com/Yametaro/items/11dcd3ec26027ff30214
Haskellをかけない中年
https://qiita.com/Yametaro/items/e163affa4b2db48f9957

Food & Beverage by Dolce & Gabbana | Italian specialties | Dolce & Gabbana | Dolce & Gabbana
https://world.dolcegabbana.com/food-beverage/

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

JS オブジェクト,プロパティ,メソッドについて (JSアウトプット)

あいさつ

初めての人は初めまして!知っている人はこんにちは!
中学生バックエンドプログラマーのAtieです!
今回はJSの「オブジェクト」「プロパティ」「メソッド」について学んできたのでアウトプットします!
では!

オブジェクト

オブジェクトとは「データと機能をまとめたもの」です

プロパティ

プロパティとは「オブジェクト内のデータ」に相当します

メソッド

メソッドとは「オブジェクト内の機能(関数)」に相当します

超ざっくり説明しました

食べ物を例に説明

先ほどの説明だけではわかりずらいので「食べ物」でたとえてみます!

タイトルなし.png

”食べ物”というオブジェクトの中に”果実”というオブジェクトが入っています
プロパティは”果物”メソッドは”甘い”
そしてその”果物”オブジェクトの中にバナナというオブジェクトが入っています
プロパティは”バナナ”メソッドは”86kcal”

同じようにりんごなどもあります

ではこれをコードで再現してみます

main.js
let food = {
    fruits:[
        {
            name: "banana",
            kcal: function() {
                console.log('バナナのカロリーは86kcalです!');
            }
        },
        {
            name: "apple",
            kcal: function() {
                console.log('リンゴのカロリーは57kcalです!');
            }
        } 
    ]
}

console.log(food.fruits[0].name);
food.fruits[0].kcal();
console.log(food.fruits[1].name);
food.fruits[1].kcal();

大きく二つに分かれています
上がオブジェクトの定義部分です
オブジェクトを定義するには

main.js
let オブジェクト名 = {
    //オブジェクトの内容
};

と定義します
中身を見てみましょう

main.js
fruits:[
        {
            name: "banana",
            kcal: function() {
                console.log('バナナのカロリーは86kcalです!');
            }
        },
        {
            name: "apple",
            kcal: function() {
                console.log('リンゴのカロリーは57kcalです!');
            }
        } 
]

fruitsという配列の中にnameというプロパティとkcalというメソッドが入っています
メソッドの処理を実行するとコンソールに「バナナのカロリーは86kcalです!」などと表示されるようになっています
配列については今度解説します

オブジェクト内にアクセスする方法ですが二通りあります

.で区切ってアクセスする方法

mian.js
food.fruits[0].kcal();

こんな感じです
基本この方法でアクセスしますが変数などにアクセスする場合などは二つ目の方法を使う必要があります

角カッコを使ってアクセスする方法

main.js
food["fruits"][0]["kcal"]();

.で区切る場合に比べて少しわかりずらいですがいちようこの方法でもアクセスできます

window document

最後にwindow documentについてです

JSはブラウザで動く言語。ブラウザ自体もオブジェクトで定義されていてメソッドやプロパティにアクセスできます
その機能を使うことでDOM操作やデータのやり取りができます

main.js
window.fetch(); //外部とデータをやり取りする
window.document.getElementById("food"); //DOM要素のfoodという要素を収得する

documentは省略可能です
ほかにもたくさんwindowオブジェクトのメソッドがあります

例えばJSでよく使われる「console.log」や「alert」なども実はwindowオブジェクトのメソッドだったのです

main.js
window.console.log("hello");
window.alert("hello");

これでも正常に実行できます

windowというオブジェクトを使うことでJSのお仕事であるデータのやり取りDOM操作ができるようになります!

まとめ

オブジェクトはデータと機能をまとめたもの
プロパティはデータに相当する
メソッドは機能に相当する
ブラウザもオブジェクトで使うことでJSのお仕事の二つができるようになる

最後に

今回の記事は少し短かったですがこれもかなり大切な概念です

ではまた!次の記事で!

AtieのTwitter
しまぶーのIT大学さんのTwitter

学習、参考動画

【JavaScript入門 #3】オブジェクト・プロパティ・メソッドについて理解しよう【ヤフー出身エンジニアの初心者向けプログラミング講座】

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

SweetAlertでメッセージ表示

SweetAlertでメッセージ表示

基本情報

本家

https://sweetalert.js.org/guides/#getting-started

概要

JavaScriptの標準装備でアラート、メッセージを出しても良いが、ひと手間加えて、お洒落にメッセージを表示する。

基本的な使い方

メッセージを出すだけ

4C2F4B81-4B95-43FE-A912-D86B9DBF3F8F_4_5005_c.jpeg

sweetAlertSample.js
function basicSample(){
    swal("基本の使い方");
}

タイトルとテキストをつける

B3D15906-35A7-4A0E-995A-A1EF9E1599DD_4_5005_c.jpeg

sweetAlertSample.js
function basicSample2(){
    swal("ここにタイトル!", "そして、ここにテキスト!");
}

アイコンをつける

D558C8C3-A779-4F5B-9EFA-0DCCB1B6ECF8_4_5005_c.jpeg

sweetAlertSample.js
function basicSample3(){
    swal("アイコンつき!", "ボタンも付いてる!", "success");
}

アイコンの使い分け

使用可能なアイコンは4種類。

success

BFC66868-6CF5-4C00-A577-6D6410B5A979_4_5005_c.jpeg

sweetAlertSample.js
function iconSample1(){
    swal({
        title: "success",
        text: "successのicon",
        icon: "success",
        button: "OK",
    });
}

error

11E3B688-BDCB-46D3-81DC-7BBE81FB190B_4_5005_c.jpeg

sweetAlertSample.js
function iconSample2(){
    swal({
        title: "error",
        text: "errorのicon",
        icon: "error",
        button: "OK",
    });
}

info

A8A6915E-D6E7-4206-9D26-C51CB089CB34_4_5005_c.jpeg

sweetAlertSample.js
function iconSample3(){
    swal({
        title: "info",
        text: "infoのicon",
        icon: "info",
        button: "OK",
    });
}

warning

EEBB3C38-9B60-4073-9001-5D9409729ADE_4_5005_c.jpeg

sweetAlertSample.js
function iconSample4(){
    swal({
        title: "warning",
        text: "warningのicon",
        icon: "warning",
        button: "OK",
    });
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ReactがわからないというよりJavaScriptがわからなかったのです

はじめに

Reactはユーザインタフェース構築のためのJavaScriptライブラリです。次のような利点があるため、HTMLCSSjQuery を学習し、Web制作者目指しているような方もReactにも興味を持つと良いと思いました。JavaScriptをある程度学習していれば、Reactはそんなに難しくないからです。

  1. コンポーネントベース
    • ブロックを組み立てるようにユーザインタフェースを構築できる
    • 見通しがよく、苦痛が少ない
  2. 一度学習すれば応用が効く
    • サーバー上でもレンダーできる
    • モバイルアプリケーションの中でも動く(React Native)

Reactを二ヶ月くらい学習し、真似してコードが多少書けるようになった時、私はそのコードにどうにも腑に落ちませんでした?。その頃の私はReact以前にJavaScriptがよくわかっていなかったのです。そんな少し前の自分に説明するつもりで書いてみた記事になります。

サンプルコード1

このサンプルは[Toggle]ボタンをクリックするたびに、ボタンの上の絵文字が?と?の間で切り替わるという簡単なものです。

image.png

import React, { useState } from "react";

const App = () => {
  const [liked, toggleLiked] = useState(false);
  const handleToggle = () => toggleLiked(!liked);

  return (
    <>
      <p>useState Sample #1</p>
      <p>{liked ? "?" : "?"}</p>
      <button onClick={handleToggle}>Toggle</button>
    </>
  );
};

export default App;

StackBlitz

今の自分useStateステートフック)を使用しています。シンプルで何の問題もないですね。」

昔の自分「なんだか、わかったようなわからないような。すっきりしないのです?。そもそもフックってどういうこと?意味があまりわからないです?。」

今の自分フックは何種類かあるのですが、ステートフックとはクラスコンポーネントでは普通に持つことができるステート(コンポーネントがその内部に変数のように持つオブジェクト)をファンクショナルコンポーネント(関数コンポーネント)にも持たせられるようにするための関数です。それでは、次のコードはわかりますか?」

サンプルコード2

こちら見た目や動きは先程のものと同じです。コードの行数は16から36(うち3行はコメントだけの行)と倍増しました。

import React from "react";

export default function App() {
  const stateArray = React.useState(false); // stateArray[0]の初期値をfalseにする
  // 戻り値は以下の配列
  // stateArray[0]: true(?) または false(?)が入る
  // stateArray[1]: stateArray[0]を設定する関数
  let liked = stateArray[0];
  const setLiked = stateArray[1];

  function handleToggle() {
    if (liked === true) {
      setLiked(false);
    } else {
      setLiked(true);
    }
  }

  if (liked === true) {
    return (
      <div>
        <p>useState Sample #2</p>
        <p>?</p>
        <button onClick={handleToggle}>Toggle</button>
      </div>
    );
  } else {
    return (
      <div>
        <p>useState Sample #2</p>
        <p>?</p>
        <button onClick={handleToggle}>Toggle</button>
      </div>
    );
  }
}

StackBlitz

昔の自分「コードが長くなりましたが、大分親しみやすいコードです。React.useSateが要素数2の配列を返し、0番目の要素が設定した値の参照用。1番目の要素が、その値を設定する関数を指し示すわけですね。」

今の自分サンプルコード1(StackBlitz)の以下の部分は、Reactライブラリのエントリポイント(名前空間)のReactをインポートし、Reactの中にあるuseStateを特定の名前付きでインポートをしています。この辺りの詳細はこちらを見てください。」

import React, { useState } from "react";

今の自分サンプルコード1(StackBlitz)の以下の部分は、分割代入(Destructuring assignment)という構文で、配列から値(あるいはオブジェクトからプロパティ)を取り出して、別個の変数に代入できる書き方になります。

const [liked, toggleLiked] = useState(false);

昔の自分アロー関数は知っているのですが、Appという関数の中に handleToggleという関数があるのはどういうことでしょうか?」
今の自分「これはクロージャというやつです。マトリョーシカ人形の親子で例えると、親(関数)から子(関数)の心の内(子の変数など)は見ることができませんが、子からは親の心の内(親の変数など)を見ることができる仕組みです。」

昔の自分「なるほど。この辺り、Reactの話しではなくて、全部JavaScriptの話ですね?」

今の自分「そのとおりです。そして、return で返しているHTMLみたいなタグ構文はJSXと呼ばれるJavaScriptを拡張した書き方です。」

昔の自分「それは教材で習いました。これがコンパイルされて、ブラウザで実行可能なコードに変換するのでしたね?」

今の自分「そうです。上のコードはReactのライブラリの関数を1つ呼んでいることと、JSXが使われている以外は表面上はただのJavaScriptとして理解できます。」

Reactを少し勉強してみて感じたこと

1. JSXを用いたUIコンポーネント作成は楽しくてわかりやすい

  • 見通しよく作成でき、他人と分担もしやすいです
   <Navbar />
   <TodoList />
   <Footer />

2. 親コンポーネントが子コンポーネントに props を渡す方法は1方向で単純でわかりやすい

3. ファンクショナルコンポーネントクラスコンポーネントよりシンプルで書きやすい

  • これから作るだけならファンクショナルコンポーネントだけでよいと思います
  • 必要ならファンクショナルコンポーネント以下のフックを組み合わせるなどして、ローカルステート、グローバルステート、ライフサイクルを扱うなど、クラスコンポーネントと同じようなことができる

4. BabelWebpackとかのややこしい設定は不要

5. CSSは好みに合わせて色々な書き方ができる(CSS とスタイルの使用

  • 個人的にはstyled-componentsが良さそうに思っていますが、賛否両論あるみたいですね。

参考資料

  1. 現代の JavaScript チュートリアル
  2. JavaScript学習の格安教材( LinkdIn Premium、ドットインストールPREMIUM、YouTube ) // 私のブログのメモです
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JS】2次元のオブジェクトの要素を変更・追加する方法

2次元のオブジェクトの要素を変更・追加するには、mapを2回使う必要がある。

2次元の要素の変換をした後、1次元の要素を変換する処理となる。

目次

  1. 要素の追加
  2. 要素の変換
  3. 要素の追加と変換
  4. 指定した要素のみ値を変更/追加
  5. 要素を変更/追加してから特定の要素を排除

2次元のオブジェクトの要素を変更する実例

要素の追加

[{a:1, b:2, c:[{d:1, e:2}]}のようなオブジェクトのプロパティcの値にf:9を新たに追加する場合は以下のようになる。

obj = [{a:1, b:2, c:[{d:1, e:2}]}, {a:3, b:4, c:[{d:3, e:4}]}

obj = obj.map( elem => {
      newC = elem.c.map( x => {
       return {...x, f: 9}
      })

      return {...elem, c: newC}
})

console.log(obj)
//[{a:1, b:2, c:[{d:1, e:2, f:9}]}, {a:3, b:4, c:[{d:3, e:4, f:9}]

obj.map( elem => {処理}
まずは、元のオブジェクトにmapを使って、要素をひつづつ取り出す。

newC
プロパティcにf:9を追加した要素を代入する変数を準備。

elem.c.map( x => {処理})
元のオブジェクトの要素のプロパティcの要素を一つずつ取り出す。

ここでは x = {d:1, e:2} 。
プロパティcの中に複数のオブジェクトがある場合は、一つづつ取り出す処理となる。

return {...x, f: 9}
スプレッド構文でプロパティcを展開して、f:9 を追加した後、再度{ }で閉じる。これを戻り値とする。

この処理で、 newC = [{d:1, e:2, f:9}] となる。

return {...elem, c: newC}
1次元目のオブジェクトをスプレッド構文で展開し、作成したnewCをプロパティCの値に置き換える。

この処理で、 {a:1, b:2, c:[{d:1, e:2, f:9}]} となる。

これを要素の数だけ繰り返し完了となる。


要素の変換

要素を変換する場合は、上記の処理で、プロパティcを展開した時に、変換したいプロパティを指定して値を代入すればOK。

obj = [{a:1, b:2, c:[{d:1, e:2}]}, {a:3, b:4, c:[{d:3, e:4}]}

obj = obj.map( elem => {
      newC = elem.c.map( x => {
       return {...x, d: 100}
      })

      return {...elem, c: newC}
})

console.log(obj)
//[{a:1, b:2, c:[{d:100, e:2}]}, {a:3, b:4, c:[{d:100, e:2}]

{...x, d: 100}
この処理で、プロパティcの要素(x)を展開し、その中のプロパティdの値を100に変換している。


要素の追加と変換

追加と変換を行う場合は、上記の処理を組み合わせる。

obj = [{a:1, b:2, c:[{d:1, e:2}]}, {a:3, b:4, c:[{d:3, e:4}]}

obj = obj.map( elem => {
      newC = elem.c.map( x => {
       return {...x, d: 100, f: 9}
      })

      return {...elem, c: newC}
})

console.log(obj)
//[{a:1, b:2, c:[{d: 100, e: 4, f: 9}]}, {a:3, b:4, c:[{d: 100, e: 4, f: 9}]

{...x, d: 100, f: 9}
dを100に変更し、f: 9を新たに追加している。


指定した要素のみ値を変更/追加

上記の例だと該当するプロパティを持つ要素が一括変換となる。

ifで条件分岐を設けることで、指定した要素のみ変換することができる。



▼2番目の要素のみ変更・追加

obj = [{a:1, b:2, c:[{d:1, e:2}]}, {a:3, b:4, c:[{d:3, e:4}]}

obj = obj.map( (elem, index) => {
    if(index==1){
            newC = elem.c.map( x => {
                 return {...x, d: 100, f: 9}
            })
            return {...elem, c: newC}

    } else {return elem}
})

console.log(obj)
//[{a:1, b:2, c:[{d:1, e:2}]}, {a:3, b:4, c:[{d: 100, e: 4, f: 9}]

obj.map( (elem, index) => { }
mapの第2引数はインデックス番号を格納する変数となる。今回は、if文の条件としてこれを利用。

if( index == 1 ){ 処理 }
インデックス番号が1の時のみ、newCを作成する処理を実行する。

else { return elem }
ifの条件がtrue以外の時は、元の要素をそのまま返す。


要素を変更/追加してから特定の要素を排除

オブジェクトに変更を加え、更に不要な要素を削除する場合は、filterで指定条件に該当する要素を除外すればOK。

例えば、以下のような3つの要素が入った2次元のオブジェクトで、2番目の要素のみプロパティを変更・追加し、1番目の要素を削除する場合を考える。

元のオブジェクト
[{a:1, b:2, c:[{d:1, e:2}]}, {a:3, b:4, c:[{d:3, e:4}]}, {a:5, b:6, c:[{d:5, e:6}]}]`

  ↓

処理後
[{a:3, b:4, c:[{d: 100, e: 4, f: 9}]}, {a:5, b:6, c:[{d:5, e:6}]}]

▼コード実例

obj = [{a:1, b:2, c:[{d:1, e:2}]}, {a:3, b:4, c:[{d:3, e:4}]}, {a:5, b:6, c:[{d:5, e:6}]}]

obj = obj.map( (elem, index) => {
    if(index==1){
            newC = elem.c.map( x => {
                 return {...x, d: 100, f: 9}
            })

            return {...elem, c: newC}

    } else {return elem}
})
.filter( (_elem, _index) => {
        return !(_index == 0)
} )

console.log(obj)
//[{a:3, b:4, c:[{d: 100, e: 4, f: 9}]}, {a:5, b:6, c:[{d:5, e:6}]}]

オブジェクトの追加・変更処理はこれまでと同様。
最後に filterで、index番号が0となる要素を除外している。



2次元のオブジェクトを変更する場合は、mapを2つ使う。変換したい次元の数だけmapが入れ子になる。

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

CSS について深い知識はないけどこれだけ知ってれば何とかなるでしょ、的な雑記帳

CSS が苦手です。border とか margin とかちょろちょろっと触るだけならできますけど、flexbox とか新しいやつはほとんど知りません。とはいえフロントエンド畑にいると、CSS フレームワークを使わずに自力で CSS 書いて見た目を調整する事とか、ありますよね(管理画面系とか)。

CSS について深い知識はないけどこれだけ知ってれば何とかなるでしょ、的な雑記帳としてメモを残しておこうと思います。

ブロック要素をドラッグ&ドロップでリサイズ可能に

https://liveweave.com/ZdsRON

TL;DR
- resize でリサイズ可能になる

  <header>
    <div>contents</div>
    <div>contents</div>
    <div class="resizable-h">resizable-h</div>
  </header>

  <nav class="resizable-v">resizable-v</nav>
.resizable-h {
  min-width: 10em;
  overflow: hidden; /* これがないとリサイズ効かない */
  resize: horizontal; /* 横方向のリサイズ */
  background: #516f8d;
}

.resizable-v {
  min-height: 10em;
  overflow: hidden; /* これがないとリサイズ効かない */
  resize: vertical; /* 縦方向のリサイズ */
  background: #516f8d;
}

dl>ddの空要素にハイフンをデフォルト表示

https://liveweave.com/3lBwTm

TL;DR
- dd:empty で空要素の判定ができる

<dl>
  <dt>Artist</dt>
  <dd>Queen</dd>
  <dt>Title</dt>
  <dd>Made in Heaven</dd>
  <dt>Release date</dt>
  <dd></dd> <!-- 空要素 -->
  <dt>Review</dt>
  <dd>this is the best Album</dd>
</dl>
dd:empty::before {
  content: '-';
}

フォーカスした時にバリデーションエラー表示

https://liveweave.com/yrDXFO

TL;DR
- focus-within を使えば div タグに「子要素がフォーカスを持っている時」の CSS を書ける
- focus-visible でフォーカスリングを消す
- caret-color でキャレットの色を変更

<!-- FontAwesome 使用 -->
<div>
  <i class="fa fa-user"></i>
  <input type="text">
</div>
input {
  caret-color: red;
}

/* 子要素がフォーカスを持つ時に適用される CSS */
div:focus-within i {
  color: #f56161;
}
div:focus-within input {
  border: 1px solid #f56161;
}

/* 影つけて強調 */
input:focus {
  box-shadow: 2px 2px 2px 0 #f56161;
}

/* フォーカスリングを消す */
input:focus-visible {
  outline: none;
}

横並びグリッドレイアウト

https://liveweave.com/XiBLlR

TL;DR
- grid-template-columns: repeat(3, 30%) で 30 %幅の列を 3 個作る
- grid-template-columns: 100px 90px 80px とすれば列ごと個別の幅指定も可
- grid-template-rows とすれば列でなく行で同じ事ができる

<div>
  <article><img src="https://picsum.photos/id/237/200/300"/></article>
  <article><img src="https://picsum.photos/id/1025/200/300"/></article>
  <article><img src="https://picsum.photos/id/1062/200/300"/></article>
</div>
div {
  display: grid;
  grid-template-columns: repeat(3, 30%);
  grid-gap: 3%;
}

ライブラリ使わないフワッと出るダイアログ

https://liveweave.com/Ba3UZZ

TL;DR
- <dialog> 設置
- document.getElementById("id").showModal() でダイアログオープン
- CSS で transition を使えばフワッと感を出せる

<dialog id="dialog">
  <!-- form がないとボタンが反応しないので必須 -->
  <form method="dialog">
    <p>これ便利じゃないですか?</p>

    <!-- button の value が dialog.returnValue になる -->
    <button value="yes">yes</button>
    <button value="no">no</button>
  </form>
</dialog>

<button id="trigger">Open dialog</button>
const $trigger = document.getElementById("trigger");
const $dialog = document.getElementById("dialog");

$trigger.addEventListener("click", () => {
  $dialog.showModal();
});

$dialog.addEventListener("close", () => {
  $result.value = $dialog.returnValue;
});
dialog {
  /* opacity 効かせるために display 必須 */
  display: block;
  opacity: 0;
}

dialog[open] {
  /* 2秒間かけて opacity を 0 > 1 に変化させる(フワッ) */
  opacity: 1;
  transition-property: opacity;
  transition-duration: 2s;
  transition-timing-function: ease;
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

膝の写真を送ると変形性膝関節症か判定するbotを作ったので、膝が痛いご両親・ご家族に使ってください。

「膝のレントゲンなんて持ってないっすよ。」

以前に書いた記事、『誰が使うかわからないけど、膝のレントゲン写真を送ったら、その膝がどの程度痛んでいるのか教えてくれるラインbotを作ってみた。』を様々な人に紹介したところ、「すごくいいと思うけど、膝のレントゲンなんて持ってないよ(笑)」と多くの方から生暖かい目で諭されました。

安心してください。想定の範囲に入ってますよ。

そこで、今回は膝の外表面写真からその膝が変形しているかどうか判定するbotを作りました。
挙動は以下の通りです。

* 「変形性膝関節症って何?」という方は、僕が書いたこちらの記事をぜひお読みください。
変形性膝関節症とは:その治療法・進行予防について

注意1) 「変形性膝関節症って何?」という方は、僕が書いたこちらの記事をぜひお読みください。
変形性膝関節症とは:その治療法・進行予防について

注意2) このボットはあくまで啓蒙活動の一貫で作成したもので、正確な診断ツールではありません!最終的な診断については、おかかりいただいた先生にお伺いください。

システムの概要

9ea404e8-d468-6d23-5b48-75818889e05d.png

開発環境について

今回はAIメーカーを利用して実装いたしました。
実装の内容については上記の記事『誰が使うかわからないけど、膝のレントゲン写真を送ったら、その膝がどの程度痛んでいるのか教えてくれるラインbotを作ってみた。』をご確認ください。

判定の方法の医学的根拠について

今回、判定の方法に用いた医学的根拠について詳述します。興味のある方はお読みください。
「早く使いたい!」という方は、一番下のQBコードまで進んでください。

1)膝の痛みを主訴に受診された患者様の膝外表面写真を、膝を中心に足関節が入るように撮影

2)上記患者様の膝のレントゲン写真を、経験のある整形外科(つまり私)が、KL分類をもとに分類
*ちなみにKL分類は下記の分類です。
スクリーンショット 2020-12-01 18.23.24.png

3)レントゲンの分類をもとに、外表面写真を正常群22例(グレード0とグレード1)、変形性膝関節症群35例(グレード2,グレード3,グレード4)の2群に分類

4)AIメーカーに正常膝と変形性関節症膝の2つのラベルを用意し、そこに画像をアップロードして学習

完成したラインbot

完成したラインbotのQRコードがこちらです。
E9E8B47D-2C4F-41AD-8C51-6289E33D526A.png

使用上注意点と今後の課題

1)まだまだ精度が低いので写真を増やしたり、撮り方を統一したりなどの調整を行なっていきます。
2)膝を中心に足首が入るように素足で写真を撮ってください。
3)繰り返しになりますが、診断ツールではありません。啓蒙ツールです。ご利用は計画的にお願いします。

その他の記事

1) 近すぎると小池都知事が『密です。』と連呼するデバイスを作ったら腹筋が崩壊したので、皆さんにも試して欲しい。
2) 憧れのギニュー特戦隊の誰に似てるか判定するLINEbotを作ったから、ぜってぇ見てくれよなっ!

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

Most Breakthrough Generatorではありますまいか

この記事は

Works Human Intelligence社、ワークス社とは関係ない、文責:個人の趣味の投稿です。
私は何がやりたいのか、あるいは何がやりたくないのかという話、私の場合 を考えたついでにバランスを取りたくて書きました。

Most Breakthrough Generatorとは

日常の何気ない文章をものすごい問題解決にしてくれるブレイクスルーエンジンです。クソアプリアドベントカレンダーとても賑わっているようですが、これは決してクソアプリではなくモストブレイクスルーアプリです。

https://e99h2121.github.io/breakthrough-generator/

image.png
image.png

というかジョークおもちゃです、こんなの業務中に作っててごめんなさい
なるほど、ブレイクスルーしています。

Most Breakthrough

Most Breakthroughとは、かつてのワークスアプリケーションズ社時代の社内表彰です。英語としてどうなの。

裏側

https://github.com/e99h2121/breakthrough-generator/blob/main/index.html

完全にクライアントサイド

javascriptだけで作っています。javascriptはお手軽で良かった
全てクライアントサイドで解析を行うため、セキュリティの観点から見て安全です。

Ver.1 ランダム、文字列置換

ver1.js
<script type="text/javascript">
    function breakthrough() {
        const messages = ['問題解決こそが仕事でありますぞ。',
                          '本当の仕事とは、何か大きな問題に直面したときから始まる。',
                          'プロ、もしくはプロであるべきポジションの仕事人の無自覚や無神経さを「まぬけ」と呼びます。',
                          '例えば「どこでもつながる携帯電話」というのを考えるとしましょう。これは理想として言えば、世界中のどこに行ってもつながる、地下だろうが高い山のてっぺんだろうが、太平洋のど真ん中だろうが通話ができるっていうのが理想じゃありませんか?'];
        const messageNo = Math.floor( Math.random() * messages.length);
        const suffix = ['ではありますまいか。',
                          'ぞ。'];
        const suffixNo = Math.floor( Math.random() * suffix.length);

        var mackytext;
        mackytext =  messages[messageNo] + '\n';
        mackytext =  mackytext + '\n' + document.getElementById('origin').value.replace(/。/g, suffix[suffixNo]);
        document.getElementById('mackytext').value = mackytext;
    }
</script>
index.html
      <TD><textarea cols="80" rows="5" id="origin"></textarea></TD>
(中略)
        <p><input type="button" value="ブレイクスルーする" onclick="breakthrough();"></p>
(中略)
        <p><textarea cols="80" rows="10" id="mackytext">ここに表示されるのですぞ。</textarea></p>

30分くらいでつくりましたが、細かい命名には、気をつけました(色々な意味において)。

Ver.1 参考文献

特定の文字列を全て置換する[Javascript]
文字列ランダム表示

Ver.2 形態素解析ライブラリとTweet

ver2.js
<script type="text/javascript" src="tiny_segmenter.js" charset="UTF-8"></script>
<script type="text/javascript" charset="UTF-8">
    var segmenter = new TinySegmenter();

    function breakthrough() {
        var segmenter = new TinySegmenter();
        var segs = segmenter.segment(document.getElementById('origin').value);
        var conv = "";
        for (const elem of segs) {
            var str = elem;
            str = str.replace("です", "ですぞ")
            str = str.replace("でした", "でしたぞ")
            str = str.replace("ます", "ますまいか");
            str = str.replace("", "たぞ");
            conv = conv + str;
        }
        const messages = ['問題解決こそが仕事でありますぞ。',
                          '本当の仕事とは、何か大きな問題に直面したときから始まる。',
                          'プロ、もしくはプロであるべきポジションの仕事人の無自覚や無神経さを「まぬけ」と呼びます。',
                          '例えば「どこでもつながる携帯電話」というのを考えるとしましょう。これは理想として言えば、世界中のどこに行ってもつながる、地下だろうが高い山のてっぺんだろうが、太平洋のど真ん中だろうが通話ができるっていうのが理想じゃありませんか?'];
        const messageNo = Math.floor( Math.random() * messages.length);
        const suffixNo = Math.floor( Math.random() * suffix.length);

        var mackytext = "";
        //mackytext =  mackytext + '\n' + document.getElementById('origin').value.replace(/。/g, suffix[suffixNo]);
        mackytext =  conv + '\n';
        mackytext =  mackytext + messages[messageNo] ;
        document.getElementById('mackytext').value = mackytext;
    }
    function tweet() {
        var left = Math.round(window.screen.width / 2 - 275);
        var top = (window.screen.height > 420) ? Math.round(window.screen.height / 2 - 210) : 0;
        window.open(
            "https://twitter.com/intent/tweet?text=" + encodeURIComponent(document.getElementById("mackytext").value + " at: https://e99h2121.github.io/breakthrough-generator/" ),
            null,
            "scrollbars=yes,resizable=yes,toolbar=no,location=yes,width=550,height=420,left=" + left + ",top=" + top);
    }
</script>

もう少し真面目にやりだしました。形態素解析Javascriptという、便利なライブラリがありました。

Ver.2 参考文献

TinySegmenter:Javascriptだけで書かれたコンパクトな分かち書きソフトウェア
オープンソースのコードを取り込んだ時のライセンス表記について
オリジナルのツイートボタンの呟き内容をjavascriptsで動的に変える方法

公開

当然自分でホストすれば良いんですが、これを発見
https://sites.google.com/view/how-to-with-new-sites/embeds/embed-with-htmljavascript
することで、まずは社内でも簡単に公開できました。

Editey というものもあるようだ。参考:WebブラウザでUnityで作ったシーンを歩き廻ろう!
考えているうちに、https://pages.github.com/ で良いか。となりました。

解説

私の会社の、前の名前の会社の創業者、「牧野正幸」氏の謎の口癖?ブログ上の人格?に多分にエナジャイズされた 社内ウケを狙ったジョークおもちゃ モストブレイクスルーです。

元ネタ

「問題解決」こそ仕事

在職エントリ

私の勤めるWorks Human Intelligence社は2019年、ワークスアプリケーションズ社から分割して誕生しました。引用:https://www.works-hi.co.jp/news/20190801

当時のことを黒歴史のように語るのはタブーに触れる行為のように多々おもわれる節を感じたり、感じなかったり、かもしれませんが、内部で新卒時代からそれなりに業務をこなせるようになった今を過ごしてきたひとりにとって、そしてひとりの「開発」にとって、会社の「枠」なんて、わりとどうでもいいことです。単にそこに「1000をゆうに数える日本の大企業であるお客様の業務を支えている」という圧倒的な面白みがあるから相変わらず続けている、というのが、私個人の気持ちです。

image.png
image.png
Delphiでこんなものも作ったりしていた新人時代。

「卒業生」もそれなりに多くの分野で活躍しており、私も「卒業してこそ一人前」「まだまだ自分はその度胸がない」な~という気持ちでいるのですが、弊社、開発者が実名で、会社のタイトルを明かしつつも内部の雰囲気を語るという機会がなぜかない(少ない?)。中はそれなりに相変わらず楽しいよと、どこかで一発、在職エントリをかましてやりたいなと思っていました。偉い人から怒られそうな気もしますが、私なりの会社愛として受け止めてくれると信用する意味で投稿しています。

後日消されていたら察してください。


以下記事が最近トレンドに載ったのでこちらも宣伝。
Delphiなんかで開発していて恥ずかしくないの?
「失敗を許容する」なんて言われても失敗したくないです
真面目なことも書いてるよ

また12/5 には人事プロダクト「COMPANY」のバッチ処理の裏側を、きちんと投稿する予定です。
Develop fun!を体現する Works Human Intelligence Advent Calendar 2020
Develop fun!を体現する Works Human Intelligence #2 Advent Calendar 2020

では!

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

Most Breakthrough Generator

この記事は

Works Human Intelligence社、ワークス社とは関係ない、文責:個人の趣味の投稿です。
私は何がやりたいのか、あるいは何がやりたくないのかという話、私の場合 を考えたついでにバランスを取りたくて書きました。

Most Breakthrough Generatorとは

日常の何気ない文章をものすごい問題解決にしてくれるブレイクスルーエンジンです。クソアプリアドベントカレンダーとても賑わっているようですが、これは決してクソアプリではなくモストブレイクスルーアプリです。

https://e99h2121.github.io/breakthrough-generator/

image.png
image.png

というかジョークおもちゃです、こんなの業務中に作っててごめんなさい
なるほど、ブレイクスルーしています。

Most Breakthrough

Most Breakthroughとは、かつてのワークスアプリケーションズ社時代の社内表彰です。英語としてどうなの。

裏側

https://github.com/e99h2121/breakthrough-generator/blob/main/index.html

完全にクライアントサイド

javascriptだけで作っています。javascriptはお手軽で良かった
全てクライアントサイドで解析を行うため、セキュリティの観点から見て安全です。

Ver.1 ランダム、文字列置換

ver1.js
<script type="text/javascript">
    function breakthrough() {
        const messages = ['問題解決こそが仕事でありますぞ。',
                          '本当の仕事とは、何か大きな問題に直面したときから始まる。',
                          'プロ、もしくはプロであるべきポジションの仕事人の無自覚や無神経さを「まぬけ」と呼びます。',
                          '例えば「どこでもつながる携帯電話」というのを考えるとしましょう。これは理想として言えば、世界中のどこに行ってもつながる、地下だろうが高い山のてっぺんだろうが、太平洋のど真ん中だろうが通話ができるっていうのが理想じゃありませんか?'];
        const messageNo = Math.floor( Math.random() * messages.length);
        const suffix = ['ではありますまいか。',
                          'ぞ。'];
        const suffixNo = Math.floor( Math.random() * suffix.length);

        var mackytext;
        mackytext =  messages[messageNo] + '\n';
        mackytext =  mackytext + '\n' + document.getElementById('origin').value.replace(/。/g, suffix[suffixNo]);
        document.getElementById('mackytext').value = mackytext;
    }
</script>
index.html
      <TD><textarea cols="80" rows="5" id="origin"></textarea></TD>
(中略)
        <p><input type="button" value="ブレイクスルーする" onclick="breakthrough();"></p>
(中略)
        <p><textarea cols="80" rows="10" id="mackytext">ここに表示されるのですぞ。</textarea></p>

30分くらいでつくりましたが、細かい命名には、気をつけました(色々な意味において)。

Ver.1 参考文献

特定の文字列を全て置換する[Javascript]
文字列ランダム表示

Ver.2 形態素解析ライブラリとTweet

ver2.js
<script type="text/javascript" src="tiny_segmenter.js" charset="UTF-8"></script>
<script type="text/javascript" charset="UTF-8">
    var segmenter = new TinySegmenter();

    function breakthrough() {
        var segmenter = new TinySegmenter();
        var segs = segmenter.segment(document.getElementById('origin').value);
        var conv = "";
        for (const elem of segs) {
            var str = elem;
            str = str.replace("です", "ですぞ")
            str = str.replace("でした", "でしたぞ")
            str = str.replace("ます", "ますまいか");
            str = str.replace("", "たぞ");
            conv = conv + str;
        }
        const messages = ['問題解決こそが仕事でありますぞ。',
                          '本当の仕事とは、何か大きな問題に直面したときから始まる。',
                          'プロ、もしくはプロであるべきポジションの仕事人の無自覚や無神経さを「まぬけ」と呼びます。',
                          '例えば「どこでもつながる携帯電話」というのを考えるとしましょう。これは理想として言えば、世界中のどこに行ってもつながる、地下だろうが高い山のてっぺんだろうが、太平洋のど真ん中だろうが通話ができるっていうのが理想じゃありませんか?'];
        const messageNo = Math.floor( Math.random() * messages.length);
        const suffixNo = Math.floor( Math.random() * suffix.length);

        var mackytext = "";
        //mackytext =  mackytext + '\n' + document.getElementById('origin').value.replace(/。/g, suffix[suffixNo]);
        mackytext =  conv + '\n';
        mackytext =  mackytext + messages[messageNo] ;
        document.getElementById('mackytext').value = mackytext;
    }
    function tweet() {
        var left = Math.round(window.screen.width / 2 - 275);
        var top = (window.screen.height > 420) ? Math.round(window.screen.height / 2 - 210) : 0;
        window.open(
            "https://twitter.com/intent/tweet?text=" + encodeURIComponent(document.getElementById("mackytext").value + " at: https://e99h2121.github.io/breakthrough-generator/" ),
            null,
            "scrollbars=yes,resizable=yes,toolbar=no,location=yes,width=550,height=420,left=" + left + ",top=" + top);
    }
</script>

もう少し真面目にやりだしました。形態素解析Javascriptという、便利なライブラリがありました。

Ver.2 参考文献

TinySegmenter:Javascriptだけで書かれたコンパクトな分かち書きソフトウェア
オープンソースのコードを取り込んだ時のライセンス表記について
オリジナルのツイートボタンの呟き内容をjavascriptsで動的に変える方法

公開

当然自分でホストすれば良いんですが、これを発見
https://sites.google.com/view/how-to-with-new-sites/embeds/embed-with-htmljavascript
することで、まずは社内でも簡単に公開できました。

Editey というものもあるようだ。参考:WebブラウザでUnityで作ったシーンを歩き廻ろう!
考えているうちに、https://pages.github.com/ で良いか。となりました。

解説

私の会社の、前の名前の会社の創業者、「牧野正幸」氏の謎の口癖?ブログ上の人格?に多分にエナジャイズされた 社内ウケを狙ったジョークおもちゃ モストブレイクスルーです。

元ネタ

「問題解決」こそ仕事

在職エントリ

私の勤めるWorks Human Intelligence社は2019年、ワークスアプリケーションズ社から分割して誕生しました。引用:https://www.works-hi.co.jp/news/20190801

当時のことを黒歴史のように語るのはタブーに触れる行為のように多々おもわれる節を感じたり、感じなかったり、かもしれませんが、内部で新卒時代からそれなりに業務をこなせるようになった今を過ごしてきたひとりにとって、そしてひとりの「開発」にとって、会社の「枠」なんて、わりとどうでもいいことです。単にそこに「1000をゆうに数える日本の大企業であるお客様の業務を支えている」という圧倒的な面白みがあるから相変わらず続けている、というのが、私個人の気持ちです。

image.png
image.png
Delphiでこんなものも作ったりしていた新人時代。

「卒業生」もそれなりに多くの分野で活躍しており、私も「卒業してこそ一人前」「まだまだ自分はその度胸がない」な~という気持ちでいるのですが、弊社、開発者が実名で、会社のタイトルを明かしつつも内部の雰囲気を語るという機会がなぜかない(少ない?)。中はそれなりに相変わらず楽しいよと、どこかで一発、在職エントリをかましてやりたいなと思っていました。偉い人から怒られそうな気もしますが、私なりの会社愛として受け止めてくれると信用する意味で投稿しています。

後日消されていたら察してください。


以下記事が最近トレンドに載ったのでこちらも宣伝。
Delphiなんかで開発していて恥ずかしくないの?
「失敗を許容する」なんて言われても失敗したくないです
真面目なことも書いてるよ

また12/5 には人事プロダクト「COMPANY」のバッチ処理の裏側を、きちんと投稿する予定です。
Develop fun!を体現する Works Human Intelligence Advent Calendar 2020
Develop fun!を体現する Works Human Intelligence #2 Advent Calendar 2020

では!

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

strapi+nuxt.jsでアドベントカレンダーを作ろう

strapiを触ってみよう

巷でウワサのheadlessCMS「strapi」を触ってみました。

アドベントカレンダーの記事ということで、簡単なアドベントカレンダーを作ってみます。
記事表示の部分はnuxt.jsを利用します。

strapiの構築

npxコマンド一発で簡単に初期構築を行ってくれます。

npx create-strapi-app advent_api --quickstart

ユーザ登録を済ませたら、記事用のコンテンツタイプ(データ定義)を作成します。
項目の名前とデータ型、その他設定(一意制約や必須など)を選択することで簡単にデータ項目を増やしていくことができます。

スクリーンショット 2020-12-01 12.04.07.png

コンテンツタイプを作成すると、下記の基本的なAPIが自動で生成されます。

  • 一覧
  • 件数
  • 詳細
  • 追加
  • 更新
  • 削除

この段階ではAPIのアクセスが許可されていないので、デフォルトで存在するpublicロールに、取得系APIのアクセス権限を設定してあげます。

スクリーンショット 2020-12-01 13.03.48.png

試しに記事を2件ほど書いてみます(投稿もAPI経由で行うことができますが、時間の都合上デフォルトの管理画面上で作成します)

スクリーンショット 2020-12-01 12.07.57.png

この状態で、自動生成された記事一覧のAPIにアクセスしてみると...

http://localhost:1337/articles/

記事情報のjsonが取得できました。

[
  {
    "id": 1,
    "title": "シュトレンを作ってみた",
    "text": "## シュトレンとは\n\nシュトレンというお菓子を知っていますか?\nシュトレンとは・・・",
    "date": "2020-12-01",
    "published_at": "2020-11-30T12:16:18.523Z",
    "created_at": "2020-11-30T12:16:15.854Z",
    "updated_at": "2020-12-01T03:07:40.608Z"
  },
  {
    "id": 2,
    "title": "アドベントカレンダーを作ろう",
    "text": "## はじめに\n\n**アドベントカレンダー**作りたい... 作りたくない?\n",
    "date": "2020-12-02",
    "published_at": "2020-11-30T12:17:53.678Z",
    "created_at": "2020-11-30T12:17:51.696Z",
    "updated_at": "2020-12-01T03:06:19.338Z"
  }
]

Viewの作成(Nuxt.js)

APIから取得したデータを表示する部分はnuxt.jsで作ります。
こちらもコマンド一発で初期構築。
対話式で利用するプラグインなどを設定できるので、
CSSフレームワークとしてVuetify を導入します。

npx create-nuxt-app advent_front

/pages/配下に、記事一覧ページと記事詳細ページを作ります。

記事一覧

Vuetifyにはカレンダー用のmoduleが用意されているのでこれを利用します。
apiから取得したデータを詰め替えて、カレンダーのイベントとして登録していきます。

index.vue
<template>
  <v-container>
    <v-sheet height="80vh">
      <v-calendar light height="80vh" start="2020-12-01" end="2020-12-25" :events="events" @click:event="showEvent"></v-calendar>
    </v-sheet>
  </v-container>
</template>

<script>
import axios from 'axios'

export default {
  data() { return {
    events:[]
  }},
  methods: {
    showEvent ({ nativeEvent, event }) {
      this.$router.push(`/${event.id}`)
    }
  },
  async mounted() {
    const {data} = await axios.get("/api/articles/");
    this.events = data.map(el=>{
      return {
        id: el.id,
        start: new Date(el.date),
        name: el.title,
        timed: false,
      }
    })
  }
}
</script>

記事詳細

nuxtはファイル名から自動でルーティングを生成してくれますが、ファイル名の先頭に_をつけた場合パスパラメータとして認識され、$route.paramsで取得することが可能です。
また、本文はmarkedライブラリを利用してmarkdownからhtmlに変換しています。

_id.vue
<template>
  <v-container>
    <v-sheet height="80vh" class="pa-3">
      <div tag="h2" class="headline" v-html="article.title"></div>
      <div v-html="article.date" class="mb-3"></div>
      <v-sheet v-html="article.text">
      </v-sheet>
      <v-btn class="mt-5" to="/">カレンダーに戻る</v-btn>
    </v-sheet>
  </v-container>
</template>

<script>
import axios from 'axios'
import marked from 'marked'

export default {
  data() { return {
    article: {
      title: "hoge",
      date: "2020-12-01",
      text: "hogehoge",
    }
  }},
  props:["id"],
  methods: {
    md2Html(val) {
      return marked(val);
    }
  },
  async mounted() {
    const {data} = await axios.get("/api/articles/"+this.$route.params.id);
    this.article = {
      title: data.title,
      date : data.date,
      text : this.md2Html(data.text)
    }
  }
}
</script>

今回はstrapi,nuxtそれぞれlocalhostの別portで動かす(別ドメイン扱いになる)ため、nuxtでproxy設定を行うことでクロスオリジン制約を回避します。

nuxt.config.js
  modules: [
    '@nuxtjs/axios',
    '@nuxtjs/proxy'
  ],
  proxy: {
    '/api': {
    target: 'http://localhost:1337',
    pathRewrite: {
      '^/api' : '/'
      }
    }
  },

これでこのように表示されます。見た目ダサいのは気にしない

http://localhost:3000/

スクリーンショット 2020-12-01 13.02.54.png

http://localhost:3000/1/

スクリーンショット 2020-12-01 13.03.00.png

完走した感想

実際に利用するには権限周りの設定などもう少し作り込む必要がありますが、それでもstrapi+nuxtの組み合わせはかなり簡単に初期構築を行えるなと感じました。
今回のような超小規模だとWordpressでよくね?となってしまう感はあるものの、

  • Nuxtできちんとサーバサイドレンダリングをする
  • 並行してアプリ化もする

など、headlessCMSの強みを活かせるような要件では強力だと思います。


:christmas_tree: FORK Advent Calendar 2020
:arrow_left: 1日目 Google Cloud Dataproc でデータ解析基盤を構築 @numatch00
:arrow_right: 3日目 Coming soon @sugar-engine

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

JavascriptのforEachでindexの使用例

forEachのindexとかarrayの使いどころがわからなかった

forEachでコールバック関数を利用するのみでindex,arrayを使ったことがなかった。
face-api.jsの中身を見ていてああ、なるほどと思ったのでまとめます。

face-api.jsの中で使われていたコードの雰囲気

test.js
array.slice(1).forEach(({ x, y }, index) =>
            {
                const from = array[index];
                ctx.moveTo(from.x, from.y);
                ctx.lineTo(x, y);
            })
ctx.stroke();
//~~~

これは顔の特徴点と特徴点同士を線でつなぎ、canvas上に描画する処理の一部。↓の青線を引く。
image.png

slice関数で1番目以外の点を取得し、それぞれについて一つ前の点から次の点へ直線を引くという流れです。

あとがき

いままで使える機会があったが、わざわざ別で変数を用意して繰り返しインクリメントさせる
という非常にスマートでない方法でやっていた。。。
apiの中身を見てみるといろいろと発見がある!

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

【JS】オブジェクトから要素を削除する方法。filterとfindの活用

オブジェクトから該当する要素を削除する方法について。

例えば、以下のようなオブジェクトがあった場合に、指定した要素を削除する場合を考える。

obj = [{a:1, b:2}, {a:1, b:3}, {a:2, b:3}, {a:3, b:4}]

目次

  1. 元のオブジェクトを利用する場合
    1. 要素が1つの場合
    2. 要素が2つ以上の場合
  2. 元のオブジェクトを利用しない場合
  3. どれか一つのプロパティが一致する要素を削除する場合


元のオブジェクトを利用する場合

元のオブジェクトを使って除外する要素を指定する場合は判定式が使える。

要素が1つの場合

要素が一つの場合は、filterを使えば実現可能。例えば、配列番号3の{a:3, b:4}を削除する場合は以下のような式になる。

obj = [{a:1, b:2}, {a:1, b:3}, {a:2, b:3}, {a:3, b:4}]
removeObj = obj[3]

res = obj.filter( elem => elem != removeObj)

console.log(res)
//[{a:1, b:2}, {a:1, b:3}, {a:2, b:3}]

obj.filter( elem => elem != removeObj)

  • objの要素を一つづつ抜き出し、elemという変数に代入する
  • =>以降の処理がtrueの場合のみ要素を抜き出す。
  • つまり、要素がremoveObjと一致する場合は抜き出さない(削除)する処理となる。



removeObj = obj[3]のように、元のオブジェクトを使って要素を指定しているため、要素どうしの比較が可能


要素が2つ以上の場合

要素が2つ以上の場合はfilterとfindを使う必要がある。配列番号0と1の要素を排除する場合は以下のようになる。

obj = [{a:1, b:2}, {a:1, b:3}, {a:2, b:3}, {a:3, b:4}]
removeObj2 = [obj[0], obj[1]]

res2 = obj.filter( elem => !removeObj2.find( x => x == elem ))

console.log(res2)
//[{a:2, b:3}, {a:3, b:4}]

obj.filter( elem => 処理)

  • filterを使ってobjの要素を一つ一つ抜き出す。(変数elemに代入)

!removeObj2.find( x => x == elem )

  • findは処理結果がtrueになった要素を返す処理。
  • !をつけ、排除したい要素とobjの要素が一致した場合はfalseを返す。
  • filterはtrueの場合は要素を抽出するが、falseの場合は抽出しない(除外する)


元のオブジェクトを利用しない場合

元のオブジェクトを利用しない場合は、filterとfindを使うことで、指定した要素を排除できる。

obj = [{a:1, b:2}, {a:1, b:3}, {a:2, b:3}, {a:3, b:4}]
removeObj = [{a:1, b:2}, {a:3, b:4}]

res = obj.filter( elem => !removeObj
    .find( x => x.a == elem.a && x.b == elem.b )
    )

console.log(res)
//[{a:1, b:3}, {a:2, b:3}]

元のオブジェクトからfilterを使って要素を一つづつ取り出し、中のプロパティを一つ一つ比較していく必要がある。

removeObj.find( 変数 => 処理 )

  • 処理結果がtrueになった最初の要素を返す。
  • x => x.a == elem.a && x.b == elem.b removeObjのプロパティaとbが元のオブジェクトの要素と一致した場合にその値を返す。(xはremoveObjの要素を一つ一つ取り出した変数)
  • !をつけ、排除したい要素とobjの要素が一致した場合はfalseを返す。


どれか一つのプロパティが一致する要素を削除する場合

複数あるプロパティの中で、a:1となっている要素を全て除外する場合はfilterのみで実現可能。

obj = [{a:1, b:2}, {a:1, b:3}, {a:2, b:3}, {a:3, b:4}]

res = obj.filter( elem => !(elem.a == 1) )

console.log(res)
//[{a:2, b:3}, {a:3, b:4}]

!(elem.a == 1)
filterで抜き出した要素(elem)のプロパティaが1となる場合はfalseを返し除外する。


(補足)filterのみで記述する場合

要素が1つの場合は、findを使わずfilterのみで記述することも可能だが汎用性がないため特に不要。

記述は以下のようになる。

obj = [{a:1, b:2}, {a:1, b:3}, {a:2, b:3}, {a:3, b:4}]
removeObj3 = [{a:3, b:4}]

res3 = obj.filter( elem => !(removeObj3[0].a == elem.a && removeObj3[0].b == elem.b )
    )
console.log(res3)
//[{a:1, b:2}, {a:1, b:3}, {a:2, b:3}]

removeObj3[0].aのようにプロパティの値を直接指定し、比較している。

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

YOUは何処から日本へ?(ページにアクセスしたユーザーがどこから来たか調べる)

アクセスしたユーザーの国や地域、IPアドレス取得

案件で多言語サイトのデフォルト言語をユーザーごとに出し分けたいという要望があったため
フロント、サーバー側で国や地域、IPアドレスを取得する方法を調査した

JavaScripts で対応

JS単体では端末のIPを取得することはできないがAPIを利用することで取得できる
以下は中でもフリーのサービスを抜粋したもの

1. ipinfo.io

 アクセスするとIPを取得して表示してくれるサービス。
 JSON形式で情報が返却される。
 アカウント作成とアクセス回数に上限がある
 ipinfo.io

2. Geolocation API

 WiFi、GPS、IPアドレスなどから位置情報を取得するAPI。
 JSON形式で情報が返却される。
 位置情報の取得にはユーザーの許可が必要なため拒否されれば当然取得できない。
 WiFiや緯度経度からの取得はGoogleが提供していることが多いため中国などでは利用できないことがある。
 Geolocation API

3. ipify

 フリーのIP取得サービス。
 JSON形式で情報が返却出来る。
 上限は無し。
 ipify
 API

4. jsonip.com

 無料無制限のIP取得サービス。
 JSON形式でIPを取得できるがおまけに広告とドキュメントのURLが含まれている。
 以前は政治的メッセージも含まれていたがなくなったらしい。
 上限なし。
 jsonip.com
 API

5. JSON Test

 フリーのIP等取得サービス。
 IPアドレス単体やリクエストヘッダー情報をJSON形式で取得できる。
 名前のせいで情報が見つけにくい。
 JSON Test
 API

6. ipapi.co

 フリーと商用ライセンスのあるIP等取得サービス。
 アカウント登録が必要。
 IPだけでなく国や地域、緯度経度など詳細なデータをJSON形式で取得できる。
 珍しくドキュメントもそろっているので親切。
 ipapi.co

PHP で対応

PHPはIPアドレスを取得できるが国や地域を取得するにはライブラリかAPIが必要になる(一部有料)

1. IPinfoAPI

 IPアドレスを渡すと国や地域のコードをJSON形式で取得できるサービス
 上限が毎秒2回まで。

2. Geo IP2

 MaxMind社から提供されたデータベースファイルをサーバー側に設置しアクセスしたIPで検索してどの国や地域からアクセスがあったかを調べる。
 無償版ライセンスでは国まで、有償版では地域まで取得できるので用途によって使い分け可能。
 Geo IP

AWS で対応

AWSのCloudFront-Viewer-Country はIPアドレスから判断したアクセス元の国コードを返却するためAWSを利用していれば対応可能。
 AWS

まとめ

  • JavaScriptsは単体ではIPを取得できないがAPIを使用すればIPもしくは国コードなどのアクセス元情報が取得できる。
    ただしIPアドレスの取得方法によっては対象外の国や地域があるため注意が必要。

  • PHPはIPアドレスは単体で取得可能だがアクセス元の国や地域のコードを取得するにはライブラリ、APIが必要。
    ライブラリのライセンスは詳細な情報が必要であれば有償版、国コードが必要なだけであれば無償版でよい。

  • その案件がAWSを利用しているのであればCloudFront-Viewer-Country で対応可能

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

JavaScriptでコラッツ予想の動作を実行

コラッツ予想の動作を確認してみる

JavaScriptでコラッツ予想の動作を実行するプログラムを作成してみました。

・自然数を入力する
・「偶数なら2で割り、奇数なら3倍して1を足す」という操作を繰り返す
・1になったら終了する

上記操作で必ず1になってプログラムが停止する、というのが「コラッツ予想」です。
「予想」と呼ばれるとおり、2020年現在証明はされていません。

コラッツ予想.png

CollatzProblem.html
<html>
<head>
  <meta http-equiv="Content-Type" context="text/html; charset=UTF-8" />
  <meta charset="UTF-8" />
  <title>JavaScriptでコラッツ予想</title>
<script>

function doCalc() {
    var txtN1 = document.getElementById("txtN1");

    var n1, n2, strResult;
    var vWork = [];
    var vOut = [];

    n1 = parseInt(txtN1.value, 10);
    n2 = n1;

    vOut.push("" + n1);

    while (1) {
        if (n2 <= 1) { break; }

        if (n2 % 2 == 0) {
            n1 = n2
            n2 = n2 / 2;
            strResult = "" + n1 + " / 2 = " + n2;
            vOut.push(strResult);
        } else {
            n1 = n2
            n2 = n2 * 3 + 1;
            strResult = "" + n1 + " * 3 + 1 = " + n2;
            vOut.push(strResult);
        }
    }

    document.getElementById("result").innerHTML = vOut.join("\n");
}

</script>
</head>
<body>

<h2>コラッツ予想</h2>

<ul>
<li>自然数を入力する</li>
<li>「偶数なら2で割り、奇数なら3倍して1を足す」という操作を繰り返す</li>
<li>1になったら終了する</li>
</ul>

4桁 まで
<input type="text" id="txtN1" value="" maxlength="4" size="7" oninput="value = value.replace(/[^0-9]+/i,'');" />
<input type="button" value="計算" onclick="doCalc()" /><br />
<br />

<textarea id="result" rows="50" col="20" style="width:700px;height:400px;">
</textarea>

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

JSのお仕事 (JSアウトプット2)

あいさつ

初めての人は初めまして!知っている人はこんにちは!
中学生プログラマーのAtieです!
今回もJSを学んできたのでそのアウトプットをしていきます!
今回は第二回目ですがもし第一回の記事を読んでいなければそちらのほうから読んでいただくとわかりやすいと思います!なぜJSが必要なのか? (JS学習アウトプット)
今回は「JSのお仕事」ということでJSのお仕事(役割)について学んできました!
それでは!

前回の復習

従来のweb
- レスポンスがhtml
- ページ全体がレンダリング

現代のweb
- レスポンスがJSON
- 必要なところだけレンダリング
- リッチで使いやすい

JSのお仕事

JSのお仕事には主に二つあります
一つ目が

データのやり取り

二つ目が

DOM操作

です

順に解説していきます

データのやり取り

ではまずJSの一つ目のお仕事「データのやり取り」についてです
Twitterを例に解説していきます

まずはTwitterのhtmlの構造を見ていきましょう!(この構造とは限りませんし省略してあります)

home.html
<ul>
    <li>
        <div>
            <img src="~">
        <div>...</div>
        </div>
    </li>
</ul>

もし構造がこのようになっているとします
しかしこれだとスクロールすると新しいツイートが表示されないので
スクロールするとJSがwebAPIに「データが欲しいです!」と頼みに行きます
そしてwebAPIがDBなどにアクセスしてデータをとってきてJSに「ほれ!」とデータを渡します
そしてこの時に渡されるデータが「JSON」なのです
JSONについては前回学んだので説明は省きます

それでは渡されたJSONのデータを見てみましょう(これはあくまでも一例です本物のTwitterがこうだとは限りません)

data.json
{
    "data" : [
        {
            "name": "~~",
            "bady": "~~",
            "src": "~~"
        },
        {
            "name": "~~",
            "bady": "~~",
            "src": "~~"
        },
        {
            "name": "~~",
            "bady": "~~",
            "src": "~~"
        }
    ]
}

このようなデータになっていました!
しかしデータを受け取ったところでこのまま表示させるわけにはいきません
読みにくいのでhtmlに変換しますこれをパースといいます
パース自体は「解析」という意味で何もhtmlに変換することすべてがパースと呼ぶわけではありません
パースについてですがここからはDOM操作です

DOM操作

さぁ!先ほどwebAPIからjsonをもらうことができました!
「わーい!」
...
しかしまだ仕事は残っています
先ほど説明しましたがjsonをhtmlに変換して見やすくします
これをパースと呼びます

DOM操作の例を見るためにhtmlとjsonのデータを見てみます

home.js
<li>
    <div>
        <img src="data[0].src">  <!--jsonデータの0番目のsrcのデータがここに入る-->
        <h1>data[0].name</h1>    <!--jsonデータの0番目のnameのデータがここに入る-->
        <p>data[0].bady</p>      <!--jsonデータの0番目のbadyのデータがここに入る-->
    </div>
</li>
data.json
{
    "data" : [
        {
            "name": "~~",
            "bady": "~~",
            "src": "~~"
        },
        {
            "name": "~~",
            "bady": "~~",
            "src": "~~"
        },
        {
            "name": "~~",
            "bady": "~~",
            "src": "~~"
        }
    ]
}

htmlにはコメントを書いたのでhtmlファイルとjsonのデータを見比べてみてください
わかりやすいと思います
このようにhtmlをjsonに変換することをパースと呼びましたがこのようなことがDOM操作です

パースが終わることで新しいデータが読み込まれ新しい投稿が表示されます

まとめ

今回のまとめです!

JSのお仕事
- データのやり取り
- DOM操作

データのやり取りではJSがwebAPIにデータを取りに行きます
DOM操作ではとってきたjsonデータをhtmlに変換します(パースします)
この二つが主なJSのお仕事です

データのやり取りには
- 保存する
- ローカルに保存する
- cookie
なども含まれています

DOM操作では
- 追加
- 変更
- 削除
などもしています

最後に

ここまで読んでいただきありがとうございました
次回は実際にコードを書いてみます!
それでは!また次回に!

前回

AtieのTwitter
しまぶーのIT大学さんのTwitter

参考

【基礎から学ぶ JavaScript 入門 #2】Twitterを例にJavaScriptがどんな働きをするのか理解しよう【ヤフー出身エンジニアが教える初心者向けプログラミング講座】

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

【JS】配列やオブジェクトは比較できない?比較できる場合とできない場合。

配列やオブジェクトの要素同士を比較するときの注意点メモ。

個別に定義した場合は比較できない

配列やオブジェクトの中身の値が同じでも、等価式にするとfalseになる。

▼配列同士の比較
image.png

▼2次元配列の場合
image.png

▼オブジェクトの場合
image.png

▼配列内のオブジェクトの場合
image.png

いずれの場合も見た目は完全に一致してるが、評価結果はことごとくfalseになる。

これは、各配列の要素を指すポインタが異なる場所を示しているため。


比較可能な場合

元の配列やオブジェクトの要素を参照した場合は比較が可能になる。

▼配列の場合
image.png

▼オブジェクトの場合
image.png


filterの応用

上記の結果とfilterを使うと、必要な要素のみ残したり、除外したりすることができる。

例えば除外する場合は以下のようになる。

▼配列の場合

image.png



▼オブジェクトの場合

image.png



配列やオブジェクトの要素をごっそりフィルタリングしたい場合は評価式の評価対象に注意が必要。

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

Kinx での JIT、そして MIR の話 ... Ruby だけでは勿体ないネ

この記事は「言語実装 Advent Calendar 2020」 21日目の記事です。

本記事は、主に JIT コンパイルに関する話題ですが、一般的なものではなく私の身の回りでやってることを中心にご紹介したいと思います。

はじめに

今回は、言語実装 Advent Calendar ということで 自作言語である Kinx における、最新の native 実装状況、そして JIT ライブラリのお話、および JIT 関連の注目ライブラリ MIR に関してご紹介します。本記事は、以下で構成されています。

  1. JIT コンパイラのプラットフォーム依存性について
    • JIT コンパイラの概要と課題。一般的には実行時にどこをコンパイルするかとかトレーシングがどうとかなのかもしれませんが、プラットフォーム依存も一つの問題です。Ruby は独特の方法で回避しましたが...
  2. Kinx Native - sljit による JIT コンパイル
    • native というキーワードに関して元々は ここ(JIT Compile) でいくつか紹介していたのですが、その後、割とアップデートさせたので再度まとめ直してみました。
  3. JIT ライブラリ - 手軽に JIT コンパイルを体験
    • 直接アセンブラを使わないまでも、抽象化されたアセンブリを直接扱うためのライブラリを用意しています。その紹介と今後したいことなど。
  4. MIR 感想
    • 注目の JIT コンパイラ基盤。Cコンパイラが付いている。Ruby の人たちはよく知ってるアレです。非常に楽しみなプロジェクトで、少し触ってみた感想などをご紹介。

しかし本業が忙しすぎて色々なことを進める時間が圧倒的に足りなくなっている今、いつできるのか...。ちょっと前まではもう少し時間が取れたのですが。まぁ個人的なサイドプロジェクトなので、焦らず気長に行きます。もちろん、もし少しでもご興味を持っていただけるのであれば、どなたでもどんな内容でも コントリビュート大歓迎 です!(Kinx の GitHub は こちら

【ご参考】Kinx とは

Kinx の最初のコミットは 2019/11/28 のようです。約 1 年(本記事の公開は 2020/12/21 ですが、書き始めたのは 11/11)経ちました。さて、元々の動機は下記を参照していただくとして、キャッチフレーズがありますので例によってそこからスタートします。Here we go!

「見た目は JavaScript、頭脳(中身)は Ruby、(安定感は AC/DC)」 でお届けしているスクリプト言語 Kinx。ご存じない方は下記をご参照ください。

つい昨日(12/20)、「令和時代の基礎文法最速マスター Advent Calendar 2020」 20日目の記事として「Kinx 基礎文法最速マスター」がアップされましたので、そちらも合わせてご覧ください。

1. JIT コンパイラのプラットフォーム依存性について

あらためて、Just In Time、つまり まさにその場で コンパイルして実行する方式。ネイティブコード(機械語、マシン語1)にコンパイルして実行するため実行速度を追い求める人たちの間では 欲しくて欲しくてたまらない機能 の一つです。

ただし、普通は プロセッサーごとに個別に実装しないといけない。x64 だけ、とかならまだいいんだけど、どっかで ARM くらいはサポートすっかなー、とか、PowerPC は、SPARC?、とかになると、そもそもテストするのが大変です。

そして、同じ x64 でも Windows と Linux は違います。Microsoft x64 Calling Convention と System V Calling Convention という、いわゆる 呼出規約 というものが違っています。

Microsoft x64 / System V Calling Convention

そもそも関数呼び出しで使うレジスタが違います。これは読み替えればよいのだけれど。

引数 1 引数 2 引数 3 引数 4 引数 5 引数 6 ... and more
Microsoft rcx rdx r8 r9 (stack) (stack) (stack)
System V rdi rsi rdx rcx r8 r9 (stack)

double 型の受け渡しにはどちらも SSE レジスタを使いますが、Microsoft x64 の場合は xmm0 から xmm3 まで、System V の場合は xmm0 から xmm7 までとなっています。そして、以下のように Microsoft x64 の場合は位置によって使うレジスタが固定であるのに対し、System V では 位置がズレます

引数 1
int
引数 2
double
引数 3
int
引数 4
double
Microsoft ecx xmm1 r8d xmm3
System V edi xmm0 esi xmm1

こういうのを考えてプラットフォームごとに JIT コンパイラを作るわけですが、こういうのを意識しても所詮 x64 の世界の内側だけの話であって ARM にすら対応できていない、という現状なのです。

Ruby 方式

そこで、Ruby は なんと C ソースを出力してコンパイルして実行するという大胆な方式 を採用しました。メリットは上記に書いた多くのプロセッサへの対応を 他人に丸投げ(いい意味で)できること、デメリットはファイル入出力やプロセス起動などのオーバーヘッドが大いに気になることと 別途コンパイラを要求する ところ。実行環境にコンパイラを要求するのは、まぁ Linux なら gcc があるし、とか思うけど Windows が問題 ですよね。普通の人にコンパイラをインストールして、とかなかなか言いづらい。しかも本体とコンパイラを合わせないとイケなかったり。

これをやるなら、私なら C コンパイラも含めてパッケージに入れて提供したい、と思う。LLVM とか使えるのあるしね。デカいけど。ちなみに、以前 Clang で JIT するやり方を以下に公開してみました。Clang/LLVM 8.0.0 の頃なので多少古いが、今でも役に立つかな?

x64 だけに絞っても、全部スタティックリンクすると約 35MB の実行ファイルが出来上がります。デカいぜ。

そして(多少)時は流れ...

...と、ここまでは以前も書いた内容とほぼ同じ。で、やはり誰かが計画するわけですね。C コンパイラの同梱。そのためのプロジェクトが MIR です。がっつり Ruby の JIT に組み込むことを計画してます。軽量な最適化機能付き(関数のインライン展開付き) JIT コンパイラです。さらに、十分抽象化された中間表現 を持ち、それによって 様々なプラットフォームへの対応を可能 にしています。そして、C コンパイラが付いているのが超絶に便利 です。Ruby JIT に使うので当然でしょう。ところが、実はそれは単純に C スクリプト環境としても最適ということを意味します。あぁ、これ欲しかったですよ。ただし、まだバグも色々ありますが。

ある意味、C 言語にトランスパイルする言語はこれで全て実行可能にすることもできる でしょう。新たに C 言語にトランスパイルする言語を実装しようとしたとき、その言語実装自体は 超簡単 になりそうです2。...いや、いろいろ難しいかもしれませんが。イメージで話しました。すみません。

元々 Kinx でも同様に一気にアセンブルするやり方を試したりできないかなー、と kcs という自作 C JIT コンパイラを使ってみようと思ってました。ただ、x64 だけなのと最適化はほぼ無いので、MIR のほうが断然良いですね。もちろん kcs は JIT 基盤にしようとした訳ではなく、単に C スクリプトを実現しようとしたものなので、そもそもの目的が違ってます。しかし MIR みたいなのが出てくるとなると彼ももうお払い箱ですね…。残念ですが。一応 kcs の紹介だけ(更新止まってます)。

MIR は Ruby JIT への組み込みが計画されていますが、それだけでは勿体ないですね。これほどフレキシブルでライトウェイトでありながら、かつ十分抽象化された IR レイヤを持つ MIT ライセンスの(C コンパイラを含む)JIT コンパイラは他に見つけられなかったので、Kinx でも活用できないか、他に何か良い使い方が無いか、夢が膨らみます。MIR については後述します。つーか、Clang/LLVM はとにかくデカいんです(くどいですが)。

2. Kinx Native - sljit による JIT コンパイル

さて、本題に戻りましょう。まずは Kinx の native からです。

Kinx では、native というキーワードを使うことでネイティブ・コードにコンパイルして実行するという機能を持っています。いわゆるトレーシングとかは無しに、いきなりネイティブ・コードにコンパイルして実行させます。これは数値計算を主とする各種ベンチマークではそれなりに結構いい感じの結果を出してます。例えば「この記事」参照。

...が、実用上、通常のプログラムの中ではイマイチ使いこなせていません(私自身が。アイタタタ)。まぁでも、他の言語にはあまりない Kinx の特長でもあるので、大切に育てていきたい機能ではあります。

さて、Kinx の native による JIT コンパイルでは、上記のプラットフォームに依存してしまうという問題に対し、sljit を採用することで回避しています。sljit はアセンブラを抽象化しており、その抽象化アセンブラで書くことでコード出力時に x64 や ARM などに合わせて出力ができるようになっています。その代わり、レジスタ数とかどのプロセッサーでも共通に使えるようにミニマムに合わせておかなければなりません。

ちなみに、sljit のサポートアーキは以下の通りとのこと。

  • Intel-x86 32
  • AMD-x86 64
  • ARM 32 (ARM-v5, ARM-v7 and Thumb2 instruction sets)
  • ARM 64
  • PowerPC 32
  • PowerPC 64
  • MIPS 32 (III, R1)
  • MIPS 64 (III, R1)
  • SPARC 32

ただし、x64 以外ではテストしていない & 64ビットしか想定していないので悪しからず。

具体的なソースコードとして、ネイティブ・コード用 IR 出力ルーチンは src/ast_native.c にあり、sljit を使ったネイティブ・コード用コンパイラは src/nir_compile.c にあります。

また、sljit 自体の使い方(と事例)は、先日「Kinx 実現技術 - JIT: sljit の使い方」に書いてみましたので、興味ある方はこちらも参照してみてください。

Native - 個別の実装状況

では、本題となるアップデート状況です。ここ(JIT Compile) での紹介の後、ちょっとずつ進化してます。

IR (Intermediate Representation) :tada:

native ではネイティブ・コードに変換する上で扱いやすいように、スクリプト VM 用の IR と全く異なるコードを出力します。なぜなら例外の扱いとかコンパイル時点における型チェックの厳密さとかが全く異なるためで、それに合わせて違う体系にしています。

今回、IR 出力時のレジスタ割り当て方式を変更したところ、必要なレジスタ数が減ってだいぶスッキリさせられました。また、余計なコードもだいぶ出さないようにすることもしました。

前回の例でいうと、

native nfib(n) {
    if (n < 3) return n;
    return nfib(n-2) + nfib(n-1);
}

これは以下のようになります。

nfib(1, 3):
  .L0
  .L1
       0:   load                    r1, $(0,0)
       1:   lt                      r2 = r1, 3
       2:   jz                      .L4
  .L2
       3:   load                    r1, $(0,0)
       4:   ret                     r1
  .L3
  .L4
       5:   load                    r1, $(0,0)
       6:   sub                     r2 = r1, 2
       7:   arg                     r2
       8:   call                    r1 = [rec-call]()
       9:   excpt                   r2 = check
       a:   jz                      .L6
  .L5
       b:   ret                     0
  .L6
       c:   load                    r2, $(0,0)
       d:   sub                     r3 = r2, 1
       e:   arg                     r3
       f:   call                    r2 = [rec-call]()
      10:   excpt                   r3 = check
      11:   jz                      .L8
  .L7
      12:   ret                     0
  .L8
      13:   add                     r3 = r1, r2
      14:   ret                     r3
  .L9
      15:   ret                     0

以前に比べて使用するレジスタ数が 10 → 3 に削減。ライン数も 0x20 → 0x15 に削減。

例外処理

例外機能は以前の記事から変わっていません。なので、現時点でも以下の制限があります。

  • スタックトレースは取得できない。
  • オブジェクトがまだ十分使えないので、キャッチする例外オブジェクトは実質あまり使えない。例外のフローはきちんと動作する。

いやぁ、もう少し機が熟す必要がありますね。

型チェック

これも以前から変わっていません。関数の入口で引数の型をチェック。レキシカル変数をアクセスする場合もチェック。関数から返ってきたときにもチェック。型が期待するものと違ってたらすぐさま例外を上げます。

Switch-Case :tada:

Switch-Case は、以前の記事では未サポートでしたが、頑張ってサポートしました。最新版では以下の形で実装しています。

  • Case の数に応じて 2 分探索と線形探索を切り替える。
  • Case 条件は Integer のみ。
  • ジャンプテーブル化は現在未サポート。

たぶんジャンプテーブルも実現可能な感じには sljit をハックしたのですが、まずは 2 分探索で十分だろうと判断して上記で実現。少なくともこれで実用的な Switch-Case を実現することはできるので。ジャンプテーブル化は時間があれば実施しますが、優先度は低いです。

VM 関数呼び出し

これは現時点でも未サポートです。子 VM を起動してジャンプすれば良いとは思いますが、入れ子になった VM 内で例外が発生した場合の処理が大変そうなので、まだ手がつけられていません。おそらく以下のような感じでできるのでは、と思っています。

  • フレームに native からのコールであるフラグを立てる。
  • return 時、native からであれば復帰値をセットして VM から return する。
  • 例外発生時、スタックを巻き戻している最中に native コールされたフラグを見つけた場合、そこでスタックトレース情報の作成を中断し、native 向けの例外情報を設定して native にリターン。native 側に帰ってきたら、native での例外伝搬の仕組みで伝搬させていく。

例外のスタックトレースを native 関数内でも保存するようにしないといけません。やってみる時間があれば挑戦してみる、という予定ですが、今は難しい。ただ、たぶん面倒くさいだけで実現可能だとは思います。

オブジェクトへのアクセス :tada:

これは一部実装しました。具体的には IntegerDouble への配列をサポートしました。以下のように Quick Sort くらいはできます。

native quicksort_n(a:int[], first, last) {
    var i = first;
    var j = last;
    var x = a[(first + last) / 2];
    while (true) {
        while (a[i] < x) i++;
        while (x < a[j]) j--;
        if (i >= j) break;

        [a[i], a[j]] = [a[j], a[i]];
        ++i; --j;
    }
    if (first  < i - 1)
        quicksort_n(a, first, i - 1);
    if (j + 1 < last)
        quicksort_n(a, j + 1, last);
}

const N = 20;
var a = N.times { => Integer.parseInt(Math.random() * 100) };
System.print("Before:");
a.each { System.print(" %2d" % _1); };
System.print("\n");

quicksort_n(a, 0, N - 1);

System.print("After: ");
a.each { System.print(" %2d" % _1); };
System.print("\n");

結果。

Before: 97  6 27  1 21 25 96 52  6  0 11 98 97 66 76 66 69 99 68 80
After:   0  1  6  6 11 21 25 27 52 66 66 68 69 76 80 96 97 97 98 99

整数配列は a:int[] と記述し、実数の場合は a:dbl[] という記述をします。

レジスタ割り付け :tada:

実際には変数へのレジスタ割り付け自体はやっていません。

...が、IR 出力時のレジスタ割り当て方式を 大幅に 変更したので、それに伴って演算結果で保存可能なものをレジスタに割り当てることにした結果、劇的にメモリアクセスが減りました。例えば、上記 fib の例でいうと以下のようになります。

以前のコードでは fib(n-2) の部分は以下のように出力されていました(無駄なコードがいっぱいです)。

      d7:   48 8b 14 24                                 mov rdx, [rsp]
      db:   48 89 94 24 30 02 00 00                     mov [rsp+0x230], rdx
      e3:   48 8b 94 24 30 02 00 00                     mov rdx, [rsp+0x230] 
      eb:   48 83 ea 02                                 sub rdx, 0x02
      ef:   48 89 94 24 38 02 00 00                     mov [rsp+0x238], rdx
      f7:   48 8b 94 24 38 02 00 00                     mov rdx, [rsp+0x238]
      ff:   48 89 54 24 18                              mov [rsp+0x18], rdx     
     104:   48 c7 84 24 18 01 00 00 01 00 00 00         mov [rsp+0x118], 0x01
     112:   48 8b 4b 18                                 mov rcx, [rbx+0x18]
     116:   48 89 d8                                    mov rax, rbx
     119:   49 8b 57 08                                 mov rdx, [r15+0x8]
     11d:   48 89 54 24 10                              mov [rsp+0x10], rdx
     122:   48 8d 74 24 08                              lea rsi, [rsp+0x8]
     127:   48 89 c7                                    mov rdi, rax
     12a:   ff d1                                       call rcx
     12c:   48 89 84 24 40 02 00 00                     mov [rsp+0x240], rax

これがこうなりました。

      93:   48 8b 2c 24                                 mov rbp, [rsp]
      97:   48 8d 45 fe                                 lea rax, [rbp-0x2]
      9b:   48 89 44 24 18                              mov [rsp+0x18], rax
      a0:   48 c7 84 24 18 01 00 00 01 00 00 00         mov qword [rsp+0x118], 0x1
      ac:   48 8b 4b 18                                 mov rcx, [rbx+0x18]
      b0:   48 89 d8                                    mov rax, rbx
      b3:   49 8b 57 08                                 mov rdx, [r15+0x8]
      b7:   48 89 54 24 10                              mov [rsp+0x10], rdx
      bc:   48 8d 74 24 08                              lea rsi, [rsp+0x8]
      c1:   48 89 c7                                    mov rdi, rax
      c4:   ff d1                                       call rcx
      c6:   48 89 c5                                    mov rbp, rax

分かりづらいとは思いますが全部載せるとこんな感じで、メモリアクセスがかなり減りました。

nfib: (native-base:0x7fbd9c030010)
       0:   53                                          push rbx
       1:   41 57                                       push r15
       3:   41 56                                       push r14
       5:   41 55                                       push r13
       7:   55                                          push rbp
       8:   41 54                                       push r12
       a:   48 8b df                                    mov rbx, rdi
       d:   4c 8b fe                                    mov r15, rsi
      10:   4c 8b f2                                    mov r14, rdx
      13:   48 81 ec 38 02 00 00                        sub rsp, 0x238
      1a:   49 8b 47 08                                 mov rax, [r15+0x8]
      1e:   48 83 c0 01                                 add rax, 0x1
      22:   49 89 47 08                                 mov [r15+0x8], rax
      26:   48 3d 00 04 00 00                           cmp rax, 0x400
      2c:   72 29                                       jb 0x57
      2e:   48 c7 43 20 01 00 00 00                     mov qword [rbx+0x20], 0x1
      36:   48 c7 43 28 06 00 00 00                     mov qword [rbx+0x28], 0x6
      3e:   48 c7 c0 00 00 00 00                        mov rax, 0x0
      45:   48 81 c4 38 02 00 00                        add rsp, 0x238
      4c:   41 5c                                       pop r12
      4e:   5d                                          pop rbp
      4f:   41 5d                                       pop r13
      51:   41 5e                                       pop r14
      53:   41 5f                                       pop r15
      55:   5b                                          pop rbx
      56:   c3                                          ret
      57:   49 83 bf 10 01 00 00 01                     cmp qword [r15+0x110], 0x1
      5f:   0f 85 11 01 00 00                           jnz 0x176
      65:   49 8b 57 10                                 mov rdx, [r15+0x10]
      69:   48 89 14 24                                 mov [rsp], rdx
      6d:   48 8b 2c 24                                 mov rbp, [rsp]
      71:   48 89 e8                                    mov rax, rbp
      74:   48 83 f8 03                                 cmp rax, 0x3
      78:   7d 19                                       jge 0x93
      7a:   48 8b 2c 24                                 mov rbp, [rsp]
      7e:   48 89 e8                                    mov rax, rbp
      81:   48 81 c4 38 02 00 00                        add rsp, 0x238
      88:   41 5c                                       pop r12
      8a:   5d                                          pop rbp
      8b:   41 5d                                       pop r13
      8d:   41 5e                                       pop r14
      8f:   41 5f                                       pop r15
      91:   5b                                          pop rbx
      92:   c3                                          ret
      93:   48 8b 2c 24                                 mov rbp, [rsp]
      97:   48 8d 45 fe                                 lea rax, [rbp-0x2]
      9b:   48 89 44 24 18                              mov [rsp+0x18], rax
      a0:   48 c7 84 24 18 01 00 00 01 00 00 00         mov qword [rsp+0x118], 0x1
      ac:   48 8b 4b 18                                 mov rcx, [rbx+0x18]
      b0:   48 89 d8                                    mov rax, rbx
      b3:   49 8b 57 08                                 mov rdx, [r15+0x8]
      b7:   48 89 54 24 10                              mov [rsp+0x10], rdx
      bc:   48 8d 74 24 08                              lea rsi, [rsp+0x8]
      c1:   48 89 c7                                    mov rdi, rax
      c4:   ff d1                                       call rcx
      c6:   48 89 c5                                    mov rbp, rax
      c9:   48 8b 43 20                                 mov rax, [rbx+0x20]
      cd:   48 83 f8 00                                 cmp rax, 0x0
      d1:   74 19                                       jz 0xec
      d3:   48 c7 c0 00 00 00 00                        mov rax, 0x0
      da:   48 81 c4 38 02 00 00                        add rsp, 0x238
      e1:   41 5c                                       pop r12
      e3:   5d                                          pop rbp
      e4:   41 5d                                       pop r13
      e6:   41 5e                                       pop r14
      e8:   41 5f                                       pop r15
      ea:   5b                                          pop rbx
      eb:   c3                                          ret
      ec:   4c 8b 24 24                                 mov r12, [rsp]
      f0:   49 8d 44 24 ff                              lea rax, [r12-0x1]
      f5:   48 89 44 24 18                              mov [rsp+0x18], rax
      fa:   48 c7 84 24 18 01 00 00 01 00 00 00         mov qword [rsp+0x118], 0x1
     106:   48 8b 4b 18                                 mov rcx, [rbx+0x18]
     10a:   48 89 d8                                    mov rax, rbx
     10d:   49 8b 57 08                                 mov rdx, [r15+0x8]
     111:   48 89 54 24 10                              mov [rsp+0x10], rdx
     116:   48 8d 74 24 08                              lea rsi, [rsp+0x8]
     11b:   48 89 c7                                    mov rdi, rax
     11e:   ff d1                                       call rcx
     120:   49 89 c4                                    mov r12, rax
     123:   48 8b 43 20                                 mov rax, [rbx+0x20]
     127:   48 83 f8 00                                 cmp rax, 0x0
     12b:   74 19                                       jz 0x146
     12d:   48 c7 c0 00 00 00 00                        mov rax, 0x0
     134:   48 81 c4 38 02 00 00                        add rsp, 0x238
     13b:   41 5c                                       pop r12
     13d:   5d                                          pop rbp
     13e:   41 5d                                       pop r13
     140:   41 5e                                       pop r14
     142:   41 5f                                       pop r15
     144:   5b                                          pop rbx
     145:   c3                                          ret
     146:   4a 8d 44 25 00                              lea rax, [rbp+r12]
     14b:   48 81 c4 38 02 00 00                        add rsp, 0x238
     152:   41 5c                                       pop r12
     154:   5d                                          pop rbp
     155:   41 5d                                       pop r13
     157:   41 5e                                       pop r14
     159:   41 5f                                       pop r15
     15b:   5b                                          pop rbx
     15c:   c3                                          ret
     15d:   48 c7 c0 00 00 00 00                        mov rax, 0x0
     164:   48 81 c4 38 02 00 00                        add rsp, 0x238
     16b:   41 5c                                       pop r12
     16d:   5d                                          pop rbp
     16e:   41 5d                                       pop r13
     170:   41 5e                                       pop r14
     172:   41 5f                                       pop r15
     174:   5b                                          pop rbx
     175:   c3                                          ret
     176:   48 c7 43 20 01 00 00 00                     mov qword [rbx+0x20], 0x1
     17e:   48 c7 43 28 07 00 00 00                     mov qword [rbx+0x28], 0x7
     186:   48 c7 c0 00 00 00 00                        mov rax, 0x0
     18d:   48 81 c4 38 02 00 00                        add rsp, 0x238
     194:   41 5c                                       pop r12
     196:   5d                                          pop rbp
     197:   41 5d                                       pop r13
     199:   41 5e                                       pop r14
     19b:   41 5f                                       pop r15
     19d:   5b                                          pop rbx
     19e:   c3                                          ret

最適化 :tada:

定数伝搬と定数の畳み込み を実装しました。他はやっていませんが、enum とか const とかしてシンボル化した値を伝搬させて畳み込むのは結構効果があります。

Kinx Native - 今後の予定

以下はやりたいですが、全然取り掛かれる気配がないのが問題ですね。やはり焦らず気長に。。。

  • オブジェクトの配列
  • スクリプト関数の呼び出し
  • オブジェクト・プロパティアクセス
  • BigInteger のオブジェクト利用効率化
  • 例外オブジェクトの送出

3. JIT ライブラリ - 手軽に JIT コンパイルを体験

さて、次は JIT ライブラリです。

Kinx では、native 以外にも直接 JIT を楽しめる JIT ライブラリがあります。これはまさしく sljit をラップしたライブラリです(sljit の使い方の例等は「こちら」)。必要なレジスタ数などの自動計算などもあり、直接使うより便利にはしています。

JIT ライブラリに関しては以下に記事を載せています。私個人的にはこれを拡張して色々できると楽しいなー、と思っていますが、なかなか手が付けられませんね。うーん。

以前と同じことを書いても仕方がないので、サンプルを再掲して今後したいことなど。細かい使い方に関しては上記の「JIT ライブラリ」をご参照ください。

JIT ライブラリ・サンプル

こんな感じで、抽象化アセンブラを Kinx 上から手軽に使えます。

using Jit;

var c = new Jit.Compiler();
var entry1 = c.enter();
    var jump0 = c.ge(Jit.S0, Jit.IMM(3));
    c.ret(Jit.S0);
    var l1 = c.label();
    c.sub(Jit.R0, Jit.S0, Jit.IMM(2));
    c.call(entry1);
    c.mov(Jit.S1, Jit.R0);
    c.sub(Jit.R0, Jit.S0, Jit.IMM(1));
    c.call(entry1);
    c.add(Jit.R0, Jit.R0, Jit.S1);
    c.ret(Jit.R0);

jump0.setLabel(l1);
var code = c.generate();

for (var i = 1; i <= 42; ++i) {
    var tmr = new SystemTimer();
    var r = code.run(i);
    System.println("[%8.3f] fib(%2d) = %d" % tmr.elapsed() % i % r);
}

Jit.Compiler オブジェクトを作って、enter で関数エントリを作り、色々レジスタをいじくりまわして ret するコードを書きます。で、実行するときは generate() して run()、となります。generate() して dump() とすると、アセンブル・リストを見ることもできます。

上記は、よく見ないと分からないかもしれませんが(ご想像の通り)フィボナッチ数列を求めるプログラムです。アセンブル・リストを出してみましょう。実行せずに code.dump() とすると以下が表示されます。

       0:   53                                          push rbx
       1:   41 57                                       push r15
       3:   41 56                                       push r14
       5:   48 8b df                                    mov rbx, rdi
       8:   4c 8b fe                                    mov r15, rsi
       b:   4c 8b f2                                    mov r14, rdx
       e:   48 83 ec 10                                 sub rsp, 0x10
      12:   48 83 fb 03                                 cmp rbx, 0x3
      16:   73 0d                                       jae 0x25
      18:   48 89 d8                                    mov rax, rbx
      1b:   48 83 c4 10                                 add rsp, 0x10
      1f:   41 5e                                       pop r14
      21:   41 5f                                       pop r15
      23:   5b                                          pop rbx
      24:   c3                                          ret
      25:   48 8d 43 fe                                 lea rax, [rbx-0x2]
      29:   48 89 fa                                    mov rdx, rdi
      2c:   48 89 c7                                    mov rdi, rax
      2f:   e8 cc ff ff ff                              call 0x0
      34:   49 89 c7                                    mov r15, rax
      37:   48 8d 43 ff                                 lea rax, [rbx-0x1]
      3b:   48 89 fa                                    mov rdx, rdi
      3e:   48 89 c7                                    mov rdi, rax
      41:   e8 ba ff ff ff                              call 0x0
      46:   49 03 c7                                    add rax, r15
      49:   48 83 c4 10                                 add rsp, 0x10
      4d:   41 5e                                       pop r14
      4f:   41 5f                                       pop r15
      51:   5b                                          pop rbx
      52:   c3                                          ret

ここでは Linux ですが、Windows だと使われるレジスタが変わります。そう、例の呼出規約の話です。型チェックなどがバッサリ無い分、そして書いた通りのコードが出ているので(大体のところ)非常に綺麗ですね。スタックの操作とかレジスタの退避とかは必要な分だけ自動的に行われるので、その辺りもラクチンです。

JIT ライブラリ・ベンチマーク

では、ベンチマークを載せてみましょう。fib(42) を再帰で計算させた結果です。

言語 版数 User 時間
Kinx(Jit-Lib) 0.10.0 0.828
HHVM 3.21.0 2.227
Kinx(native) 0.10.0 2.250
PyPy 5.10.0 3.313
PHP 7.2.24 11.422
Ruby 2.5.1p57 14.877
Kinx 0.10.0 27.478
Python 2.7.15+ 41.125

JIT ライブラリは結構使い方次第で役に立ちそうだとは思ってはいるんですけどね。何気に native もいい結果なので、目下、自己満足中です。しかし Ruby 速いなー。どんどん速くなるなー。いいナー3。それ以上に PHP が速いけど。

JIT ライブラリ・今後の予定

そんな JIT ライブラリとしては、以下を計画したいと思っています。

  • 文字列操作機能(Kinx オブジェクトも使用可能かつある程度抽象化した形で)。
  • C 関数のお手軽呼び出し機能。

C 関数をお手軽に呼べるようにできれば、かなり拡張できるようになります。具体的には dll からエントリ・ポイントを取得してコールするようにすれば良さそうなので、やればできそうです。

あー、やりたいことは沢山あります。焦らず、気長に。

4. MIR 感想

そしてお待ちかねの MIR です。Windows でも Linux でもそれなりには行けました。ここでは Linux での内容をお伝えします。オリジナルのリポジトリは以下です。

Build してみよう

では早速 MIR にトライしてみましょう。clone します。

$ git clone https://github.com/vnmakarov/mir.git
Cloning into 'mir'...
remote: Enumerating objects: 435, done.
remote: Counting objects: 100% (435/435), done.
remote: Compressing objects: 100% (231/231), done.
remote: Total 10386 (delta 280), reused 336 (delta 204), pack-reused 9951
Receiving objects: 100% (10386/10386), 3.59 MiB | 3.08 MiB/s, done.
Resolving deltas: 100% (6372/6372), done.
Checking out files: 100% (1370/1370), done.

$ cd mir

$ ls
CMakeLists.txt      c2mir              mir-gen-ppc64.c   mir-interp.c  mir.h
HOW-TO-PORT-MIR.md  check-threads.sh   mir-gen-s390x.c   mir-ppc64.c   mir2c
LICENSE             include            mir-gen-stub.c    mir-reduce.h  mir3.svg
MIR.md              llvm2mir           mir-gen-x86_64.c  mir-s390x.c   mirall.svg
Makefile            mir-aarch64.c      mir-gen.c         mir-tests     real-time.h
README.md           mir-bin-driver.c   mir-gen.h         mir-utils     sieve.c
adt-tests           mir-bitmap.h       mir-gen.svg       mir-varr.h
c-benchmarks        mir-dlist.h        mir-hash.h        mir-x86_64.c
c-tests             mir-gen-aarch64.c  mir-htab.h        mir.c

cmake がありますが、Makefile がありますので Linux(実際は WSL)では直接 make します。

$ make
cc -std=gnu11 -Wno-abi -fsigned-char -fno-tree-sra -fno-ipa-cp-clone -DMIR_PARALLEL_GEN -c -O3 -g -DNDEBUG -o mir.o mir.c
cc -std=gnu11 -Wno-abi -fsigned-char -fno-tree-sra -fno-ipa-cp-clone -DMIR_PARALLEL_GEN -c -O3 -g -DNDEBUG -o mir-gen.o mir-gen.c
cc -std=gnu11 -Wno-abi -fsigned-char -fno-tree-sra -fno-ipa-cp-clone -DMIR_PARALLEL_GEN -O3 -g -DNDEBUG -I. mir-gen.o c2mir/c2mir.c c2mir/c2mir-driver.c mir.o -lm -ldl -lpthread -o c2m
cc -std=gnu11 -Wno-abi -fsigned-char -fno-tree-sra -fno-ipa-cp-clone -DMIR_PARALLEL_GEN -I. -O3 -g -DNDEBUG -o m2b mir.o mir-utils/m2b.c
cc -std=gnu11 -Wno-abi -fsigned-char -fno-tree-sra -fno-ipa-cp-clone -DMIR_PARALLEL_GEN -I. -O3 -g -DNDEBUG -o b2m mir.o mir-utils/b2m.c
cc -std=gnu11 -Wno-abi -fsigned-char -fno-tree-sra -fno-ipa-cp-clone -DMIR_PARALLEL_GEN -I. -O3 -g -DNDEBUG -o b2ctab mir.o mir-utils/b2ctab.c

あっさりと、うまくいきました。

ベンチマークしてみよう

では恒例のフィボナッチ、行ってみましょう。JIT ライブラリのときと同じ条件で fib(42) で実行し、結果をマージしてみます。

int printf(const char *fmt, ...);
#define N 42

int fib(int n)
{
    if (n < 3) return n;
    return fib(n-2) + fib(n-1);
}

int main(void)
{
    printf("fib %d = %d\n", N, fib(N));
    return 0;
}

gcc と比較してみるため、gcc でもコンパイル。

$ gcc -O0 -o fib_gcc_o0 fib.c
$ gcc -O2 -o fib_gcc_o2 fib.c

さて、結果です。

言語 版数 User 時間 備考
GCC -O2 7.5.0 0.531 コンパイル時間を除く
Kinx(Jit-Lib) 0.10.0 0.828
GCC -O0 7.5.0 1.109 コンパイル時間を除く
c2m -eg - 1.500 全ての関数を最初にコンパイルする方式
c2m -el - 1.750 関数が呼び出される直前にコンパイル、必要な関数のみコンパイル
HHVM 3.21.0 2.227
Kinx(native) 0.10.0 2.250
PyPy 5.10.0 3.313
PHP 7.2.24 11.422
Ruby 2.5.1p57 14.877
Kinx 0.10.0 27.478
Python 2.7.15+ 41.125
c2m -ei - 71.453 全て MIR インタプリタで実行

さすがに GCC -O2 が速いです。といってもコンパイルが無視されていますがこの程度だと 15ms くらいだったのでまぁ良いでしょう。今のところ、GCC の最適化無しでも c2m より速いようですが、コンパイル時間を除くと若干差は縮まるとは思われます。尚、c2m は -O2 がデフォルトのようです。-O0 を付けると遅くなります。元々、gcc とか clang とかはスタートアップとコンパイルフェーズが長いのでそれを回避したい、ということなので、ランタイムは多少遅くても良いでしょう。そういう意味では gcc とは比較したい場所が違いますね。

この MIR、まだプロジェクトは初期のステージとのことなのでこれからと言ったところでしょう。現時点での結果としては、全然悪くないです。

IR 自体まだ仕様が安定してなさそうなのですが、非常に楽しみです。

(その他)MIR での標準ライブラリについて

printf を自分でプロトタイプ宣言してますが、MIR では標準ライブラリを 動的にロードして 実行していました。おお、画期的。こんなやり方できたんですね。目からうろこでした。実際本当にいいのかはよくわかりませんが、このやり方でうまく動いています。

具体的には以下のソースコードです(リンク先が変わってしまったらごめんなさい)。

この技、今度使おう。

今後

いくつか妄想だけでも...

  • Ruby 方式の簡易版 JIT コンパイルを Kinx で実現してみたり。
  • C 拡張スクリプトとして色んな言語の拡張を C の動的コンパイルで実現できるようにしてみたり。
  • 簡単なサンプル自作言語として C へのトランスパイラを作成してみたり。

時間がいくらあっても足りない。

と、いいつつ、MIR を使いたい一心で何か始めます。というか始めてます。ちゃんと記事ができるようになったら紹介できるかもしれません。

おわりに

ここまで読んでくださってありがとうございます。

今後、以下のようなことは考えています。

  • Kinxnative 機能をゆっくりでも育てていく4
  • Kinx の JIT ライブラリを機能拡張する。
  • MIR でチャレンジ精神を発揮する。

言語実装 Advent Calendar ということで、自作言語 Kinx を紹介させていただくと共に、それに関する JIT 関連と最近の興味を記事にしてみました。全部と言わずとも少しでも皆さんの興味にヒットして、何かしらのお役に立てれば幸いです。

また、もし宜しければ Kinx は GitHub にソースコードを公開していますので、Star とか貰えるとやる気出ます(やる気があっても時間がないのはいかんともしがたいが...)。泣き言言ってますが、焦らず、気長に見守っててください。もちろん、最初にも書きましたが、どなたでもどんな内容でも コントリビュート大歓迎 ですので、何かありましたらぜひぜひ宜しくお願い致します。

ではこの辺で、またどこかでお会いしましょう。

改めて、最後までお読みいただき、ありがとうございました。


  1. 全く関係ない話ですが、昔、何かの本で「マシン語は一部で『魔神語』と呼ばれ恐れられている」(←うろ覚え、適当)みたいなことが書いてあって、子供ながらに面白いと思った記憶が多少残ってます(なんだっけなー)。 

  2. 当時(Kinx を作り始めたのは約 1 年前)、これの完成版があったら、もしかしたら最初から全て C 言語にトランスコンパイルする形で設計したかもしれません。というより、Kinx 以前に同じシンタックスで作っていた前身言語は、Kinx 同様 VM 実行させるように実装した後、Clang ベースで別バージョンを作った過去があります。当然のように起動がモッサリしました。MIR はこういうのを避けられそうで Good です。 

  3. ちなみに Kinx は Ruby 2.4.0 と大体同じくらいの実行速度なレベルです。VM 性能は、割とがっつりチューニングしているのでそんなに変わらん気がするのだが。GC の差かな。あまり頑張って調べる気はないのですけど。Kinx の GC は当初から変わらず Stop The World での Mark & Sweep を堅持しているので。しかし、Ruby は1.8.7 の頃とは全く比較になりませんね。 

  4. Kinx 自体はデバッガ作りたいとか、Language Server をサポートしたい(主に VSCode)とか、パッケージマネージャーを用意したいとか、マニュアルをコンプリートさせたいとか、GUI ライブラリ取り揃えたいとか、やりたいことはテンコ盛りなのですが…。 

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

【JS】関数を代入した変数を複数記述するときの注意点

Booleanを返す関数を代入した変数の処理で、複数の条件(&&||)を設定する場合の記述の注意点メモ。


正しい記述

複数の真偽値を設定する場合は、以下のように、引数を明示して双方の変数にそれぞれ渡す必要がある。

OK事例
moreThanOne = ({arr}) => arr.length > 1
lessThanThree = ({arr}) => arr.length < 3

//正常な記述
judgeOK = (params) => lessThanThree(params) && moreThanOne(params)



これを、引数を記述せず、変数をつなげると意図しない結果になってしまう。

NG事例
//間違った記述
judgeNG = lessThanThree && moreThanOne


なぜ意図しない結果になってしまうのか?

NG事例
judgeNG = lessThanThree && moreThanOne

この式は、lessTanThreeの結果と、moreThanOneの結果を&&で結んでいるのではなく、 変数自体をつなげている事になる。

&&は前者がtrue / false以外の場合、後方の値を返す

このため、上記式はjudgeNG = moreThanOneと同じになってしまう。



▼true/false以外の場合の例
image.png

▼確認用コード

arr = [1,2,3,4,5]

moreThanOne = ({arr}) => arr.length > 1
lessThanThree = ({arr}) => arr.length < 3

//意図しない動き
judgeNG = lessThanThree && moreThanOne
console.log( judge({arr}) ) //true

//意図した動き
judgeOK = (params) => lessThanThree(params) && moreThanOne(params)
console.log( judge({arr}) ) //false

上記のように、引数を記述しない場合は、後方のmoreThanOneのみが評価され、lessThanThreeが無視された結果になってしまう。

一方で引数を記述した場合は、true && false = falseが返る。


paramsとは何か?

paramsは渡された引数全てを表している。引数名は任意なので、params以外でも問題ない。

arr = [1,2,3,4,5]

moreThanOne = ({arr}) => arr.length > 1
lessThanThree = ({arr}) => arr.length < 3

judge = (o) => lessThanThree(o) && moreThanOne(o)

console.log( judge({arr}) )
//false

上記の場合、oには{arr}が入っている。


変数が一つの場合

変数が1つの場合引数を渡さずとも意図した動きになるため、これが混乱する要因にもなっていた、、

moreThanOne = ({arr}) => arr.length > 1

//意図した動きになる
judge = moreThanOne
judge({arr})

これは、judgeがmorethanOneに格納された変数そのものになっているため、引数を渡さずとも真偽値が返る。

judge = moreThanOne
     ↓↑
judge = ({arr}) => arr.length > 1

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

【入門者向け】Canvas入門講座#15 モザイクしてみよう【JavaScript】

問題15

問題13~15は問題9がベースとなっています。まず問題9を実装してください。

画像読み込み後、mosaic押下時に8x8のモザイク処理を行え。
モザイク処理とは平均の色で塗りつぶすことである。

以下のHTMLを使用すること。(今回の問題はcanvasのサイズが640x320になっています。)

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>問題15</title>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script>
$(() => {

    $('#my-file').on('change', e => {
        const img = new Image();
        $(img).on('load', e => {            
        });
        img.src = URL.createObjectURL(e.target.files[0]);
    });

    $('#mosaic-button').click(e => {        
    });

});
</script>
</head>
<body>
<canvas id="my-canvas" width="640" height="320"></canvas>
<br>
<input id="my-file" type="file" />
<br>
<input id="mosaic-button" type="button" value="mosaic" />
</body>
</html>

解答

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>問題15</title>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script>
$(() => {

    const MOSAIC_WIDTH = 8,
        MOSAIC_HEIGHT = 8; 

    $('#my-file').on('change', e => {
        const img = new Image();
        $(img).on('load', e => {
            // コンテキストを取得
            const ctx = $('#my-canvas')[0].getContext('2d');

            // canvasを黒色で塗りつぶす
            ctx.fillStyle = '#000000';
            ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);            

            // canvasと画像のアスペクト比を求める
            const canvasWidth = $('#my-canvas').prop('width'),
                canvasHeight = $('#my-canvas').prop('height');
                canvasAspect = canvasWidth / canvasHeight;  // canvasのアスペクト比
                imgAspect = img.width / img.height; // imgのアスペクト比

            // canvasと画像のアスペクト比を比較し、貼り付ける領域を決定する
            let dstWidth, dstHeight;
            if(canvasAspect > imgAspect) {// canvasの方が横長
                dstHeight = canvasHeight;
                dstWidth = dstHeight * imgAspect;               
            } else {// canvasの方が縦長
                dstWidth = canvasWidth;
                dstHeight = dstWidth / imgAspect;
            }

            // 画像を貼り付ける
            ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, dstWidth, dstHeight);
        });
        img.src = URL.createObjectURL(e.target.files[0]);
    });

    $('#mosaic-button').click(e => {
        const canvas = $('#my-canvas')[0],
            ctx = canvas.getContext('2d');

        ctx.save();

        const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height),
            data = imgData.data;

        for(let y = 0; y < canvas.height; y += MOSAIC_HEIGHT) {
            for(let x = 0; x < canvas.width; x += MOSAIC_WIDTH) {

                // モザイクの色を計算する
                let r, g, b;
                r = g = b = 0;
                for(let j = 0; j < MOSAIC_HEIGHT; j += 1) {
                    for(let i = 0; i < MOSAIC_WIDTH; i += 1) {
                        // 画素のインデックスを求める
                        const p = ((y + j) * canvas.width + (x + i)) * 4;
                        r += data[p];
                        g += data[p + 1];
                        b += data[p + 2];
                    }
                }

                // 各色の平均を計算する
                const pixelCount = MOSAIC_WIDTH * MOSAIC_HEIGHT; // モザイクの画素数            
                r /= pixelCount;
                g /= pixelCount;
                b /= pixelCount;

                // モザイクをかける            
                for(let j = 0; j < MOSAIC_HEIGHT; j += 1) {
                    for(let i = 0; i < MOSAIC_WIDTH; i += 1) {
                        // 画素のインデックスを求める
                        const p = ((y + j) * canvas.width + (x + i)) * 4;
                        data[p] = r;
                        data[p + 1] = g;
                        data[p + 2] = b;
                    }
                }
            }
        }

        // imageDataをcanvasに貼り付ける
        ctx.putImageData(imgData, 0, 0);

        ctx.restore();
    });

});
</script>
</head>
<body>
<canvas id="my-canvas" width="640" height="320"></canvas>
<br>
<input id="my-file" type="file" />
<br>
<input id="mosaic-button" type="button" value="mosaic" />
</body>
</html>

モザイク処理ができました。かっこいいですね。

image.png

解説

基本的な流れは問題13,14と似ています。
やりたいことは8x8の平均の色で塗りつぶすことです。

グレースケールや2値化では1つのfor文で回していましたが
今回のモザイク処理ではx方向とy方向の2つのfor文でループさせたほうがわかりやすいと思います。
下図のようにYの値を8ずつ変えながらX方向に8ずつ走査します。

image.png

for(let y = 0; y < canvas.height; y += MOSAIC_HEIGHT) {
    for(let x = 0; x < canvas.width; x += MOSAIC_WIDTH) {
        // ここでモザイク処理
    }
}

今回のモザイクサイズは8x8ですので、
現在の画素からさらにx方向とy方向の2つのfor文でループさせて、まずは画素の各成分を全部足し合わせます。
それをモザイクの画素数で割れば平均が出ます。
その平均の色で塗りつぶします。

// モザイクの色を計算する
let r, g, b;
r = g = b = 0;
for(let j = 0; j < MOSAIC_HEIGHT; j += 1) {
    for(let i = 0; i < MOSAIC_WIDTH; i += 1) {
        // 画素のインデックスを求める
        const p = ((y + j) * canvas.width + (x + i)) * 4;
        r += data[p];
        g += data[p + 1];
        b += data[p + 2];
    }
}

// 各色の平均を計算する
const pixelCount = MOSAIC_WIDTH * MOSAIC_HEIGHT; // モザイクの画素数            
r /= pixelCount;
g /= pixelCount;
b /= pixelCount;

// モザイクをかける            
for(let j = 0; j < MOSAIC_HEIGHT; j += 1) {
    for(let i = 0; i < MOSAIC_WIDTH; i += 1) {
        // 画素のインデックスを求める
        const p = ((y + j) * canvas.width + (x + i)) * 4;
        data[p] = r;
        data[p + 1] = g;
        data[p + 2] = b;
    }
}

発展

今回canvasのサイズが640x320で幅、長さ共に8で割り切れましたが
割り切れない場合はどのように実装すればよいでしょうか?
よい練習になると思うのでぜひやってみてください。(こちらが参考になるかも)

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

人物画像の姿勢推定をNode-REDで行う方法

日立製作所OSSソリューションセンタの横井です。前の記事では、RedHat OpenShiftのNode-REDオペレータの使用方法をご紹介しました。この記事では、OpenShiftのNode-RED環境と、TensorFlowコンテナを接続する手順についてご説明します。

使用例

ここでは、自動車を運転する際の警報システムを実現するNode-REDのフローの例を使用します。日本では、ご存知のように自動車は左側の道路を走り、サイクリストもその自動車の同じ左側を走ります。そのため、サイクリストが自動車に近づく際は、ドライバーは衝突を回避する必要があります。この状況を支援するため、このシステムは、サイクリストが右折する兆候を示したことを人間の姿勢推定モデルを使用して判定し、ドライバーへ警告を表示します。

次の手順を行う前に、前の記事で説明したようにNode-REDオペレータを使用して、事前にNode-RED環境を作成する必要があります。すべての操作は、OpenShiftウェブコンソール上で、adminロールではなく、developerアカウントで行う必要があります。

(1) TensorFlowコンテナのデプロイ

ウェブコンソール上で、「+Add」->「Container image」の項目へアクセスします。

1.png

次のウィザードでは、Image name from external registryに「quay.io/codait/max-human-pose-estimator」を貼り付けます。このコンテナには、入力画像から人間の姿勢を推定するREST API サーバが含まれています(興味がある場合は、詳細を参照してください)。Generalフィールドの"application name"と"name"の他の項目については、イメージ名を貼り付けた後、自動的に入力されます。

2.png

ウィザードのCreateボタンをクリックしてから約2分が経過すると、max-human-pose-estimatorインスタンスのリングが青になり、デプロイが成功したことが分かります。

3.png

OpenShiftのロゴが表示されているmax-human-pose-estimatorインスタンスをダブルクリックすることによって、詳細サイドエリアのResourcesタブのRoutesフィールドの中にREST APIのエンドポイントURLが生成されていることを確認できます。

4.png

エンドポイントのURLにアクセスすると、Swagger UIからREST APIサーバに用意されているメソッドを呼び出すことができます。最後に、Node-REDのフローを編集する時に用いるため、このエンドポイントのURLを覚えておいてください。

(2) Node-REDのフローを読み込み

GitHubからサンプルフローを読み込むには、まずNode-REDフローエディタのメニューで「プロジェクト」->「新規」を選択します。Node-REDオペレータによって作成されたNode-RED環境では、このプロジェクト機能と呼ばれるGitHubとの統合機能がデフォルトで有効になっています。

以下のウィザードで「リポジトリのクローン」ボタンをクリックして、サンプルのNode-REDフローを含むリポジトリを複製します。

次のウィンドウでは、gitコマンドが使用する名前と電子メールアドレスを入力する必要があります。ただし、ここでの手順では、本情報を必要とするgit commitコマンドが登場しないため、この情報は使用されません。

最後に、GitリポジトリのURLに「https://github.com/kazuhitoyokoi/node-red-pose-estimation-demo.git」を貼り付けます。プロジェクト名は自動的に入力されます。また、その他のフィールドは空である必要があります。

この時点では、いくつかのNode-REDノードのモジュールが存在しないため、読み込み処理の後にエラー通知がポップアップ表示されます。この問題を解決するには、まず「プロジェクトの依存関係を管理」のボタンをクリックしてプロジェクト設定UIを開きます(もし、このボタンがない場合は、情報タブにあるプロジェクト名の横の「・・・」ボタンをクリックします)。

ja10.png

プロジェクト設定UIの依存関係タブには、次のようにいくつかのNode-REDモジュールが不足していることが示されます。これらのモジュールをNode-REDにインストールするには、各インストールボタンをクリックします。

ja11.png

モジュールが存在しない問題を修正すると、Node-REDのフローが、赤い点線を持つ不明なノードがないフローになります。

(3) エンドポイントを編集

エンドポイントのURLを、OpenShift環境にデプロイしたTensorFlowコンテナに変更するには、まずhuman-pose-estimatorノードをダブルクリックします。

flow.png

エンドポイントの設定UIに移動するには、ノードプロパティUIにある鉛筆ボタンをクリックします。

エンドポイント設定UIのホスト欄については、前もってデプロイしておいたmax-human-pose-estimatorインスタンスからコピーしたエンドポイントのURLを貼り付けます。なお、名前のフィールドをopenshiftに変更することは必須ではありません。エンドポイント設定UIの「更新」ボタン、ノードプロパティUIの「完了」ボタンをクリックし、フローエディタの画面に戻った後、右上のデプロイボタンをクリックします。

(4) フローの動作確認

左上にあるinjectノードの左側にあるボタンをクリックすると、右上のimage previewノードの下に注釈付きの画像が表示されます。同時に、OKの画像が右下のimage previewノードの下に出現します。両者の間にあるanalyzeノードによって、人間が自転車に乗っているときの通常のポジションかどうかが判断されます。

okflow.png

次の手順として、2番目のinjectノードのボタンをクリックすると、結果として警告の画像が表示されます。右側の腕が上がったため、サイクリストがすぐに右に曲がるだろうと、analyzeノードを使用して判断されました。

ngflow.png

必要に応じて、http requestノードだけではなく、その他のプロトコルを介して、その自動車に装備されているカメラに接続することもできます。

この記事では、Node-REDとRedHat OpenShiftにデプロイした他のコンポーネントとを統合する例を説明しました。MySQLノードのようなNode-REDノードモジュールで接続できる便利なコンポーネントがOpenShiftにはいくつかあるため、今後もこれらのコンポーネントを調べ、その他の利用例についても考えてゆこうと思います。

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

【保存版】個人開発の進め方 -全5ステップ-

はじめに

個人開発の手順をまとめました。
手順通り進めれば、必ずサービスは出来ます。

各ステップが、なるべく省エネになるように意識して書いています。
本手順の目指すスタイルは、小さく繰り返すプラン②です
lifecycle.png

是非、参考にしてみて下さい。
( twitterやってます!@gjfcgmi )

目次

STEP タイトル 内容
アイディアを探す まずはアイディアはどう探すのか?
実際アイディア出しに使ったエクセルを紹介
サービスを練り上げる 見切り開発を減らして、クローズ率を少しでも下げる
時間を節約できる箇所? 時短ポイントを3つ紹介
開発の進め方 開発着手前が大事なことを伝えたい
ユーザの獲得方法 RTされるコツとDM作戦が大事なことを伝えたい

コンテンツ

① アイディアを探す

一番最初に、サービスの核となるアイディアを探します。

アイディアがなく、何から着手すれば良いか分からない時はよくあります。
3つ探し方を紹介します。

①-1 美味しんぼ法

1. 他人のサービスを見る
2. いいなと思うサービスを探す
3. そのサービスを素因数分解する(どの要素がいいのか書き出す)
4. その類似サービスを探す
5. また素因数分解をする
6. 4.-5.を繰り返す。
7. 良い箇所を掛け合わせる
8. 要素を1つ変えて、サービスが成り立つか考える

イメージは、「美味しんぼ」です。
「美味しんぼ」の主人公が料理を食べた時に、具材を細かく言い当て・分析します。
そのようなイメージです

?美味しんぼ法を実践した例

「インフルエンサー×広告主のC2Cサービス」を作ったときの結果です。
(一部消えていたので、参考程度ですが)

  • 3.~6.を繰り返し、下記の表を作成
    縦セルは、思いついた時に追加し、空欄は後か埋める
  • 7.で各サービスの特徴・良い箇所を赤セルへ
  • 8.で競合に勝てそうなアイディア(B行)を作成 スクリーンショット 2020-11-30 11.03.47.png

①-2 他人のアイディアを見る

他人のサービスが見られるサイトを紹介します。

サイト名 紹介
Product Hunt
The best new products in tech.
毎日かなりのサービスが投稿されています。
個人開発者から大手サービスまで、おそらく世界最大の投稿サービス
スマホアプリもあるので、暇な時に見るのがおすすめ
BetaList:
Discover and get early access to tomorrow's startups
UIが綺麗
個人開発は少なく、スタートアップが多いイメージ
Startup Lister 投稿数が少ないが、その分シンプルで見やすい。
まずはこのサイトを見てみるのがおすすめ
Service Safari こちらも投稿数が少ないが、日本語サイトなので、ピックアップ。

慣れてきたら、機能がしっかりしているProduct Huntのみで良いと思います。

①-3 友達の話は、しっかり聞く

他業種の友人が入れば、業務について根掘り葉掘り聞くようにしています。
その結果、いくつかサービスに結びつきました。
テストユーザになってくれる点も有り難いです。

? Point
(専門知識・業務フロー)x(IT技術)の観点を意識して話を聞くと、アイディアが浮かび易い
ex: 弁護士 x 機械学習 → 類似判例検索エンジン

↓のサービスは弁護士の知り合いとの話がきっかけで出来たサービスです。
新卒, Webサービスを作ってみた話

他にも保育士や土地管理業者など様々な人の手を借りつつ、サービスは作っています。

② サービスを練り上げる

アイディア出しをしているうちに、いくつかサービスのイメージは出来ていると思います。
その中で、可能性の高いサービスを見つけて、さらに磨きをかけるステップです。

②-1 コンセプト良し悪し占い

自分がいつもやっているフローです。
A4.png

自身のアイディアに当てはめて、考えてみて下さい。

②-2 サービスのコンセプトを話してみる

誰かにコンセプトを話してみることをおすすめします。
きちんと言語化できるかは、早い段階で確認した方が良いです

? Tips
開発終盤で、コンセプトが弱いことに気づくことは多々あります
この段階で、類似サービスとしっかり違いを出せるか検討する時間を取った方が良いです

この段階で没案になる可能性は高いです。。
ダメでしたら、またアイディア探しに戻ります

②-3 もっとサービスを練り上げる

収益を考えるなら、もう少しサービスを練り上げます
下記の項目をさらっと紙に書き出せるか試してみることをお勧めします。

項目 回答
サービスの全体像
解決出来ること
ビジネスモデル
ビジネスモデル(お金の流れ中心)
利用方法
市場の規模
収益構造

書き出している時に疑問点・類似サービスも見つかるので
STEP②には時間を書けるようにしています

ビジネスコンテストではないので、ざっくりやってみるのお勧めです。

? 実例

C2Cサービスを作成した時のメモです。
(縦長になってしまうので、横で)

サービスの全体像 解決出来ること ビジネスモデル ビジネスモデル(お金の流れ中心) 利用方法 市場の規模 収益構造
全体像.png 解決できる事.png ビジネスモデル.png ビジネスモデル(お金中心).png 利用方法.png 市場規模(小中高生).png 収益構造.png

③ 時間を節約できる箇所?

③-1 デザイン

デザインは、費用対効果が高いので買う。
綺麗なコードが買えますし、何より安いです。
→「design template premium」でぐぐるべし

テンプレートが買えるサイトを一つピックアップします
WordPress Themes & Website Templates from ThemeForest

自分は、こちらのサイトで買いました。
1. creative-tim
2. materializecss

③-2 機能の精査

個人開発で即クローズは多々あります。
使われない機能に費やす時間は勿体無いので、実装する機能を一回精査します。

No. 問いかけ 問の意図
1 運用でカバー出来ないか サービス立ち上げ初期はそこまで人は来ません。
最初はお問い合わせベースで、件数増えてきたら機能実装でも良いはず
2. 今の段階で、本当に必要か No.1と似ています。
例えば、決済画面。
会員数0で決済画面を作る必要があるのか。
段階リリースをして、手応えを感じてからでも良いはず
3 ネイティブアプリである必要があるか リリース(審査)で時間がかかってしまうので、
特にネイティブである必要がなければ、webサイトで良いはず
手応え感じたら、側ネイティブを作って行く選択肢もあり
4 代用出来るSaaSがないのか フルスクラッチで作成すると時間がかかるので、
出来る限り利用できそうなSaaSを見つけて、フリープランで試せないか探します

毎回使うのはこの辺り
VPSの代りにfirebase, 決済サービスStripe。firebaseはCronが設定出来るので、NoSqlで表現しづらい機能が実装できる。

③-3 使うツール・サービスを絞る

個人開発では、企画・開発・マーケティングまで幅広く担当します。
学習コストもかなり膨らんでくるので、極力ツールを減らし、時短を狙う

ツール

ツール名 ざっくり説明 参考リンク
エクセル 絶対絶対必須。
基本全部これで完結(Macであれば、Numbers)
調査からWBS,不具合管表までシートで分て1ファイルにまとめてます
↓こんな感じimage.png
-
Sketch 図作成ツール。
エクセルで出来ないことは、スケッチでやっています。
記事の中の資料もスケッチで作っています
学習用記事の該当箇所
IntelliJ IDEA
WebStorm
超高機能なIDE
大抵の機能がデフォルトで備わっているのが、Goodです
https://www.jetbrains.com/ja-jp/

他のツールは使っていません。

サービス

サービス名 ざっくり説明 参考リンク
Googleアナリティクス アクセス集計 アクセス解析担当が必ずやってるGoogleAnalytics設定のまとめ【2018年版】
Firebase
Heroku
サーバの代わり。
ドメイン設定も簡単に出来る。SSL設定も。
公式サイトががわかりやすい。
FirebaseHeroku
FreeLogoDesign ロゴ自動作成ツール 他にも色々あるみたいなので。
https://liskul.com/logomaker-29137

git等は割愛です

④ 開発の進め方

④-1 開発着手前

1. 思いつく機能を書き出す。
2. 必須な機能を絞る(参考: ③-2 機能の精査)
3. 機能の優先順位を決定
4. 開発順番を考える
5. スケジュールに落とし込む
    1. 開発順番が早い順に機能を書き出す
    2. 実装をイメージして、一段階深掘り
    3. ざっくり日数見積もり
    4. 見積もり結果を踏まえて、どこで初回リリースするか決める

? 「5. スケジュールに落とし込む」の実例

C2Cサービスを作成した時の予定表を使います
5.1~5.3で出来上がる成果物を添付しました。

ステップ 5-1.
開発順番が早い順に機能を書き出す
5-2.
実装をイメージして、一段階深掘り
5-3.
ざっくり日数見積もり
成果物 スクリーンショット 2020-11-30 12.49.16.png image.png 青セルは実施予定日image.png

④-2 開発中

予定表通り進めて、必要に応じてスケジュール変更を繰り返すのみ
開発中に出てきそうな疑問をQ&A形式で紹介します。

No. 質問  自分の意見
1 バグに気づいた。いつ対応するのか クリティカルの場合:即対応
そうではない:メモだけして後回し

自分は不具合管理表を作っていますimage.png
2 二回目以降のリリースはいつするのか 機能は出来次第、リリース
バシバシリリース
github Actionを使うとデプロイが楽です
3 スケジュールがのズレが出て、エクセル手動で更新するのが大変 土日はバッファにして、開ける。
次第に見積もれるようになるはずです
4 単体テストは書くのか 不要。シナリオテストを作るのみ作成。
自分もjunit, jestで単体テスト書いていたが、コスパ悪い気が。
(もちろん書いた方がいいが、時間との相談)
5 開発に遅れが出てきた 生活リズムが崩れていないか確認!!
会社員は、出社前に時間を取ることをおすすめします。
残業でいつ帰れるか見えないので..
? Tips
ウェブサービスを作るなら、スマホに最適化を。
立ち上げ初期は、9割スマホからのアクセス。その後、しばらく7割程度に落ち着く
(あくまでも体感)

⑤ ユーザの獲得方法

SEO対策

一応導入するが、今はそこまで時間はかけない。(追々対応する。
下記のサイトを参考に、キーワードをよしなに作る。
- Google トレンド
- ラッコワード

SNS投稿作戦

RT伸びやすいようにコツを紹介

1. サービス概要が分かる画像を添付
2. 読み飛ばされないように、改行は沢山使う
3. シンプルに
4. 「-----」、絵文字等でもっとシンプルに
5. 最後に何をして欲しいかを一言で

DM作戦

最低限の機能の実装が終わったら、一回開発をストップ & 検証フェーズへ

DMを送って、受信者の反応を見て
クローズするか、もう少し開発継続するか決めています。

意外とやっていない人多いので、DM作戦お勧めです。
返信率は、ざっくり50%~70%くらいです。
(スパムのように送ってしまうとまずいので、その辺りは考慮が必要です)

終わりに

まだまだ書きたい事があります。。

後半、疲れが出てしまいました。
特に、集客パート。
開発続けるか、クローズするかのKPI表についても書きたかった

今後アップデートしてきます
変更通知送るので、ストック?いいねして頂ければと思います。

個人開発やっていて思うことは、楽しいです!

以上!!

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

【入門者向け】Canvas入門講座#14 2値化してみよう【JavaScript】

問題14

問題13~15は問題9がベースとなっています。まず問題9を実装してください。

画像読み込み後、binarize押下時に2値化処理をせよ。
2値化処理はグレースケール後、127より大きい場合は255,それ以外の場合は0となるように処理すること。

グレースケールとは以下の式で表せる。
Gray = 0.299 * Red + 0.587 * Green + 0.114 * Blue;

以下のHTMLを使用すること。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>問題14</title>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script>
$(() => {

    $('#my-file').on('change', e => {
        const img = new Image();
        $(img).on('load', e => {            
        });
        img.src = URL.createObjectURL(e.target.files[0]);
    });

    $('#binarize-button').click(e => {        
    });

});
</script>
</head>
<body>
<canvas id="my-canvas" width="500" height="300"></canvas>
<br>
<input id="my-file" type="file" />
<br>
<input id="binarize-button" type="button" value="binarize" />
</body>
</html>

解答

問題9の分の回答も併せて貼っておきます。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>問題14</title>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script>
$(() => {

    $('#my-file').on('change', e => {
        const img = new Image();
        $(img).on('load', e => {
            // コンテキストを取得
            const ctx = $('#my-canvas')[0].getContext('2d');

            // canvasを黒色で塗りつぶす
            ctx.fillStyle = '#000000';
            ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);            

            // canvasと画像のアスペクト比を求める
            const canvasWidth = $('#my-canvas').prop('width'),
                canvasHeight = $('#my-canvas').prop('height');
                canvasAspect = canvasWidth / canvasHeight;  // canvasのアスペクト比
                imgAspect = img.width / img.height; // imgのアスペクト比

            // canvasと画像のアスペクト比を比較し、貼り付ける領域を決定する
            let dstWidth, dstHeight;
            if(canvasAspect > imgAspect) {// canvasの方が横長
                dstHeight = canvasHeight;
                dstWidth = dstHeight * imgAspect;               
            } else {// canvasの方が縦長
                dstWidth = canvasWidth;
                dstHeight = dstWidth / imgAspect;
            }

            // 画像を貼り付ける
            ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, dstWidth, dstHeight);
        });
        img.src = URL.createObjectURL(e.target.files[0]);
    });

    $('#binarize-button').click(e => {
        const canvas = $('#my-canvas')[0],
            ctx = canvas.getContext('2d');

        ctx.save();

        const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height),
            data = imgData.data;

        for(let p = 0; p < data.length; p += 4) {
            // rgbを求める
            const red = data[p],
                green = data[p + 1],
                blue = data[p + 2];

            // グレースケールの値を求める
            let gray = 0.299 * red + 0.587 * green + 0.114 * blue;
            // 2値化する
            if(gray > 127) {
                gray = 255;
            } else {
                gray = 0;
            }

            // グレースケール化する(グレーで塗りつぶす)
            for (let i = 0; i < 3; i += 1) {
                data[p + i] = gray;
            }
        }

        // imageDataをcanvasに貼り付ける
        ctx.putImageData(imgData, 0, 0);

        ctx.restore();
    });

});
</script>
</head>
<body>
<canvas id="my-canvas" width="500" height="300"></canvas>
<br>
<input id="my-file" type="file" />
<br>
<input id="binarize-button" type="button" value="binarize" />
</body>
</html>

2値化できました!
image.png

解説

問題13と酷似しているのでポイントだけ説明します。
グレースケール化するときの値を計算して、閾値(今回は127)より大きい場合は255,それ以外は0にします。

// グレースケールの値を求める
let gray = 0.299 * red + 0.587 * green + 0.114 * blue;
// 2値化する
if(gray > 127) {
    gray = 255;
} else {
    gray = 0;
}

発展

2値化の閾値は手動で決める場合と、計算して求める場合があります。
計算で2値化の閾値を求める方法として「大津の2値化」というロジックが有名です。
興味のある方は調べてみてください。

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

[react-native]FlatListの使い方

目的

App.jsにを表示する。レンダーする要素は別コンポーネント化。

App.js

import React, { useState } from 'react';
import { 
  StyleSheet, 
  View, 
  FlatList 
} from 'react-native';

import GoalItem from './components/GoalItem';

export default function App() {

  const [courseGoals, setCourseGoals] = useState([]);

  //courseGoalsには以下のような配列が入っている。
 Array [
   Object {
     "id": "0.1283113872926156",
     "value": "Aaa",
   },
   Object {
     "id": "0.1515628656002196",
     "value": "Aaa",
   },
 ]
 //

  return (
    <View style={styles.screen} >
      <FlatList 
        keyExtractor={(item, index) => item.id } //デフォルト値なので省略可
        data={courseGoals}   // ここに繰り返したい配列をいれる。
        renderItem={itemData => <GoalItem id={itemData.item.id} title={itemData.item.value} />} 
     // viewの部分 引数itemDataの.itemプロパティに配列の要素が一つづつ繰り返し入ってくるような感じ
      />
    </View>
  );
}

GoalItem.js

レイアウトは任意

import React from 'react';
import { View, Text, StyleSheet,TouchableOpacity } from 'react-native';

const GoalItem = props => {
  return (
  <TouchableOpacity>
    <View>
      <Text>{props.title}</Text>
    </View>
  </TouchableOpacity>
  );
};


export default GoalItem;

所感

意外と癖が強い。
繰り返しの部分はそれぞれにユニークなkeyを指定しないとreact nativeが警告を出す。
特定のリストを消す時にfilterメソッドを使ったりする時にも使える。

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