mitsu's techlog

Customer Reliability Engineerになりました。備忘録も兼ねて技術ネタを適当に。技術以外はこっち→http://mitsu9.hatenablog.com/

「Webを支える技術」を読んだ(/・ω・)/

「Webを支える技術 -HTTP、URI、HTML、そしてREST-」を読んだ(/・ω・)/

これまでWebサービスを使ったり、実装したりとしてきたけど、改めて体系的にWebについて勉強するといろいろ良かった。 就職前に読んでおいて良かったなーと思った。 Webサービス作り始めの頃とかに読んでおきたかった。

各章で書いてあったことと感じたことをメモしておく。 (自分のメモ書きなのでこれを読めば本一冊読んだことになる的なやつではありません)

1部

1章 Webとは何か

Webっていろんなところで使われてるねって話。

2章 Webの歴史

  • Web以前のインターネット・ハイパーメディアの紹介・問題点
  • Webの登場と標準化の歴史

Web以前のインターネットとか知らないことが多くておもしろかった。

3章 REST - Webのアーキテクチャスタイル -

  • REST = アーキテクチャスタイル
    • クライアント/サーバ
    • ステートレスサーバ
    • キャッシュ
    • 統一インターフェース
    • 階層化システム
    • コードオンデマンド

Web世界の秩序を守るためにもRESTfulなサービスやAPIを作ろう。

2部 URI

4章 URIの仕様

  • URI = Uniform Resource Identifier
    • URIスキーム
    • ユーザ情報
    • ホスト名
    • ポート番号
    • パス
    • クエリパラメータ
    • URIフラグメント
  • 絶対URIと相対URI
    • 相対URIを使うときはベースURIが必要
    • ベースURIはHTMLので指定できる
  • URIで使用できない文字を使うときはUTF-8で%エンコーディングする

相対URIとかは気にしたことなかったので勉強になった。

5章 URIの設計

  • 良いURI = 変わらないURI
  • URIが変わるときはリダイレクトする
  • URIの重要性
    • URIはリソースの名前
    • URIは寿命が長い
    • URIはブラウザがアドレス欄に表示する

リンク先に飛んだら404とか悲しい気持ちになるし、Web的にも良くないよね。 良いURIをつけられるようになろう。

3部HTTP

6章 HTTPの基本

  • TCP/IP上で動作するアプリケーション層のプロトコル
  • HTTP 1.1が今広く使われている
  • リクエストメッセージ
    • リクエストライン
    • ヘッダ
    • ボディ
  • レスポンスメッセージ
    • ステータスライン
    • ヘッダ
    • ボディ
  • ステートレス
    • 利点:サーバはアプリケーション状態を覚える必要がない→シンプル、スケーラブル
    • 欠点:パフォーマンスの低下、通信エラーへの対処

HTTPは凄いシンプルなプロトコル。だからこそここまで広く使われているんだろうなーと思った。 一方でシンプルだからこその欠点もあるのでそれは知っておく必要があるしいい勉強になった。

7章 HTTPメソッド

  • 8つのメソッド
    • GET、POST、PUT、DELETE、HEAD、OPTIONS、TRACE、CONNECT
  • POSTとPUTの使い分け
    • POST:クライアントはリソースのURIを指定できない
    • PUT:リソースのURIはクライアントが決める
  • べき等性と安全性
    • べき等 = ある操作を何回行っても結果が同じ
    • 安全 = 操作対象のリソースの状態を変化させない

べき等と安全は意識しないと、使い勝手の悪いサービスを作ってしまうなーと感じた。

8章 ステータスコード

良くみるステータスコードは勝手に覚えてるけど、それ以外のものはいつかゆっくり見てみたい。

9章 HTTPヘッダ

  • ヘッダにのせられる情報
    • 日時
    • MIMEメディアタイプ
    • 言語タグ
    • コンテントネゴシエーション
    • Content-Length、チャンク転送
    • 認証
    • キャッシュ
    • 持続的接続

HTTPがシンプルに設計され、かつ広く使われているのはこのヘッダが大きな役割をしてるなと感じた。 上手く使いこなせるようにまずはどんなヘッダがあるかを知らないとなーと。

4部 ハイパーメディアフォーマット

10章 HTML

  • HTML = Hypertext Markup Language
  • XML名前空間
    • 複数のXMLフォーマットを組み合わせる時に名前の衝突を防ぐ

HTMLの本当の基本しか書かれていなかったので知ってることが多かった。 XML名前空間の話とかは見たことあったけどよくわかってなかったので勉強になった。

11章 microformats

  • セマンティックWeb
    • セマンティクス = 意味論
    • リソースが持つ意味を確定させる
    • 人間が読んで解釈するだけでなくプログラムも解釈できるようにする
  • 標準化済みのmicroformats(一部)
    • hCalendar:イベント情報
    • hCard:プロフィール情報
    • rel-lisence:ライセンス情報

イベントとかよく使うものは標準化しておくとスクレイピングなどするときに統一感があって楽になるって感じなのかな。 標準化されててもそれを使うか独自実装するかは個々人の勝手やけど、標準化されたものに従えばWeb世界は整理された綺麗なところになる。 ここらへんは知ってるか知らないかで大きく変わるからいろいろ知っておきたい。

12章 Atom

  • Atomの目的 = RSSの標準仕様策定
  • AtomRSS(ブログ)だけでなく検索エンジンや写真管理など様々使える
  • Atomのリソースモデル
    • メンバリソース
      • エントリリソース
        • メタデータ:ID、タイトル、著者、更新日時が必須。他には概要説明やカテゴリなど。
        • 内容:text、htmlなどを入れられる
      • メディアリソース
    • コレクションリソース(フィード)
      • メタデータ:ID、タイトル、著者、更新日時が必須
      • フィード独自のメタデータ:サブタイトル、生成プログラム、アイコン、ロゴ
  • Atomの拡張

RSSとかでAtomって文字は見たことあったけど、今回初めてきちんと勉強した。 拡張もいろいろされていて、広く使われているんだろうなーという印象。 便利やとは思うけどどういう時に適しているかまではわからなかったので、一回自分で書くなりして触ってみたいなと感じた。

13章 Atom Publishing Protocol

AtomのエントリーをGETとかPOSTとかできるよーぐらいの理解しかしてない。 ブログサービスとかはこういう風にして設計・実装されてるのかなーとか思った。

14章 JSON

JSONAPI叩く時に良く見てたし便利だよねって感じ。

5部 Webサービスの設計

15章 読み取り専用のWebサービスの設計

  • リソース設計
  • リソース指向アーキテクチャ
    1. Webサービスで提供するデータの特定
    2. データをリソースに分ける
    3. リソースにURIで名前をつける
    4. クライアントに提供するリソースの表現を設計する
    5. リンクとフォームを利用してリソース同士を結びつける
    6. イベントの標準的なコースを検討する
    7. エラーについて検討する

郵便番号検索サービスの例をあげて説明されてたのでイメージしやすくわかりやすかった。 設計に関しては場数踏むしかないのかなとは思ってるので、本に書かれていたことを意識しながらいろいろ作っていきたいなと思った。

16章 書き込み可能なWebサービスの設計

バッチ処理トランザクション排他制御まで考えて実装したことがなかったので、大規模なサービスとかエラーが許されないサービスとかだといろいろ考えないといけないんだなと感じた。 また、これらを実現するために特別なプロトコルなどは利用しておらずHTTPのみで実現されているのは凄いなと思った。 ここらへんはある程度手法が確立されていてデザインパターンみたいになっているのかなと思うので、そこらへんも勉強していかなきゃなーと感じた。

17章 リソースの設計

設計手法は様々知っていると適切なカードが切れて将来便利かもしれないと思った。

AWSでマイクラ鯖たててみた٩( 'ω' )و

友達と遊ぶようにマイクラ鯖たてました。 とは言ってもそんなことのできるデスクトップPCは持ってないのでAWS使いました。

自宅鯖だとポート開放やら何やらあるけどAWSなので簡単に公開できました٩( ‘ω’ )و

公開するなら荒らし対策などやることはいろいろあるけど、今回は友達で遊べる最低限の設定まで。 (最低限やるべきことが出来てなかったら教えてください、、)

AWSインスタンスの生成

  • Amazon Linux
  • セキュリティグループの設定
    • 25565ポートを開けておく
  • Elastic IPの設定

マイクラサーバーの設定

とりあえず必要なもののインストール。

$ sudo yum update -y
$ java -version // java入ってるか確認。バージョンも確認。必要なら変更などする。
$ sudo yum -y install git

今回はspigotを使ってサーバー環境を整えていきます。 詳細まではわかってないけど、調べたところ公式よりもspigotが良いらしい。

$ mkdir ~/minecraft_spigot
$ cd ~/minecraft_spigot
$ wget https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar
$ java -jar BuildTools.jar
$ java -Xms1024M -Xmx1024M -XX:MaxPermSize=128M -jar spigot-1.11.2.jar nogui
$ vi eula.txt

eula=false -> true

$ java -Xms1024M -Xmx1024M -XX:MaxPermSize=128M -jar spigot-1.11.2.jar nogui

クライアントからログインできるかテスト→ログインできたらstopで一旦停止

起動/停止スクリプトの作成

いちいちコマンド打つのもあれなのでスクリプト作ります。

起動スクリプト(spi_start.sh)

#!/bin/bash

USERNAME='ec2-user'
SERVICE='spigot-1.11.2.jar'
SCNAME='spigot'
SPI_PATH='/home/ec2-user/minecraft_spigot/'

XMX="1024M"
XMS="1024M"

cd $SPI_PATH

ME=`whoami`

if [ $ME == $USERNAME ] ; then
if pgrep -u $USERNAME -f $SERVICE > /dev/null
then
echo "$SERVICE is already running!"
else
echo "Starting $SERVICE..."
screen -AmdS $SCNAME java -Xmx$XMX -Xms$XMS -XX:MaxPermSize=128M -jar $SERVICE nogui
fi
else
echo "Please run the minecraft user."
fi

停止スクリプト(spi_stop.sh)

#!/bin/bash

USERNAME='ec2-user'
SERVICE='spigot-1.11.2.jar'
SCNAME='spigot'
SPI_PATH='/home/ec2-user/minecraft_spigot'

cd $SPI_PATH

ME=`whoami`

if [ $ME == $USERNAME ] ; then
  if pgrep -u $USERNAME -f $SERVICE > /dev/null
    then
      echo "Stopping $SERVICE"
      screen -p 0 -S $SCNAME -X eval 'stuff "say SERVER SHUTTING DOWN IN 10 SECONDS. Saving map..."\015'
      screen -p 0 -S $SCNAME -X eval 'stuff "save-all"\015'
      sleep 10
      screen -p 0 -S $SCNAME -X eval 'stuff "stop"\015'
      sleep 10
      echo "Stopped minecraftserver"
    else
      echo "$SERVICE was not runnning."
  fi
else
  echo "Please run the minecraft user."
fi

権限の設定

$ chmod 744 spi_start.sh
$ chmod 744 spi_stop.sh

ちゃんと動くか確認しておく

監視ツールの導入など

mackerelエージェントのインストー

サーバたてたらとりあえずmackerel入れとけ派の人なので入れます。 自分は何かあったらslackに飛ばすように設定してるので鯖落ちとかはすぐ気付けるようにしてます。

$ curl -fsSL https://mackerel.io/file/script/amznlinux/setup-all-yum.sh | MACKEREL_APIKEY='API_KEY' sh

定期的に再起動するように設定

ずっと起動しっぱなしは良くないので定期的に再起動するように設定します。

日本時間に変更

$ sudo vim /etc/sysconfig/clock

ZONE="Asia/Tokyo" 
UTC=false

$ cp /usr/share/zoneinfo/Japan /etc/localtime
$ sudo /etc/init.d/crond restart

再起動スクリプト(spi_restart.sh)

#!/bin/bash

./spi_stop.sh; ./spi_start.sh

cronの設定

$ crontab -e

0 4 * * * /home/ec2-user/minecraft_spigot/spi_restart.sh | logger -t minecraft -p local0.info

$ crontab -l
$ sudo tail -f /var/log/messages // ログの保存先

参考HP

AWSで爆速でMinecraft Plugin鯖を建てる方法! - Qiita

さくらのVPSにspigotインストール(CentOS 7) | server-memo.net

EC2 Amazon Linux を立ち上げた時にする初期設定 - Qiita

CrontabでEC2を指定の時間にシャットダウンさせる - Qiita

Areas on the Cross-Section Diagram 解き方メモ

模式断面図の面積 | アルゴリズムとデータ構造 | Aizu Online Judgeを解く時に苦労したのでそのメモφ(・

問題

模式断面図の面積 | アルゴリズムとデータ構造 | Aizu Online Judgeに書いてある通りです、ってのも素っ気ないので以下にスクショ貼ります。

f:id:mtdtx9:20170424200941p:plain

図のような状況において、水たまりそれぞれの面積と全体の面積を出すという問題です。

入力として\_/の列を受け取り、出力は全総面積、水たまりの数、各水たまりの面積になります。

上の図に対応する入力、出力は以下のようになります。 出力は総面積が35、水たまりの数が5、各水たまりの面積が4 2 1 19 9となっていることを示しています。

f:id:mtdtx9:20170424201033p:plain

考え方

最初自力で考えてたんですが、全くわからなかったのでALDS1_3_D: Areas on the Cross-Section Diagram - State-of-the-Arsのコードを参考にしながら考えていきました。

コードを読んで参考になった考え方は、

  • \の場所をスタックに保存することで、/を受け取った時にそれと対応する\を見つけることができる
  • 上記の方法で、対応する\/を用いて面積を計算すること = 横向きに面積を計算していくということ

アルゴリズムとしては、

  • 入力が\のときはそのインデックスをスタックに保存する
  • 入力が/の時はスタックからpopし、現在のインデックスと組み合わせて横向きに面積を計算する
  • 計算した面積の総和をとって総面積を計算する

といった感じです。

ただ、これだけでは各水たまりの面積を計算できないので、

  • /の時にpopしたインデックスと計算した面積のタプルを保存する
  • 必要に応じて上記のタプルを結合していく(水たまりを結合する操作に相当)

ということもします。

以上のために2つのスタックを用意します。

  1. \のインデックスを保存するスタック
  2. popしたインデックスと計算した面積のタプルを保存するスタック

これだけだと意味不明なので例を出しながら説明します。

具体例で説明

具体例として、先ほどの問題の水たまりが1, 19となっている箇所を用いて説明します。

ちなみに入力は\/\\\\/_/\\///となります。 今後はこの入力をインデックスと合わせて説明するので、簡単な表を書いておきます。

index 0 1 2 3 4 5 6 7 8 9 10 11 12 13
value \ / \ \ \ \ / _ / \ \ / / /

index = 0

\なのでスタック1にインデックスを保存します。

stack stack2
0

index = 1

/なのでスタック1からpopします。

面積 = (現在のindex) - (popしたindex) = 1 - 0 = 1となります。 popしたインデックスとこの計算結果をスタック2に保存します。

stack stack2
(0, 1)

index = 2 ~ 5

\らをスタック1に積んでいきます。

stack stack2
5
4
3
2 (0, 1)

index = 6

/なのでスタック1からpopします。

面積 = 6 - 5 = 1となります。

popしたindexの5は、スタック2に保存したタプルに入っているindexである0より大きいので、そのままスタック2に保存します。

stack stack2
4
3 (5, 1)
2 (0, 1)

スタック2が水たまりの様子を表しているので、これは面積1の水たまりが2つあることを示しています。 実際にここまで読み込んだ時の様子は以下のようになっています。

f:id:mtdtx9:20170424225147j:plain:w300

index = 7, 8

index = 7のとき_なので特に何もしません。 index = 8のとき/なので、スタック1からpopします。

面積 = 8 - 4 = 4となります。

popしたindexである4はスタック2に積まれているタプルに保存されたindexである5より小さいので、水たまりは結合されます。 したがって、popしたindexである4と面積 = (タプルに保存されていた面積である) + (先ほど計算した面積) = 1 + 4 = 5を合わせて保存します。 この時のスタックの状態は以下のようになります。

stack stack2
3 (4, 5)
2 (0, 1)

f:id:mtdtx9:20170424225138j:plain:w300

index = 9, 10

ちょっと面倒になってきたのでスタックの状態だけ、、

stack stack2
10
9
3 (4, 5)
2 (0, 1)

index = 11

stack stack2
9 (10, 1)
3 (4, 5)
2 (0, 1)

index = 12

stack stack2
(9, 4)
3 (4, 5)
2 (0, 1)

この時の状態はこんな感じ。

f:id:mtdtx9:20170424225915j:plain:w300

index = 13

/なのでスタック1から3をpopする。

面積 = 13 - 3 = 10となる。

popしたindexである3に対して、スタック2にある(4, 5), (9, 4)はindexの値が大きいのでこれらを結合する。

stack stack2
(3, 19)
2 (0, 1)

図的にはこんな感じ。

f:id:mtdtx9:20170424230620j:plain:w300

結果

スタックの状態は以下のとおり。

したがって、スタック2を見ると、面積1の水たまりと、面積19の水たまりがあることがわかる。

stack stack2
(3, 19)
2 (0, 1)

コード

rubyで書きました。

コードは以下の通り。まだ最適化できそうやけどとりあえずはこれで解けます。

sum = 0
stack = [] # \を受け取ったindexのstack
res = [] # /に対応する\のindexとその時に計算した面積のtupleのstack. 最後に各断面の面積として表示する
gets.split("").each_with_index { |v, i|
  if v == "\\"
    stack << i
  elsif v == "/"
    j = stack.pop
    if !j.nil? # \より先に/が来たときのため
      a = i - j # indexの引き算で横向きに計算する
      sum += a

      loop do
        k, b = res.pop
        if !k.nil?
          if j < k
            a += b
          else
            res << [k, b] # 一旦popしてるので特に何もなければ再度pushして戻す
            break
          end
        else
          break
        end
      end
      res << [j, a]
    end
  end
}
puts sum
if res.size() == 0
  puts "0"
else
  puts "#{res.size()} #{res.map{|n| n[1]}.join(" ")}" # size == 0の時にmapがうまくいかない??
end

githubにもあげてます。

github.com

最後に

今回は最初さっぱりわからず30分くらい問題とにらめっこしてたのですが、横向きに面積を計算する、その時面積はインデックスの引き算で求めることができる、ということに気づくと後はすぐに解けました。

見方を変えれば簡単に解けるけど、その見方に気付けないという典型的な問題でした。

こういう見方は普段から引き出しを増やすしかないのかなーと思うので今後も勉強を続けていきたいなーと思いました。

ruby+opencvで画像処理(2値化)

画像処理の授業取っててruby+opencvで実装したのでその時のことをメモメモφ(・

opencvといえばc++pythonってイメージがあるけど、最近勉強中のrubyで実装したいなーと思ったのでしてみた。

開発環境

rubyopencvを使うためのgemがopencv3系に対応していないのでこんな感じ。

インストール関係で叩いたコマンドは以下の通り。 地味にハマったのはopencv3も入っているからオプションでopencv2の方を見るようにしないといけなかったってところ。

$ brew install opencv
$ gem install ruby-opencv -- --with-opencv-dir=/usr/local/Cellar/opencv/2.4.13.2

2値化とは

画像を白(1)か黒(0)の2つの値のみにすること。 色の情報が必要では無く形に着目するときとかに使うと良い。 また、情報量を減らせるので計算の面でもシンプル&高速になるとかなんとか。

現実世界やと、駐車場でナンバープレート見て清算済みなら勝手に開けてくれるとかあるけど、ああいうときに使われているらしい。

(雑な理解やけど多分だいたいあってる)

2値化の仕方

さて、どうやって2値化するかって話やけど、中身はすごい簡単。 各ピクセルの濃度値が閾値より大きければ黒、小さければ白にするだけ。 自分で実装してもそんなに大変ではないけど、opencvには閾値を渡せば2値化してくれるメソッドがあるので凄い簡単。

2値化するところは以下の通り。 カラーの画像をグレースケールにしてから、threshold()を呼べば終わり。 正直引数については詳しく調べてなくて、255は8bitカラーの最大値からきててとりあえずこう書いとけばいいという雑な理解。

def binarize(file, t)
  image = CvMat.load(file)
  gray_img = image.BGR2GRAY
  bin_img = gray_img.threshold(t.to_i, 255, :binary)
end

2値化は簡単にできるけど、問題になるのはその閾値をどうやって決めるかという話になって、それはいくつかアルゴリズムがあるらしい。

授業で出てきたのは、

の4つ。

今回はこの中で微分ヒストグラム法と判別分析法を実装した。

微分ヒストグラム

微分という名前の通り、濃度値の変化が大きいところに着目して閾値を決める方法。 ヒストグラム法やからヒストグラムを作るんやけど、どういうものかというと横軸が濃度値、縦軸が微分値の和になるようなヒストグラム。 多分普通は縦軸が単に画素数になるから、ここでは微分値を重みとしてちょっと変化させてるという感じ。 微分値の定義としては、| (基準となる画素の濃度値) - (隣の画素の濃度値) |を周辺8マス分計算してその総和を取る。

ヒストグラムを作るところは以下のように実装した。

def create_diff_histogram(gray_img)
  hist = Array.new(256, 0)
  [*0...gray_img.rows].product([*0...gray_img.cols]).each{ |(y, x)|
    b = gray_img.at(y, x)
    diff = 0
    # 周辺8マスとの差分の絶対値の総和の出す.
    # 自分自身との差は0になって結果に影響ないので9マス分計算している.
    [*y-1..y+1].product([*x-1..x+1]).each{ |(y2, x2)|
      if y2.between?(0, gray_img.rows-1) && x2.between?(0, gray_img.cols-1)
        diff += (b[0] - gray_img.at(y2, x2)[0]).abs
      end
    }
    hist[b[0]] += diff
  }
  hist
end

全てのピクセルを網羅するためにx座標とy座標の直積を取る必要があって、それは[*0...gray_img.rows].product([*0...gray_img.cols]).eachと書いた。 product()を使うと直積を取ってくれるのだが、配列で渡す必要があるので[*0...gray_img.rows]としてrangeではなく配列にした。 個人的にはこの行がなんかもうちょっといい感じに書けないのかなーと思ってるけど、書き方がわからないのでこのまま。

各座標における色はat(y, x)で取れる。 このat(y, x)の返り値はCvScalar型になってて、カラーだとRGB値、グレースケールだと輝度値が入っている。 CvScalarは4つのdoubleを持つ配列になっているので、b[0]のような書き方になる。 ちなみに、カラーのときはBGRの順に値を持っているので注意が必要。

あと、個人的に引っかかった点は引数が(x, y)でなく(y, x)だったこと。 最初何も考えずに(x, y)で書いていたのでout of rangeでエラー吐きまくってた。

微分値を計算するところは、周辺8マスを計算するためにまた直積を書いて実装している。 この書き方やと縦横3×3の9マスを網羅してて基準になるマスも入るけど、計算結果は変わらないので9マス分計算している。 あとは、端っこのピクセルやと隣がいない場合があるのでそのためのチェックもしてる。

普通なら2重ループになる直積が一行で書けるのはいいなーと思うけど、なんとなく読みづらいのが直したいポイント.

判別分析法

判別分析法は別名大津の2値化ともいうらしい。

ある値を閾値にしたときの分離度なる値を算出してそれが最大になるような閾値を採用するというアルゴリズム。 分離度は、閾値を元にそれより暗い/明るいの2つのグループに分けて、それぞれのグループでの分散を求めて、クラス内分散やクラス間分散を定義して、って感じで定義される。 この分離度だが、今回はその最大値を求めるという点で大小関係の比較ができれば良いので、結局各グループ内の画素数と濃度値の平均があれば十分になる。

数式の詳しい説明はちょっと長くなるのでこちら

実装としては、閾値をある値にした時の分離度を求めて、それが最大になる閾値を返すというメソッドを作るだけなので簡単で、以下の通り。

def discriminant(file)
  image = CvMat.load(file)
  gray_img = image.BGR2GRAY
  hist = create_histogram(gray_img)
  eval_value, best_t = 0, 0
  (0...255).each{ |t|
    # t := 閾値
    # w1(w2) := 左側(右側)の画素数
    # m1(m2) := 左側(右側)の平均
    w1, w2 = 0, 0
    m1, m2 = 0, 0

    w1 = hist[0..t].reduce(:+)
    m1 = hist[0..t].map.with_index {|n, idx| n * idx}.reduce(:+)/w1 rescue 0

    w2 = hist[t+1..255].reduce(:+)
    m2 = hist[t+1..255].map.with_index(t+1) {|n, idx| n * idx}.reduce(:+)/w2 rescue 0

    e = w1 * w2 * (m1 - m2) ** 2
    if eval_value < e
      eval_value = e
      best_t = t
    end
  }
  best_t
end

create_histogram()は横軸が濃度値、縦軸が画素数のシンプルなヒストグラムを返すように実装したメソッド。 indexが濃度値、値が画素数の配列の形でデータを保持している。

素数は単に配列の値を足していけば良いだけなのでreduce(:+)するだけ。

平均値は、画素数×濃度値を計算するためにindexと値が両方なので、map.with_index{|n, idx| n * idx}のようにしてまず画素数×濃度値を計算した。 そのあとは同じくreduce(:+)で総和を求めて、画素数wで割って終わり。 画素数wは0になることがあるのでZeroDivisionErrorの時のためにrescue 0を書いている。 ただ、この書き方をすると他のエラーを握りつぶしてしまうのでもしかしたらよくないかも。

あと工夫しているところは閾値より大きいところを見るときにhist[t+1..255].map.with_index(t+1)という書き方をしているところ。 配列の後ろ側を切り取ってからmapするときに、もとの配列でのindexを使いたい場合はwith_index(t+1)とすることでindexを任意の値から数えることができる。

実行結果

画像処理定番のlenna使ってみました。 lennaだと2つの手法の差があまり出なくて向き不向きみたいなのが見えなくて残念やけど。

元画像

f:id:mtdtx9:20170414215613p:plain

グレースケールにした画像

f:id:mtdtx9:20170414215630p:plain

微分ヒストグラム法を使って2値化した画像(閾値=129)

f:id:mtdtx9:20170414215647p:plain

判別分析法を使って2値化した画像(閾値=122)

f:id:mtdtx9:20170414215656p:plain

最後に

一応プログラムはgithubにあげてます。

今後も授業でいろいろ実装していく予定なので適宜まとめていこうかなと思ってます。

github.com

2017年2月の振り返り

2月も終わりなので今月の振り返り。

研究

就活に時間割いたので基本的に何もしてない。
M1の進捗とM2の予定をプレゼンしなきゃいけなかったのでそれの作成と発表しかしてないですね。
3月もする予定は特に無いですw

技術

前半はVPS上でdockerいろいろ触ってました。
インフラエンジニアで就活してたのでさすがに知らなさすぎてやばいなと思って勉強してたって感じ。
その流れでgoに入門したりもしてました。

後半はrubyrailsの勉強をしてました。
これはインターン先がrailsだったから勉強してました。
js周りの勉強もその流れでしてました。

基本的には就活に向けてってのが多かったけど、インフラからフロントまで一通り触った1ヶ月だったなーという感想。
今年に入って広く浅くやってる感じになってるから、そろそろどれかに決めて深く勉強したいなーと思ってます。

技術アウトプット

前半はしっかりインプットもアウトプットもできたけど、後半はひたすらインプットしてました。
アウトプットの意識忘れないようにしていかないと。

就活

今月は選考が進んでて面接があったりインターンがあったりという感じ。
その中で人に会う度にそういう考え方もあるのかーと思ってました。
そのおかげで自分の強みや弱み、どういうことに興味があるのか、何をしたいのかとかを深掘りできたから良かったと思う。
院生の就活って推薦使ったり一般でも受ける企業が少なかったりするけど、自分が将来何をしたいのかが不明確なのであれば足を動かしていろんな人に会うと良いと感じました。
適当に選んで数年で転職とかはもったいないと思うし、楽しく仕事するためにしっかりこの機会に頭使わないとと思いました。

まとめ

就活、インターンでいろんなエンジニアの人に会った一ヶ月でした。
特にインターンは大変やったけど楽しい一週間やった。
面接や説明会と違ってランチやといろいろ聞けるのでインターンは良い体験でした。

技術的には様々学んだけど実際に何か作ったりしていないのでちゃんと身についてない感がある。
インターンも終わって自分で好きなものを勉強できる期間に入るので、来月は手も動かしながら深く学んでいきたいと思います。

A Tour of GoでGoに入門した

Goの勉強にはまずA Tour of Go読むと良いっぽかったので読みました。
途中途中に練習問題があって、それを解きながら進めると適度に手も動かせて良かったです。

以下は自分用のまとめです。
最後に練習問題の解答をまとめて載せています。
(合ってるかは保証できません)

Packages, variables, and functions

  • importはまとめて書くのがGo流
import (
	"fmt"
	"math"
)
  • 大文字から始めると他から参照可能。小文字からだと外からはアクセスできない。
  • 関数の宣言は"func 関数名 (変数名 型, 変数名 型) 返り値の型"。型が同じ時はまとめてok。返り値は複数でもok。
func add(x int, y int) int {
	return x + y
}
func add(x, y int) int {
	return x + y
}
func swap(x, y string) (string, string) {
	return y, x
}
  • 返り値にあらかじめ名前をつけておくこともできる。その場合returnだけでよし。
func split(sum int) (x, y int) {
	x = sum * 4 / 9
	y = sum - x
	return
}
  • "var 変数名 型"で変数宣言。初期値を与えないときはゼロ値が入る。
  • 初期化するときは"変数名 := 値"で宣言と初期化をまとめてできる。型は推論される。
var i, j int
k := 3
  • 型変換は"型名(値)"で可能。
  • constキーワードを用いると定数を宣言可能。定数の時は":="でなく"="。

Flow control statements: for, if, else, switch and defer

  • For文は括弧無しversion。式の省略も自由
for i := 0; i < 10; i++ {
    sum += i
}
for ; sum < 1000; {
    sum += sum
}
  • While文はないのでFor文で表現
// while文
for ; sum < 1000; {
    sum += sum
}

// 無限ループ
for {
    // do something
}
  • If文も括弧無しverison。
if x < 0 {
    // do something
}
  • 特殊なのは簡単な式をIf文の中で書ける。スコープはIf文内のみ。
if v := math.Pow(x, n); v < lim {
    return v
}
  • Switch文はcaseで強制break。続けるときは"fallthrough"をcaseの最後に書く。
  • Deferを使うと呼び出し元の関数がreturnする時まで評価を遅らせる。(引数は評価されるがその関数は評価されない)
  • 複数deferがあるときはLIFOの順で処理される。

More types: structs, slices, and maps.

  • ポインタが使える。&でポインタを取得、*でポインタが示す先の値を取得。
func main() {
	i, j := 42, 2701

	p := &i         // point to i
	fmt.Println(*p) // read i through the pointer
	*p = 21         // set i through the pointer
	fmt.Println(i)  // see the new value of i

	p = &j         // point to j
	*p = *p / 37   // divide j through the pointer
	fmt.Println(j) // see the new value of j
}
  • 構造体もある。"type 構造体名 struct"の形で宣言。
  • 構造体へはドットでアクセス。
type Vertex struct {
	X int
	Y int
}
func main() {
	v := Vertex{1, 2}
	v.X = 4
	fmt.Println(v.X)
}
  • ポインタを用いて構造体へアクセスもできる。このとき(*p).Xと書かなくても勝手に解釈してくれる。
func main() {
	v := Vertex{1, 2}
	p := &v
	p.X = 1e9
	fmt.Println(v)
}
  • 配列は"var 変数名 [サイズ]型"で宣言。配列の長さは後から変えれない。
var a [10]int
  • 配列を便利に扱うためにsliceがある。
primes := [6]int{2, 3, 5, 7, 11, 13}
var s []int = primes[1:4]
fmt.Println(s)
// [3 5 7]
  • sliceは元の配列の部分を参照しているだけ。したがって、sliceの値を変えると元の配列の値も変わる。
  • sliceを重ねて新たなsliceを作ることができる。
s := []int{2, 3, 5, 7, 11, 13}
s = s[1:4]
fmt.Println(s) // [3 5 7]
s = s[:2]
fmt.Println(s) // [3 5]
s = s[1:]
fmt.Println(s) // [5]
  • sliceはlengthをcapacityを持っている。lengthはsliceに含まれる要素数。capacityはsliceの最初の要素から数えて元の配列の最後の要素までの数。
  • lengthとcapacityはlen(s)とcap(s)の形で取得可能。
  • make関数を用いてsliceを作成。make関数はarrayを作成しそのsliceを返す。
a := make([]int, 5) // len(a)=5
b := make([]int, 0, 5) // len(b)=0, cap(b)=5
  • append関数を用いてsliceに要素を追加することができる。capacityを超えたとき、より大きなサイズの配列を割り当て直す。
var s []int
s = append(s, 0)
s = append(s, 1)
  • rangeを使えば反復処理ができる。rangeを使うと2つの値が返され、1つ目がindex、2つ目がその要素の値。
  • 使わない時は"_"で捨てることができる。
var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}
for i, v := range pow {
    fmt.Printf("2**%d = %d\n", i, v)
}
for _, value := range pow {
    fmt.Printf("%d\n", value)
}
  • Mapを用いてキーと値を関連づけることができる。
  • make関数を用いてmapを作成することで要素の追加などが可能。
m = make(map[string]Vertex)
m["Bell Labs"] = Vertex{
	40.68433, -74.39967,
}
  • "elem, ok = m[key]"の形でキーに対する要素が存在するかを確認できる。キーがあれば"ok = true"、なければ"ok = false"になる。
m := make(map[string]int)

m["Answer"] = 42 // 初期化
fmt.Println("The value:", m["Answer"])

m["Answer"] = 48 // 更新
fmt.Println("The value:", m["Answer"])

delete(m, "Answer") // 削除
fmt.Println("The value:", m["Answer"])

v, ok := m["Answer"] // 取得
fmt.Println("The value:", v, "Present?", ok)
  • 無名関数を渡すことも可能。
hypot := func(x, y float64) float64 {
	return math.Sqrt(x*x + y*y)
}
fmt.Println(hypot(5, 12))

Methods and interfaces

  • Goにクラスはないが、レシーバ引数を用いてっぽいことができる。レシーバ引数を使う時は"func (変数名 型) 関数名 (引数名 型) 返り値の型 "の形で使う。
  • レシーバ引数を使うとドットを用いてアクセス可能。
  • レシーバを伴うメソッドは同じパッケージ内でする必要がある。
type Vertex struct {
	X, Y float64
}

func (v Vertex) Abs() float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
	v := Vertex{3, 4}
	fmt.Println(v.Abs())
}
  • ポインタレシーバを用いて関数を宣言することも可能。レシーバの値を更新する時はこちらで。
func (v *Vertex) Scale(f float64) {
	v.X = v.X * f
	v.Y = v.Y * f
}
  • ポインタレシーバの時は値でもポインタでもどちらを渡してもうまくやってくれる。
var v Vertex
v.Scale(5)  // OK
p := &v
p.Scale(10) // OK
  • インターフェースの宣言もできる。明示的に書かなくても実装すればok。
type I interface {
	M()
}
type T struct {
	S string
}
func (t T) M() {
	fmt.Println(t.S)
}
  • nilをレシーバーにして呼び出すときがあり得るのでnilチェックはするべき
func (t *T) M() {
	if t == nil {
		fmt.Println("<nil>")
		return
	}
	fmt.Println(t.S)
}
  • nilインターフェースの値は値も具体的な型ももたない。メソッドを呼び出すとランタイムエラーになる。
type I interface {
	M()
}

func main() {
	var i I
	describe(i)
	i.M() // ランタイムエラー
}
  • アサーションができる。"t, ok := i.(T)"の形で、iがTを保持してればtにその値が入り、okにtrueが入る。otherwise, t=ゼロ値でok=false。
var i interface{} = "hello"

s, ok := i.(string)
fmt.Println(s, ok)
switch v := i.(type) {
case int:
	fmt.Printf("Twice %v is %v\n", v, v*2)
case string:
	fmt.Printf("%q is %v bytes long\n", v, len(v))
default:
	fmt.Printf("I don't know about type %T!\n", v)
}
  • Goではエラーをerror変数を用いて表す。エラー型は以下のインターフェース。
type error interface {
    Error() string
}

Concurrency

  • goroutineという並列化機構がある。"go f(x, y, z)"と書けば別スレッドで動かせる。
  • チャネル型を用いて値のやりとりができる。
ch := make(chan int) // チャネルの作成
c <- sum // 値の送信
x := <-c // 値の受信
  • チャネルはバッファとして使える
ch := make(chan int, 100) // 2つ目の引数でバッファのサイズを指定
  • これ以上値を送信しないことを示すためにチャネルのcloseができる。受信側は2つ目の引数でcloseされているかを知ることができる。
close(ch) // チャネルを閉じる
v, ok := <-ch // チャネルが閉じていればok=false
  • selectを用いて複数のチャネルを待つことができる。defaultを用いれば待ち状態にはならずdefaultを実行する。
select {
case <-tick:
	fmt.Println("tick.")
case <-boom:
	fmt.Println("BOOM!")
	return
default:
	fmt.Println("    .")
        time.Sleep(50 * time.Millisecond)
}
  • syncパッケージを用いて排他制御などをおこなうことができる。
type SafeCounter struct {
	v   map[string]int
	mux sync.Mutex
}

func (c *SafeCounter) Inc(key string) {
	c.mux.Lock()
	c.v[key]++
	c.mux.Unlock()
}

Execises

  • Loops and Functions
package main

import (
	"fmt"
	"math"

)

func Sqrt(x float64) float64 {
	z := 1.0
	for i := 0; ; i++ {
		diff := ( z * z - x ) / ( 2 * z )
		if math.Abs(diff) < 1e-10 {
			fmt.Printf("loop count is %d\n", i)
			return z
		} 
		z -= diff
	}
}

func main() {
	fmt.Println(Sqrt(2))
}
  • Slices
package main

import "golang.org/x/tour/pic"

func Pic(dx, dy int) [][]uint8 {
	pic := make([][]uint8, dy)
	for i := range pic {
		pic[i] = make([]uint8, dx)
	}
	
	for x := 0; x < dx; x++ {
		for y := 0; y < dy; y++ {
			pic[x][y] = uint8(x^y)
		}
	}
	
	return pic
}

func main() {
	pic.Show(Pic)
}
  • Maps
package main

import (
	"golang.org/x/tour/wc"
	"strings"
)

func WordCount(s string) map[string]int {
	m := make(map[string]int)
	for i := range strings.Fields(s) {
		m[strings.Fields(s)[i]] += 1
	}
	return m
}

func main() {
	wc.Test(WordCount)
}
  • Fibonacci closure
package main

import "fmt"

// fibonacci is a function that returns
// a function that returns an int.
func fibonacci() func() int {
	n1, n2 := 0, 1
	return func() int {
		n1, n2 = n2, n1+n2
		return n1
	}
}

func main() {
	f := fibonacci()
	for i := 0; i < 10; i++ {
		fmt.Println(f())
	}
}
  • Stringers
package main

import "fmt"

type IPAddr [4]byte

// TODO: Add a "String() string" method to IPAddr.
func (ip IPAddr) String() string {
	return fmt.Sprintf("%v.%v.%v.%v", ip[0], ip[1], ip[2], ip[3])
}

func main() {
	hosts := map[string]IPAddr{
		"loopback":  {127, 0, 0, 1},
		"googleDNS": {8, 8, 8, 8},
	}
	for name, ip := range hosts {
		fmt.Printf("%v: %v\n", name, ip)
	}
}
  • Errors
package main

import (
	"fmt"
)

type ErrNegativeSqrt float64

func (e ErrNegativeSqrt) Error() string {
	return fmt.Sprintf("cannot Sqrt negative number: %f", float64(e))
}

func Sqrt(x float64) (float64, error) {
	if x < 0.0 {
		return 0.0, ErrNegativeSqrt(x)
	}
	z := 1.0
	for i := 0; ; i++ {
		if z2 := z - (z*z-x)/(2*z); z2 - z < 1e-10 {
			return z, nil
		} else {
			z = z2
		}
	}
}

func main() {
	fmt.Println(Sqrt(2))
	fmt.Println(Sqrt(-2))
}
  • Readers
package main

import "golang.org/x/tour/reader"

type MyReader struct{}

func (r MyReader) Read(b []byte) (int, error) {
	b[0] = 'A'
	return 1, nil
}


func main() {
	reader.Validate(MyReader{})
}
  • rot13Reader
package main

import (
	"io"
	"os"
	"strings"
)

type rot13Reader struct {
	r io.Reader
}

func (reader *rot13Reader) Read(b []byte) (n int, err error) {
    n, err = reader.r.Read(b) // bに文字を読み込む
	// 以下で文字を変換する
    for i := range b {
        if ('A' <= b[i] && b[i] <= 'M') || ('a' <= b[i] && b[i] <= 'm') {
            b[i] += 13
        } else if ('N' <= b[i] && b[i] <= 'Z') || ('n' <= b[i] && b[i] <= 'z') {
            b[i] -= 13
        }
    }
    return n, err
}

func main() {
	s := strings.NewReader("Lbh penpxrq gur pbqr!")
	r := rot13Reader{s}
	io.Copy(os.Stdout, &r)
}
  • Images
package main

import (
	"golang.org/x/tour/pic"
	"image"
	"image/color"
)

type Image struct{
	w, h int
}

func (i Image) ColorModel() color.Model {
	return color.RGBAModel 
}

func (i Image) Bounds() image.Rectangle {
	return image.Rect(0, 0, i.w, i.h)
}

func (i Image) At(x, y int) color.Color {
	return color.RGBA{uint8(x*y), uint8(x+y), 255, 255}
}

func main() {
	m := Image{100, 100}
	pic.ShowImage(m)
}
  • Equivalent Binary Trees
package main

import (
	"fmt"
	"golang.org/x/tour/tree"
)

// Walk walks the tree t sending all values
// from the tree to the channel ch.
func Walk(t *tree.Tree, ch chan int) {
	if t == nil {
		return
	}
	Walk(t.Left, ch)
	ch <- t.Value
	Walk(t.Right, ch)
}

// Same determines whether the trees
// t1 and t2 contain the same values.
func Same(t1, t2 *tree.Tree) bool {
	c1, c2 := make(chan int), make(chan int)
    go func() {
        Walk(t1, c1)
        close(c1)
    }()
    go func() {
        Walk(t2, c2)
        close(c2)
    }()
	for {
        x, ok1 := <-c1
        y, ok2 := <-c2
		if !ok1 || !ok2 {
			return true
		}
        if x != y {
            return false
        }
    }
    return true
}

func main() {
	ch := make(chan int)
	t := tree.New(1)
	go func() {
		Walk(t, ch)
		close(ch)
	}()
	for v := range ch {
		fmt.Println(v)
	}
	
	if Same(tree.New(1), tree.New(1)) {
		fmt.Println("Same")
	}
	if !Same(tree.New(1), tree.New(2)) {
		fmt.Println("Not Same")
	}
}
  • Web Crawler
package main

import (
	"fmt"
	"sync"
)

type Fetcher interface {
	// Fetch returns the body of URL and
	// a slice of URLs found on that page.
	Fetch(url string) (body string, urls []string, err error)
}

func Crawl(url string, depth int, fetcher Fetcher) {
	m := make(map[string]bool)
	crawl(url, depth, fetcher, m)
}

// Crawl uses fetcher to recursively crawl
// pages starting with url, to a maximum of depth.
func crawl(url string, depth int, fetcher Fetcher, m map[string]bool) {
	if depth <= 0 {
		return
	}
	mux := &sync.Mutex{}
	mux.Lock()
	if v, ok := m[url]; v && ok {
		return
	}
	m[url] = true
	mux.Unlock()
	
	body, urls, err := fetcher.Fetch(url)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Printf("found: %s %q\n", url, body)
	
	wg := &sync.WaitGroup{}
	for _, u := range urls {
		wg.Add(1)
		go func (u string) {
			crawl(u, depth-1, fetcher, m)
			wg.Done()
		}(u)
	}
	wg.Wait() 
	return
}

func main() {
	Crawl("http://golang.org/", 4, fetcher)
}

// fakeFetcher is Fetcher that returns canned results.
type fakeFetcher map[string]*fakeResult

type fakeResult struct {
	body string
	urls []string
}

func (f fakeFetcher) Fetch(url string) (string, []string, error) {
	if res, ok := f[url]; ok {
		return res.body, res.urls, nil
	}
	return "", nil, fmt.Errorf("not found: %s", url)
}

// fetcher is a populated fakeFetcher.
var fetcher = fakeFetcher{
	"http://golang.org/": &fakeResult{
		"The Go Programming Language",
		[]string{
			"http://golang.org/pkg/",
			"http://golang.org/cmd/",
		},
	},
	"http://golang.org/pkg/": &fakeResult{
		"Packages",
		[]string{
			"http://golang.org/",
			"http://golang.org/cmd/",
			"http://golang.org/pkg/fmt/",
			"http://golang.org/pkg/os/",
		},
	},
	"http://golang.org/pkg/fmt/": &fakeResult{
		"Package fmt",
		[]string{
			"http://golang.org/",
			"http://golang.org/pkg/",
		},
	},
	"http://golang.org/pkg/os/": &fakeResult{
		"Package os",
		[]string{
			"http://golang.org/",
			"http://golang.org/pkg/",
		},
	},
}

docker、jenkinsの導入

今回やること

  • dockerの導入
  • jenkinsの導入

サーバ環境

手持ちのさくらVPS上で環境作ります。

$ cat /proc/version
Linux version 3.13.0-108-generic (buildd@lgw01-60) (gcc version 4.8.4 (Ubuntu 4.8.4-2ubuntu1~14.04.3) ) #155-Ubuntu SMP Wed Jan 11 16:58:52 UTC 2017
$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=14.04
DISTRIB_CODENAME=trusty
DISTRIB_DESCRIPTION="Ubuntu 14.04.5 LTS"

dockerの導入

公式サイト(Get Docker for Ubuntu - Docker)を参考に導入しました。
随時更新されていると思うので最新の情報を参照した方が良いと思います。

  • curlをいれておくことをオススメされたのでいれる
$ sudo apt-get update
$ sudo apt-get install curl \
    linux-image-extra-$(uname -r) \
    linux-image-extra-virtual
  • 公式サイト通りにインストールを進める
$ sudo apt-get install apt-transport-https \
                       ca-certificates
$ curl -fsSL https://yum.dockerproject.org/gpg | sudo apt-key add -
$ apt-key fingerprint 58118E89F3A912897C070ADBF76221572C52609D
$ sudo apt-get install software-properties-common
$ sudo add-apt-repository \
       "deb https://apt.dockerproject.org/repo/ \
       ubuntu-$(lsb_release -cs) \
       main"
$ sudo apt-get update
$ sudo apt-get -y install docker-engine
  • 動作確認
$ sudo docker run hello-world

表示を見た感じちゃんと動いていれば大丈夫だと思います。

  • バージョン確認
$ docker version
Client:
 Version:      1.13.0
 API version:  1.25
 Go version:   go1.7.3
 Git commit:   49bf474
 Built:        Tue Jan 17 09:50:17 2017
 OS/Arch:      linux/amd64

というわけで1.13.0が入ったみたいです。
ちなみにインストールする際にバージョンを指定することも可能です。
インストール可能なバージョンは以下のコマンドで確認できます。

$ apt-cache madison docker-engine
  • ユーザグループの作成

sudo無しで実行できるようにdockerグループを作成します。
僕の場合グループは既に存在していたので、ユーザを追加するだけでした。

$ sudo groupadd docker
groupadd: group 'docker' already exists
$ sudo usermod -aG docker $USER

1回ログインし直して設定を適用します。
sudo無しで実行できればokです。

$ docker run hello-world

jenkinsの導入

公式のイメージがあるのでそれを使います。
https://hub.docker.com/_/jenkins/

$ docker search jenkins
NAME                                  DESCRIPTION                                     STARS     OFFICIAL   AUTOMATED
jenkins                               Official Jenkins Docker image                   2475      [OK]
$ docker run -p 8080:8080 jenkins
  • コンテナの状態を確認します。
$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                               NAMES
a15377c1714f        jenkins             "/bin/tini -- /usr..."   24 minutes ago      Up 24 minutes       0.0.0.0:8080->8080/tcp, 50000/tcp   pensive_brahmagupta
  • ブラウザからjenkinsをひらきます。(IP_ADDRESS:8080)

docker runコマンドの実行時にパスワードが表示されているのでそちらを入力します。
その後pluginはオススメを選び、ユーザの作成など順序通りに進みます。

f:id:mtdtx9:20170205054918p:plain

dockerの使い方

docker psコマンドを用いてidを取得します。
その後docker stop、docker startコマンドで停止、起動をおこないます。
コンテナの削除はdocker rmコマンドでできます。

$ docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                   PORTS                               NAMES
a15377c1714f        jenkins             "/bin/tini -- /usr..."   About an hour ago   Up 7 seconds             0.0.0.0:8080->8080/tcp, 50000/tcp   pensive_brahmagupta
$ docker stop pensive_brahmagupta
$ docker start pensive_brahmagupta
$ docker stop pensive_brahmagupta # rmの前はstopしなければならない
$ docker rm pensive_brahmagupta

さいごに

dockerとjenkinsの環境構築ができました。
次回はjenkinsの設定をしてテストの自動化などをしていきたいと思います。