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

簡易相関図ツールを作成した

概要

ログイン不要、無料で簡易の相関図を作れるツールを作成しました。
作成した図はimgタグ情報コピー機能があるので、htmlにそのまま貼り付けられます。
wordpressや記事投稿サイトに使用するときは画像のダウンロード、アップロードが不要です。
(文字数はすごいことになりますが・・・)
qiitaはそのまま貼り付けできないみたいです。

png形式でダウンロードも可能です。
https://skz-app.tontonton.work/

技術

なぜ作ったか

記事を書いているときに、

「説明するための図が欲しいな」

「そんなに詳細な図じゃなくていいんだけど、図を作成するのがだるい」

「作成するためのアプリを立ち上げるのが煩わしい」

「作成した図を保存→サイトにアップロードというプロセスが面倒」

という、なるべく怠けたい熱い思いから作成に至りました。

まとめ

Firebaseは初めて使用しましたが、簡単にデプロイできて胸熱ですね。
試しに使ってみてください。

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

高速フーリエ変換(FFT)を行列で理解する - 自分用メモ

何番煎じか分からんですが、どうしても数式が多いと読む気がなくなるので、
厳密さは棚上げして、なんで計算量が減るのかのイメージを把握する目的で、記事を残してみます。

前提知識

  • 三角関数を知っている
  • 複素数を知っている
  • オイラーの公式を知っている
  • 積分を知っている1
  • 行列の積の計算規則を知っている

結構なレベルの数学知識が要求される内容なんだと改めて思いますね。。

フーリエ変換

上記の前提知識があれば、下記が分かりやすいと思います。
【画像45枚あり】フーリエ変換を宇宙一わかりやすく解説してみる

高速フーリエ変換

数学・工学系の記事にしてはめずらしく、Wikipediaの解説(下記)がイメージ付きやすいように思います。
- 計算したい式(離散フーリエ変換)
- 高速フーリエ変換を行列で表現

上記(Wikipedia記事)の補足説明

上記のWikipediaの式(行列の計算式のとこ)で使われている記号と同じ記号を割り当てます。

W=e^{2\pi i\frac{1}{N}}

とおくと、高速フーリエ変換では下記の$X_0, X_1, X_2,...,X_{N-1}$が求める対象になります。

X_t = \sum_{k=0}^{N-1}W^{k\ t}x_k

例として、$N=8$のときは下記のようになります。(通常、高速フーリエ変換を適用する際は$N$は$2$のべき乗を前提とします。)

\begin{align}
X_0&=W^0x_0+W^0x_1+W^0x_2+W^0x_3+W^0x_4+W^{0}x_5+W^{0}x_6+W^{0}x_7\\

X_1&=W^0x_0+W^1x_1+W^2x_2+W^3x_3+W^4x_4+W^{5}x_5+W^{6}x_6+W^{7}x_7\\

X_2&=W^0x_0+W^2x_1+W^4x_2+W^6x_3+W^8x_4+W^{10}x_5+W^{12}x_6+W^{14}x_7\\
   &:\\
X_7&=W^0x_0+W^7x_1+W^{14}x_2+W^{21}x_3+W^{28}x_4+W^{35}x_5+W^{42}x_6+W^{49}x_7
\end{align}

下記のようにベクトルにみたてて、

\begin{pmatrix}
X_0\\
X_1\\
X_2\\
:\\
X_7\\
\end{pmatrix}

と書くと、下記のような行列とベクトルの積として表現できます。左辺の行列の要素をクリックすると、関連する要素が強調表示されます。
(※下記で左辺の$x$の添え字の並びが連番ではなくなっているのは意図的なものです。)

See the Pen Matrix Image of Fast Fourier Transform by kob58im (@kob58im) on CodePen.

(描画サイズが大きいので、縮小表示(0.5x)もしくはCodePen上での閲覧推奨です。)

上記の分解は、$W^N=e^{2\pi i}=1$であることにより、$W^8=1\ (=W^0)$が成り立つことを利用しています。

右辺の右側から結合させる(積を求める)のがミソのようで、
右辺の右側の行列(真ん中というべきかも(?))が、$\frac{N}{2}$次の行列2つ分相当になり、再帰的に分解ができる構造になります。
さらに、$W^{N/2}=e^{\pi i}=-W$であることを使って計算を効率化させたりするようです。

実際の計算手順(バタフライ演算)と計算量について

下記が分かりやすいかと思います。
高速フーリエ変換(千葉大学 講義資料)


  1. 高速フーリエ変換を計算するだけであれば無くてもどうにかなるが、フーリエ変換自体の理解には必要 

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

高速フーリエ変換(FFT)を行列で理解する

何番煎じか分からんですが、どうしても数式が多い1と読む気がなくなるので、
厳密さは棚上げして、なんで計算量が減るのかのイメージを把握する目的で、記事を残してみます。

前提知識

  • 三角関数を知っている
  • 複素数を知っている
  • オイラーの公式を知っている
  • 積分を知っている2
  • 行列の積の計算規則を知っている

結構なレベルの数学知識が要求される内容なんだと改めて思いますね。。

フーリエ変換

上記の前提知識があれば、下記が分かりやすいと思います。
【画像45枚あり】フーリエ変換を宇宙一わかりやすく解説してみる

高速フーリエ変換

数学・工学系の記事にしてはめずらしく、Wikipediaの解説(下記)がイメージ付きやすいように思います。
- 計算したい式(離散フーリエ変換)
- 高速フーリエ変換を行列で表現

上記(Wikipedia記事)の補足説明

上記のWikipediaの式(行列の計算式のとこ)で使われている記号と同じ記号を割り当てます。3

W=e^{2\pi i\frac{1}{N}}

とおくと、高速フーリエ変換では下記の$X_0, X_1, X_2,...,X_{N-1}$が求める対象になります。

X_t = \sum_{k=0}^{N-1}W^{k\ t}x_k

例として、$N=8$のときは下記のようになります。(通常、高速フーリエ変換を適用する際は$N$は$2$のべき乗を前提とします。)

\begin{align}
X_0&=W^0x_0+W^0x_1+W^0x_2+W^0x_3+W^0x_4+W^{0}x_5+W^{0}x_6+W^{0}x_7\\

X_1&=W^0x_0+W^1x_1+W^2x_2+W^3x_3+W^4x_4+W^{5}x_5+W^{6}x_6+W^{7}x_7\\

X_2&=W^0x_0+W^2x_1+W^4x_2+W^6x_3+W^8x_4+W^{10}x_5+W^{12}x_6+W^{14}x_7\\
   &:\\
X_7&=W^0x_0+W^7x_1+W^{14}x_2+W^{21}x_3+W^{28}x_4+W^{35}x_5+W^{42}x_6+W^{49}x_7
\end{align}

下記のようにベクトルにみたてて、

\begin{pmatrix}
X_0\\
X_1\\
X_2\\
:\\
X_7\\
\end{pmatrix}

と書くと、下記のような行列とベクトルの積として表現できます。左辺の行列の要素をクリックすると、関連する要素が強調表示されます。
(※下記で左辺の$x$の添え字の並びが連番ではなくなっているのは意図的なものです。)

See the Pen Matrix Image of Fast Fourier Transform by kob58im (@kob58im) on CodePen.

(描画サイズが大きいので、縮小表示(0.5x)もしくはCodePen上での閲覧推奨です。4

上記の分解は、$W^N=e^{2\pi i}=1$であることにより、$W^8=1\ (=W^0)$が成り立つことを利用しています。

右辺の右側から結合させる(積を求める)のがミソのようで、
右辺の右側の行列(真ん中というべきかも(?))が、$\frac{N}{2}$次の行列2つ分相当になり、再帰的に分解ができる構造になります。
さらに、$W^{N/2}=e^{\pi i}=-W$であることを使って計算を効率化させたりするようです。

実際の計算手順(バタフライ演算)と計算量について

下記が分かりやすいかと思います。
高速フーリエ変換(千葉大学 講義資料 PDF)


  1. 結局、記事書いてみたら数式が多くなってしまった… 

  2. 高速フーリエ変換を計算するだけであれば無くてもどうにかなるが、フーリエ変換自体の理解には必要 

  3. 厳密にはWikipediaのほうは指数部にマイナス符号がついてるが、本質には影響しないはず。 

  4. 環境によっては縮小できない模様・・・。 

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

datetimepicker datepicker 違い

初めに

僕が携わっている案件での修正部分にdatetimepickerの知識が必要だったので、調べるとともに記事にしてまとめました。

datetimepickerとは

ユーザーが日付や時間を入力する際に、直感的な操作で簡単に日時を指定できる
jQueryに依存しており、導入も比較的簡単

datetimepickerとdatepickerの違い

datetimepickerは日付と時間を両方指定できるが、datepickerは日付のみ指定できる(名前の通りでした、、)
datetimepicker
スクリーンショット 2020-08-30 22.55.56.png
datepicker
スクリーンショット 2020-08-30 22.56.03.png

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

Vue の transition で entering/leaving が非対称なトランジションを実装する

Vue には v-if などの条件付き描画を使ってコンポーネントを表示/非表示する際に CSS の transition や animation の動きを付与できる transition ラッパーコンポーネントというものがあります。今回は表示・非表示で非対称な動きをする Vue transition と CSS の実装についてメモします。

時間差でフェードイン、クリック時にフェードアウトするカード

次のようなカードの要素を作ってみます。なおカードの上にあるボタンは動作確認用のものなので、記事の本題とは関係ありません。

See the Pen vue-transition-asynchronously-fade-in/out-1 by hamakou108 (@hamakou108) on CodePen.

このカードは Vue インスタンスがマウントされてから3秒後に表示され、クリックするとフェードアウトします。

実装を見ると分かる通り、 enter-active-class と leave-active-class に別々の CSS を当てることで非対称なトランジション・アニメーションを実現しています。 leave-active-class の方は transition を利用していますが、トランジション完了時の opacity の値を leave-to-class に指定することでフェードアウトを実現できます。

時間差でフェードインした後に時間差でフェードアウトするカード

次に少しひねったパターンとして、フェードインしてしばらく時間が経つと勝手に消えるカードを作ってみます。
クリック時にフェードアウトする動作も残しておきます。

See the Pen vue-transition-asynchronously-fade-in/out-2 by hamakou108 (@hamakou108) on CodePen.

enter-active-class が利用する @keyframes を変更して自然にフェードアウトするようにしました。
ただ @keyframes の変更だけだと幾つか問題があります。

まず enter-active-class に指定したスタイルはアニメーション終了後に削除されます。
これにより、アニメーション終了後は .card に指定したスタイルがそのまま当たるため、 visibility: visible; opacity: 1 となってフェードアウトさせたカードが再表示されてしまいます。
これを避けるため .card には visibility: hidden; opacity: 0; を設定しています。

さらにこの設定によって今度は leave-active-class のトランジションの初期状態が visibility: hidden; opacity: 0; となるため、クリック時はフェードアウトではなく瞬時に消えてしまうような動きになってしまいます。
これを避けるため .fade-leave-active には visibility: visible; opacity: 1; を指定して、表示された状態からトランジションが開始するようにしています。

まとめ

Vue transition を使って非対称なトランジション・アニメーションを行う際、少しの動作の変更が細かな部分に影響してきます。
特に entering 終了後および leaving 開始前の状態がどうなっているべきかを考慮して CSS を当てていくことが重要になりそうです。

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

Angular ngForで配列要素の最初の1つだけを取得する方法

ngForの最初の配列要素だけ取る

*ngFor='let item of items | slice:start:end;'

このようにsliceを使う事でできました!

スクリーンショット 2020-08-27 12.18.45.jpg

sliceを使う事で、自由に取得したい配列要素を指定できます。

*ngFor='let item of items | slice:0:10;'

例えば、こうすれば10個だけ取得することができます!

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

bootstrap-selectで作ったセレクトボックスを活性化できないときの対応方法

  • 環境
    • CentOS Linux release 7.8.2003 (Core)
    • openjdk version "11.0.7" 2020-04-14 LTS
    • JSF 2.3.9
    • jquery 3.2.1
    • bootstrap 4.3.1
    • bootstrap-select 1.13.14

事象 : bootstrap-selectで作ったセレクトボックスを活性化できない

  1. 初期表示時にセレクトボックスを非活性にする
  2. チェックボックスを変更するとonchangeイベントでセレクトボックスの活性非活性を切り替える

はずが活性化しない・・・・
a.gif

XHTML
<!--省略-->
<h:form id="formId">
<h:panelGroup>
  <ui:remove>[使う]チェックボックス</ui:remove>
  <h:selectBooleanCheckbox id="chekBox" value="#{sampleBean.chekBoxValue}" onchange="changeDisabled();" />
  <h:outputLabel value="使う" for="chekBox" />
</h:panelGroup>
<h:panelGroup>
  <ui:remove>セレクトボックス</ui:remove>
  <h:selectOneMenu id="select" styleClass="selectpicker" value="#{sampleBean.selected}">
    <f:selectItems value="#{sampleBean.selectItems}" />
  </h:selectOneMenu>
</h:panelGroup>
</h:form>
<!--省略-->
JavaScript
$(document).ready(function(){
    changeDisabled();
});

/** セレクトボックスの活性非活性を切り替える. */
function changeDisabled() {
    /** @type {boolean} チェックボックスの選択状態. */
    var checked = $("#formId\\:chekBox").prop("checked");
    if (checked) {
        // 活性化する
        $("#formId\\:select").removeAttr('disabled');
        $("#formId\\:select").next().children().removeClass('disabled');
    } else {
        // 非活性にする
        $("#formId\\:select").attr('disabled', 'disabled');
        $("#formId\\:select").next().children().addClass('disabled');
    }
}
SampleBean
// 省略
public class SampleBean implements Serializable {
    /** serialVersionUID. */
    private static final long serialVersionUID = -6782548672735889274L;
    /** セレクトボックスの選択肢. */
    @Getter
    private List<SelectItem> selectItems;
    /** セレクトボックスで選択した値. */
    @Getter
    @Setter
    private Integer selected;
    @Getter
    @Setter
    private Boolean chekBoxValue = false;

    /** Beanの初期化処理. */
    @PostConstruct
    public void init() {
        setSelectItems();
    }

    /** 選択肢を設定する. */
    private void setSelectItems() {
        selectItems = new ArrayList<SelectItem>();
        selectItems.add(new SelectItem(0, "   "));
        selectItems.add(new SelectItem(1, "いぬ"));
        selectItems.add(new SelectItem(2, "ねこ"));
        selectItems.add(new SelectItem(3, "さる"));
        selectItems.add(new SelectItem(4, "ぽんすけ"));
    }
}

原因 : 初期表示時に非活性にする処理を実行するタイミングがreadyだから

JavaScriptをデバックしながらHTMLの出力を見ているとbootstrap-selectのHTMLが構築されるのはonloadっぽかったのでいろいろ試してみた。
試してみると初期表示の非活性処理は同じでも、タイミングがreadyonloadでdisabledが付く場所に違いがあった。
また、バージョンが異なると出力されるHTMLが変わるからかdisabledが付く場所も変わった。

非活性処理
// selectタグにdisabled属性をつける
$("#formId\\:select").attr('disabled', 'disabled');
// selectの隣のタグの子供タグにclass属性のdisabbledをつける
$("#formId\\:select").next().children().addClass('disabled');

凡例) ★ : 出力されたHTMLで「selectの隣のタグの子供タグ」になるタグ

bootstrap-select 1.13.14 1.13.14 1.6.3 1.6.3
disableするタイミング ready onload ready onload
disabled属性が付く select select select select
class属性にdisabbledがつく 一番上のdiv,
button
button直下のdiv★ button★,
li
button★,
buttonの隣のdiv
aria-disabled属性が付く button なし なし なし

今回JavaScriptで活性非活性を切り替える際に行っている処理の場合、onloadでdisabledが付く場所にしか対応していなかった

活性化処理
$("#formId\\:select").removeAttr('disabled');
$("#formId\\:select").next().children().removeClass('disabled');

bootstrap-select 1.13.14

  • bootstrap 4.3.1
  • bootstrap-select 1.13.14

非活性化しない場合に出力されるHTML

<div class="dropdown bootstrap-select">
  <select id="formId:select" name="formId:select" class="selectpicker" size="1" tabindex="-98">
    <option value="0">   </option>
    <option value="1">いぬ</option>
    <option value="2">ねこ</option>
    <option value="3">さる</option>
    <option value="4">ぽんすけ</option>
  </select>
  <button type="button" class="btn dropdown-toggle btn-light" data-toggle="dropdown" role="combobox" aria-owns="bs-select-1" aria-haspopup="listbox" aria-expanded="false" data-id="formId:select" title="Nothing selected">
    <div class="filter-option"><div class="filter-option-inner"><div class="filter-option-inner-inner">Nothing selected</div></div></div>
  </button>
  <div class="dropdown-menu ">
    <div class="inner show" role="listbox" id="bs-select-1" tabindex="-1">
      <ul class="dropdown-menu inner show" role="presentation"></ul>
    </div>
  </div>
</div>

readyで非活性化した場合に出力されるHTML

<div class="dropdown bootstrap-select disabled">
  <select id="formId:select" name="formId:select" class="selectpicker" size="1" disabled="disabled" tabindex="-98">
<!-- 非活性化しない場合と同じなので省略 -->
  <button type="button" class="btn dropdown-toggle disabled btn-light" data-toggle="dropdown" role="combobox" aria-owns="bs-select-1" aria-haspopup="listbox" aria-expanded="false" data-id="formId:select" tabindex="-1" aria-disabled="true" title="Nothing selected">
<!-- 非活性化しない場合と同じなので省略 -->

onloadで非活性化した場合に出力されるHTML

<!-- 非活性化しない場合と同じなので省略 -->
  <select id="formId:select" name="formId:select" class="selectpicker" size="1" tabindex="-98" disabled="disabled">
<!-- 非活性化しない場合と同じなので省略 -->
    <div class="filter-option disabled"><div class="filter-option-inner"><div class="filter-option-inner-inner">Nothing selected</div></div></div>
<!-- 非活性化しない場合と同じなので省略 -->

bootstrap-select 1.6.3

  • bootstrap 3.3.0
  • bootstrap-select 1.6.3

非活性化しない場合に出力されるHTML

<select id="formId:select" name="formId:select" class="selectpicker" size="1" style="display: none;">
  <option value="0">   </option>
  <option value="1">いぬ</option>
  <option value="2">ねこ</option>
  <option value="3">さる</option>
  <option value="4">ぽんすけ</option>
</select>
<div class="btn-group bootstrap-select">
  <button type="button" class="btn dropdown-toggle selectpicker btn-default" data-toggle="dropdown" data-id="formId:select" title="   ">
    <span class="filter-option pull-left">   </span>&nbsp;<span class="caret"></span>
  </button>
  <div class="dropdown-menu open">
    <ul class="dropdown-menu inner selectpicker" role="menu">
      <li data-original-index="0" class="selected"><a tabindex="0" class="" data-normalized-text="<span class=&quot;text&quot;>   </span>"><span class="text">   </span><span class="glyphicon glyphicon-ok check-mark"></span></a></li>
      <li data-original-index="1"><a tabindex="0" class="" data-normalized-text="<span class=&quot;text&quot;>いぬ</span>"><span class="text">いぬ</span><span class="glyphicon glyphicon-ok check-mark"></span></a></li>
      <li data-original-index="2"><a tabindex="0" class="" data-normalized-text="<span class=&quot;text&quot;>ねこ</span>"><span class="text">ねこ</span><span class="glyphicon glyphicon-ok check-mark"></span></a></li>
      <li data-original-index="3"><a tabindex="0" class="" data-normalized-text="<span class=&quot;text&quot;>さる</span>"><span class="text">さる</span><span class="glyphicon glyphicon-ok check-mark"></span></a></li>
      <li data-original-index="4"><a tabindex="0" class="" data-normalized-text="<span class=&quot;text&quot;>ぽんすけ</span>"><span class="text">ぽんすけ</span><span class="glyphicon glyphicon-ok check-mark"></span></a></li>
    </ul>
  </div>
</div>

readyで非活性化した場合に出力されるHTML

<select id="formId:select" name="formId:select" class="selectpicker" size="1" disabled="disabled" style="display: none;">
<!-- 非活性化しない場合と同じなので省略 -->
  <button type="button" class="btn dropdown-toggle selectpicker disabled btn-default" data-toggle="dropdown" data-id="formId:select" tabindex="-1" title="   ">
<!-- 非活性化しない場合と同じなので省略 -->
      <li data-original-index="0" class="disabled selected"><a tabindex="-1" class="" data-normalized-text="<span class=&quot;text&quot;>   </span>" href="#"><span class="text">   </span><span class="glyphicon glyphicon-ok check-mark"></span></a></li>
      <li data-original-index="1" class="disabled"><a tabindex="-1" class="" data-normalized-text="<span class=&quot;text&quot;>いぬ</span>" href="#"><span class="text">いぬ</span><span class="glyphicon glyphicon-ok check-mark"></span></a></li>
      <li data-original-index="2" class="disabled"><a tabindex="-1" class="" data-normalized-text="<span class=&quot;text&quot;>ねこ</span>" href="#"><span class="text">ねこ</span><span class="glyphicon glyphicon-ok check-mark"></span></a></li>
      <li data-original-index="3" class="disabled"><a tabindex="-1" class="" data-normalized-text="<span class=&quot;text&quot;>さる</span>" href="#"><span class="text">さる</span><span class="glyphicon glyphicon-ok check-mark"></span></a></li>
      <li data-original-index="4" class="disabled"><a tabindex="-1" class="" data-normalized-text="<span class=&quot;text&quot;>ぽんすけ</span>" href="#"><span class="text">ぽんすけ</span><span class="glyphicon glyphicon-ok check-mark"></span></a></li>
<!-- 非活性化しない場合と同じなので省略 -->

onloadで非活性化した場合に出力されるHTML

<select id="formId:select" name="formId:select" class="selectpicker" size="1" disabled="disabled" style="display: none;">
<!-- 非活性化しない場合と同じなので省略 -->
  <button type="button" class="btn dropdown-toggle selectpicker btn-default disabled" data-toggle="dropdown" data-id="formId:select" title="   ">
<!-- 非活性化しない場合と同じなので省略 -->
  <div class="dropdown-menu open disabled">
<!-- 非活性化しない場合と同じなので省略 -->

対応方法 : 初期表示時に非活性にする処理をonloadでやる

a.gif

$(window).on('load', function(){
    changeDisabled();
});
// 省略
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

IMI住所変換コンポーネントを改造してリバースジオコーディングに対応してみた

先日、経産省が公開する「住所変換コンポーネント」についての性能を調べた記事を投稿しました。

IMI住所変換コンポーネントでいろんな住所を正規化してみた
https://qiita.com/uedayou/items/4c9d30fc031a9bf6762e

「IMI住所変換コンポーネント」は、表記にゆれがある住所表記を正規化してくれるとても便利なツールです。

たとえば

霞が関2 -> 東京都千代田区霞が関二丁目

のようになります。

このツールのもう一つの大きな特徴として、その住所に対する位置情報(緯度経度)も取得することができる点があります。

霞が関2 -> 東京都千代田区霞が関二丁目 -> {"緯度": "35.675551", "経度": "139.750413"}

所謂「ジオコーディング機能」があります。
これは、ツール内にあらかじめ住所に対する位置情報データが含まれることを意味し、作り方によってはこの逆、位置情報(緯度経度)から住所にも変換することができるのではないか?と思いました。

{"緯度": "35.675551", "経度": "139.750413"} -> 東京都千代田区霞が関二丁目

これを一般的にリバースジオコーディング(逆ジオコーディング)と言いますが、本家コードを改造してこのリバースジオコーディング機能をつけてみました。

使い方

ツールを使うにはまず、Node.jsとリバースジオコーディング対応住所変換コンポーネントモジュールのインストールが必要です。経産省が公開するものとは違うものをインストールします。
ファイルは

https://github.com/uedayou/imi-enrichment-address-plus/releases/download/v1.0.0/imi-enrichment-address-plus-1.0.0.tgz

にあります。このツールには3つの使い方があります。

  • (1) コマンドラインインターフェイス
  • (2) Web API
  • (3) Node.js

(1) は、npm でグローバルにインストール

npm install -g https://github.com/uedayou/imi-enrichment-address-plus/releases/download/v1.0.0/imi-enrichment-address-plus-1.0.0.tgz

(2), (3) はローカルインストールしてください。

npm install https://github.com/uedayou/imi-enrichment-address-plus/releases/download/v1.0.0/imi-enrichment-address-plus-1.0.0.tgz

(1) コマンドラインインターフェイス

リバースジオコーディングをコマンドラインで利用する場合は

$ imi-enrichment-address-plus --lat [緯度] --lng [経度]
$ imi-enrichment-address-plus --lat 35.675551 --lng 139.750413
出力
{
  "@context": "https://imi.go.jp/ns/core/context.jsonld",
  "@type": "場所型",
  "住所": {
    "@type": "住所型",
    "表記": "東京都千代田区霞が関二丁目",
    "都道府県": "東京都",
    "都道府県コード": "http://data.e-stat.go.jp/lod/sac/C13000",
    "市区町村": "千代田区",
    "市区町村コード": "http://data.e-stat.go.jp/lod/sac/C13101",
    "町名": "霞が関",
    "丁目": "2"
  },
  "地理座標": {
    "@type": "座標型",
    "緯度": "35.675551",
    "経度": "139.750413"
  }
}

(2) Web API

ツール内にWebサーバ機能があり、サーバ経由でWeb APIとして利用することができます。

$ node node_modules/imi-enrichment-address-plus/bin/server.js 8080

上記を実行して http://localhost:8080/ をブラウザで開くと以下のようなWebページが開きます。

Webページ

ここの緯度経度の部分の変換ボタンを押すと、実行結果には以下のように表示されます。

実行結果

プログラム上から使う場合は、以下のJSONをPOSTしてください。

{
  "@type": "座標型",
  "緯度": "[緯度の値]",
  "経度": "[経度の値]"
}
cURL
$ curl -X POST -H 'Content-Type: application/json' -d '{"@type": "座標型","緯度": "35.170915","経度": "136.881537"}' localhost:8080
出力
{
  "@context": "https://imi.go.jp/ns/core/context.jsonld",
  "@type": "場所型",
  "住所": {
    "@type": "住所型",
    "表記": "愛知県名古屋市中村区名駅一丁目",
    "都道府県": "愛知県",
    "都道府県コード": "http://data.e-stat.go.jp/lod/sac/C23000",
    "市区町村": "名古屋市",
    "区": "中村区",
    "市区町村コード": "http://data.e-stat.go.jp/lod/sac/C23105",
    "町名": "名駅",
    "丁目": "1"
  },
  "地理座標": {
    "@type": "座標型",
    "緯度": "35.170778",
    "経度": "136.882494"
  }
}

(3) Node.js

ツールはNode.jsのモジュールなので、Node.js のコード上からも利用可能です。
リバースジオコーディングは以下のように利用できます。

const convert = require('imi-enrichment-address-plus');
const point = {
  lat: 35.675551,
  lng: 139.750413
}
convert(point).then(json=>{
  console.log(json);
});

利用についての注意

本ツールのリバースジオコーディングは、半径1km内から最も近い代表点を持つ住所データを1件返します。与える緯度経度によっては必ずしもその位置の住所を表さない可能性がありますので、くれぐれも正確なデータではなくある程度あいまいなデータであることを留意して使用してください。

ただ、間違っていたとしてもかなり近い住所であることがほとんどだと思います。ざっくりとした住所でも構わない場合も結構あると思いますが、そういう場合はかなり便利だと思います。

ソースコード

ソースコードは以下のリポジトリで公開しています。
詳しい使い方は、README.mdを参照してください。

https://github.com/uedayou/imi-enrichment-address-plus

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

【React】なぜsuper(props)は必要なの?

 元(?)記事

https://qiita.com/hand-dot/items/61a4b808f110b12e4281

とても参考になったのですが、もう少し噛み砕いてわかりやすく解説していきます?‍♂️

 【疑問その1】なぜ、super()を呼び出すのか

結論から言うとthis.nameのようなthisを伴う初期化を親クラスで行うため。下記はReactではないJSのクラスです。

class Parent {
  constructor(name) {
    //ここで初期化している
    this.name = name;
  }
}

class Child extends Parent {
  constructor(name) {
    console.log(this.name); // エラーになる
    super(name);
  }
}

const test = new Child("佐藤");

直感的にthis.nameが使えそうに感じるが、初期化(親クラスのconstructorを実行)してないので、使えない。Reactの場合も同様。

// React.componentの内部
class Component {
  constructor(props) {
    this.props = props;
    // ...
  }
}

class Child extends React.component {
  constructor(props) {
    console.log(this.props); // エラーになる
    super(props);
  }
}

 propsを渡す必要性

結論、親と自分のコンストラクターの実行が終わるまで、this.propsは未定義だから。

実は、React内部でpropsがインスタンスに割り当てられている。

// React内部
const instance = new YourComponent(props);
instance.props = props;

しかし、これはコンストラクター実行後に行われる処理なので、コンストラクター内でthis.propsを使った処理を書けなくなってしまう。

以上の理由から、super(props)を書きましょう。

何か間違っているところがありましたらご指摘よろしくお願いします?‍♂️

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

【React】stateについて学ぶ

 stateとは

  • コンポーネントが持つ状態のこと。
  • クラスコンポーネントで使用される。(hooksは一旦置いておく。)
  • コンストラクタ内で初期値が決められる。
  • setStateで更新する。
  • 値が変更されるとrenderされる。
import React from "react";

class Test extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      name: "佐藤",
    };
  }

  render() {
    return (
      <>
        <h1>{this.state.name}</h1>
      </>
    );
  }
}

export default Test;

次にstateの更新について。以下のように書かなくてはいけない。

this.state({
  name = "山田"
})

以下のように書くのはNG。再レンダリングされないから。
jsx
this.state.name = "山田"

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

80mm フィルム (58mm フィルム) を発明してみた

レシート用のサーマルロール紙 (幅 80 mm or 58 mm) を映画のフィルムにしちゃいます!

01.jpg

receiptline シリーズ最終回では、インスタントカメラを作って、最後にレシートプリンターを自撮り連写しました。
この写真を眺めていたら、動画もできるんじゃないか?と思ったのです。

02.jpg

自動用紙カットを解除

receiptline は、印刷が終わると用紙を自動でカットしてくれます。
映画のフィルムにするには、この自動用紙カットを解除しなくてはなりません。
変換ライブラリ lib/receiptline.js のソースコードを調べます。

lib/receiptline.js
//
// ESC/POS
//
const _escpos = {
    // start printing: ESC @ GS a n ESC M n FS ( A pL pH fn m ESC SP n FS S n1 n2 ESC 3 n ESC { n
    open: printer => '\x1b@\x1da\x00\x1bM0\x1c(A' + $(2, 0, 48, 0) + '\x1b \x00\x1cS\x00\x00\x1b3\x00\x1b{' + $(printer.upsideDown),
    // finish printing: GS r n
    close: function () {
        return this.cut() + '\x1dr1';
    },

見つけました。close メソッドの this.cut() + を削除します。

lib/receiptline.js
//
// ESC/POS
//
const _escpos = {
    // start printing: ESC @ GS a n ESC M n FS ( A pL pH fn m ESC SP n FS S n1 n2 ESC 3 n ESC { n
    open: printer => '\x1b@\x1da\x00\x1bM0\x1c(A' + $(2, 0, 48, 0) + '\x1b \x00\x1cS\x00\x00\x1b3\x00\x1b{' + $(printer.upsideDown),
    // finish printing: GS r n
    close: function () {
        // return this.cut() + '\x1dr1';
        return '\x1dr1';
    },

インスタントカメラを改造

インスタントカメラのコードと実行環境を流用します。
映像クリックで撮影開始/終了するように変更しました。

wwwroot/index.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>80mm Film Camera</title>
    <script type="text/javascript">
        async function initialize() {
            const video = document.querySelector('video');
            const canvas = document.querySelector('canvas');
            let timer = 0;
            // video
            video.srcObject = await navigator.mediaDevices.getUserMedia({ audio: false, video: { width: 1280, height: 720 } });
            video.onclick = event => {
                if (timer > 0) {
                    // stop
                    clearInterval(timer);
                    timer = 0;
                } else {
                    // start
                    timer = setInterval(() => {
                        // image
                        canvas.getContext('2d').drawImage(video, 0, 0, canvas.width, canvas.height);
                        let data = `{i:${canvas.toDataURL('image/png').slice(22)}}\n`;
                        // barcode
                        const now = new Date();
                        const iso = new Date(now.getTime() - now.getTimezoneOffset() * 60000).toISOString();
                        data += `{c:${iso.replace(/\D/g, '').slice(2, 14)};o:ean,24}|`;
                        // send data
                        const xhr = new XMLHttpRequest();
                        xhr.open('POST', 'printer');
                        xhr.setRequestHeader('Content-Type', 'text/plain; charset=utf-8');
                        xhr.send(data);
                    }, 2000);
                }
            };
        }
    </script>
</head>
<body onload="initialize()">
    <video autoplay style="width: 100%;"></video>
    <canvas width="512" height="288" style="display: none;"></canvas>
</body>
</html>
  • レシートプリンター
    • TM-T88V (80mm)
  • 記録メディア
    • 80 ミリフィルム (80mm 幅のサーマルロール紙)
  • アスペクト比
    • 16 : 9
  • 画素数
    • 512 x 288 ピクセル
  • フレームレート
    • 0.5 fps (2 秒間隔で印刷)
  • 音声
    • なし

ストリームデータ処理にすれば、もっとフレームレートを上げられると思います。

80 ミリカメラ

80 ミリカメラのハードウェアです。家にあるものを組み合わせました。

03.jpg

  • ノートパソコン (カメラデバイス)
  • レシートプリンター
  • AC アダプタ
  • LAN ケーブル
  • ポータブル電源
  • ベビーキャリア

レシートプリンターが据え置き型なので重装備になってしまいました。
撮影しているとフィルムがはみ出してくるので、巻き取り装置がほしいです。

80 ミリ映写機

80 ミリ映写機は、まだこれから。そこで・・・

撮影済みのフィルムをイメージスキャナで取り込んで GIF アニメーションを作りました。
倍速でお届けします!

04.gif

05.gif

06.gif

いかがでしょうか?
また何か作ったら投稿します。ではまた!

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

【JS】即時関数を使ってメソッドをモジュール化してみよう

 即時関数とは

定義と同時に実行される関数のこと。

 なんで使うの?

即時関数の中のreturn内でメソッド定義することで、グローバルスコープの汚染を防ぐことができるから。まあ、即時関数自体の名前はぶつかる可能性があるが。

 サンプルコード

const fn = (() => {
  // 初期化処理。即時で実行される。
  console.log("即時関数実行!");
  let name = "佐藤";

  //以降は明示的に呼び出してやる(fn.callName())みたいな形で。
  return {
    callName: () => {
      console.log(name);
    },
  };
})();

// return{}内のメソッドに関しては、関数名.メソッド名で呼び出すことができる。ここでは、fn.callName()で呼べる
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[GAS][JS] Momentライブラリのdiffでの日付差分

GASで話をしますが、Javascriptでも同じです。

GAS版 MomentライブラリのIDは下記です。

MHMchiX6c1bwSqGM1PZiW_PxhMjh3Sh48

やりたかったこと

こんな表を作って、毎朝8時台にGASが起動して、「締め切りまであと3日」以内になったら「締め切り近いけど大丈夫?」というリマインドをしてくれる機能を作りたい。

task.png

そのため、Momentライブラリをつかって「日付の差分」を取りたかった。

ダメだった例

簡略化するためにA2セルの日付だけを対象にします。

始めはこう書きました。

function sample1() {
  // 日付のセルからgetValue()したら Dateオブジェクト が取れるのでこう書いてしまいます
  const deadline_date = new Date("2020-09-10");

  const deadline_moment = Moment.moment(deadline_date);
  const now_moment      = Moment.moment();

  const diff = deadline_moment.diff(now_moment, "days");
  console.log(diff);
}

そして「今日は 2020/9/9 だとする」じゃないですか。
A2セルは 2020/9/10 なので 1 が出力されることを期待するじゃないですか。
だけど 出力結果は「0」

ダメだった理由

上記で 「今日は 2020/9/9 だとする」 と書いたのですが、
実際はこのGASが実行される時刻は 2020/09/09 12:34:56 だったりするわけです。

そして deadline として指定しているのは 「2020-09-10 00:00:00」 なのです。

実験してみるとこうなりました↓

function sample2() {
  const deadline_date = new Date("2020-09-10 00:00:00");
  const deadline_moment = Moment.moment(deadline_date);

  const now_moment1 = Moment.moment("2020-09-09 00:00:00");
  const diff1 = deadline_moment.diff(now_moment1, "days");
  console.log(diff1); //=> 1 

  const now_moment2 = Moment.moment("2020-09-09 00:00:01");
  const diff2 = deadline_moment.diff(now_moment2, "days");
  console.log(diff2); //=> 0
}

now_moment1 と now_moment2では「1秒」異なります。

diff1 では deadlineである 2020-09-10 00:00:00 と 2020-09-09 00:00:00 の差分が 24:00:00 なので 1日。
diff2 では deadlineである 2020-09-10 00:00:00 と 2020-09-09 00:00:01 の差分が 23:59:59 なので 0日。

という動きのようだ。

どうすればいいか

「日付」で比較したいのだからこうしたらいいのでは。

「時分秒ミリ秒にゼロをセットしちゃう」作戦

function sample3() {
  const deadline_date = new Date("2020-09-10 00:00:00");
  const deadline_moment = Moment.moment(deadline_date);

  // 時分秒ミリ秒にゼロをセットしちゃう
  const today_moment = Moment.moment().hour(0).minutes(0).second(0).millisecond(0);
  const diff = deadline_moment.diff(today_moment, "days");

  console.log(diff);
}

これで、実行時刻が「2020-09-09 11:11:11」であっても「1」が出力されます。

↓ 念のため実験

function sample4() {
  const deadline_date = new Date("2020-09-10 00:00:00");
  const deadline_moment = Moment.moment(deadline_date);

  const now = new Date("2020-09-09 11:11:11")
  const today_moment = Moment.moment(now).hour(0).minutes(0).second(0).millisecond(0);

  const diff = deadline_moment.diff(today_moment, "days");
  console.log(diff); //=> 1
}

予想通り!

本当にそうなの?

"days" で比較するときに、「24時間未満かどうかで判定している」というのは、動作させた結果を見て私が推測しているところなのですが、実際どういう実装になってるのか momentライブラリのdiff.jsソースコードを見てみました。(これはmoment.jsのソースコードであってGASのライブラリのコードではないので、GASでは違う実装かもしれませんが、きっと同じであろう)

export function diff(input, units, asFloat) {
    var that, zoneDelta, output;

    if (!this.isValid()) {
        return NaN;
    }

    that = cloneWithOffset(input, this);

    if (!that.isValid()) {
        return NaN;
    }

    zoneDelta = (that.utcOffset() - this.utcOffset()) * 6e4;

    units = normalizeUnits(units);

    switch (units) {
// 略
        case 'day':
            output = (this - that - zoneDelta) / 864e5;
            break; // 1000 * 60 * 60 * 24, negate dst
// 略
    }

    return asFloat ? output : absFloor(output);
}

864e5 とは 1日をミリ秒にしたときの 86400000 の指数表記。
よって 時刻の差分を 1日を表すミリ秒で割ってる のね。

diff関数の第3引数である asFloat は指定していないので、

return asFloat ? output : absFloor(output);

では absFloor(output) が return される。

absFloor() 関数の定義は こちら

export default function absFloor(number) {
    if (number < 0) {
        // -0 -> 0
        return Math.ceil(number) || 0;
    } else {
        return Math.floor(number);
    }
}

Math.ceil() : 引数として与えた数以上の最小の整数を返します。
Math.floor() : 引数として与えた数以下の最大の整数を返します。

ということなので、output(時刻の差分を 1日を表すミリ秒で割った数値) が

  • 0.999 であれば 0 を返すし、
  • 1.234 なら 1 を返すし、
  • -3.55 なら -3 を返す。

私の推測はあってたようです!(...というここまでの流れが間違ってたら教えてほしいです)

結論

ということで 「日にち」を比較したいのであれば、日にち(date)より小さい単位(hour, minutes, second, millisecond)はゼロで埋めてあげる

補足ですが、ミリ秒がでてくるなら

const deadline_date = new Date("2020-09-10 00:00:00.000");

としたほうが粒度は揃いますね。
(ミリ秒まで指定してDateオブジェクトをnewしたことが無いのでへんな感じする)

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

リスト処理関数(cons,car,cdr,atom,list)実装例まとめ

拙作記事『7行インタプリタ実装まとめ』について,そろそろSchemeとPython以外にも対応しないとなあと思っていろいろ整理した結果,『S式入力の実装部分がほとんどじゃないこれ?』→『あと,リスト処理内容に基準を設けてないと言語ごとに実装方針がバラバラになりそう』となり,とりあえず『cons car cdr atom listが使える』ようにする記述例を先にまとめていくことにした次第.ターゲット言語上での純LISP機能実装に近いとでもいいますか.

※Python版については,これを用いたS式入出力記述例も掲載しています(他の言語版も揃ったら別記事に移すかも).

仕様

  • ドット対(cons cells)を定義
  • アトムは全て文字列,空リストはNULL
  • cons car cdrを実装
  • アトムか否かを判定するatomを実装
  • 文字列の羅列から単方向リストを生成するlistを実装

Python(CPython 3.7.3)

ドット対はペア要素のタプルで定義(不変としたいため).空リストNULLはNoneを使用.

#### Cons cells are created by using tuple.
#### All of atoms are string and the null value is None.
def cons(x, y): return (x, y)
def car(s): return s[0]
def cdr(s): return s[1]
def atom(s): return isinstance(s, str) or s == None
def list(ts):
    if not ts:
        return None
    else:
        return cons(ts[0], list(ts[1:]))

利用例は次の通り.

def mkassoc(a, b):
    if a == None or b == None:
        return None
    else:
        return cons(cons(car(a), car(b)), mkassoc(cdr(a), cdr(b)))

def assoc(k, vs):
    if vs == None:
        return None
    else:
        if car(car(vs)) == k:
            return car(vs)
        else:
            return assoc(k, cdr(vs))
>>> vs = mkassoc(list(("hoge", "hage", "hige")), list(("10", "20", "30")))
>>> assoc("hage", vs)
('hage', '20')
>>> car(assoc("hage", vs))
'hage'
>>> cdr(assoc("hage", vs))
'20'

(上記定義を用いたS式入出力記述例)

#### Cons cells are created by using tuple.
#### All of atoms are string and the null value is None.
def cons(x, y): return (x, y)
def car(s): return s[0]
def cdr(s): return s[1]
def atom(s): return isinstance(s, str) or s == None
def list(ts):
    if not ts:
        return None
    else:
        return cons(ts[0], list(ts[1:]))

#### s_read

# '(a b c)'
# => ['(', 'a', 'b', 'c', ')']
def s_lex(s):
    return s.replace('(', ' ( ').replace(')', ' ) ').split()

# ['(', 'a', 'b', 'c', ')']
# => ('a', 'b', 'c')
def s_syn(s):
    t = s.pop(0)
    if t == '(':
        r = []
        while s[0] != ')':
            r.append(s_syn(s))
        s.pop(0)
        return tuple(r)
    else:
        if t == 'None':
            return None
        else:
            return t

# ('a', 'b', 'c')
# => ('a', ('b', ('c', None)))
def s_sem(s):
    if atom(s):
        return s
    elif len(s) == 0:
        return None
    elif s[0] == '.':
        return s_sem(s[1])
    else:
        return cons(s_sem(s[0]), s_sem(s[1:]))

def s_read(ss): return s_sem(s_syn(s_lex(ss)))

#### s_print

def s_prcons(s):
    sa_r = s_print(car(s))
    sd = cdr(s)
    if sd == None:
        return sa_r
    elif atom(sd):
        return sa_r + ' . ' + sd
    else:
        return sa_r + ' ' + s_prcons(sd)

def s_print(s):
    if atom(s):
        return s
    elif s == None:
        return None
    else:
        return '(' + s_prcons(s) + ')'
>>> s_print(s_read('((Apple . 100) (Orange . 120) (Lemmon . 250))'))
'((Apple . 100) (Orange . 120) (Lemmon . 250))'
>>> x = s_read('((Apple . 100) (Orange . 120) (Lemmon . 250))')
>>> car(x)
('Apple', '100')
>>> car(car(cdr(x)))
'Orange'
>>> s_print(cdr(x))
'((Orange . 120) (Lemmon . 250))'

C言語(gcc 8.3.0)

atom実装のため,まず,文字列とドット対ポインタの両方を扱うことができるnode_t構造体を定義,それを用いてドット対cons_t構造体を定義.空リストNULLはNULLポインタを使用.

#include <stdio.h>
#include <stdlib.h>

/* Cons cells are created by using typedef struct. */
/* All of atoms are char* and the null value is NULL. */

typedef unsigned int value_t;
enum NODE_TAG { NODE_STRG, NODE_CONS };

typedef struct _node_t_ {
  value_t value;
  enum NODE_TAG tag;
} _node_t, *node_t;

node_t node(value_t value, enum NODE_TAG tag)
{
  node_t n = (node_t)malloc(sizeof(_node_t));
  n->value = value; n->tag = tag;
  return (n);
}

typedef struct _cons_t_ {
  node_t x;
  node_t y;
} _cons_t, *cons_t;

node_t cons(node_t x, node_t y)
{
  cons_t c = (cons_t)malloc(sizeof(_cons_t));
  c->x = x; c->y = y;
  node_t n = node((value_t)c, NODE_CONS);
  return (n);
}

#define str_to_node(s)  (node((value_t)(s), NODE_STRG))
#define node_to_str(s)  ((char *)(s->value))

#define car(s)  (((cons_t)(s->value))->x)
#define cdr(s)  (((cons_t)(s->value))->y)
#define atom(s) (s->tag == NODE_STRG)

#define MAXSTR 64

node_t list(const char s[][MAXSTR], const int n)
{
  node_t r = str_to_node(NULL);
  for (int i = n - 1; i >= 0; i--) {
    r = cons(str_to_node(s[i]), r);
  }
  return (r);
}

利用例は次の通り.

#include <string.h>

node_t mkassoc(node_t a, node_t b)
{
  if (node_to_str(a) == NULL || node_to_str(b) == NULL) {
    return NULL;
  } else {
    return cons(cons(car(a), car(b)), mkassoc(cdr(a), cdr(b)));
  }
}

node_t assoc(node_t k, node_t vs)
{
  if (node_to_str(vs) == NULL) {
    return NULL;
  } else {
    if (strcmp(node_to_str(car(car(vs))), node_to_str(k)) == 0) {
      return car(vs);
    } else {
      return assoc(k, cdr(vs));
    }
  }
}

int main(void)
{
  const char s1[][MAXSTR] = { "hoge", "hage", "hige" };
  const char s2[][MAXSTR] = { "10", "20", "30" };
  node_t vs = mkassoc(list(s1, 3), list(s2, 3));

  node_t k = str_to_node("hage");
  node_t r = assoc(k, vs);
  printf("car(assoc(\"hage\", vs)) = %s\n", node_to_str(car(r)));
  printf("cdr(assoc(\"hage\", vs)) = %s\n", node_to_str(cdr(r)));

  free(vs);
  free(k);
  free(r);

  return (0);
}
car(assoc("hage", vs)) = hage
cdr(assoc("hage", vs)) = 20

Common Lisp(SBCL 1.4.16)

あくまで参考.ドット対はクロージャで実現.オリジナルと区別するため,s_cons s_car s_cdr s_atom s_listの名前で定義.空リストNULLはNILを使用.

;;;; Cons cells are created by using lambda closure.
;;;; All of atoms are string and the null value is NIL.
(defun s_cons (x y) (lambda (f) (funcall f x y)))
(defun s_car (c) (funcall c (lambda (x y) x)))
(defun s_cdr (c) (funcall c (lambda (x y) y)))
(defun s_atom (s) (and (not (functionp s)) (not (equal s NIL))))
(defun s_list (s) (if (null s) NIL (s_cons (car s) (s_list (cdr s)))))

利用例は次の通り.

(defun s_mkassoc (a b)
  (if (or (equal a NIL) (equal b NIL)) NIL
      (s_cons (s_cons  (s_car a) (s_car b))
              (s_mkassoc (s_cdr a) (s_cdr b)))))

(defun s_assoc (k vs)
  (if (equal vs NIL) NIL
      (if (equal (s_car (s_car vs)) k)
          (s_car vs)
          (s_assoc k (s_cdr vs)))))
* (defparameter vs
    (s_mkassoc (s_list '("hoge" "hage" "hige")) (s_list '("10" "20" "30"))))
VS
* (s_assoc "hage" vs)
#<CLOSURE (LAMBDA (F) :IN S_CONS) {50F3E645}>
* (s_car (s_assoc "hage" vs))
"hage"
* (s_cdr (s_assoc "hage" vs))
"20"

Ruby(CRuby 2.5.5)

ドット対は二要素の(凍結)配列で定義.空リストNULLはnilを使用.

#### Cons cells are created by using Array.
#### All of atoms are string and the null value is nil.
def cons(x, y) [x, y].freeze end
def car(s) s[0] end
def cdr(s) s[1] end
def atom(s) s.is_a?(String) || s == nil end
def list(s) s.size == 0 ? nil : cons(s[0], list(s[1..-1])) end

利用例は次の通り.

def mkassoc(a, b)
  if a == nil || b == nil then
    return nil
  else
    return cons(cons(car(a), car(b)), mkassoc(cdr(a), cdr(b)))
  end
end

def assoc(k, vs)
  if vs == nil then
    return nil
  else
    if car(car(vs)) == k then
      return car(vs)
    else
      return assoc(k, cdr(vs))
    end
  end
end
>> vs = mkassoc(list(["hoge", "hage", "hige"]), list(["10", "20", "30"]))
=> [["hoge", "10"], [["hage", "20"], [["hige", "30"], nil]]]
>> assoc("hage", vs)
=> ["hage", "20"]
>> car(assoc("hage", vs))
=> "hage"
>> cdr(assoc("hage", vs))
=> "20"

JavaScript (Node.js 10.21)

ドット対は二要素の(凍結)配列で定義.空リストNULLはnullを使用.

//// Cons cells are created by using Array.
//// All of atoms are string and the null value is null.
function cons(x, y) { return Object.freeze([x, y]); }
function car(s) { return s[0]; }
function cdr(s) { return s[1]; }
function atom(s) { return typeof s == 'string' || s == null; }
function list(s) { return s.length == 0 ? null : cons(s[0], list(s.slice(1))); }

利用例は次の通り.

function mkassoc(a, b) {
  return a == null || b == null
         ? null
         : cons(cons(car(a), car(b)), mkassoc(cdr(a), cdr(b)));
}

function assoc(k, vs) {
  if (vs == null) {
    return null;
  } else {
    if (car(car(vs)) == k) {
      return car(vs);
    } else {
      return assoc(k, cdr(vs));
    }
  }
}
> vs = mkassoc(list(["hoge", "hage", "hige"]), list(["10", "20", "30"]))
[ [ 'hoge', '10' ], [ [ 'hage', '20' ], [ [Array], null ] ] ]
> assoc("hage", vs)
[ 'hage', '20' ]
> car(assoc("hage", vs))
'hage'
> cdr(assoc("hage", vs))
'20'

備考

記事に関する補足

  • 参照用を想定していることもあり,エラーチェックもモジュール化もガーベジコレクションもなにそれおいしいの状態.実用のS式パーサとかは既にたくさんあるしなあ.
  • 現バージョンのlistだと,Common Lisp版を含めて『リストのリスト』が作れない…cons使えばいっか(いいかげん).

変更履歴

  • 2020-08-31:JavaScriptの実装例を追加
  • 2020-08-30:利用例を連想リスト実装に統一
  • 2020-08-30:Rubyの実装例を追加
  • 2020-08-30:初版公開(Python,C,Common Lisp)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

カタカタカタッターンのほかにクリック音とカーソルの移動音も可視化した!

はじめに

こちらの投稿(カタカタカタッターンを可視化した)、そしてそのChrome拡張機能、知っている方も多いのではないでしょうか?

先日確認したところ、拡張機能がなくなってしまっていたので、自分で作ってみました。
(※公開はしていません、Github上にあるファイルで自身のChromeでは使用可能です。)

こんな感じになりました

こちらで試せます
https://koichirokato.github.io/katakatademo/

katakatademo.gif

内容とキャレット位置の取得方法

本家(?)様と同様です。
以下、引用(引用元:https://qiita.com/ampersand/items/3ef94ebe9cba8c07a157)

内容

input要素のtypeがtextまたはsearchの要素、もしくはテキストエリア内におけるキャレットの位置を画面内の絶対値として取得し、その周辺に打鍵と同時に画像を散らかしています。

キャレット位置の取得方法

これの実現については今回は自前実装ではなく caretposition.jsという素晴らしいライブラリがあったので、使わせていただいてます。
http://d.akiroom.com/2012-06/jquery-textarea-caret-position-javascript-library/

実装

以下が実装内容ですが、キーボード入力、クリック、カーソルの移動と3つに分かれています。

// Kachi Kachi
document.onkeydown = function (e) {
    var current = document.activeElement;
    if (e.key === 'Backspace') {
        return true;
    }
    if (current.type === 'textarea' || current.type === 'text' || current.type === 'search') {
        var isEnter = e.key === 'Enter';
        var prefix = isEnter ? 'tan' : 'kata';
        var size = isEnter ? rand(90, 120) : rand(40, 50);
        var caretPosition = Measurement.caretPos(current);
        var imgUrl = chrome.extension.getURL('images/' + prefix + '_' + rand(1, 4) + '.svg');
        chrome.storage.sync.get("kata", function(items) {
            if(items.kata==true) outputimage(caretPosition.left, caretPosition.top, imgUrl, size, isEnter);
        });
    }
}
// Pochi Pochi
document.body.addEventListener( "click", function( event ) {
    var x = event.pageX ;
    var y = event.pageY ;
    var size = rand(40, 50);
    var imgUrl = chrome.extension.getURL('images/' + 'pochi' + '_' + rand(1, 4) + '.svg');
    chrome.storage.sync.get("pochi", function(items) {
        if(items.pochi==true) outputimage(x,y,imgUrl,size);
    });
} ) ;


// Byu-n
var lastx=0;
var lasty=0;
document.body.addEventListener("mousemove", function(event){
    var x = event.pageX;
    var y = event.pageY;
    var size = rand(60, 70);
    var imgUrl = chrome.extension.getURL('images/' + 'byun' + '_' + rand(1, 4) + '.svg');
    chrome.storage.sync.get("byun", function(items) {
        if(items.byun==true) outputimage(x,y,imgUrl,size,isEnter=false,mousemove=true,lastx=lastx,lasty=lasty);
    });
    lastx = x;
    lasty = y;

});

// Output Image
function outputimage(x, y, imgUrl, size, isEnter=false, mousemove=false,lastx=0,lasty=0){
    var $img = $('<img width="' + size + '">');
    $img.attr('src', imgUrl);
    $img.css({
        'position': 'absolute',
        'top': mousemove ? lasty : y+rand(-10, 10),
        'left': mousemove ? lastx : x+rand(-10, 10),
        'zIndex': 99999
    });
    $('body').append($img);

    $img.animate({
        'top': mousemove ? y : y+rand(-40, 40),
        'left': mousemove ? x : x+rand(-40, 40),
        'width': size + (isEnter ? rand(30, 50) : rand(10, 20)),
        'opacity': 0
    }, mousemove ? 200 : 500, function () {
        $img.remove();
    })
}

Chrome拡張機能

拡張機能用にポップアップを利用した設定ができる用にしました。
アイコンをクリックでチェックボックスと保存用のボタンが出てきます。うるさいときは止めれますね。

image.png

おわりに

Chromeの拡張機能の部分、なかなか大変でしたがなんとかできてよかったです。
公開するかはまた決めようと思います。(そもそもいいのか?という)

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

【JS】async,awaitを理解していく

https://qiita.com/soarflat/items/1a9613e023200bbebcb3

すごくわかりやすかったのでアウトプットしていく。

 async,awaitとは

  • Promiseより簡潔に書ける。非同期処理構文

 asyncとは

非同期関数を定義することができる。関数の前にasyncをつけることで非同期の関数を定義することができる。

// asyncを前につける
async function asyncFunction() {}
//アロー関数の場合
const asyncFunction = async () => {}

非同期関数は戻り値(returnした)をresolveの値として返される。例外やthrowした場合はrejectの値として返される。

 実際に確認してみる

resolve(成功)の場合

const asyncFunction = async () => {
  name = "hoge";
  return name;
};

asyncFunction().then((value) => {
  console.log(value);
});

reject(失敗)の場合

const asyncFunction = async () => {
  throw new Error("reject");
};

asyncFunction().catch((error) => {
  console.log(error);
});

どちらの場合でもPromiseを返していることがわかると思います。とにかくasyncがついた関数(非同期関数)はPromiseを返すみたいですね。

 awaitとは

  • 非同期関数(async function)内で使う
  • 関数の前にawaitをつけて使う
  • awaitがつけられた関数がPromiseを返すまで待機する。
const asyncFunction = async () => {
  // sampleResolve()の処理が終わるまで、待機する
  const result = await sampleResolve();
  console.log(result);
};

awaitを使うことでasync function内の処理を指定した場所で止めることができます。

 忘備録

then関数の役割は二つ。

  • 非同期の処理を同期的にする。
  • 前の処理が成功(resolve)だった場合に実行する。

この前者の部分をawaitが担うことはできる。しかし、後者の部分は補えないので、完全になくなることはない。

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

FetchAPIでReadableStreamからMP4を読みながらvideoタグで再生する

やりたいこと

認証がかかったストレージのAPIからJavaScriptで動画データを取得してvideoタグで再生したかった.

FetchAPIやXHRでMP4ファイルにアクセスはできるので,これをブラウザ上で再生したいだけだけなのだけど...

  • 普通にvideoタグのsrcにAPIのURLを突っ込む → リクエストヘッダ等をセットできなくて認証できない
  • FetchAPIでデータを読んでblobを再生する → ファイル全体を読まないと再生開始できない(動画は数百MBある) (参考記事)
  • MPEG-DASH等のFragmented MP4を読み込んでMSEで再生する → 事前にサーバ側に置くデータを変換しないと再生できない (参考記事)

意外とめんどう.

JavaScriptでFragmented MP4を作る

+------------+    +-----------+    +----------------+                             +-------------+    +-----------+
| 動画データ | → | Fetch API | → | ReadableStream | → (なんか良い感じの変換) → | MediaSource | → | videoタグ |
+------------+    +-----------+    +----------------+                             +-------------+    +-----------+

この(なんか良い感じの変換)の部分が欲しい.

MediaSourceは通常のMP4ではなくて,Fragmented MP4を入れる必要がある.

良さげなライブラリが無いか探しているけど,https://github.com/gpac/mp4box.js くらいしか見当たらない.(探し足りないのかもしれないけど)
やりたいことに対してライブラリが大きすぎるのと,ダウンロードしたデータを入れるとsegmentができたときにコールバックが呼ばれる感じなので,必要な量だけStreamから読むという挙動にしにくい.

Fragmentedが付いても所詮はMP4なので簡単に変換できるだろうと思って,とりあえずライブラリ使わず再生してみた.

とりあえず動いたやつ: https://github.com/binzume/mp4player-js

再生するだけで意外と面倒くさかったので,必要だった作業をメモ.

変換方法のメモ

https://www.w3.org/TR/mse-byte-stream-format-isobmff/ この辺の説明と実際のMP4のデータを見ながら適当に変換していけばよい.

MP4とかISOBMFFのドキュメントは大きくて真面目に把握するのはめんどいので,なるべく元のMP4のBox構造を触らずに書き換えが必要なBoxだけ触ることに.
最初,比較用にffmpegで生成したfragmentを見てたけど,どういうわけかffmpegで生成したやつはChromeのMediaSourceで読めなかった.

以下はMP4の構造は知っている前提の記述です.

initialization Segment

最初にinitialization Segmentを一度だけ渡す必要がある.内容は以下のような感じ.

  • ftyp
  • moov
    • moovの下はMP4からコピーして少し修正
    • mvex
      • trex

元がMP4なら,ftyp + moovのうち各tracのstbl下のboxのうちstsd以外を空にして,mvexを追加すればよいだけ.
trexにはトラック番号だけ書いておけば良さそう.

media segment

普通のmp4ならmoovに入っていた情報を複数のmoofに分割して格納する必要がある.

moov → moof,trac → traf等対応しているBoxは名前でわかりやすいが,中身の格納方法は別物なので変換は地味に面倒.

  • moof
    • mfhd
    • traf
      • tfhd
      • tfdt
      • trun
  • mdat

生成しなければいけない主なBox

trakとかmoofとか単に他のboxを持つだけの親は省く.

trex

基本的に各fragmentのtfhdに入れれば良いので少しでもバイト数削りたい場合以外は気にしなくて良さそう.
ただボックス自体は省略できないので,トラック番号だけ入れておく.

fthd,ftdt,trun

trunがサンプルの在り処を指す.stco,stsc,stsz,stts,stss,cttsから生成できる.
trunが持てる情報色々あって難しく感じるけど,生成するときは必要なやつ以外は書き込まなければよいので割とシンプル.

mdat

元のMP4のmdatから必要な範囲をコピーしてくる.どのサンプルがどの場所にあるかはstco,stsc,stszを見る.

MediaSource.addSourceBuffer() に渡す mimeType

video/mp4; codecs="avc1.42e01e,mp4a.40.2" みたいな文字列を渡すのだけどど,Chromeとかだとcodecsの指定が必須.
(多くのサンプルコードで特に説明無くハードコードされてて,残念な雰囲気がある)

MP4のstsd boxからconfiguration recordを取り出して中身を見る必要がある.avc1とかmp4aのコーデック名はstsdの先頭あたりにそのまま入ってるが,後ろの数字はコーデックごとのパラメータを見ないといけない.
手抜き実装をするなら,h264のパラメータはavcCの後にある固定位置の3バイトを16進にして,aacは40.2固定にしてもほとんどの場合は良さそう.

雑感

  • https://github.com/gpac/mp4box.js とかあるけど,単純に再生するのに使いやすそうなのが見当たらない
  • ちゃんと動くの作るのは色々めんどう
  • バイト列の扱いだるいのでwasmでやるのが良さそう
  • MediaSourceとReadableStream,変なことしなくても普通につながってほしい
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JS】Promiseをがっつり学んでいく。

初学者なので、何かご指摘、アドバイス等ありましたらコメントしていただけるとありがたいです?‍♂️

 Promiseとは

Promise は非同期処理の最終的な完了もしくは失敗を表すオブジェクトです。

参考:https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Using_promises

つまりは、下記のとおり。

  • 非同期処理の成功と失敗を分岐することができる。
  • 複数の非同期処理を平行,直列的に処理することができる。

参考:https://qiita.com/saka212/items/9b6cfe06b464580c2ee6

 簡単な例から見ていく。

let myPromise = new Promise((resolve, reject) => {
  // 非同期処理をコールバック関数として渡す。
  setTimeout(function () {
    resolve("非同期処理が成功しました。");
  }, 1000);
});

myPromise
  .then(function (value) {
    // 非同期処理が成功した場合
    console.log("成功メッセージ→" + value); // => 成功メッセージ→非同期処理が成功しました。
  })
  .catch(function (value) {
    // 非同期処理が失敗した場合
    console.log("エラーメッセージ" + value); // => でエラーかreject()が出た場合に呼ばれる
  });

Promise内のコールバック関数(アロー関数)がresolve()を返したときはthen()関数が実行されます。また、Promise内のコールバック関数(アロー関数)がエラーを検出したり、reject()を返した場合はcatch()関数が実行されます。

 Promise関数内で複数の値を返す

let myPromise = new Promise((resolve, reject) => {
  // 非同期処理をコールバック関数として渡す。
  setTimeout(function () {
    resolve(["成功", "失敗してないよ"]);
  }, 1000);
});

myPromise
  .then(function (value) {
    // 非同期処理が成功した場合
    console.log(value[0]); // => 成功
    console.log(value[1]); // => 失敗してないよ
  })
  .catch(function (value) {
    // 非同期処理が失敗した場合
    console.log("エラーメッセージ" + value); // => でエラーかreject()が出た場合に呼ばれる
  });

こんな感じでresolve()内で配列で渡してあげればよき。ちなみに、当然オブジェクトも返すことができる。

let myPromise = new Promise((resolve, reject) => {
  // 非同期処理をコールバック関数として渡す。
  setTimeout(function () {
    const obj = {
      sucess: "成功",
      notFailure: "失敗してないよ",
    };
    resolve(obj);
  }, 1000);
});

myPromise
  .then(function (value) {
    // 非同期処理が成功した場合
    console.log(value.sucess); // => 成功
    console.log(value.notFailure); // => 失敗してないよ
  })
  .catch(function (value) {
    // 非同期処理が失敗した場合
    console.log("エラーメッセージ" + value); // => でエラーかreject()が出た場合に呼ばれる
  });

 複数の非同期処理を同期的に。(直列)

fn1fn2fn3のようにそれぞれの非同期処理を同期的に処理したいとします。では、書いていきましょう。

Promise.resolve()
  .then(fn1)
  .then(fn2)
  .then(fn3)

// それぞれの関数は省略

みたいに書くことで、順番に処理されます。

 複数の非同期処理を並列に。

 Promise.allを使う

fn4fn5の処理がどちらも成功した場合に実行したい処理があるとします。

Promise.all([fn4, fn5]).then(() => {
  // fn4,fn5がどちらも成功した時に実行する処理をここに書く。
});

 Promise.raceを使う

渡された関数のどれかがresolve(またはreject)を返したタイミングで返す。

Promise.race([fn6, fn7]).then(() => {
  // fn6かfn7がresolveを返した時点で実行する処理
}).catch(() => {
  console.log('error');
});

ちなみに、thenメソッドが呼ばれた後もPromiseの処理は継続されます。例えば、fn6が先にresolveを返した後でもfn7は実行されます。

こんな感じでまとめてみたので次はAsync/awaitを学んでいきたいと思います。

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

トースト通知のライブラリ(jQuery非依存版)を作ってみた

かなり前に、Androidっぽい見た目のトースト通知(数秒で消える通知メッセージ)のjsライブラリを作成して、投稿したのですが、がっつりJQueryを使っていたため、jQuery非依存に書き換えました。
またコードをGitHubに公開し、npmでinstallできるようにもしてみました。

今回作った物:a-toast

Androidっぽい見た目のトースト通知なので、そのaです。
リポジトリ:
https://github.com/negi141/a-toast

※先にメジャーなトースト通知ライブラリを紹介

デモ

GitHubリポジトリ:
https://github.com/negi141/a-toast

デモページ
https://negi141.github.io/a-toast/

image.png

使い方

npm install negi141/a-toastでインストール。
または、/build/内のjsとcssを手元に持ってくる。

html
<link rel="stylesheet" href="a-toast.css" />

<script src="a-toast.js"></script>
js
var toast = new aToast();

// スピードや表示位置の設定 (任意)
//   speed : 表示時間. millisecond. default=4000.
//   position : 表示位置. 'top' or 'bottom'. default='top'.
toast.setOption(speed, position);

// 通知を表示
//   message : 表示するテキスト
//   style : 通知の色. '' or 'success' or 'warn' or 'danger'. default=''. 
toast.show(message, style);
toast.success(message);
toast.warn(message);
toast.danger(message);

その他:jQuery⇒jQuery非依存にあたって

fadeIn()はjQueryを使わない場合、どう書くのかなーと思いましたが、以下のようにCSSのtransition-(時間的変化)で書き換え可能でした。

js
$('.a-toast').hide().fadeIn('fast');

 ↓ ↓ ↓

js
t.classList.add("a-toast-show");
css
.a-toast {
  transition-property: opacity; 
  transition-duration: 0.8s;
  opacity: 0;
}
.a-toast-show {
  opacity: 1;
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

実質20行ほどでニコニコ動画風のコメント表示機能を作る 〜素の JavaScript とアニメーション用ライブラリ(GSAP)を利用〜

はじめに

この記事は、以下のツイートの動画にあるようなニコニコ動画風のコメント表示を、HTML+JavaScript で実装した際の話です。

こちらの動画の内容を実現しているものは、ボタンがクリックされたら動的にテキストを生成して画面上に流すという Webページと、その Webページとカメラ映像とをクロマキー合成する機能(OBS による)です。

記事のこの後の部分では、上記の Webページ の部分を作るまでの流れを書き、そして仕組みの話へと入っていきます。また、仕組みの話の後には、オンラインでコメント・リアクションをやりとりするサービス・プロトタイプの事例をいくつか書いています。

仕組みのところだけ知りたいという方は、「作った仕組みについて」の項目からお読みください。

着手するまでの流れ1(OBS向けに過去に作った仕組み)

自身のプライベートの活動で、ここ最近はオンラインイベントの対応(自身でのイベント主催やLT登壇など)が増加しています。そんな中、配信・登壇者の側を便利にしてくれる仕組みや、イベントの参加者と登壇者をつなげる仕組みが自作できないか、と思っていました。

そして、以下の Qiita の記事やツイートの動画にあるような、配信などに使われるオープンソースのソフト「OBS」の「obs-websocket というプラグイン」を活用した遠隔操作の仕組みを作ったりしていました。
 ●OBS をスマホや M5GO(M5Stack)から遠隔制御 〜 MQTT や obs-websocket を利用 〜 - Qiita
  https://qiita.com/youtoy/items/23fb0e16f1a4428b5c9b

この配信者側で活用する仕組みを作ったところで、さらに配信者と視聴者をつなぐ部分の何かを作ることもできれば、と思いました。

着手するまでの流れ2(今回の内容の着手)

上記の仕組みを作った後に、自分も主催メンバーに入っている共同主催のイベント(もくもく会)を開催しました。

そのもくもく会でやる内容を考えていた時に、ふと「また OBS と組み合わせて使える何かを作ってみよう」と思いました。そして、おおまかな内容を考えた後に以下のツイートにあるような試作を始めました

作った仕組みについて

プロトタイプを作るために考えたこと・やったこと

この記事の後半の「既存のサービスやプロトタイプ」という項目に書いたような仕組みの中で、参加者の操作が行われた際に登壇者側に何か表示がされる方向のもの作りたい、と思いました。

そして、最初に手をつけようと思ったのが「ニコニコ動画風のコメント表示機能」でした。

コメント表示に必要な機能

まずは、「ニコニコ動画風のコメント表示機能」を最小限の実装で実現するために、必要な仕組みを頭の中で整理しました。その結果は以下のとおりです。

  • 画面に流すテキストの要素の動的な生成と設定
    • 要素の動的な生成
    • 動的に生成した要素の画面上への配置
    • 動的に生成した要素へのテキストの設定
    • 処理に必要なスタイル・属性などの設定(id や style)
  • 画面上での要素の移動
    • アニメーションの仕組みを用いた要素の移動
  • クロマキー合成のための設定
    • 背景色をクロマキー用の色に設定

最終的には外部から受け取ったテキストやリアクションのトリガーに基づいて、画面上に流すテキスト等の表示を変えるつもりでしたが、表示部分のみを試す段階では外部との連携は省きました。とりあえず版の実装は「画面上にボタンを配置し、ボタンが押されるごとにテキストの動的な生成とアニメーションを行う」というものにしました。

アニメーションの処理の実装

テキストを動かすアニメーションの部分は、CSS か JavaScript の処理により自前での実装もできる部分ですが、後々、表示のさせ方を工夫したくなった際などの拡張性を考慮すると、ライブラリを使っておいたほうが良いと考えました。

以下の記事など、まとめ記事をいくつか見るなどして、最終的に速度・多機能製に優れると書かれていた「GSAP」を用いることにしました。
 ●現場で使えるアニメーション系JSライブラリまとめ(2020年版) - GSAP, CreateJS, WebAnimation, Velocityなど - ICS MEDIA
  https://ics.media/entry/14973/

GSAP_-_GreenSock.jpg

そして、簡素な Webページを作り、動的に生成した要素を動かせることを確認しました。
(※「着手するまでの流れ2(今回の内容の着手)」の項目に掲載していた動画の内容)

実装したソースコードについて

ライブラリの調査・選定と機能テストを行い、その後に全体の実装を行いました。そこで書いた処理部分は「実質20行ほど」です(「実質」という部分に含まれていないのは HTMLの必須の構成要素で、今回用に手を加えてない部分)。

冒頭の動画で、OBS で合成していたテキストが流れる画面は、以下のようなものになります。
ニコニコ動画風のコメント表示_と_03_ニコニコ動画風のインタフェースを作る_と_Slack___random___VS_Code_Meetup.jpg

以下がソースコードですが、今回、「サクッと自分が試せれば良いや」という発想で作った段階のものであるため、ワンソースで適当な実装になっています(エラーハンドリング的なもの等は入ってないです)。もし、「こんな実装のほうが良いのでは?」等といったことがありましたら、記事のコメント欄にてコメントをいただけましたら幸いです。

<!DOCTYPE html>
<html style="background:#00FF00;">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ニコニコ動画風のコメント表示</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.5.1/gsap.min.js"></script>
</head>
<body>
  <button id="button01">テキストを流す</button>

<script>
const button01 = document.getElementById("button01");
button01.addEventListener("click", function(){createText()}, false);
let count = 0;

async function createText() {
  let div_text = document.createElement('div');
  div_text.id="text"+count; //アニメーション処理で対象の指定に必要なidを設定
  count++;
  div_text.style.position = 'fixed'; //テキストのは位置を絶対位置にするための設定
  div_text.style.whiteSpace = 'nowrap' //画面右端での折り返しがなく、画面外へはみ出すようにする
  div_text.style.left = (document.documentElement.clientWidth) + 'px'; //初期状態の横方向の位置は画面の右端に設定
  var random = Math.round( Math.random()*document.documentElement.clientHeight );
  div_text.style.top = random + 'px';  //初期状態の縦方向の位置は画面の上端から下端の間に設定(ランダムな配置に)
  div_text.appendChild(document.createTextNode('移動するテキスト'+count)); //画面上に表示されるテキストを設定
  document.body.appendChild(div_text); //body直下へ挿入

   //ライブラリを用いたテキスト移動のアニメーション: durationはアニメーションの時間、
   //        横方向の移動距離は「画面の横幅+画面を流れるテキストの要素の横幅」、移動中に次の削除処理がされないようawait
  await gsap.to("#"+div_text.id, {duration: 5, x: -1*(document.documentElement.clientWidth+div_text.clientWidth)});

  div_text.parentNode.removeChild(div_text); //画面上の移動終了後に削除
}
</script>
</body>
</html>

動作確認は Mac版の Chrome(バージョン: 84.0.4147.135)で行っていました。

なお、今回、画面で流しているテキストは div_text.appendChild(document.createTextNode('移動するテキスト'+count)); の部分で生成したものですが、仮のものなので今後は手を加えます。

具体的には、外部から MQTT などでリアルタイムに受け取ったテキストを表示させる形にするか、MQTT などでリアクションの ID的なものを受け取って ID に紐付いたテキストを表示させる、という実装を考えています。

配信用などに合成する(OBS が関わる部分)

上記を用いることで、グリーンバックの Webサイトにテキストが流れる画面が出せるようになりますが、配信用などに用いるにはクロマキー合成を行う必要があります。

詳細は割愛しますが、例えば一例として OBS を使って以下の対応をすれば OK です。

  1. PC に外部モニタをつけるなど画面を 2つにし、一方に OBS、もう一方にブラウザ(今回作ったものを表示)を置く
  2. OBS の「シーン」の「ソース」の中にコメント表示を重畳させたい対象を追加(記事の冒頭の動画は「映像キャプチャデバイス」で「PC内蔵のカメラ」を設定しています)
  3. OBS の「シーン」の「ソース」の中に「画面キャプチャ」も追加し、上記1)のブラウザを指定
  4. 上記2)で追加した 「画面キャプチャ」の「フィルタ」で「クロップ/パッド」と「クロマキー」の 2つを設定
  5. 「クロップ/パッド」のフィルタ設定で「上」の数値を増やし、ブラウザのタブの部分などが見えないようにクリッピングする(「クロマキー」のフィルタ設定は、今回の記事内の背景色を指定していれば、特別な設定は不要)

既存のサービスやプロトタイプ

ここでは、今回作ったもの・今回の作ったものの先にある流れと同じ方向性のもの・同じ機能を備えたもので、使ったことがあるもの・気になっているものをいくつか記載します。

オンラインイベント等で使えるコメントやリアクションを表示するためのサービス

Comment Screen

今回の内容は「ニコニコ動画風のコメント表示機能を作る」というものですが、オンラインイベントで活用できるコメント表示サービスで例えば「Comment Screen」があります。
こちらは、コメント等を受ける側がアプリを起動すると、PCのデスクトップ上にコメントやアイコンなどを重畳表示するためのレイヤーができ、コメントを送る側が入力したテキスト等がPCのデスクトップに重畳表示されます。
commentscreen_com.jpg
このサービスは、自分が運営メンバーとなったイベントや、一般参加者になったイベントで何度も使われていてお世話になっています。

リモート擬音さん

また、ニコニコ動画風ではないですが、オンラインイベントで参加者がリアクションをしたら、登壇者側の画面にリアクションに応じた表示が行われるものに「リモート擬音さん」というものがあります。
リモート擬音さん.jpg
以下の使い方の記載にあるように、表示側の仕組みは Comment Screen とは異なります。こちらは、グリーンバックで構成された Webサイトが生成されるので、それを OBS などでキャプチャしてクロマキーを適用する、という使い方です。
リモート擬音さん2.jpg

上記以外のオンラインフィードバックシステムのプロトタイプ

「いいね!」を受け取る仕組み+可視化

その他、イベントでプロトタイプに関するLTが行われたもので、例えば以下のものがあります。
 ●「作った登壇リアクションシステムをベースにNode-REDダッシュボード機能をゆるく紹介」 - ビジュアルプログラミングIoTLT vol3 - Speaker Deck
  https://speakerdeck.com/1ftseabass/biziyuarupuroguraminguiotlt-vol3
オンラインイベント用ではなく、発端はオンライン授業で生徒さんの反応を受け取るため、という感じとのことです。オンラインで「いいね!」のリアクションを受け取り、その 5秒ごとの推移をグラフ化したりなどしています(可視化の部分は Node-RED による実装)。
「作った登壇リアクションシステムをベースにNode-REDダッシュボード機能をゆるく紹介」.jpg

リアクションを受け取り音にする

こちらも、イベントでプロトタイプに関するLTが行われたものです。
YouTube Live のアーカイブはこちら ⇒ https://youtu.be/lp3-ywQKKAc?t=966
【19_30開始】IoT縛りの勉強会__IoTLT_vol_65_-_YouTube_?.jpg
仕組みとしては、登壇者側と視聴者側とを WebSocket でつなぎ、それを登壇者側はラズパイで受信する形のようでした。ラズパイは、リアルタイム通信で視聴者側のリアクションのトリガーを受け取り、そのタイミングで音声を再生してPCへはマイク入力で音を流し込む、というもののようでした。
【19_30開始】IoT縛りの勉強会__IoTLT_vol_65_-_YouTube_?2.jpg

終わりに

今回、ニコニコ動画風のコメント表示機能を「素の JavaScript とアニメーション用ライブラリ(GSAP)」を使って実装する、ということをやりました。

現状は、表示するテキストは内部で生成しているので、この部分は本文でも書いたとおり MQTT などで外部から受け取る形に変えようとしているところです。

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

JavaScriptでカンマ区切りCSVとタブ区切りテキストの双方をパース

外部プラグイン利用不可、サーバサイド処理不可という条件で、JavaScriptのみでCSVのパースが必要だったので、実装した処理をメモです。

必要要件

  • システム側から出力されるタブ区切りテキストの取り込み
  • ユーザーがExcelから出力したCSVの取り込み
  • セルの中にカンマが入る事もある
  • Excel出力したファイルは、必要なセルしかダブルクォーテーションで囲まれない

以上の事から、単純にsplitできる物ではなかったため、実装処理を備忘録として残します。

実際には、さらにSJISとUTF-8(BOM付き)のどちらのパターンも対応する必要がありましたが、ここではそれは割愛しますが、データを読み込んで、先頭にBOMがついていれば、文字コード指定して再読み込みする処理をいれて対応しました。

実装

簡単に言えば、
1. 行末改行で区切ってループ
2. ダブルクォーテーションの数が奇数なら次の行と結合して行データにする
3. 行データごとにループ
4. 先頭からセルを切り出す

という流れで構築しています。

image.png

Excelから出力したCSV(UTF-8)
id,"nam,e",price,about,,d
1,スイカ,1200,熊本産,a,e
2,"メロン
メロン2","2,500","マスクメロン
贈答品にお勧め",b,f
3,そら豆,500,塩茹でしてビールとともに,c,g
タブ区切りテキスト
id  nam,e   price   about       d
1   スイカ   1200    熊本産   a   e
2   "メロン
メロン2" 2,500   "マスクメロン
贈答品にお勧め"  b   f
3   そら豆   500 塩茹でしてビールとともに    c   g
index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8" />
    <link rel="stylesheet" href="style.css" />
</head>
<body>
    <form name="form">
        <input type="file" name="filechoose">
    </form>
    <table></table>
    <script src="script.js"></script>
</body>
</html>
style.css
@charset "UTF-8";
table {
    background: #ccc;
    border-collapse: separate;
    border-spacing: 1px;
}
th, td {
    min-width: 80px;
    padding: 5px 10px;
}
th {
    background: #efefef;
}
td {
    background: #fff;
}
script.js
const table = document.querySelector('table');
const input = document.querySelector('[name="filechoose"]');
const reader = new FileReader();

const make_col = (label, tag) => {
    const col = document.createElement(tag);
    if (label !== null)
        col.innerHTML = label.replace('\n', '<br />').replace('\x22\x22', '\x22');
    return col;
}
const make_row = (line, tag) => {
    const tr = document.createElement('tr');
    line.forEach(label => {
        tr.appendChild(make_col(label, tag));
    });
    return tr;
}

// 画面出力
const show = (header, items) => {
    table.innerHTML = '';
    table.appendChild(make_row(header, 'th'));

    // 値
    items.forEach(line => {
        table.appendChild(make_row(line, 'td'));
    });
}

// ファイル選択確認
const file_choosed = () => {
    if (input.files.length === 0) return;
    const file = input.files[0];
    reader.readAsText(file);
}

// ファイル読み込み
const file_load = () => {
    let csv = reader.result;
    // 空データなら処理しない
    if (csv.length === 0) return false;

    // Excel出力されたCSVの最終行の空行を無視
    // Excel出力されたセル中の改行は\n、行末の改行は\r\n(macは行末\nかも...)
    csv = csv.replace(/\r\n$/, '');
    console.log(csv);

    // 改行区切りしたデータ
    const parse = csv.split(/\r\n/m);
    let data = [];

    // 行データごとにまとめる
    let line = [];
    let quot = 0;
    for (const current of parse) {
        const this_quots = current.match(/\x22/g);
        if (this_quots == null && line.length == 0) {
            data.push(current);
        } else if (this_quots == null) {
            line.push(current);
        } else {
            quot += this_quots.length;
            line.push(current);
            if (quot % 2 == 0) {
                data.push(line.join('\r\n'));
                quot = 0;
                line = [];
            }
        }
    }

    /**
     * セル区切りパターン
     */
    // 空セル
    const pt_1 = new RegExp(/^\x22?[,\t]\x22?/, 'm');
    // ダブルクォーテーションつきのセルはダブルクォーテーション後カンマ、タブが出てくるまで
    const pt_2 = new RegExp(/^\x22([^\x22]+)\x22(\,|\t)/, 'm');
    // ダブルクォーテーションなしのタブ区切りでカンマが出てくるパターン
    const pt_3 = new RegExp(/^([^\t]+)(\t)/);
    // ダブルクォーテーションで囲まれていない場合はカンマかタブが出現するまで
    const pt_4 = new RegExp(/^([^\,\t]+)(\,|\t|$)/);

    // ヘッダ、内容出力データ用配列
    const header = [];
    const items = [];
    before = null;
    data.forEach((v, i) => {
        cell = null;
        while (v.length > 0) {
            if (pt_1.exec(v)) {
                cell = [pt_1.exec(v)[0], null, null];
            } else if (pt_2.exec(v)) {
                cell = pt_2.exec(v);
            } else if (pt_3.exec(v)) {
                cell = pt_3.exec(v);
            } else if (pt_4.exec(v)) {
                cell = pt_4.exec(v);
            } else {
                // 最終行
                cell = [v, v, v];
            }
            if (i === 0) {
                header.push(cell[1]);
            } else {
                if (typeof(items[i - 1]) === 'undefined') items[i - 1] = [];
                items[i - 1].push(cell[1]);
            }
            v = v.substring(cell[0].length, v.length);
        }
    });

    // 画面出力
    show(header, items);
}

// Events
input.addEventListener('change', file_choosed, false);
reader.addEventListener('load', file_load, false);

取り込み結果

image.png

あとがき

タブ区切りテキストで、ダブルクォーテーションで囲まれていないセルの中に、ダブルクォーテーションが入っている時の処理も入れたら、ほぼほぼカバーできます。この処理はさらに精度を高めた処理にしていきたいと思います。

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

mediaQueryの記述を効率化するためのラッパーを npm package で作ってみた

はじめに

毎回微妙に違うけど似たようなロジックを、プロジェクト毎に組んでしまいがちです。
どうせいつも使うなら、自分用に npm package 化し、方針変更をバージョンアップで受け取れるようにしておく方が、理にかなってるような気がしたので、今回学習と実験を兼ねて作ってみました。

簡単に作れそうなものとして、css の MediaQueries を題材に選んでみました。

おそらく似たようなpackageは大量に存在しているとは思っていますが、自力で車輪を発明することも今回の趣旨なので、ご容赦を。

大枠の方針

  • link要素での使用は禁止。
  • .class内で細かく分岐させる。
  • breakpointは 任意の数を定数で管理。プロジェクトに応じて簡単にoverride可能にする。
  • MediaQueries用のラッパー関数を中継して記述。生では書かない。
  • mobile first / desktop first のどちらの方針も対応できる。
  • window.matchMediaを使い、可能な限り似たロジックでjs版もつくる。

今回作った2つの npm package

scss用とjs用として2つに分割して作成。
ほぼ自分用の側面が強いので名前を scoped modules にします。

(統合する方針で当初構想したが、そもそも、 MediaQueries 管理をしないと行けない状況は最終手段感がある、 命名が完全には揃えられない、使うべき理由がそもそも大きく異なるはず、等の理由で分けて扱う事に。)

原則scssで完結できる世界を願い、jsの方はおまけです。

mq-scssの方針

cssコーディングは、絵を扱うという性質上、まま理不尽な状況が多い気がします。
また webサイト なのか webアプリ なのかといった違いで、求められるブレイクポイントの粒度が全然異なります。
これらの事情を鑑み、安全に運用するために、グローバルなbreakpoint管理イレギュラーな状況に対応できる の2点を同時に担保できる方法を目指します。

インストール

$ npm i -D @rm-labo/mq-scss
@import 'path/to/node_modules/@rm-labo/mq-scss/_mq.scss';

JavaScriptのエントリポイントからCSSを処理する場合は、スタイルシートをモジュールとして直接インポート。

import '@rm-labo/_mq.scss'

使い方

3種類 * 2方向の@mixinsを、キャメルケースかケバブケースで使えるようにした。

任意の箇所でで使うときは以下。各mixinは以下の場面で使う。

  • mq-{'width'|'height'}-up($bp) {} : スマホファーストの記述用
  • mq-{'width'|'height'}-down($bp) {} : デスクトップファーストの記述用
  • mq-{'width'|'height'}-between($bp1, $bp2) {} : 特定範囲用
// CamelCase or kebab-case
.class-name {
  // width camelCase 
  @include mqUp($bp) {}
  @include mqDown($bp) {}
  @include mqBetween($bp1, $bp2) {}

  // width kebab-case (same as camelCase) 
  @include mq-up($bp) {}
  @include mq-down($bp) {}
  @include mq-between($bp1, $bp2) {}

  // height camelCase 
  @include mqHeightUp($bp) {}
  @include mqHeightDown($bp) {}
  @include mqHeightBetween($bp1, $bp2) {}

  // height kebab-case (same as camelCase) 
  @include mq-height-up($bp) {}
  @include mq-height-down($bp) {}
  @include mq-height-between($bp1, $bp2) {}
}
arguments type required default value description
$bp string , number false md sm, md, lg, xl, Free px number
$bp1 string , number true - sm, md, lg, xl, Free px number
$bp2 string , number true - sm, md, lg, xl, Free px number

全体で共用するbreakpointが!defaultで定義されている。

// デフォルト値
$mq-breakpoints: (
  sm: 600px,
  md: 900px, // default
  lg: 1200px,
  xl: 1800px
) !default;
$mq-breakpoints-default-key: 'md' !default;

プロジェクトに合わせてoverrideして使う。

// override例
$mq-breakpoints: (
  micro: 320px,
  small: 620px,
  medium: 840px, // default
  large: 1280px,
  extra: 1900px,
  // .. 好きなだけ追加 ..
);
$mq-breakpoints-default-key: 'medium';

@import 'path/to/node_modules/@rm-labo/mq-scss/_mq.scss';

基本的にいはスマホファースト且つ、$breakpointsで定義した共用の値のkey sm md lg xl を引数として受け取るが、その一箇所でしか使わないイレギュラーケース用として、ピクセル値による指定も許容するよう実装する。
また、PC/スマホだけのような1ブレイクポイントの簡易的な実装も多いため、省略してスッキリかけるようにも対応する。

.className {

  // SINGLE BREAK POINT
  @include mqUp() { /* 900px (default: md) and up */ }
  @include mqDown() { /* 900px (default: md) and down */ }

  // MOBILE FIRST POLICY 
  @include mqUp('sm') { /* 600px and up */ }
  @include mqUp(900px) { /* 900px and up */ } // イレギュラーな px数でもOK
  @include mqUp('lg') { /* 1200px and up */ }
  @include mqUp('xl') { /* 1800px and up */ }

  // DESKTOP FIRST POLICY 
  @include mqDown('xl') { /* 1799px and down */ }
  @include mqDown(1200px) { /* 1199px and down */ } // イレギュラーな px数でもOK
  @include mqDown('md') { /* 899px and down */ }
  @include mqDown('sm') { /* 599px and down */ }

  // SPECIFY BY RANGE  
  @include mqBetween('sm', 'md') { /* 600px ~ 899px */ }
  @include mqBetween('sm', 'lg') { /* 600px ~ 1199px */ }
  @include mqBetween('sm', 1600px) { /* 600px ~ 1599px */ } // 混在OK
  @include mqBetween(1234px, 850px) { /* 850px ~ 1233px */ } // 大小逆もOK
} 

mq-jsの方針

原則cssで完結するべきだが、どうしてもjsによるサポートが必要なケースもある。
そのため、scssとなるべく近いコンセプトのjsもおまけで作ってみた。

window.matchMedia を使い、ブレイクポイントのkeyの名前とcallbackを受け取るようなラッパーを生成する方針。

インストール

$ npm i -D @rm-labo/mq-js
import Mq from '@rm-labo/mq-js'

使い方

読み込んでインスタンスを作り、それに対して以下のようなメソッドを実行していきます。
当たり前ですがjsなので kebab-case はありません。

import Mq from '@rm-labo/mq-js'
const mq = new Mq()

mq.up( bpKeyName1, matchHandler [, unmatchHandler ])
mq.down( bpKeyName1, matchHandler [, unmatchHandler ])
mq.between( bpKeyName1, bpKeyName2, matchHandler [, unmatchHandler ])

パラメータは以下

arguments type required
bpKeyName1 string true
bpKeyName2 string true
matchHandler function true
unmatchHandler function false
// 単一ブレイクポイントの場合
mq.up(
  'sm', 
  () => { /* Fires in 600px and up */ },
  () => { /* Fires in 599px and down */ }
)

// 別の明示的な書き方もできるように
mq
  .down('md', () => { /* Fires in 899px and down */ })
  .up('md', () => { /* Fires in 900px and up */ })

// 特定の範囲だけを対象にしたい場合
mq
  .between(
    'md', 
    'lg', 
    () => { /* Fires in 900 ~ 1199px */ },
    () => { /* Fires in ~ 899px and 1200px ~ */ })

// すべての範囲を網羅したい場合はメソッドチェーンでつなげる
mq
  .down('sm', () => { /* Fires in 599px and down */ })
  .between('sm', 'md', () => { /* Fires in 600 ~ 899px */ })
  .between('md', 'lg', () => { /* Fires in 900 ~ 1199px */ })
  .between('lg', 'xl', () => { /* Fires in 1200 ~ 1799px */ })
  .up('xl', () => { /* Fires in 1800px ~ */ })

scssと同様で高さを対象にできるように。

mq
  .heightUp(bpKeyName1, matchHandler [, unmatchHandler ])
  .heightDown(bpKeyName1, matchHandler [, unmatchHandler ])
  .heightBetween(bpKeyName1, bpKeyName2, matchHandler [, unmatchHandler ])

ブレイクポイントもoprion経由でoverrideできるようにする。

// 例
const option = {
  breakpoints: {
    'micro' : 320,
    'small' : 620,
    'medium' : 840,
    'large' : 1280,
    'extra' : 1900,
    // .. 好きなだけ追加 ..
  }
}
const mq = new Mq(option)
mq
  .down('micro', () => { /* Fires in 319px and down */ },)
  .between('micro', 'small', () => { /* Fires in 320 ~ 619px */ },)
  .between('small', 'medium', () => { /* Fires in 620 ~ 839px */ },)
  .between('medium', 'large', () => { /* Fires in 840 ~ 1279px */ },)
  .between('large', 'extra', () => { /* Fires in 1280 ~ 1899px */ },)
  .up('extra', () => { /* Fires in 1900px and up */ },)

demo

実際に動かしているページも一応用意した。

もう少しスマートな方法もありそうではあるが、一旦これを使ってみる。

課題

pxベース限定になっている点。
emベースなど別の単位で管理したいケースもあるが今回は実装を見送った。バージョンアップで対応するかもしれない。
(em等、px以外の単位で MediaQuery を書きたい場合に必要なcalc計算が、一部レガシーブラウザ待ちの状況。)

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

初投稿

どうも :shamrock:

エンジニア就職を目指している20代です

現在はTechCamp82期生として最終課題に取り組んでいます

就職までの学習を記録します

既存の内容も多く投稿することになると思います:airplane:

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

Array.map() あれこれ

Array.map() あれこれ

関数の中でmapが好きなので、いろいろな言語のmapを調べてみました。

※ここで言うmapは、主に配列(言語によってはリスト)を受け取って、内部で処理して(多くは無名関数などのコールバック関数)、新しい配列を作って返すような言語に込み込みの標準関数を想定しています。

mapの気に入っているところ

個人的に下記が気に入っている理由です。

  • ループ処理を書かなくて良い
  • 元の配列に影響しない
  • コードが短くて済む

JavaScript

JSは最近コード書くことが多いのですが、forやforEachよりもmapを良く使います。

const array = ['Perl', 'Ruby', 'Scala'];
console.log(array);
let array2 = array.map(v => { return `"Hello ${v}!"`});
console.log(array2);

Ruby

RubyのmapはArrayクラスの実装では無く、Arrayクラスが継承するEnumerableモジュールにあるんですね。

array = ['Perl', 'Ruby', 'Scala']
p array
array2 = array.map {|v| "Hello #{v}!"}
p array2

Perl

map歴はPerlからですが、Rubyを使った時にPerlとmapへの配列の渡し方が違うので戸惑いました。

my @array = ('Perl', 'Ruby', 'Scala');
print("@array\n");
my @array2 = map { "Hello $_!" } @array;
print("@array2");

Scala

ScalaはJavaとRubyなどのスクリプト言語の手軽さを合わせたような言語です(個人的なイメージ)

    val array = List("Perl", "Ruby", "Scala")
    println(array)
    val array2 = array.map(v => s"Hello $v!")
    println(array2)

Clojure

Scalaと同じく、JVM上で動作するLisp記法の言語で関数型と並列処理が強み(個人的なイメージです)


(def array ["Perl" "Ruby" "Scala"])
(println array)
(def array2 (map #(format "Hello %s!" %) array))
(println array2)

https://clojuredocs.org/clojure.core/format

PHP

PHPも無名関数が使えるんですね。

<?php
$array = ['Perl', 'Ruby', 'Scala'];
var_dump($array);
$array2 = array_map(function ($v){return "Hello " . $v . "!";}, $array);
var_dump($array2);
?>

アロー関数

Swift

Swift使ったこと無いですが、型推論があるようでスクリプト言語っぽく書けるので中々良いかもです。

変数の文字列展開は(変数名)な感じで書けます。

let array = ["Perl", "Ruby", "Scala"]
print(array)
let array2 = array.map {"Hello \($0)!"}
print(array2)

Python

大人気言語パイソンです。Pythonでは無名関数の定義にlambdaと言う宣言?を使うようです。

array = ["Perl", "Ruby", "Scala"];
print(array);
array2 = list(map(lambda v: "Hello " + v + "!", array));
print(array2);

lambda
f-string
array2 = list(map(lambda v: f"Hello {v}!", array));
リスト内包表記

Haskell

Haskellです。関数型言語で型推論を持つ言語です。(多分)

main = do
  let array = ["Perl", "Ruby", "Scala"]
  print array
  let array2 = map (\ v -> "Hello " ++ v ++ "!") array
  print array2

感想

kintoneアプリ開発の会社に所属している為、普段は限定された範囲のJavaScriptしか書かないのですが、書籍を持っている範囲の言語で1つの関数の使い方をまとめてみると、言語によっていろいろな考え方などを発見することが出来たので、良かったと思います。

JavaScriptで関数言語のような使い方に傾倒しているので、ScalaとClojureは書きやすいなと感じています。(あと、Swiftも意外と違和感が無かった)

参考

動作確認には paiza.IO を利用させていただきました。ありがとうございます。

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

[JavaScript] クロージャー(closure) をわかりやすくまとめてみた No.1

なんと、このコードもクロージャーなんです。

const myName = 'rika'

function printName(){
  console.log(myName)
}

PrintName() // rika

他の言語では、functionの内側の変数から外側の変数にアクセスすることは出来ないのですが、JavaScriptだとそれが可能になります。それを「クロージャー」と呼びます。

上の例でも、グローバル変数myNameをfunctionの中での使用することが出来るんです。

一番使われるクロージャーの形はfunction内のfunction!

function outerFunction(outerVariable) {
 return function innerFunction(innerVariable) {
      console.log(`Outer Variable: ${outerVariable}`) //結果 outside
      console.log(`Inner Variable: ${innerVariable}`) //結果 inside
 }
}

const newFunction = outerFunction('outside')
newFunction('inside')

ここでもouterVariableが、innerFunction内でもアクセス出来ちゃってます。つまり「クロージャー」が使われているということですね。

たとえば、functionの外側に新しく変数を置いてみましょう。

function outerFunction(outerVariable) {
   const name = 'rika' 定数nameを追加してみました。↓
 return function innerFunction(innerVariable) {
      console.log(`Outer Variable: ${outerVariable}`) //結果 outside
      console.log(`Inner Variable: ${innerVariable}`) //結果 inside
      console.log(name) //結果 rika と出力されます
 }
}

const newFunction = outerFunction('outside')
newFunction('inside')

つまり、functionの内側から、外側の変数・定数にアクセス出来ますよー!というのがクロージャーの特徴になりますね! 

なぜクロージャーを使うのか?

たとえばグローバル変数を使えば、どこからでもアクセスが出来ます。
ですが、あえてクロージャーを使う利点をあげるとすれば、その名の通り外部から変数にアクセスできない仕組みになっているからです!(カプセル化と呼びます)これはコードの安全性を高める上で重要なテクニックです。クロージャーのテクニックを利用することでより安全で保守性の高いプログラムにすることが可能です。

参考動画: https://www.youtube.com/watch?v=3a0I8ICR1Vg

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

5chの「なんJ」でAAが「貼れない」規制を突破できるようにAAを変換するサイト (をgithub.ioで無料公開する方法)

AAを貼れるように変換するサイトはこちら

https://j-j-j-j.github.io/

  • 上のurlにアクセスするとAAを貼れるように変換できます

  • 使い方は入力フォームにAAをコピーして「実行」ボタンを押すだけ
    変換ずみのAAがクリップボードにコピーされます

=========================

現時点でも「なんj AA 貼れない」などと検索しても出てこないのでここで紹介しておきます。qiitaなら多分上位に来ると思うので
chmateなど専ブラがAA変換に対応してくれればいいのにって感じだけど
ちなみにこの記事の著者は製作者じゃありません

=========================

なんJなど実況系板のAA規制とは

一定以上空白がある文章が書き込めなくなったため、5chの実況系板でほとんどのAAがはれなくなった規制。

スクリプトの内容

この変換サイトだと

空白やらNG判定になりそうなところを読み込んで
置き換えて
新しい文字列の固まりとして出すのは
jsのプログラミング部分でやってる

それをクリックポチポチッてできるWebのサイトとして成り立たせてるのはHTMLとgithub.ioの鯖

  • index.html

サイトにアクセスすると基本的にindex.htmlが最初に表示されます。

<!DOCTYPE html>
<meta charset="utf-8">
<script src="index.js"></script>
<link rel="stylesheet" href="index.css">
<title>j-j-j-j</title>
<div>
    <textarea id="input" cols="64" rows="16" onfocus="this.select();">              /.i
          /i∠__ノ
       /.i∠__ノ‘j’ )ノ  エーライヤッチャ
     ∠/.i‘j’ )ノ==|┘
    〈,(∠__ノノ /.i__〈  エーライヤッチャ
     〈,( ‘j’∠_/.i
     └i=〈,(∠__ノノ     
      〈__〈,( ‘j’ )ノ  ジェイジェイジェイジェイ
         └i===|┘
           〈__〈</textarea>
</div>
<div>
    <button type="button" onclick="main()">実行</button>
    <p id="result"></p>
</div>
<div>
</div>

3行目の<script src="index.js"></script>で「index.jsに以下の内容を受け渡す」ということを宣言しています

2行目<link rel="stylesheet" href="index.css">はスタイルシートといい、サイトのデザイン(アイコン画像など)を決める.cssファイルを指定しています

4行目の<textarea id="input" cols="64" rows="16" onfocus="this.select();">は入力フォームを作っています。ここで渡された情報はindex.jsに渡されます。

index.jsではfunction escapeToUnicode(s)という関数内で文字列をunicodeに変換(これで5chの規制は適用されなくなる) 、function clipboard(s) でクリップボードにコピーします

  • index.js
let CHARS = ' .,\'`"\\:;\\-=~_|\/<>';

function main() {
    let s = document.getElementById('input').value;
    s = escapeToUnicode(s);
    clipboard(s);
    checkByteCount(s);
}

function escapeToUnicode(s) {
    let r = /[  \n][  ]{3}[  \n]/;
    while (r.test(s)) {
        s = avoid(s, r);
    }

    r = RegExp(`[${CHARS}\n][${CHARS}]{3}[${CHARS}\n]`);
    while (r.test(s)) {
        s = avoid(s, r);
    }

    return s;

    function avoid(s, r) {
        return s.replace(r, (sub) => {
            // 最後の1文字を数値参照化
            // 最後の1文字が改行の場合は最後から2文字目を数値参照化する
            if (sub.slice(-1) === '\n') {
                return sub.slice(0, -2) + toEntity(sub.slice(-2, -1)) + '\n';
            } else {
                return sub.slice(0, -1) + toEntity(sub.slice(-1));
            }
        });
    }

    function toEntity(c) {
        return '&#' + c.charCodeAt() + ';';
    }
}

function clipboard(s) {
    const tempElement = document.createElement('textarea');
    tempElement.style = "position: absolute; left: -9999px; top: -9999px";
    tempElement.value = s;
    document.body.appendChild(tempElement);
    tempElement.select();
    document.execCommand('copy');
    document.body.removeChild(tempElement);

    const result = document.getElementById('result');
    result.textContent = 'クリップボードにコピーされました';
    result.className = '';
}

function checkByteCount(s) {
    // バイト数チェック
    // UTF-8ではなくShift_JISでカウントされる

    const bytes = countBytes(s);
    if (bytes > 4096) {
        result.textContent = `総バイト数が${bytes - 4096}バイト超過 連続するスペースや記号を減らしてください`;
        result.className = 'error';
    }

    s
        .replace('\n', ' <br> \n')
        .split('\n')
        .some((s, i) => {
            const bytesOfRow = countBytes(s)
            if (bytesOfRow > 1024) {
                result.textContent = `${i + 1}行目が${bytesOfRow - 1024}バイト超過 連続するスペースや記号を減らしてください`;
                result.className = 'error';

                return true;
            } else {
                return false;
            }
        });

    function countBytes(s) {
        let bytes = 0;
        for (let c of s) {
            let code = c.charCodeAt();
            if ((code >= 0x0 && code < 0x81) || (code >= 0xff61 && code < 0xffa0) || (code >= 0xf8f0 && code < 0xf8f4)) {
                bytes += 1;
            } else {
                bytes += 2;
            }
        }

        return bytes;
    }
}

こういう簡単なフロントエンドのwebアプリなんでソース見ればやってることはすぐわかるよね

以下のgithubのレポジトリに置いてあります。

https://github.com/j-j-j-j/j-j-j-j.github.io

github.ioでの公開の仕方

GitHubリポジトリ画面の「Settings」 → GitHub Pages → Source → master branch → Save ボタン → https://ユーザ名.github.io/リポジトリ名 にサイトが公開される

5ch運営はなぜAAを規制したのか

5ch運営の行動は基本的に不可解なので追求するだけ無駄。

運営がjimワトキンスになってから日本語読めない人が技術的な部分を担うようになってる(jimやcode monkeyは8chanの運営にしか興味ない)ので仕方ない

米国では掲示板のRedditのDAUやアクセス数(平均して全米4位-7位にいる)がinstagramやtwitterなどSNSを上回っているというのに、遥か前から掲示板文化のある日本ではゴミみたいな5chや爆サイが最大の掲示板で、どんどん衰退してるというのは、不幸なこと(板やスレのあるシステムは不特定多数との交流に本当に優れている)

5ch運営がタグ導入によるSEO最適化や、旧態依然としたUIでもChmate等の専用ブラウザ導入を促すなどやっていれば状況は違ったかもしれないけど、今も昔も運営にやる気がないので仕方ない

5ch+chmateは軽量でジェスチャーなどredditよりも使いやすいところが多いのに専門板はこういう意味不明な規制や荒らし放置で人が減るばかり、伸びてる板はなんJやyoutube板のようなゴミばっか、

一方のredditの日本語コミュニティーはAPI規制で移住したケンモメンの残党(ほとんどはchmateなどが使えるようになった時点で嫌儲に帰った)のあつまるnewsokurが内ゲバで崩壊

日本でも掲示板文化の復興が望まれます

5chでもredditでもいいので。。。

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

GitHubリポジトリのファイルをクロスオリジンで配信する超簡単な方法

小ネタです。

ちょっとしたスクリプトを配信したいな〜というとき。

GitHubリポジトリのファイル実体が保管されているhttps://raw.githubusercontent.comから直接src属性などで引っ張ろうとしても、レスポンスヘッダにAccess-Control-Allow-Origin: *がないためCORSエラーで弾かれてしまいます。

CORS-Error
<script src="https://raw.githubusercontent.com/dojyorin/myrepo/master/dist/index.min.js"></script>

jsDelivrを使おう

実は、みんな大好きいつもお世話になっているjsDelivrさんからGitHubレポジトリのファイルへ クロスオリジン でアクセスできます!!

準備

jsDelivrでの配信準備はとても簡単。

  1. タグを発行する
  2. タグをpushする
  3. おわり

タグがリモートリポジトリと同期されると、jsDelivr側も即座に反映されます。

アクセス方法

タグが同期されたら、あとは以下のようにタグ名ごとのURLでアクセスできます。
タグ名をlatestにすれば常に最新版をロードできるのはいつもと変わりません。

CORS-OK
<script src="https://cdn.jsdelivr.net/gh/dojyorin/myrepo@tagname/dist/index.min.js"></script>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[高階関数/Higher-order function] 英語の記事・動画まとめ No.1

The coding trainの解説

元動画: https://www.youtube.com/watch?v=H4awPsyugS0

function hello () {
}

上は普通のfunction。

高階関数とは2つ以上の関数(function)を使用するということ。それはparameterに入れたり、戻り値としてfunctionを設定したり、いろんなことが出来るんです。

function hello(function) {
  return funtion 
}

具体的にみてみよう

1) 例えば以下のようにsingという関数がla la la -- という文字数を出すとしましょう

function sing(){
  console.log('la la la la la')
}

2) la la la laの他にも他の音を一緒に出したい時はどうしましょう?
このような時は、別でmeow meowというfunctionを作って、singの引数として渡してしまえるんです!

function sing(callback){
  console.log('la la la la la')
  callback();
}

function meow(){
    console.log('meow meow')
}

3) 以下のようによりシンプルな表現にすることも可能です

sing(function(){
  console.log('meow meow');
});

function を戻り値にする場合

function multiplier (x, factor) {
  return x * factor; 
}

でも、もしこの計算をしてくれるfunctionを戻り値として出したい場合はどうなるでしょうか?

function multiplier (factor) {
  return function(x) {
    return x * factor; 
  }
}

1) multiplierに数字を入れて、その結果を変数に入れてみると・・・

let doubler = multiplier(2)といれると、undefined となりますが、
ここで戻ってきているのは関数・・・そう、function(x){return x * 2}ということになる!! 

2)その次に,doubler(2) と入力すると、 そう・・・・ 4が結果として表示されます!!

Screen Shot 2020-08-29 at 17.56.22.png

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

ReduxとFluxはどう違うのか

Reduxはわかった。Fluxとはなんなのだ?

React/Reduxは昨今のWebフロントではよく見る構成なので、学んでいる方は多いと思う。
Reduxの関連文献を読むと度々Fluxという言葉が出てくるがあれは一体なんなのか。

この記事では、Reduxとの違いを意識しながらFluxを紐解いていく。

Fluxとはアーキテクチャである

Facebookが生み出したクライアントサイドウェブアプリ用のアーキテクチャである。
Reduxはライブラリの名前であるが、Fluxはアーキテクチャの名前である。つまり特定のライブラリを指す言葉ではない。
(とはいえfacebook/fluxがutilを提供しており、これが使われることが多い)

このアーキテクチャの実装を助けるライブラリが多数生まれたが、その中で一際人気を誇ったのがRedux。
ReduxはFluxのような単一方向のデータパスに基づく設計思想になっているが構成要素に違いがある。

なぜFluxなのか MVCでは難しい実装ケース

ReduxとFluxの話に入る前に、もう一つ有名なアーキテクチャのMVCの話を最初にする。
MVCモデルではどんな時に不都合なのだろうか。

端的に書くとMVCでは1つのデータソースを複数箇所のUIから操作されるようなケースでコードが複雑になる。
公式の例ではチャットの未読数のカウント表示が挙げられている。

  • チャットは不特定多数の人から送られ、その内どれかが既読になればカウントを-1する
  • 2カ所からチャット開けるならそれぞれの箇所で既読時にカウントを-1するロジックをいれる
  • 2カ所に存在するカウント表示にデータの変更を反映させる

このように、共通のデータを複数箇所から変更 & 参照する場合、Viewのデータソースとなる値をどこでどう管理するのかが難しくなる。
MVCでは複数箇所から更新され、複数箇所に対して更新を反映する複雑なデータフローになるところを、Fluxであれば単一で1方向のデータフローで表現することができる。

Fluxの主要素

下記の要素がFluxの主要素と言われている。

  • dispatcher
  • store
  • view
  • action

いずれもReduxの経験者であればなんとなく耳にしたことがあるような単語ではあるが、それぞれ少しずつ意味合いが異なる。

flux-simple-f8-diagram-with-client-action-1300w.png

図の引用: Flux公式ページ

dispatcher

storeに対してcallbackを登録し、呼び出しに応じてそれを起動する存在。
Action Creatorメソッドから呼び出され、新しいデータを持ったActionオブジェクトをStoreに伝える役割を持つ。
1つのdispatcherが複数のcallbackを登録することができ、データをブロードキャストすることができる。

store

データを管理するオブジェクト。
getterを持つがsetterは持たず、dispatcherによって登録されたcallbackからのみ更新される。
Reduxとは異なり、storeは複数存在することができる。それぞれのstoreに対してdispatcherはcallbackを登録する。

view

storeのデータを参照して作成されるアプリケーションの見た目の部分。
storeからデータを受け取るviewのことを Controller view と呼び、子孫コンポーネントに対してPropsを渡していく役割を持っている。
viewとController viewに分けることで、viewの責務をシンプルに保つことができる。

Action

Actionはストアを更新する際の新しいデータを含んだオブジェクト。
dispatcherに対してActionを渡す関数をAction Creatorと呼ぶのはReduxと同じだが、FluxではActionsというオブジェクトのメソッドとしてAction Creatorが実装されることが多いらしい。(Flux公式でもAction Creatorはmethodであると書かれている)

Fluxコードを体験してみたいなら

facebookがチュートリアル付きのexampleを提供しているので、Readme.mdに従ってコーディングをしてみるとFluxの書き味が体験できる。
flux-todomvc

TodoMVCをFluxで作っていくチュートリアルで序盤は写経できるサンプルと解説、最後には解答無しの練習課題がついているので良い練習になる。
flux/utilを使用するので書き方はReduxに近いものがあり、Reduxでのアプリ制作経験があるの人ならそれほど時間をかけずに終えられると思う。
(筆者はこの記事を書くにあたってトライした)

Reduxとの違い

上記のFluxの特徴と重複する部分はあるが、Reduxの違いにフォーカスしてまとめる。

dispatcherの存在

ReduxにDispatcherは無い。Store.dispatch()というメソッドはあるが別物。
上記の通り、dispatcherはstoreに対してcallbackを登録する役割を持つ。action creatorからDispatcher.dispatch()がcallされることからfluxの中心的な位置にいると言っていい。

Reduxでは、dispatcherが登録するcallbackの役割をreducerが担っている。

action creatorの役割

dispatcherに関連してaction creatorの動きも少しだけ違う。
fluxのaction creatorはactionを引数にしてdispatcher.dispatchをcallする役割がある。

Reduxにおけるaction creatorは呼ばれた時にactionを返却する。
creatorという名前のイメージにはこちらの方が近い。dispatcherとの結合がなくシンプル。

middlewareが存在しない

Fluxはmiddlewareという仕組みを持たない。Dispatcher.dispatch()はあらかじめ登録したCallbackを呼ぶための処理であり、間に他の処理を挟むことはできない。

ReduxはmiddlewareによってdispatchされたActionをロギングしたり、エラーActionのハンドリングを行うなどReduxの処理フロー全体に影響を与えるような処理を含むことができる。
redux-thunk, redux-observableなどの非同期処理用のmiddlewareを活用することで、非同期処理の責務をらAction Creatorに持たせることができるようになるためComponentをシンプルに作りやすい。

Fluxのstateは複数のstoreによって表現される

Fluxは複数のstoreを持つ。例えばTwitterのようなシステムであればUserInfoStoreとTweetListStoreのようなstoreを持つことになる。
これに対してReduxは1つのplain old Javascript object (POJO)でstateを表現する。アプリケーション全体の状態を扱いたいケースにおいては1つのオブジェクトである方が遥かに扱いやすい。
例えばSSRは、サーバーでstateを構築してクライアントに送信する必要があり単一のオブジェクトである恩恵を受けている。

stateを追加する際にはFluxは新たなstoreを作成し、dispatcherから新たなcallbackを登録する。
Reduxは純粋な関数であるReducerを追加するのみであり、拡張がシンプル。

終わりに

ReduxはFluxを基にして生まれたライブラリというだけあって使い勝手が良い面が多い。
Fluxを用いて新規にアプリケーションを作成する機会は少ないと思うが、アーキテクチャの話題では頻出単語なので正しく理解しておくと役立つと思う。

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

Dom国とJavaScript国の間の橋は狭い

昔聞いた先輩社員の名言

  • DOM国とJavaScript国の間の橋は狭いので、頻繁に行き来しないように気をつけろと言われたのを思い出したので、ある意味初歩的なことではあるが、自戒も込めて記事にしておく

先に結論

  • 要はDOM操作は遅いので、JavaScriptの(特にループの中で)DOM操作を頻繁に行うと性能劣化につながるというお話

経緯/背景

  • 私の所属するプロジェクトの終盤で、性能問題が顕在化した
  • パッケージ製品を使っており、パッケージの大幅な更改があったのだが、その前後で画面描画が倍以上遅くなっている
  • パッケージが新しくなっているのに、よく使うデフォルト的な画面が圧倒的に遅くなるのはありえない。1ヶ月以内になんとかしろと言うことに
  • その画面は表形式のデータを表示する画面で、数千行(一度の画面描画は150件程度)が処理対象になる
    • 列は10項目程度

原因

  • ソースコードレベルまで追跡した結果以下2点が判明

1. 画面描画時にリサイズイベントが呼ばれ、同じ関数が2回呼ばれている

  • 画面ロード時に自身とリサイズイベントで関数が2回呼ばれている。
    • ただ実は、これは体感的には問題にならない
    • なぜなら呼ばれる関数は描画に関数するもので、1回目が終わればレンダリングが行われ、その後の関数の再呼び出しと再レンダリングは見栄え上何も変わらないためである。

2. 二重ループの中でDOM操作

こちらが問題の本質

  • 画面に表示される表の各セルの文字が見きれないように、それぞれの幅を計算して描画幅を調節している
  • その過程で、行のループの中の列のループでセルごとに処理を実施
  • セルごとに以下の処理を行う
    • spanタグを作成
    • append
    • 幅を計測
    • remove
  • そのため、DOMアクセスが数万回単位の凄まじい回数で行われ、追加/削除も伴うため、DOM再構築も頻繁に行う

解決法

どちらも結構シンプルです

  1. そもそも別のイベントなので、リサイズのイベントを呼ばないようにする
  2. 毎回DOMの生成などをせずに、cssやその他のものを使って幅を計算する
    • 今回は幅の問題でしたが、それ以外でもDOM操作を一括で行う。画面描画分だけを対象にするなど、工夫はいくらでもできると思います。

結果、画面描画に10秒近くかかっていたのが、1秒未満になりました。

某キャラ風にいうならば

今回の件から俺が得るべき教訓は、

DOM国とJavaScript国の橋は狭く輸出入のコストが高いので、エンジニアという監察官が、通して良いものと通さないものをきちんと吟味しないと、両国が損をすることになる。そしてそれは、流通量が増えたときにしか気づかず、そのときにはすでに手遅れになっている。

ということだ

ってことですね。

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