noharaのブログ

インフラエンジニアのブログ、ゆる~く

GolangでPythonのrandom.sampleを実現する

最近趣味でPythonしか書いていなかったので、素振りとしてGoを勉強しているのですが

任意のグループからa個選ぶという処理で少し詰まったので忘備として書いておきます。

Pythonのrandom.sampleについて

任意のグループからa個選ぶというもの。

引数として渡されたリストは変更されません。 [1, 2, 3]部分

>>> random.sample([1, 2, 3], 2)
[2, 1]
>>> random.sample([1, 2, 3], 2)
[1, 3]
>>> random.sample([1, 2, 3], 2)
[3, 1]

9.6. random — 擬似乱数を生成する — Python 3.6.5 ドキュメント

golangで実現

こんな感じになると思います。

func randomSample(s []string, num int) []string {
    rand.Seed(time.Now().UnixNano())
    var sample []string
    n := make([]string, len(s))
    copy(n, s)

    for i := 0; i < num; i++ {
        index := rand.Intn(len(n))
        sample = append(sample, n[index])
        n = append(n[:index], n[index+1:]...)
    }
    return sample
}

ポイントとしては、n := make([]string, len(s)) として元のスライスを変更しないようにしています。

go始めたばかりで右も左もよくわかってないのでツッコミあればこっそり教えてください。

package main

import (
    "fmt"
    "math/rand"
    "time"
)

func main() {
    s := []string{"a", "b", "c", "d"}
    fmt.Println(randomSample(s, 2)) // [a b] or [a c] or ...
    fmt.Println(s)                  // [a b c d]
}

func randomSample(s []string, num int) []string {
    rand.Seed(time.Now().UnixNano())
    var sample []string
    n := make([]string, len(s))
    copy(n, s)

    for i := 0; i < num; i++ {
        index := rand.Intn(len(n))
        sample = append(sample, n[index])
        n = append(n[:index], n[index+1:]...)
    }
    return sample
}

Splatoon2のスケジュールをGoogle カレンダーで確認しよう

全国1億人のスプラトゥーンプレイヤーの皆さんこんにちは。

上記ツイートの通りですが、ツイートに貼ってあるikalendar.herokuapp.comの内容をみてもピンとこない人向けにGoogle カレンダーで連携する方法を解説したいと思います。

ikalendar.herokuapp.comではクエリを使って自分に必要な情報に絞ることが可能です。

すべて上げるときりがないので本エントリではいくつかのパターンに分けてイメージが付きやすいように画像付きで紹介します。

Google カレンダーへのカレンダー追加方法

画像のURLを追加から、URLを追加します。

追加するURLは取得したい情報によって変わってくるので後述します。

追加したタイミングでは名前がURLのままなので分かりづらいですが設定からカレンダーの名前は変更できます。 f:id:nohararc:20190127160914p:plain

全ルール取得したい

ikalendar.herokuapp.comの通り、

以下のURLを追加する

https://ikalendar.herokuapp.com/ical/all.ics

f:id:nohararc:20190127153339p:plain

ガチマッチの予定をすべて取得したい

以下のURLを追加する

https://ikalendar.herokuapp.com/ical/all.ics?mode=gachi&title_format=%25%7bgachi%3arule%7d%3a%20%25%7bgachi%3amap1%7d%2c%20%25%7bgachi%3amap2%7d

f:id:nohararc:20190127154104p:plain

ガチマッチ&エリアのスケジュールを取得したい

以下のURLを追加する

https://ikalendar.herokuapp.com/ical/all.ics?mode=gachi&rule=splat_zones&title_format=%25%7bgachi%3arule%7d%3a%20%25%7bgachi%3amap1%7d%2c%20%25%7bgachi%3amap2%7d

f:id:nohararc:20190127155744p:plain

特定のルールのスケジュールのみ取得したい

ガチマッチ・リーグマッチの予定2つのカレンダーを追加し同時に追加する

「ガチマッチ&エリアのスケジュールを取得したい」+ 応用で「リグマ&エリア」を追加してやればよい

「リグマ&エリア」のURLを追加し、「ガチマッチ&エリア」のカレンダーと同時表示する

https://ikalendar.herokuapp.com/ical/all.ics?mode=league&rule=splat_zones&title_format=%25%7bleague%3arule%7d%3a%20%25%7bleague%3amap1%7d%2c%20%25%7bleague%3amap2%7d%0d%0a

f:id:nohararc:20190127160436p:plain

ここで紹介していないパラメータとして、description_formatも指定できます。

詳細はikalendar.herokuapp.comを確認ください。

番外編

LINE版

Vim

github.com

Jupyter notebookでもVim scriptが書きたい!

この記事は、Vim Advent Calendar 2018 その2 1日目の記事です。

Vim その2 Advent Calendar 2018 - Qiita

皆さん、Jupyter notebookでVim scriptが書きたくなる時があると思います。

Jupyter notebookはKernelと呼ばれるものを差し替えることでPython以外も動作させることができます。

というわけで、Vim script kernel作ってみました。

動作確認はWindows10でのみ行っていますが、ほかのOSでも動くと思います。

完成図

f:id:nohararc:20181130234158p:plain

実装

json(connection)ファイル

{"argv":["python","-m","vim-kernel", "-f", "{connection_file}"],
 "display_name":"Vim"
}

python(kernel)ファイル

from ipykernel.kernelbase import Kernel
from subprocess import Popen, STDOUT, PIPE


class Vim(Kernel):
    implementation = 'Vim'
    implementation_version = '0.1'
    language = 'no-op'
    language_version = '0.1'
    language_info = {'name': 'Vim', 'mimetype': 'text/plain'}
    banner = 'Vim script'
    _d = {}
    def do_execute(self, code, silent, store_history=True,
             user_expressions=None, allow_stdin=False):

        with open('prog.vim', 'w') as f:
            f.write("\n".join(code.splitlines()))

        p = Popen(['vim', '-X', '-N', '-u', 'NONE', '-i', 'NONE', '-V1', '-e', '-s', '-S', 'prog.vim', '+qall!'], stdout=PIPE, stderr=STDOUT, shell=True)
        p.wait()
        exitcode = p.returncode
        output = p.communicate()[0].decode()
        self.send_response(self.iopub_socket, 'stream', {'name': 'stdout', 'text': output})
        return {'status': 'ok',
                'execution_count': self.execution_count,
                'payload': [],
                'user_expressions': {},
               }

if __name__ == '__main__':
    from ipykernel.kernelapp import IPKernelApp
    IPKernelApp.launch_instance(kernel_class=Vim)

2018/01/08追記

なんとあのmattnさんが改良版を作ってくださいました。

Vim scriptカーネルのインストール方法も詳細に記載されいているので試したい方はこちらを参照ください

Vim script で機械学習 - Qiita

まとめ

意外と簡単にJupyter kernelを作ることができました。

このkernelがVIm script普及拡大の一助となれば幸いです。

以下参考にしたもの

jupyter kernel作り方

Making kernels for Jupyter — jupyter_client 6.0.0.dev documentation

Vim script

Vimスクリプト基礎文法最速マスター - 永遠に未完成

vimのコマンド

$ vim -X -N -u NONE -i NONE -V1 -e -s -S prog.vim +qall!

wandboxから拝借

Vim歴約10年目のインフラエンジニアが勧める意外と知られていないコマンド3選(インサートモード編)

周りのインフラエンジニアのVim捌きを見て、「こうしたらいいのに...」と思うことが多々あったので意外と知られてであろうコマンドの紹介をする。

書き始めたはいいが、10を超えたところでめんどくさくなったので、今回はインサートモードに絞って3つほど。

ファイル名補完

Ctrl-x, Ctrl-f

f:id:nohararc:20181127002135g:plain

インフラエンジニア各位、これだけは覚えてくれ頼む。

ある程度書く→ Ctrl-x, Ctrl-f → Ctrl-p → 続き入力しながら候補を絞る が便利。

画面キャプチャ難しい><

Ctrl-x系はほかにも便利なものいろいろある(行補完等)あるので興味ある人はヘルプ参照。

https://vim-jp.org/vimdoc-ja/insert.html#compl-filename

式評価レジスタ

Ctrl-=, <任意の式>Enter

夜間作業時など、四則演算ができなくなったときに便利。 別にVimじゃなくてもいい

スマホで計算→PCでミスタイプ のコンボだけはやめてくれ頼む

https://vim-jp.org/vimdoc-ja/insert.html#i_CTRL-R

ちなみに、Ctrl-r, 0でインサートモードにいながらレジスタの内容を貼り付けらる。

vimはモード変えないとペーストさえできないと言っているあなたに。

レジスタのヘルプ

https://vim-jp.org/vimdoc-ja/change.html#registers

カーソルの上/下の行の同じ位置の文字を挿入

Ctrl-yまたはCtrl-e

地味だが強力。よく使う。

https://vim-jp.org/vimdoc-ja/insert.html#i_CTRL-E

gobgp同士でebgp接続

gobgpのバイナリを落としてきてebgp接続するまでの手順

構成図

                      192.168.33.0/24
    AS65001                                AS65002
+----------------+                   +----------------+
|                |      ebgp         |                |
|                |  <------------>   |                |
|                |.21             .22|                |
|                +-------------------+                |
+----------------+                   +----------------+

wgetでバイナリを落とす

両ノードで以下のコマン打つ

$ wget https://github.com/osrg/gobgp/releases/download/v1.33/gobgp_1.33_linux_amd64.tar.gz
$ tar xzf gobgp_1.33_linux_amd64.tar.gz

gobgpd.confを書く

.21側

[global.config]
  as = 65001
  router-id = "192.168.33.21"

[[neighbors]]
  [neighbors.config]
    neighbor-address = "192.168.33.22"
    peer-as = 65002

.22側

[global.config]
  as = 65002
  router-id = "192.168.33.22"

[[neighbors]]
  [neighbors.config]
    neighbor-address = "192.168.33.21"
    peer-as = 65001

gobgpを起動する

両ノードで以下のコマンドを打つ

$  sudo -E ./gobgpd -f gobgpd.conf

動作確認

ネイバーが確立されている

$ ./gobgp neighbor
Peer             AS  Up/Down State       |#Received  Accepted
192.168.33.22 65002 00:03:01 Establ      |        0         0

keepaliveは30秒間隔

sudo tcpdump -i enp0s8 tcp port bgp -n
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on enp0s8, link-type EN10MB (Ethernet), capture size 262144 bytes
07:51:45.797742 IP 192.168.33.21.bgp > 192.168.33.22.57554: Flags [P.], seq 2993027708:2993027727, ack 2208320632, win 114, options [nop,nop,TS val 293637 ecr 6968370], length 19: BGP
07:51:45.798091 IP 192.168.33.22.57554 > 192.168.33.21.bgp: Flags [.], ack 19, win 115, options [nop,nop,TS val 6998159 ecr 293637], length 0
07:51:46.008469 IP 192.168.33.22.57554 > 192.168.33.21.bgp: Flags [P.], seq 1:20, ack 19, win 115, options [nop,nop,TS val 6998369 ecr 293637], length 19: BGP
07:51:46.008552 IP 192.168.33.21.bgp > 192.168.33.22.57554: Flags [.], ack 20, win 114, options [nop,nop,TS val 293848 ecr 6998369], length 0

07:53:15.798364 IP 192.168.33.21.bgp > 192.168.33.22.57554: Flags [P.], seq 57:76, ack 58, win 114, options [nop,nop,TS val 383638 ecr 7058370], length 19: BGP
07:53:15.798969 IP 192.168.33.22.57554 > 192.168.33.21.bgp: Flags [.], ack 76, win 115, options [nop,nop,TS val 7088114 ecr 383638], length 0
07:53:16.053562 IP 192.168.33.22.57554 > 192.168.33.21.bgp: Flags [P.], seq 58:77, ack 76, win 115, options [nop,nop,TS val 7088369 ecr 383638], length 19: BGP
07:53:16.053599 IP 192.168.33.21.bgp > 192.168.33.22.57554: Flags [.], ack 77, win 114, options [nop,nop,TS val 383893 ecr 7088369], length 0

07:52:45.797160 IP 192.168.33.21.bgp > 192.168.33.22.57554: Flags [P.], seq 38:57, ack 39, win 114, options [nop,nop,TS val 353637 ecr 7028370], length 19: BGP
07:52:45.797508 IP 192.168.33.22.57554 > 192.168.33.21.bgp: Flags [.], ack 57, win 115, options [nop,nop,TS val 7058128 ecr 353637], length 0
07:52:46.040236 IP 192.168.33.22.57554 > 192.168.33.21.bgp: Flags [P.], seq 39:58, ack 57, win 115, options [nop,nop,TS val 7058370 ecr 353637], length 19: BGP
07:52:46.040318 IP 192.168.33.21.bgp > 192.168.33.22.57554: Flags [.], ack 58, win 114, options [nop,nop,TS val 353880 ecr 7058370], length 0

動作環境

$ cat /etc/redhat-release
CentOS Linux release 7.1.1503 (Core)
$ ./gobgp --version
gobgp version 1.33

Ansibleの出力を弄る

公式情報 : Callback Plugins

以下のようなことができる

  1. 通常の出力に実行時間などの追加情報をいれる
  2. 通常の出力形式をjson等に変更する
  3. 自分でpluginを作ってやりたい放題する
  4. 出力をslackやmailに流す

以下、1, 2, 3を試す。

1. 通常の出力に実行時間などの追加情報をいれる

実行にかかった時間を出力してみる
ansible.cfgにcallback_whitelist = timerを追加
出力

~snip~
Playbook run took 0 days, 0 hours, 0 minutes, 4 seconds

2. 通常の出力形式をjson等に変更する

出力形式をjsonに変更してみる
ansible.cfgにstdout_callback = jsonを追加 出力

~snip~
"stats": {
        "192.168.33.12": {
            "changed": 0,
            "failures": 0,
            "ok": 2,
            "skipped": 0,
            "unreachable": 0
        }
    }
}

3. 自分でpluginを作ってやりたい放題する

公式ページCallback Plugins を参考にcallback_pluginsというフォルダを作り、その中にpythonでコードを記述していきます。

書き方等はドキュメントを探すよりも実際のコードを見たほうが早いと思います。

今回は、playbookを実行時にplaybook名を出力してみます。

ansible.cftにcallback_whitelist = save2fileを追記の上、以下のファイルを作成する

callback_plugins/save2file.py

import os

from ansible.module_utils.urls import open_url
from ansible.plugins.callback import CallbackBase


class CallbackModule(CallbackBase):
    CALLBACK_VERSION = 2.0
    CALLBACK_TYPE = 'notification'
    CALLBACK_NAME = 'save2file'
    CALLBACK_NEEDS_WHITELIST = True

    def __init__(self, display=None):

        super(CallbackModule, self).__init__(display=display)

        self.playbook_name = None

    def v2_playbook_on_start(self, playbook):
        self.playbook_name = os.path.basename(playbook._file_name)
        print(self.playbook_name)

出力

host1.yml  ← playbook名

PLAY [host1] *******************************************************************

TASK [Gathering Facts] *********************************************************
~snip~

期待通りの出力になっています。
v2_playbook_on_startのほかにも関数はあるので既存のコードを参考にいろいろ試してみると面白いかもしれません。

jinja2で繰り返しのテキストを生成する

flask, ansibleでよくjinja2使っているのですが、単体で使ったことがなかったのでメモ

繰り返しのテキストを生成する2パターンを試した

テンプレートファイルをインポートして表示するパターン

ググったらよく出てくるパターン、説明不要

ソースコード

import jinja2
from jinja2 import Template, Environment, FileSystemLoader

env = Environment(loader=FileSystemLoader('./', encoding='utf8'))
tmpl = env.get_template('temp.txt')

weapons = ["スシ", "プラコラ"]
print(tmpl.render(weapons=weapons))

・テンプレートファイル(temp.txt)

{% for w in weapons %}
{
  "type": "message",
  "message": {
    "type": "text",
    "text": "{{ w }}"
  }
},{% endfor %}

・出力

{
  "type": "message",
  "message": {
    "type": "text",
    "text": "スシ" 
  }
},
{
  "type": "message",
  "message": {
    "type": "text",
    "text": "プラコラ"
  }
},

テンプレートをソースコード内で生成する方法

わからなかったので調べた。
jinja2公式によるとfrom_stringというものがあるらしい。

ソースコード

import jinja2
from jinja2 import Template, Environment

temp = """
{% for w in weapons %}
{
  "type": "message",
  "message": {
    "type": "text",
    "text": "{{ w }}"
  }
},{% endfor %}
"""
weapons = ["スシ", "プラコラ"]
tmpl = Environment().from_string(temp)
print(tmpl.render(weapons=weapons))

・出力
1つめのパターンと同じなので省略