bookmark_borderパーティーに行くタイミング|問題解決のPythonプログラミング 2章

書籍「問題解決のPythonプログラミング」を積んだままだったので読みながらコードを書いてみた.今回はその2章にある問題をといてみた.

(問題)有名人が参加する時間帯が事前に分かっているパーティーがある.このパーティーに1時間だけ参加するとき,最も多くの有名人がいる時間帯がいつか求める.

入力は有名人のいる時間帯をあらわすタプルで構成されている.例えば(6,8)の場合は6:00から8:00まで参加するということをあらわす.今回の場合は9:00 – 10:00が5人で最多となる.

タイムライン

簡単なコードを書いてみた.

guest_schdules = [(6,8), (6,12), (6,7), (7,8),
        (7,10), (8,9), (8,10), (9,12),
        (9,10), (10,11), (10,12), (11,12)]
times = []

for schd in guest_schdules:
    times.append(('start', schd[0]))
    times.append(('end', schd[1]))

times_sorted = sorted(times, key=lambda x: x[1])

current_guest = 0
max_time = max_value = 0
for time in times_sorted:
    if time[0] == 'start':
        current_guest += 1
    elif time[0] == 'end':
        current_guest -= 1

    if max_time < time[1]:
        max_time = time[1]
        max_value = current_guest

print("Answer:", "time=", max_time, "num_of_guest=", max_value)

実行結果:

$ python3 2-party.py
Answer: time= 9 num_of_guest= 5

bookmark_borderasciidocで書いた履歴書をGitLabのCIで自動ビルド

履歴書をMarkdownからAsciidocに書き直したので、この機会にビルドも自動化してみました。方法だけ知りたい方は前半を読み飛ばしてください。

Markdownを使っていた理由

これまでMarkdownで書いていた理由は以下です。

  • Markdownはシンプルな書式でエディタだけあれば書ける
  • Wordは特定の環境でしか編集ができない
  • LaTeXは細かく指定ができるが書式が冗長である

Markdownで感じた課題

一方で、Markdownで感じていた問題は以下です。

  • レイアウトを細かく指定できない
  • Markdownの方言で振り回されることがあった
  • 納得できるPDFビルド環境がない(Pandocもイマイチ)

Asciidocで書いてみて

Markdownより書き方は冗長であるものの、Markdownでは手が届かなかった部分が指定できるので満足しています。

ビルドの環境は以下のDockerイメージで構築しています。

htakeuchi/docker-asciidoctor-jp

履歴書/職務経歴書の書き方は以下の記事を参考にしました。

エンジニアが読みたくなる職務経歴書 – dwango on GitHub

GitLabのCIで自動ビルド

gitlabのCIは .gitlab-ci.ymlで設定を記述します。サンプルが豊富にあったのでLaTeXのサンプルをベースに以下の設定を作成しました。

image: htakeuchi/docker-asciidoctor-jp:latest

build-master:
  stage: build
  script:
    - asciidoctor-pdf -r asciidoctor-pdf-cjk-kai_gen_gothic -a pdf-style=KaiGenGothicJP resume.txt
  artifacts:
    paths:
      - "*.pdf"

GitLabにプッシュするとCIが走ります。右側のBrowseからビルドされたファイルを見ることができます。

ブラウザからビルド結果を確認できます。


たまにPushしてもPendingで止まって5分くらい動かない時がありました。気長に待っているとビルドが終わります。

GitHubでも良かったもののCircle CIの設定が面倒だったので1サービスで完結するGitLabで今回はやってみました。自動化しておくとWindows環境でもブラウザだけあればGitLabのWeb IDEから編集してビルドが出来るので便利だと思います。

bookmark_borderPythonのプログラムをマルチプロセスで動かした

データサイエンスをやっているときに、マシンパワーが発揮できていないことに気がつきました。

1コアしかフルで利用されていない

この原因を調べた所、PythonのGIL(Global Interpreter Lock)が原因であることが分かりました。

ライブラリと拡張 FAQ — Python 3.7.3 ドキュメント
用語集 — Python 3.7.3 ドキュメント

このGILを回避するためにマルチプロセス化をしました。以下のサイトが参考になりました。

Python高速化 【multiprocessing】【並列処理】 – Qiita
multiprocessing — プロセスベースの並列処理 — Python 3.7.3 ドキュメント

具体的にはmultiprocessingパッケージを利用してコードを書き直しました。

変更前のソース

import asyncio
import tqdm
import MeCab
import numpy as np
from multiprocessing import Pool

tab_all = []
# 時間がかかる処理を含む関数
def handle(t):
    strs = mecab.parse(t).split('n')
    table = [s.split() for s in strs]
    table = [row[:4] for row in table if len(row) >= 4]
    if len(table) == 0:
        return
    tab = np.array(table)
    tab_all.append(tab[:,[0,3]].tolist())

if __name__ == '__main__':
    mecab = MeCab.Tagger("-Ochasen")
    f = open('jawiki_small.txt').readlines()
    text = [s.strip() for s in f]
    for t in text:
        handle(t)

変更した箇所

if __name__ == '__main__':
    mecab = MeCab.Tagger("-Ochasen")
    f = open('jawiki_small.txt').readlines()
    text = [s.strip() for s in f]
    with Pool(processes=8) as pool:
        pool.map(handle, text)

実行するとCPUがフルで利用されていることが分かります。(メモリが厳しいので増設したほうが良さそう…)

CPUがフルで利用されている

実行時間を比較したところ 1/3 程度まで削減できたことが分かります。

b-old.pyが変更前、b.pyが変更後のプログラム

Pythonの裏側を理解することの大切さを学びました。

bookmark_borderSSH接続をSlackへ通知する

SlackへSSH接続があると通知する仕組みを設定したのでメモしておきます。

OpenSSHへ接続すると /etc/ssh/sshrc が実行されることを利用した。以下のファイルを /etc/ssh/sshrc として配置してパーミッションを設定する。

#!/bin/bash

# [how to use]
# 1. put this file as a /etc/ssh/sshrc
# 2. change file permission with "chmod 755 /etc/ssh/sshrc"

PATH=/usr/bin:/bin:/sbin:/usr/sbin
TIME=`LANG=C date "+%Y/%m/%d %X"`
USER=`whoami`
IP=`who | tac | head -n 1 | cut -d'(' -f2 | cut -d')' -f1`
SERVER=`hostname`

SLACK_MESSAGE="`${USER}` loggined `${SERVER}` at `${TIME}` from `${IP}`"
SLACK_WEBHOOK_URL='https://hooks.slack.com/services/XXXXXXXXXXXX'
SLACK_CHANNEL='#alerts'
SLACK_USERNAME='ssh-notice'
SLACK_ICON_EMOJI='fish'

curl -X POST --data-urlencode 'payload={"channel": "'"$SLACK_CHANNEL"'", "username": "'"$SLACK_USERNAME"'", "text": "'"${SLACK_MESSAGE}"'", "icon_emoji": "'":${SLACK_ICON_EMOJI}:"'"}' ${SLACK_WEBHOOK_URL}

動作の様子