Panda Noir

JavaScript の限界を究めるブログでした。最近はいろんな分野を幅広めに書いてます。

Elixirの内包表記、内包してなくない問題

1から10まで、各数字を2乗して足すプログラムを考えます。

1..10
|> Enum.map(&(&1*&1))
|> Enum.reduce(&(&1+&2))
|> IO.inspect

こうなります。分かりやすいですね。

それが「内包表記」だと以下のように書けます

for i <- 1..10 do
  i*i
end
|> Enum.reduce(&(&1+&2))
|> IO.inspect

(for i <- 1..10, do: i*i)
|> Enum.reduce(&(&1+&2))
|> IO.inspect

どこが「内包」なんじゃい!!

カッコで囲ってないくせに内包ってなんじゃいな!!!

そもそも内包とは?

と、ココまで書いてから「そもそも内包ってなんなんだ?」と思い調べてみました。内包というのは、[]で包まれていることではなく、数学の用語の一つだそうです。

                                  終
                         --------------------
                           制作・著作 NHK

…もうちょっとだけ説明しますね。

数学で集合を定義するとき、「集合に含まれる要素をすべて列挙するやり方」と「集合に含まれる要素が満たすべき条件を定めることで定義するやり方」があります。後者を内包というそうです。

例えば、1から10それぞれの二乗という集合は、Elixirの内包表記ではつぎのようになります。

for i <- 1..10, do: i*i

数学で内包で書くとこのような集合になります。

\{x|i \in \{1,2,\cdots,10\},x=i^2\}

Elixirの内包表記は数式そのまんまだったことがわかりました。まさに「内包」表記です。

Goのコードは読みにくい

基本的に人間はFの字で文を読もうとします。そのため、重要情報は縦で揃えると読みやすくなります。

最悪な点: if文を無視して読むことが出来ない

Goでは以下のような構文が頻出します。

if err := hoge(); err != nil {
}

次の問題をこの構文は抱えています。

  1. if文なのに条件式のところが処理になってしまっている
  2. err != nilerr == nilで意味が真逆になってしまうので、最後まで読まなければ意図がつかめない

通常、if文は飛ばしならが読むことが出来ます。なぜなら、リーダブルにコードを書くと、if文は「ある特定のパターンのときのみ処理する」ように書けるからです。特定パターンのとき以外、if文を読む必要がないということです。

しかし、Goの場合はif内に処理が組み込まれてしまいます。そのため、水平に最後までifを読まなければならず、読むスピードがガクッと下がります。

良い点: 型を後ろに書ける

最悪な点を先に書きましたが、Goの構文にはいいところもあります。最高な点ではないのが残念ですが。

var x float32 = 1
func fuga(n Network) {
}

重要な情報(変数名)が先で、修飾語(型)が後ろに置いてあるので、流れるように読むことが出来ます。

ちょっとフックがあれば知ってるけど、意外と知らないJavaScript技術

JavaScript技術は10年前とは比較にならないほど膨大で緻密になっています。それらはアンテナを張っていなければ見落としてしまいます。今回はそんな技術の一端を紹介します。

紹介する技術たち

  • Web Worker + Service Worker
  • SharedArrayBuffer + Atomics API
  • manifest.json
  • WebRTC
  • WebSocket
  • SIMD.js
  • WebAssembly(wasm), asm.js

それぞれについて簡単に解説をしています。さらに深く知りたい方はおすすめ記事を参照ください。

続きを読む

隣接するマスへの差分の配列をワンライナーで生成する

初めに言っておくと、数式が覚えられないくらい複雑なので実用性はありません。これが簡単な数式だったらまだ使えたのですがね…普通に[[0,1],[1,0],[0,-1],[-1,0]]のように地道に書いたほうが早いです。

用途としては、forループ内で配列を使わないで生成ができるくらいです。

隣接マスまでの差分とは?

(x,y)にいたとして、(x-1,y)(x+1,y+1)のような座標のことです。8近傍は斜めを含み、4近傍は含みません。

ソースコード

console.log([...Array(9)].map((_, i) => [(i/3|0)-1,i%3-1])); // 8近傍(現在のマスを含む)
console.log([...Array(4)].map((_, i) => [(i-2)*(i%2),(i-1)*(1-i%2)])); // 4近傍(現在のマスは含まない)
console.log([...Array(5)].map((_, i) => [(i-2)*(i%2),(i-2)/2|0])); // 4近傍(現在のマスを含む)

以下のような配列が得られます。

[[-1,-1],[-1,0],[-1,1],[0,-1],[0,0],[0,1],[1,-1],[1,0],[1,1]]
[[0,-1],[-1,0],[0,1],[1,0]]
[[0,-1],[-1,0],[0,0],[1,0],[0,1]]

forと組み合わせて使う

for(let i = 0; i < 9; i++)
    const [dx, dy] = [(i/3|0)-1,i%3-1]; // 8近傍(現在のマスを含む)

for(let i = 0; i < 4; i++)
    const [dx, dy] = [(i-2)*(i%2),(i-1)*(1-i%2)]; // 4近傍(現在のマスは含まない)

for(let i = 0; i < 5; i++)
    const [dx, dy] = [(i-2)*(i%2),(i-2)/2|0]; // 4近傍(現在のマスを含む)

実用的なソース

console.log([[-1,-1],[-1,0],[-1,1],[0,-1],[0,0],[0,1],[1,-1],[1,0],[1,1]]);
console.log([[0,-1],[-1,0],[0,1],[1,0]]);
console.log([[0,-1],[-1,0],[0,0],[1,0],[0,1]]);
for(let i = 0; i < 9; i++)
    const [dx, dy] = [[-1,-1],[-1,0],[-1,1],[0,-1],[0,0],[0,1],[1,-1],[1,0],[1,1]][i]; // 8近傍(現在のマスを含む)

for(let i = 0; i < 4; i++)
    const [dx, dy] = [[0,-1],[-1,0],[0,1],[1,0]][i]; // 4近傍(現在のマスは含まない)

for(let i = 0; i < 5; i++)
    const [dx, dy] = [[0,-1],[-1,0],[0,0],[1,0],[0,1]][i]; // 4近傍(現在のマスを含む)

解説

8近傍はみたままなので解説をしません。トリッキーなことをしている残り2つを解説します。

4近傍で得たいもの

4近傍では以下のような配列を得たいです。

[[0,-1],
[-1,0],
[0,1],
[1,0]]

コレを見ると、偶数、奇数のところが0になっていることがわかります。というかそうなるように並べたんですが。

ということは[index%2,1-index%2]としてやればつぎのような配列が得られます。

[[0,1],
[1,0],
[0,1],
[1,0]]

あとはindexが2未満のとき-1、indexが2以上のとき1をかけてやれば完成ですね。ただ、こうなるよう数式をいじるのは意外とめんどくさいので以下で代用しました。

  • iが1のとき-1、iが3のとき1を得られるi-2
  • iが0のとき-1、iが2のとき1を得られるi-1

現在のマスを含むパターン

現在のマスを含むほうは得る順番を若干変えることで数式をカンタンにしています。

[[0,-1],
[-1,0],
[0,0],
[1,0],
[0,1]]

真ん中が[0,1]から[0,0]になっています。こうすると、y座標の差分がカンタンに求めることができるようになります。