Shiopon Labo

しおぽん(@shiopon01)の個人ブログです

AtCoderことはじめ(ローカルの動作環境構築から問題を提出するまで)

この記事の目標は、ローカルで問題のテストをして、簡単にAtCoderに提出できる環境を構築することです。(言語は問いません、おそらく…。)

ローカルの動作環境を構築する

次のコマンドラインツールをインストールし、設定します。

npm install -g atcoder-cli
pip3 install --user online-judge-tools

atcoder-cli(以下acc)とonline-judge-tools(以下oj)を連携しておきます。連携することで、URLを指定せずにコードの提出などが可能になるようです。

acc check-oj

両方のツールでAtCoderにログインしておきます。

$ acc login
? username: shiopon01
? password: [hidden]
OK
$ oj login https://atcoder.jp/
[x] service recognized: AtCoderService.from_url('https://atcoder.jp/'): https://beta.atcoder.jp/
...
Username: shiopon01
Password: 
[x] POST: https://atcoder.jp/login
[x] redirected: https://atcoder.jp/home
[x] 200 OK
...
[x] save cookie to: /home/user/.local/share/online-judge-tools/cookie.jar

accが作成するサンプルケースのディレクトリ名を test に変更します。(デフォルトは tests となっていて、このままではojのテストが実行できないので test に変更しています。)

$ acc config default-test-dirname-format test

設定はこれで終わりです。

コンテスト用のディレクトリを作成する

ディレクトリの作成にはAtCoderのコンテストの識別子(abc081 など)が使用されます。今回は、ABC 081用のディレクトリを作成してみます。(次の実行結果のように、newした際にダウンロードする問題の選択が必要ですが、acc config default-task-choice all を設定しておくことで全問題を勝手にダウンロードすることもできます。)

$ acc new abc081
abc081/contest.acc.json created.
create project of AtCoder Beginner Contest 081
? select tasks
❯◉ A Placing Marbles
 ◯ B Shift only
 ◯ C Not so Diverse
 ◯ D Non-decreasing

atcoder.jp

作成されたディレクトリを見ると、問題ごとにテストケースの入力/出力がダウンロードされていることが確認できます。contest.acc.json にはコンテストのURLなどが保存されています。

$ tree abc081
abc081
├── contest.acc.json
├── a
│   └── test
│       ├── sample-1.in
│       ├── sample-1.out
│       ├── sample-2.in
│       └── sample-2.out
├── b
│   └── test
│       ├── sample-1.in
│       ...
│       └── sample-3.out
├── c
│   └── test
│       ├── sample-1.in
│       ...
│       └── sample-3.out
└── d
    └── test
        ├── sample-1.in
        ...
        └── sample-3.out

8 directories, 23 files

提出するコードはこの ab ディレクトリの直下に作成します。ここではbの直下にmain.goを作成しています。

$ cd abc081/b
$ touch main.go

練習問題を解いてテストし、提出する

ABC 081 Bを例に、先に作成したファイルでテストを実行してみます。テストにはojを使い、 -c オプションで実行するコマンドを指定しています。(オプションが無いと ./a.out に対してテストが実行されるようです。)

$ oj t -c "go run main.go"
[*] 3 cases found

[*] sample-1
package main: 
main.go:1:1: expected 'package', found 'EOF'
[x] time: 0.004811 sec
[-] RE: return code 1
[-] WA
output:

expected:
2

...
[x] slowest: 0.005508 sec  (for sample-2)
[x] max memory: 9.936000 MB  (for sample-2)
[-] test failed: 0 AC / 3 cases

空だった main.go に問題が解けそうなコードを書いて、もう一度テストを実行してみます。例えばこんなやつを。

gist.github.com

テストの実行↓

% oj t -c "go run main.go"
[*] 3 cases found

[*] sample-1
[x] time: 0.274811 sec
[!] WA if no rstrip
[+] AC

...
[x] slowest: 0.333075 sec  (for sample-2)
[x] max memory: 46.388000 MB  (for sample-3)
[+] test success: 3 cases

ローカルでのテストが通ったので、問題のディレクトリでそのまま acc submit を打ってAtCoderに提出します。提出先の問題は勝手に判断してくれるようです。

$ acc submit main.go
submit to: https://atcoder.jp/contests/abc081/tasks/abc081_b
[x] problem recognized: AtCoderProblem.from_url('https://atcoder.jp/contests/abc081/tasks/abc081_b'): https://atcoder.jp/contests/abc081/tasks/abc081_b
[*] code (362 byte):
...
[x] POST: https://atcoder.jp/contests/abc081/submit
[x] redirected: https://atcoder.jp/contests/abc081/submissions/me
[x] 200 OK
[+] success: result: https://atcoder.jp/contests/abc081/submissions/9533340
...

success と出たら提出成功で、AtCoderの提出ページが勝手にブラウザで立ち上がります。

f:id:shiopon01:20200116171742p:plain

その他

atcoder-cliにはこの他にもテンプレート機能やタスク機能などあるので、用途に合わせて拡張してもらえれば。設定ファイルの場所は acc config-dir で確認できます。

www.npmjs.com

ブログが長続きしないのは、環境のせいかもしれない(言い訳)

言い訳したい記事。

ブログサービス、沢山あるがどれも微妙問題

いろいろブログサービスがある中で、最も記事ページのUIが好きなのはMediumだった。近いUIのnoteも良い。でもMarkdown記法に対応していないサービスで記事を書く気にはなれなかった。そも最近のMediumは、未ログインユーザーにログインを促すモーダルを出してきたり、ログイン済で記事を多く見ている人に課金を強要してきたりと、ちょっと強引な部分が目立つようになってきたのも微妙。読者としてもけっこう面倒くさいサービスになりつつある。

ブログサービスとして一番良いと感じるのはやはりはてなブログで、必要な機能は全て揃ってるしMarkdown記法もサポートしている優秀さ。新しいブログサービスが出るたびブログを作っては爆破して結局はてなブログに帰ってきているように、自分にとってはてなブログは実質的案リスポーン地点となりつつある。ただ、ここまでお世話になっていながらもブログにランニングコストかけるのもなあ……という気持ちもあり、なかなかProには踏み出せず。

Scrapboxは気持ちよくメモを取れるが、ブログサービスではないので除外)

scrapbox.io

自分で作らざるを得ない…

GitHub Pagesでホストする形で、Markdown記法で書けるブログをちょっと前から雑に作りはじめている。まだ未完成。これを作るため既に、はてブProに課金する以上のコストが掛かっている気もするけど気にしない…。(結局、飽きてはてブProに課金する未来が見える。)

blog.shiopon.net

ファビコンやロゴ、OGPの設定もまだ雑。

Jekyllとかを使っても良かったんだけど、自分の好きなように設定したかったので結局Next.jsで作り、デザインもMaterial-UIで一から地道に作っている。 リポジトリにプッシュしたらCircle CI(GitHub Pagesに移行したい)でプロジェクトがビルドされ、生成された静的ファイルをgh-pagesブランチにプッシュする形。記事は /articles 以下のmdファイルを解釈するので、記事の追加・更新ではこのあたりを触る。ただ、ファイルベースで記事を管理すると、記事内への画像の貼り付けが非常に面倒くさい問題が…。

github.com

Shiopon Labo(このブログ)と一緒に、まだ作成途中のShiopon Blogもよろしくお願いいたします。

2020年の抱負など

明けましておめでとうございます。

今年のお正月も例のごとく、本を読んだり映画を観たりしてだらだら過ごしていました。社会人になってから1年がほんとうに早くて、もう2018年の記憶も残っていません。今年に入ってからは抱負を考えるのと同時に、散らかっていた身の回りのサービスの整理を始めました。この記事では今年の抱負とそのあたりにも言述できれば。

今年の抱負

巷では目標を100個考えるのが流行っているみたいなので、自分も100個考えている途中です。
今現在はまだ50個に満たないですが、ひとまずその中からいくつかピックアップして紹介。

  • 積み本の消化
  • 日常的に運動して減量(リングフィットアドベンチャーをクリアする!)
  • 日常的に片付けして家をキレイに保つ
  • 旅行する(沖縄とか国外とか)
  • 英語の勉強を真面目に始める
  • ビジネスカジュアルな服装を身につける
  • できるだけ、週に1回はブログを更新する
  • 煮物料理がんばる
  • 電子キーボードの特訓を再開する
  • 家計簿アプリをちゃんとつける
  • AWSの資格取得
  • 貯金頑張る
  • あわよくば車の免許を取る

今年こそ…ブログを…継続……

最近使っているサービス

AmazonプライムAmazon Mastercardゴールドで。

Evernoteはなんだかんだずっと使ってます。移行先は常に探してて、最近だとScrapboxが超良かったけどスマホからのお手軽さに欠けているような気もして…。いろいろ触ったけど結局Evernoteに帰ってしまうので、今年はもう浮気せずEvernoteで頑張っていこうという気持ち。(あとWebクリップが便利)

未読記事とかを放り込むPocketはよく使ってますが、課金しなくても一切不便ないので未課金。一時期PocketをやめてInstapaperを使っていましたが、Instapaperの全部フォルダで管理する形が気に入らなくて結局Pocketに帰ってきました。はてブの記事とか、Twitterで流れてきた気になる記事とかが沢山放り込まれます。

あと去年4月くらいから、フリーランスでもないしだれに共有するわけでもないけどToggleで労働時間を管理しています。そろそろ労働時間以外の違う使い方もしていきたいな…。

今年から始めたサービス

2020年1月1日以降に読んだ本を管理するのに読書メーターを使い始めました。

bookmeter.com

似たようなサービスで、観た映画を管理するのにFilmarksを使い始めました。

filmarks.com

今年もよろしくおねがいします!

Minikubeを無料枠のAWS EC2 t2.microで実行しよう

Kubernetesクラウドサービスで扱おうとするとどうしても課金が発生してしまうので、無料枠の t2.micro で実行できないかと思ってやってみた記事。つまり、 Minikube を AWS EC2 t2.micro で実行する記事 です。

EC2 t2.micro

今回使用するインスタンスのスペックは次の通り…

  • AMI:Ubuntu Server 18.04 LTS (HVM), SSD Volume Type - ami-09c81ecf1c2b2ef70
  • インスタンスタイプ:t2.micro
  • ストレージ:汎用SSD(gp2) 30GiB
  • vCPU:1
  • Mem:1GiB

※ コンソールでの作業時、もしsudoで sudo: unable to resolve host ip- ... みたいな文言が出力される場合は、 /etc/hosts にホスト名を追記すれば消せます。

$ sudo sh -c 'echo 127.0.1.1 $(hostname) >> /etc/hosts'

インストール作業

Docker

Kubernetesはホスト上で実行するので、 Docker をインストールしておきます。

sudo apt-get update
sudo apt-get install \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg-agent \
    software-properties-common
$ docker -v
Docker version 17.03.2-ce, build f5ec1e2

Get Docker CE for Ubuntu | Docker Documentation

kubectl

Kubernetesを操作するための kubectl をインストールしておきます。

sudo apt-get update && sudo apt-get install -y apt-transport-https
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
echo "deb https://apt.kubernetes.io/ kubernetes-xenial main" | sudo tee -a /etc/apt/sources.list.d/kubernetes.list
sudo apt-get update
sudo apt-get install -y kubectl
$ sudo kubectl version
Client Version: version.Info{ ,,,

https://kubernetes.io/docs/tasks/tools/install-kubectl/#install-kubectl-on-linux

Minikube

Minikube をインストールします。(コマンドのバイナリをダウンロードするだけです)

curl -Lo minikube https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64 && chmod +x minikube && sudo cp minikube /usr/local/bin/ && rm minikube
$ minikube version
minikube version: v1.1.1

Install Minikube - Kubernetes

Minikubeの実行

管理者になって、 minikube start からMinikubeを起動します。Kubernetesはホスト上で実行するためVMドライバーは不要で、オプションでは none になっています。

$ sudo -i
# minikube start --vm-driver=none

コマンドの実行後、1回目の起動はCPU数が問題で apiserver が起動しないかもしれません。

しかし諦めずに2回目の起動を行うと(何故か)起動できるので、2回目の実行を行ってムリヤリMinikubeを起動してあげてください。

# minikube start --vm-driver=none
...
[ERROR NumCPU]: the number of available CPUs 1 is less than the required 2
...
Sorry that minikube crashed.

# minikube start --vm-driver=none
...
Done! kubectl is now configured to use "minikube"

Minikubeが動作すると minikube statuskubectl から状態を確認することが出来ます。

# minikube status
host: Running
kubelet: Running
apiserver: Running
kubectl: Correctly Configured: pointing to minikube-vm at ...

# kubectl get nodes
NAME       STATUS   ROLES    AGE    VERSION
minikube   Ready    <none>   150m   v1.14.3

おっと、この時点でもうメモリが悲鳴を上げている気が…。

f:id:shiopon01:20190621151705p:plain
htopの結果

いろいろ動かしてみよう

ひとまずMinikubeが動いたので、その上でいろいろ動かしてみましょう。

EchoServer

動作確認を兼ねて、 gcr.io/google_containers/echoserver を使って簡易のWebサーバーを立ち上げてみます。

kubectl run hello-minikube --image=gcr.io/google_containers/echoserver:1.4 --port=8080
kubectl expose deployment hello-minikube --type=NodePort

ここで作成したサービスのポートにcurlでアクセスすると情報が返ってきます。セキュリティグループでこのポートを開放してあげれば http://IPv4パブリックIP:ポート でブラウザからもアクセスできますね。

# kubectl get svc
NAME             TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)          AGE
hello-minikube   NodePort    10.108.62.7   <none>        8080:30732/TCP   24h

# curl localhost:30732
CLIENT VALUES:
client_address= ...

EchoServerの削除は次のコマンドで行えます。

kubectl delete services hello-minikube
kubectl delete deployment hello-minikube

Jenkins

次はEchoServerよりちょっとだけ大きな Jenkins を動かしてみましょうか。

とりあえずボリュームの共有とかデータの保存とかは考えず、動作の確認をしてみましょう。JenkinsのReplicationControllerとServiceを起動するために次のYAMLテンプレートを用意しました。

kubernetes-jenkins.yaml · GitHub

applyします。

# kubectl apply -f kubernetes-jenkins.yaml
replicationcontroller/jenkins created
service/jenkins-svc created

お、ちゃんと動いているらしい…。

# kubectl get pods,svc
NAME                READY   STATUS    RESTARTS   AGE
pod/jenkins-hnz4k   1/1     Running   0          27s

NAME                  TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
service/jenkins-svc   NodePort    10.105.128.151   <none>        8080:30080/TCP   27s

でもメモリは辛そう…。

f:id:shiopon01:20190621153735p:plain
htopの結果2

※ Jenkinsを2分くらい起動していると apiserver が死にます。(kubectlが使えない!!)

# # kubectl get pods,svc
Unable to connect to the server: net/http: TLS handshake timeout

# minikube status
host: Running
kubelet: Running
apiserver: Stopped

こうなるともう minikube stop しか…。

apiserverは死にましたが他は生きてるので、一応Jenkinsにはアクセスできます。セキュリティグループでTCP 30080を開放すれば http://IPv4パブリックIP:30080 でブラウザからアクセスも可能。(性能は足りてないので運用はできないと思いますが…)

f:id:shiopon01:20190621154501p:plain
Jenkins

結果

MinikubeはEC2のt2.microでも一応動きますが、使うとしても定期タスクの実行くらいでしか使えなさそうです。Kubernetesの操作を学ぶくらいには良さそう。

GitLabとかJenkinsみたいなWebアプリの運用は性能がショボいので厳しいですね。

Erlang/Elixir のプロセスを5歳児でも分かるくらい丁寧に説明する記事(draft)

Erlang を始めて最初によく分からないと感じたのはあの癖の強い構文でしたが、一番良くわからなかったのは Erlang の目玉機能と言っても良い並列指向プログラミングをかじるようになった辺りでした。

突然現れた Erlang プロセス、メッセージパッシング、エラー処理、etc。この記事では、自分より後に Erlang を学び始めた人が少しでもこれら並列実行を構成する要素に惑わされないようにいくつかの要素を丁寧に説明していきたいと思います。

実行環境

まず大前提ですが、 Erlangコンパイル言語 です。

コンパイルされたソースコード中間言語.beam)に変換され、Erlang の実行環境 BEAMErlang Virtual Machine)で動作します。BEAM 自体は C 言語で作成されていて、 GitHub で公開されている ので誰でもソースコードを覗くことができます。(蛇足ですが、BEAM は Java で言うところの JVMRuby で言うところの YARV です)

さて、Erlang が注目されている点として、Erlang は並列指向プログラミングが可能な言語であるという点が挙げられます。この部分を理解しようと思うなら、BEAM がどうやって生成された中間言語を実行しているかという点を探ることが近道でしょう。

BEAM の仕事は単純にコンパイルされた中間言語を読み解いて実行することですが、Erlang の並列性を実現するため実行にはいくつかの要素を取り入れています。例えば、プロセス、メールボックス、スケジューリングなど。そして、この要素のいくつかを丁寧に説明することがこの記事の本題です。

Erlang プロセス

Erlang の並列性を実現するための要素としてまず現れるのは Erlang プロセス です。

BEAM は並列性を実現するため、Erlang を実行する仕組みに OS のプロセスと同じような仕組みを取り入れています。概念的には、BEAM という OS の上で動作する Erlang プロセスという認識で間違いはありません。Erlang プロセスは OS のプロセスのようにメモリやストレージまで完全に分離されています。(Golang の goroutine に近いものがあると感じています)

ただこの Erlang プロセス、OS のプロセスほど重くも複雑でもありません。実際の所、BEAM の中での Erlang プロセスの実体は process という名前の C の小さな構造体です。大変軽量なため、ErlangRuby で1つのオブジェクトを作るくらいの感覚でプロセスを生成することができます。そして多数生成したプロセスがプロセス間通信を行いながらプログラムが実行される部分が Erlang の醍醐味でもあります。

Erlang プロセスには次のようなデータが格納されています。

  • プロセス ID
  • ステート(状態保存用)
  • メールボックス(Queue)
  • 関数(メッセージリスナー)
  • etc

完全に分離されているので外部からステートの情報を操作することは出来ません。また、プロセス ID は分離されたプロセス同士がプロセス間通信を行うための住所のようなものです。

例えば、Erlang には erl で起動できる対話形式で実行できる Erlang エミュレーターがありますが、このエミュレーター自体も実体は BEAM で動作するプロセスの1つです。実行中プロセスの ID はビルドイン関数 erlang:self/0 で確認することができます。

1> self().
<0.79.0>

(この <0.79.0> みたいなのがプロセス ID です)

他の要素であるメールボックスやメッセージリスナーなどの機能はプロセス間通信を行うための仕組みであり、各プロセスに実装されているものです。

さて、ここまででもう Erlang が BEAM のプロセスを駆使して実行されていることと、Erlang プロセスがどういうものなのかを少しは分かってもらえたと思います。次からはプロセス同士が通信するための仕組みを見ていきましょう。

補足:ステート

プロセスには状態管理のために確保された専用ストレージ(ステート)が存在しています。 そのプロセスによってのみ変更可能で、基本的には関数とやり取りを行って状態を変化させていきます。

プロセスを生成するために BEAM はヒープ領域を確保しますが、初期のヒープ領域が小さめに設定されている理由は何十万ものプロセスを同時に実行するためだとかなんとか。

補足:メールボックス

他プロセスから自プロセスに送信されたメッセージを保存するためのキューを メールボックス と呼びます。プロセス間通信の基本は、送信側が相手のメールボックスにメッセージを push し、受信側が自分のメールボックスからメッセージを pop することにあります。

(途中)