Panda Noir

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

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

初めに言っておくと、数式が覚えられないくらい複雑なので実用性はありません。これが簡単な数式だったらまだ使えたのですがね…普通に[[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座標の差分がカンタンに求めることができるようになります。

graphql-go入門

graphql-goの入門記事が全く出てこなくて苦しみ悶えながらようやく理解できたので、ここに戦いの記録を残しておこうと思います。

今回の環境

今回はMongoDB+GraphQLという構成にしました。

成果物をここにまとめましたので、参照ください。 github.com

GraphQLとは?

そもそもGraphQLとはナニカ、ですがGraphQLはクエリ言語で、APIサーバーで使われます。

たとえばつぎのようなリクエストを送ってみます。

query {
    shop(name: "ラビットハウス") {
        name
        members {
            name
        }
    }
}

すると以下のようなデータが返ってきます。

{
    "data": {
        "shop": {
            "name": "ラビットハウス",
            "members": [
                {"age":16,"name":"保登心愛"},
                {"age":14,"name":"香風智乃"},
                {"age":17,"name":"天々座理世"}
            ]}
    }
}
続きを読む

Nodeで外部のwebサーバーを建ててkillするまで

Goで書いたwebサーバーを、Nodeでテストをしています(地獄)。テストする上で、Nodeのchild_processモジュールが孫プロセスをうまくkillできずに躓きました。そこで、孫プロセスをゴリ押しでkillする方法を調べて実装してみました。

孫プロセスのPIDを取得する

孫プロセスのPIDさえ取得できれば、killすることはとてもカンタンにできます。イメージはこんな感じです。

process.kill(mago_pid); // PIDがmago_pidのプロセスをkill

孫プロセスのPIDの取得は、親子共々殺したい - Qiitaを参考に書いてみます。

const child_process = require('child_process');
const server = child_process.spawn('go', ['run', `server.go`]);
// サーバーのPIDはserver.pidで取得できる
child_process.exec(`ps --ppid=${server.pid} | cut -d' ' -f1`, (error, stdout, stderr) => {
    if (error)
        throw new Error(error);
    // 孫プロセスのPID値を取得できた!
    console.log(stdout, stderr);
});

孫プロセスをkillする

あとは取得したPIDを使ってkillすれば完成です。

const child_process = require('child_process');
const server = child_process.spawn('go', ['run', `server.go`]);

(async () => {
    // サーバーが立ち上がるまで待つ
    await new Promise(resolve => {
        server.stdout.on('data', data => {
            if (data.toString() === 'Start server\nListening port 12345...\n')
                resolve();
        });
    });
    console.log('server started');

    // サーバーのPIDはserver.pidで取得できる
    const {stdout, stderr} = await new Promise((resolve, reject) => {
        child_process.exec(`ps --ppid=${server.pid}`, (error, stdout, stderr) => {
            if (error)
                reject(error);
            resolve({stdout, stderr});
        })
    });
    for (const line of stdout.split('\n').slice(1)) {
        const pid = line.trim().split(' ')[0];
        if (pid === '')
            continue;
        process.kill(pid, 'SIGINT');
    }
    console.log('finish server');
})();

番外編: util.promisifyを使ってもっと見やすく書く

util.promisifyはべんりなのでドンドン使いましょう

const util = require('util');
const child_process = require('child_process'), exec = util.promisify(child_process.exec).bind(util);
const server = child_process.spawn('go', ['run', `server.go`]);

(async () => {
    // サーバーが立ち上がるまで待つ
    await new Promise(resolve => {
        server.stdout.on('data', data => {
            if (data.toString() === 'Start server\nListening port 12345...\n')
                resolve();
        });
    });
    console.log('server started');

    // サーバーのPIDはserver.pidで取得できる
    const {stdout, stderr} = await exec(`ps --ppid=${server.pid}`);

    for (const line of stdout.split('\n').slice(1)) {
        const pid = line.trim().split(' ')[0];
        if (pid === '')
            continue;
        process.kill(pid, 'SIGINT');
    }
    console.log('finish server');
})();

ネットワークでよく出てくる「コネクション」ってなんなんだろう?

ネットワークにおいて「コネクション」という単語はよく耳にします。

  • 「WebSocketはコネクションを確立後、ずっとそのコネクションを使う」
  • 「ロングポーリングは返すたびにクライアントがコネクションを確立し直す」
  • 「TCPでは3ウェイハンドシェイクでコネクションを確立する」

これらを見ると「コネクションは通信経路のことなのかな?」と勘違いしそうになります。実際僕はそう勘違いしていました。今回はそんな実態のよくわからない「コネクション」について解説します。

続きを読む

どんな深さの配列も1次元配列にする

ES2019でArray#flatMapが導入されたので、それを使ってどんな深さの配列でも1次元にする方法を紹介します。

flatMapの仕様

flatMapはmap()してからflat()するメソッドです。

[1,2,3].map(n => [n,-n]); // [[1,-1],[2,-2],[3,-3]]
[[1,-1],[2,-2],[3,-3]].flat();  // [1,-1,2,-2,3,-3]

[1,2,3].flatMap(n => [n,-n]); // [1,-1,2,-2,3,-3]

flatMapを使って1次元にする

コード

flatMapを使って、どんな深さの配列でも1次元に落とし込みます。これは「無限回flat()を行い、1次元になった時点でやめる」のと等価なので、flatInfと名付けました。

const arr = [1, [2, [3, 4, [5]]]];

const flatInf = item => {
    if (Array.isArray(item))
        return item.flatMap(flatInf);
    return item;
};

console.log(flatInf(arr)); // [1, 2, 3, 4, 5]

flatInfの動作

  1. 配列の各要素を「1次元の配列あるいは要素」にする
  2. できた配列をflat()する

これだけです。配列の要素が1次元の配列か要素のみなら、flat()するだけで1次元になります。

// 1次元の配列か要素のみの配列
[[1, 2], 3, [4, 5, 6], 7, 8, 9].flat(); // [1, 2, 3, 4, 5, 6, 7, 8, 9]

与えられる配列の各要素の深さは1次元とは限りません。コレを1次元にするために、再帰的にflatInfを適用しています。

おまけ: コードを圧縮する

そこまで変わりませんが。

const arr = [1, [2, [3, 4, [5]]]];

const flatInf=a=>Array.isArray(a)?a.flatMap(flatInf):a;

console.log(flatInf(arr)); // [1, 2, 3, 4, 5]