20200518のPythonに関する記事は30件です。

初学者向け Python 練習問題 #1 [基本的なデータ型・if 文]

この問題集は、社内の企画職やマーケティング職の方向けに作成しました。
毎回10問程度出題しています。

分からない問題があるときには、「必要な知識」でググると出てくるはずです。上手にググることは、上達の一番のコツだと思います。最初は難しいですが、粘り強く勉強していきましょう!!

目標

  1. 四則演算ができるようになる
  2. 異なるデータ型の扱いに慣れる
  3. if文で簡単な条件分岐ができるようになる

レベルについて


とてもよく使います。できるようにしましょう。

★★
少し複雑な処理をするときに使います。

★★★
よく勉強できていますね。Python初心者を卒業できそうです。

★★★★
トリッキーですが Python ではよく使います。覚えておきましょう。

★★★★★
おぉ。業務でもPython利用できそうですね!さすがです。


出題内容

Python チュートリアル
3. An Informal Introduction to Python
https://docs.python.org/ja/3/tutorial/introduction.html
4.4. More Control Flow Tools
https://docs.python.org/ja/3/tutorial/controlflow.html

  • 基本的なデータ型
    • int
    • float
    • string
    • bool
    • list # 後で詳しく扱います
    • tuple # 後で詳しく扱います
    • dict # 後で詳しく扱います
    • set # 後で詳しく扱います
  • if 文

問題1

レベル ★
必要な知識 数値の演算/インタープリタでの実行

対話型インタープリターで

1 + 2
3 - 4
2 * 5
8 / 2

を実行したときの結果を予測し、実際に入力して確かめてください。

問題2

レベル ★
必要な知識 数値の演算/インタープリタでの実行

対話型インタープリタで、7 を 3 で割った商とあまりを求めてください。
ただし、商とあまりは整数とします。

問題3

レベル ★
必要な知識 数値の演算/コマンドラインからの実行/変数への格納/数値の出力

以下の問題は otsukai.py ファイルを作成しコマンドラインから実行してください。

あるサイトの累計ユーザー数は50,000人です。
今月は3,000人がサイトを訪れました。
この月のユーザー全体の人数に対する訪問人数の割合はどれくらいですか?

来月もこのスクリプトを使いたいので、数値は変数に格納しましょう。

問題4

レベル ★
必要な知識 文字列/文字列の結合/文字列の出力

以下の問題は aisatsu.py ファイルを作成しコマンドラインから実行してください。

あなたはWebサイトを作成しています。
name にはユーザーの名前が、message には挨拶が入力されています。
sentence 変数に、挨拶と名前を結合した文章を格納し、出力してください。
sample.py
name = 'taro'
message = 'こんにちは'

# 以下で sentence に 'こんにちは taro' となるように格納してください。
# 格納できたら sentence を出力してください

問題5

レベル ★
必要な知識 文字列の繰り返し

以下の問題は cry.py ファイルを作成しコマンドラインから実行してください。

「く」 を50文字、「そ」 を 25 文字、「ぉ」 を 10 文字連結して出力してください。

(出力例) くくくく....くくくくくそそ.....そそそぉぉぉぉぉぉぉぉぉぉ

問題6

レベル ★
必要な知識 真偽値

以下の出力結果を予測しコマンドラインで実行して確かめてください。

2 < 2
2 <= 2
3 > 1
0 == 0
0 != 0
1 is 2
3 is not -3
'3' is 3
2020 > 2019 and 2019 > 2018
2020 > 2019 or 2021 < 2020
True or False or True and False

問題7

レベル ★
必要な知識 配列へのアクセス

これ以降の問題は適切な名前のファイルを作成し、コマンドラインから実行してください。

ユーザーの名前と年齢のデータが配列に格納されています。
この配列の中から「tadokoroさん」のデータを出力してください。
sample.py
data = ['kobayashi 23', 'tanaka 53', 'tadokoro 24']

# data のなかから tadokoro さんのデータを出力してください

問題8

レベル ★
必要な知識 if 文

あなたは信号機をつくっています。
signal が 'red' なら 'stop'を 'yellow' なら 'caution!' を、
'blue' なら 'GOGO!' と出力してください。
sample.py
signal = 'red' # これは 'yellow' になるかもしれないし 'blue' になるかもしれません

# 以下でif文を利用して条件分岐してください。

問題9

レベル ★★
必要な知識 文字列の操作/文字列へのスライス/文字列の長さ

あなたは動画のサムネイルの下に説明文を追加しようと考えています。
しかし、文章が長すぎると収まらないので 20 文字より大きいの場合は19文字で切って、
 '...' を付け加える処理をしようと考えています。

20文字以下の場合はそのまま出力してください。
sample.py
sentence = 'これは、サンプルのセンテンスです。20文字以上の場合には長すぎて収まらないのでいい感じに収めていただけると助かります。'

# 以下でsentence の長さによって出力を分けてください

問題10

レベル ★★★
必要な知識 配列の長さ/配列へのアクセス

rank リストには、今年の販売業績が高い順に名前が入っています。

以下のデータを抽出してください。
(1) もっとも業績の良い3人
(2) 業績の良さが奇数番目の人
(3) もっとも業績の悪い3人
(4) 真ん中の業績の人 (ただし総人数が偶数nの時は、n/2 番目の人と n/2 - 1 番目の人も出力してください)

sample.py
rank = ['tanaka', 'sasaki', 'satou', 'simizu', 'koizumi', 'yoshioka', 'tamaru', 'kiyomiya']

# 以下で (1) - (4) を出力してください。
# また、(4) については、rank リストから一人減らしても 、正しく出力されているかを確認してください。

さていかがだったでしょうか。
実力をつけるにはアウトプットが重要です。

できなくてもあきらめずに取り組んでいきましょう!
次回は今回までの知識をベースに for 文と while 文について扱っていきます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

gzip 圧縮されたテキストファイルを書き出す

こんにちは。
Python で gzip 圧縮されたテキストファイルを書き出してみました。中身は CSV データです。"Using csv.DictWriter to output an in-memory gzipped csv file?" (Stack Overflow) を参考にしました。

$ ./write_csv_gzfile.py temp.csv.gz
$ gzip -dc temp.csv.gz
a,b
1,2
3,4
write_csv_gzfile.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import io, csv, gzip, sys
from pathlib import Path
buffer = io.BytesIO()
with gzip.GzipFile(fileobj=buffer, mode='wb') as compressed:
    with io.TextIOWrapper(compressed, encoding='utf-8', newline='\n') as wrapper:
        writer = csv.DictWriter(wrapper, ["a", "b"], lineterminator='\n')
        writer.writeheader()
        writer.writerows([{'a': 1, 'b': 2}, {'a': 3, 'b': 4}])
p = Path(sys.argv[1])
p.write_bytes(buffer.getvalue())
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

クラスDiscordに課題管理Botを導入した話

こんばんは。りーぜんとです。

題名の通りです。経緯↓

流行りのオンライン授業が弊学でも始まりました。草ですね。

もはやオンライン授業の代名詞となっているZoomではなくMicrosoftのTeamsというものを使って授業が行われています。このTeams、学生側は課題が絶望的に管理しづらいです。(個人の意見)

じゃあクラスのDiscordに管理用Bot作って入れれば良いじゃん。(名案)

他の高校生たちがクラスDiscordを運用してるかは知りませんが、うちはあるので活用するまでです。

要は、この記事ではPythonを使ってdiscordのbotを作る方法を説明します。
作成したコードはGitHubに載っているので参考にしてみてください。

目次

  1. Discord上でBotアプリを作る
  2. とりあえず接続してみる
  3. コマンドを追加してみる
  4. まとめ

Discord上でBotアプリを作る

新しいBotをDiscordに登録します。Discord Developer Portalにアクセスします。
右上のNew Applicationをクリックして好きな名前(Botの名前になります)で新しいアプリケーションを作成しましょう。
Applicatonis.png

次に左のタブのBotからAdd Botします。
Usernameの下にTokenというところがあるのでCopyしてどこかに保存しておきましょう。
Bot.png

今度はOAuth2タブに移動します。Scopesの中のbotにチェックマークを入れて、Bot PermissionのSend messagesとManage messagesにチェックを入れます。
OAuth2.png

出てきたリンクを開いてBotを自分が参加しているDiscordサーバーに追加することができます。
Discordを開いて確認してみましょう。

とりあえず接続してみる

今回はPythonを使ってコードを書きます。discord.pyを使うので各自使えるようにしておいてください。

とりあえずメッセージに反応するようにします。

main.py
import discord

TOKEN = '***'

client = discord.Client()

@client.event
async def on_message(message):
    if message.content == 'hello':
        await message.channel.send('hi')

client.run(TOKEN)

TOKENDiscord上でBotアプリを作るでコピーした文字列に置き換えてください。

実行してみましょう。
スクリーンショット 2020-05-18 23.02.21.png

かわいいですね。

client.run()でBotを実行してます。
on_message()は何かメッセージが送信されたときに実行されます。送られてきた内容がhelloだったときにhiと返すだけのBotです。
次はよくある!pのようなコマンドを実装してみます。

コマンドを追加してみる

コマンドの数だけif文はセンスがないのでコマンドリストを作っておいてそこからコマンドを読むようにしましょう。

main.py
import discord

assignment_list = []

client = discord.Client()

async def kadaihelp(message):
    string = 'コマンドリスト\n'
    for command in COMMANDS:
        string += '------------------------\n'
        string += '{}: {}\n'.format('!' + command, COMMANDS[command]['description'])
        string += '    使い方: {}\n'.format(COMMANDS[command]['use'])
        string += '    省略形: {}\n'.format(COMMANDS[command]['alias'])
        string += '------------------------'

    await message.channel.send(string)

async def newkadai(message):
    msg = message.content.split(' ')
    try:
        title, deadline, memo = msg[1:]

        assignment_list.append({
            'title': title,
            'deadline': deadline,
            'memo': memo
        })

        await message.channel.send('課題を追加しました!')
    except:
        await message.channel.send('入力形式が間違っています。')

async def deletekadai(message):
    msg = message.content.split(' ')
    for i in range(len(assignment_list)):
        if assignment_list[i]['title'] == msg[1]:
            assignment_list.pop(i)
            await message.channel.send('課題を削除しました')

async def kadailist(message):
    string = '課題一覧\n'
    for i, assignment in enumerate(assignment_list):
        string += '------------------------\n'
        string += '{}. {}\n'.format(i + 1, assignment['title'])
        string += '締切: {}\n'.format(assignment['deadline'])
        string += '備考: {}\n'.format(assignment['memo'])
        string += '------------------------\n'

    string += '現在、{}個の課題が出されています。'.format(len(assignment_list))
    await message.channel.send(string)

async def close(message):
    await client.close()

TOKEN = '***'
COMMANDS = {
    'kadaihelp': {
        'description': 'このリストを表示します。',
        'use': '!kadaihelp',
        'alias': '!kh',
        'func': kadaihelp
    },
    'newkadai': {
        'description': '新しい課題を追加します。',
        'use': '!newkadai \{タイトル\} \{締切\} \{備考\}',
        'alias': '!nk',
        'func': newkadai
    },
    'deletekadai': {
        'description': '課題削除',
        'use': '!deletekadai \{課題名\}',
        'alias': '!dk',
        'func': deletekadai
    },
    'kadailist': {
        'description': '登録されている課題一覧を表示します。',
        'use': '!kadailist',
        'alias': '!kl',
        'func': kadailist
    },
    'exit': {
        'description': 'Botを終了します。',
        'use': '!exit',
        'alias': '!ex',
        'func': close
    }
}

@client.event
async def on_ready():
    print('KadaiShosu起動')

@client.event
async def on_message(message):
    msg = message.content.split(' ')

    if message.author.bot:
        return

    for command in COMMANDS:
        if msg[0] in ['!' + command, COMMANDS[command]['alias']]:
            await COMMANDS[command]['func'](message)

client.run(TOKEN)

とりあえず5つコマンドを追加してみました。
COMMANDSの中に辞書形式でコマンドリストを作成しました。on_message()の中で全てのコマンドに対して呼び出されたかどうかを判別して実際にその動作を行う関数を実行します。

それぞれのコマンドに省略形としてaliasを用意しました。もっと良い書き方があるかもしれません。是非いろいろ試してみてください。

ここまででとりあえず課題管理Botとしては機能します。しかし、このままではプログラムを閉じたときに課題がリセットされてしまいます。
最後に変数を保存、読み込む部分だけ書いてみます。

main.py
import pickle

async def close(message):
    pickle.dump(assignment_list, open('assignments.pkl', 'wb'))

    await message.channel.send('Bye^^')

    await client.close()

def load_assignments():
    global assignment_list

    assignment_list = pickle.load(open('assignments.pkl', 'rb'))

load_assignments()

新しくpickleというモジュールを使います。このモジュールは変数の保存を実現してくれます。

close()は書き換えて、他は書き足してください。
!exitでBotを終了したときにassignments_listが保存されて、起動時に読み込まれます。

まとめ

今回はここまでとします。他にもいろいろな機能があると嬉しいと思うので、自分で実装してみてください。

よければTwitterフォローしてください。じゃあね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

オフラインで Python 環境構築

インターネットに接続していない端末に Python パッケージをインストールしたいときのやり方メモ。

インストールしたいパッケージの一覧は requirements.txt にまとまっているものとする。

Windows 10 (x86_64) + Anaconda3 環境で検証したが、他のプラットフォームでも同様にできる気がする。

オンライン端末でパッケージをダウンロードする

$ pip download --dest=src -r requirements.txt

このオンライン端末はオフライン端末 (インストール先) とプラットフォームが同じものを使う。 pip download には --platform というオプションがあるので、これをうまく指定すると異なるプラットフォームでもできるのかもしれないが未検証。
参考: pip download — pip documentation

src ディレクトリを見ると、パッケージが wheel や tarball の形式でダウンロードできていることがわかる。

オフライン端末でパッケージをインストールする

オンライン端末でダウンロードしたパッケージファイルをオフライン端末にコピーし、 src ディレクトリ以下に置いてあるものとする。

$ pip install --no-index --find-links=src -r requirements.txt

これで requirements.txt をもとにパッケージのインストールを試みるが、PyPI (インターネット) からではなく src ディレクトリにあるパッケージを探しに行くようになる。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Python】【BFS】AtCoder Beginner Contest 168-Dを解く

問題文 .. (Double Dots)

ABC168-D Double Dots

あるところに、洞窟があります。
洞窟にはN個の部屋とM本の通路があり、部屋には1からNの、通路には1からMの番号がついています。
通路iは部屋Aiと部屋Biを双方向につないでいます。どの2部屋間も、通路をいくつか通って行き来できます。
部屋1は洞窟の入り口がある特別な部屋です。
洞窟の中は薄暗いので、部屋1以外の各部屋に1つずつ道しるべを設けることにしました。
各部屋の道しるべは、その部屋と通路で直接つながっている部屋の1つを指すように置きます。
洞窟の中は危険なので、部屋1以外のどの部屋についても以下の条件を満たすことが目標です。
その部屋から出発し、「いまいる部屋にある道しるべを見て、それが指す部屋に移動する」ことを繰り返すと、
部屋1に最小の移動回数でたどり着く。
目標を達成できる道しるべの配置が存在するか判定し、存在するならばそのような配置を1つ出力してください。

解答

from collections import deque

mi = lambda:list(map(int,input().split()))
mix = lambda x:list(mi() for _ in range(x))
##########
def main(n,m,l):
    #グラフの作成
    g = creategraph(n,l)
    #独立した部屋がある場合はNG
    for i in range(1,len(g)):
        if len(g[i]) == 0:
            print("No")
            return
    #BFSで道標を作成
    navi = bfs(n,g)
    #部屋1を除く部屋の道標が更新されていなければNG
    if min(navi[2:]) == 0:
        print("No")
    else:
        print("Yes")
        for i in navi[2:]:
            print(i)

#BFS幅優先探索
def bfs(n,g):
    navi = [0]*(n+1)
    navi[1] = -1
    q = deque()
    q.append(1)
    while len(q) > 0:
        #キューから現在地を取り出す
        cp = q.popleft()
        #現在地の隣接点リストを取得
        np = g[cp]
        #隣接点が未探索ならキューに入れる+道標更新
        for i in np:
            if navi[i] == 0:
                navi[i] = cp
                q.append(i)
    return navi

#各Nodeの隣接点を保持するリストを生成する
def creategraph(n,l):
    g = {i:[] for i in range(n+1)}
    for a,b in l:
        g[a].append(b)
        g[b].append(a)
    return g

def resolve():
    n,m = mi()
    l = mix(m)
    main(n,m,l)

if __name__ == "__main__":
    resolve()
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Python】【BFS】AtCoder Beginner Contest 168-D [.. Double Dots]

※競技プログラミングででてくるアルゴリズムの実装方法をまとめる自分用備忘録です。

問題文 (.. Double Dots)

ABC168-D Double Dots

あるところに、洞窟があります。
洞窟にはN個の部屋とM本の通路があり、部屋には1からNの、通路には1からMの番号がついています。
通路iは部屋Aiと部屋Biを双方向につないでいます。どの2部屋間も、通路をいくつか通って行き来できます。
部屋1は洞窟の入り口がある特別な部屋です。
洞窟の中は薄暗いので、部屋1以外の各部屋に1つずつ道しるべを設けることにしました。
各部屋の道しるべは、その部屋と通路で直接つながっている部屋の1つを指すように置きます。
洞窟の中は危険なので、部屋1以外のどの部屋についても以下の条件を満たすことが目標です。
その部屋から出発し、「いまいる部屋にある道しるべを見て、それが指す部屋に移動する」ことを繰り返すと、
部屋1に最小の移動回数でたどり着く。
目標を達成できる道しるべの配置が存在するか判定し、存在するならばそのような配置を1つ出力してください。

解答

from collections import deque

mi = lambda:list(map(int,input().split()))
mix = lambda x:list(mi() for _ in range(x))
##########
def main(n,m,l):
    #グラフの作成
    g = creategraph(n,l)
    #独立した部屋がある場合はNG
    for i in range(1,len(g)):
        if len(g[i]) == 0:
            print("No")
            return
    #BFSで道標を作成
    navi = bfs(n,g)
    #部屋1を除く部屋の道標が更新されていなければNG
    if min(navi[2:]) == 0:
        print("No")
    else:
        print("Yes")
        for i in navi[2:]:
            print(i)

#BFS幅優先探索
def bfs(n,g):
    navi = [0]*(n+1)
    navi[1] = -1
    q = deque()
    q.append(1)
    while len(q) > 0:
        #キューから現在地を取り出す
        cp = q.popleft()
        #現在地の隣接点リストを取得
        np = g[cp]
        #隣接点が未探索ならキューに入れる+道標更新
        for i in np:
            if navi[i] == 0:
                navi[i] = cp
                q.append(i)
    return navi

#各Nodeの隣接点を保持するリストを生成する
def creategraph(n,l):
    g = {i:[] for i in range(n+1)}
    for a,b in l:
        g[a].append(b)
        g[b].append(a)
    return g

def resolve():
    n,m = mi()
    l = mix(m)
    main(n,m,l)

if __name__ == "__main__":
    resolve()
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python入門 開発環境を整えよう

以下のサイトの手順で行いました。
https://prog-8.com/docs/python-env

③pyenvのインストールでコマンドを実行する際に
 ~/.bash_profile に権限(ロック)がかかっていたためコマンドを認識してくれませんでした。以下を実行してからコマンドを実行したところ正しくインストールできました。

1.ルートフォルダを表示する
command + shift + G
2.隠しファイルを表示する
command + shift + .(コマンド + シフト + ピリオド)
3.bash_profileをクリックし、「情報を見る」からロックを解除

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

深さ優先探索(DFS)と幅優先探索(BFS)をpythonで実装する

やったこと

AtCoder Typical Contest 001 の A - 深さ優先探索 をpythonで実装してみた。

この問題は、深さ優先探索(DFS: Depth First Search)でも幅優先探索(BFS: Breadth First Search)でも解くことができるため、両方実装した。

また、深さ優先探索については、再帰関数を用いるパターンとスタック(LIFO)を用いるパターンでそれぞれ実装した。

DFS

再帰関数を用いるケース

# A 深さ優先探索 再帰関数
import sys
sys.setrecursionlimit(10**7) #再帰関数の呼び出し制限

def dfs(v_x, v_y, seen_list, c_list):
    if v_x < 0 or v_y < 0 or v_x >= w or v_y >= h:
        return
    if seen_list[v_y][v_x]:
        return
    if c_list[v_y][v_x] == "#":
        return
    seen_list[v_y][v_x] = True
    dfs(v_x+1,v_y,seen_list,c_list)
    dfs(v_x-1,v_y,seen_list,c_list)
    dfs(v_x,v_y+1,seen_list,c_list)
    dfs(v_x,v_y-1,seen_list,c_list)

h, w = map(int, input().split())
seen_list = [[False] * w for i in range(h)]
c_list = []
s = []
g = []
for i in range(h):
    _tmp = list(input())
    if "s" in _tmp:
        s = [_tmp.index("s"),i]
    if "g" in _tmp:
        g = [_tmp.index("g"),i]
    c_list.append(_tmp)

dfs(s[0],s[1],seen_list,c_list)

if seen_list[g[1]][g[0]]:
    print("Yes")
else:
    print("No")

スタックを用いるケース

# A 深さ優先探索 スタック(LIFO)
from collections import deque

def dfs(stack, seen_list, c_list):
    while len(stack)>0:
        v_x, v_y = stack.pop()
        if v_x < 0 or v_y < 0 or v_x >= w or v_y >= h:
            continue
        if seen_list[v_y][v_x]:
            continue
        if c_list[v_y][v_x] == "#":
            continue
        seen_list[v_y][v_x] = True
        stack.append([v_x+1,v_y])
        stack.append([v_x-1,v_y])
        stack.append([v_x,v_y+1])
        stack.append([v_x,v_y-1])

h, w = map(int, input().split())
seen_list = [[False] * w for i in range(h)]
c_list = []
s = []
g = []
stack = deque()

for i in range(h):
    _tmp = list(input())
    if "s" in _tmp:
        s = [_tmp.index("s"),i]
    if "g" in _tmp:
        g = [_tmp.index("g"),i]
    c_list.append(_tmp)

stack.append(s)
dfs(stack,seen_list,c_list)

if seen_list[g[1]][g[0]]:
    print("Yes")
else:
    print("No")

BFS

# A 深さ優先探索 幅優先探索で解く キュー(FIFO)
from collections import deque

def bfs(que, seen_list, c_list):
    while len(que)>0:
        v_x, v_y = que.pop()
        if v_x < 0 or v_y < 0 or v_x >= w or v_y >= h:
            continue
        if seen_list[v_y][v_x]:
            continue
        if c_list[v_y][v_x] == "#":
            continue
        seen_list[v_y][v_x] = True
        que.appendleft([v_x+1,v_y])
        que.appendleft([v_x-1,v_y])
        que.appendleft([v_x,v_y+1])
        que.appendleft([v_x,v_y-1])

h, w = map(int, input().split())
seen_list = [[False] * w for i in range(h)]
c_list = []
s = []
g = []
que = deque()

for i in range(h):
    _tmp = list(input())
    if "s" in _tmp:
        s = [_tmp.index("s"),i]
    if "g" in _tmp:
        g = [_tmp.index("g"),i]
    c_list.append(_tmp)

que.append(s)
bfs(que,seen_list,c_list)

if seen_list[g[1]][g[0]]:
    print("Yes")
else:
    print("No")
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Docker内でMySQLにPythonで接続する

DocekrもMySQLも初心者用のメモ

github url: https://github.com/kenjiSpecial/docker_mysql_python_beginner

docker-compose.yml

docker-compose.yml
version: "3"

services:
  mysql_db:
    container_name: "mysql_db"
    image: mysql:5.7
    command: mysqld --character-set-server=utf8 --collation-server=utf8_unicode_ci
    volumes:
      - db_volume:/var/lib/mysql
    environment: # Set up mysql database name and password
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: employees
      MYSQL_USER: user
      MYSQL_PASSWORD: password
    networks:
      - app-tier

  python3:
    restart: always
    build: ./python
    container_name: "python3"
    working_dir: "/root/"
    tty: true
    depends_on:
      - mysql_db
    networks:
      - app-tier
    volumes:
      - ./python:/root
      - pycache_volume:/root/.cache

networks:
  app-tier:
    driver: bridge

volumes:
  db_volume:
  pycache_volume:

DockerfileにPythonのコンテナ情報を入力する

Dockerfile
FROM python:3.7


# ADD . /root
COPY . /opt
WORKDIR /opt

RUN pip install -r requirements.txt

main.py

main.py
import mysql.connector as mysql
user_name = "user"
password = "password"
host = "mysql_db"  # docker-composeで定義したMySQLのサービス名
database_name = "employees"


conn = mysql.connect(
    host="mysql_db",
    user="user",
    passwd="password",
    port=3306,
    database="employees"
)

conn.ping(reconnect=True)

print(conn.is_connected())
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ファイルを保存時に自動でテストを実行する

テスト駆動開発において、「ファイルを更新」 -> 「コンソールでテストコマンドを実行」 というプロセスが面倒でした。
そこで、このプロセスを自動化する方法を記載します。

動作イメージ

test.gif

環境

  • Ubuntu 18.04

準備

sudo apt install inotify-tools

コード

以下のスクリプトを作成し、プロジェクトのルートに配置します。

autorun.sh
#!/usr/bin/env bash

TEST_RUNNER="pytest -s tests" # 実行したいテストコマンドを指定
TARGETS="./src ./tests" # 監視したディレクトリを指定

while inotifywait -r -e modify -e create -e delete $TARGETS; do
  $TEST_RUNNER
done

実行権限も付与。

chmod +x ./autorun.sh

実行

./autorun.sh

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ゼロから始めるLeetCode Day29「46. Permutations」

概要

海外ではエンジニアの面接においてコーディングテストというものが行われるらしく、多くの場合、特定の関数やクラスをお題に沿って実装するという物がメインである。

その対策としてLeetCodeなるサイトで対策を行うようだ。

早い話が本場でも行われているようなコーディングテストに耐えうるようなアルゴリズム力を鍛えるサイト。

せっかくだし人並みのアルゴリズム力くらいは持っておいた方がいいだろうということで不定期に問題を解いてその時に考えたやり方をメモ的に書いていこうかと思います。

Leetcode

ゼロから始めるLeetCode 目次

前回
ゼロから始めるLeetCode Day28「198. House Robber」

基本的にeasyのacceptanceが高い順から解いていこうかと思います。

Twitterやってます。

問題

46. Permutations
難易度はMedium。
Top 100 Liked Questionsからの抜粋です。

異なる数字を集めたリストが与えられるので、全ての順列を返しなさい、という問題です。
タイトルのPermutationは順列という意味らしいですよ。

Example:

Input: [1,2,3]
Output:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]

これが全ての順列を返している例ですね。

解法

class Solution:
    def permute(self, nums: List[int]) -> List[List[int]]:
        return list(itertools.permutations(nums))
# Runtime: 36 ms, faster than 87.99% of Python3 online submissions for Permutations.
# Memory Usage: 14.1 MB, less than 5.36% of Python3 online submissions for Permutations.

はい。
あっけないですね。Python様の前ではLeetCodeのMediumも赤子同然です。

これにて終了です、対戦ありがとうございました。

ごめんなさい、流石にこれだとあまりにもひどいので他に解法を考えました。

class Solution:
    def permute(self, nums: List[int]) -> List[List[int]]:
        ans = []
        self.dfs(nums, [], ans)
        return ans

    def dfs(self, nums, emp, ans):
        if not nums:
            ans.append(emp)
        for i in range(len(nums)):
            self.dfs(nums[:i]+nums[i+1:], emp+[nums[i]], ans)
# Runtime: 32 ms, faster than 96.70% of Python3 online submissions for Permutations.
# Memory Usage: 13.9 MB, less than 5.36% of Python3 online submissions for Permutations.

dfsを別個に考えて実装しました。
numsの要素数が尽きるまで再起関数で回し続けるというものです。

スライスを使えば実装できます。

念のために書いておきますが、nums[:i]の場合は最初の要素からi番目までを取得し、nums[i+1:]の場合はi+1番目から最後の要素まで取得します。

そして、要素が無くなったときにempを元々用意していたリストのansに追加することで二次元配列にしています。

こちらの方が若干速くなりました。

良さげな解答があれば追記します。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

繰り返しテキストデータ生成ツール「rpttxt」を作りました

rpttxtとは?

こちら (https://github.com/nendo-code/rpttxt) で配布しております。

例えばこんな感じのデータを作りたいとします。

output.txt
{
    "id": 1,
    "name": "John",
    "age": 21
}
{
    "id": 2,
    "name": "Paul",
    "age": 35
}
{
    "id": 3,
    "name": "Tom",
    "age": 64
}

このデータを作る場合、あなたならどうしますか?
私なら、適当に手作業で済ませるのであればこんな感じにすると思います。

1.
まず1項目分作る。

step1.txt
{
    "id": 1,
    "name": "John",
    "age": 21
}

2.
必要な項目数分コピペする。

step2.txt
{
    "id": 1,
    "name": "John",
    "age": 21
}
{
    "id": 1,
    "name": "John",
    "age": 21
}
{
    "id": 1,
    "name": "John",
    "age": 21
}

3.
各項目の値を狙いの値に書き換える。

step3.txt
{
    "id": 1,
    "name": "John",
    "age": 21
}
{
    "id": 2,
    "name": "Paul",
    "age": 35
}
{
    "id": 3,
    "name": "Tom",
    "age": 64
}

まあこんな感じで手作業してもいいですが、件数が多いとダルいしミスも発生しやすいです。
データはデータ、テンプレートはテンプレートで分けて書けるといいなあ、と私は思いました。
なので、

テンプレートを書き

template.txt
{
    "id": {No},
    "name": "{Name}",
    "age": {Age}
}

データをcsvで書いて(またはExcel等でcsvでエクスポートして)

data.csv
{No},{Name},{Age}
1,John,21
2,Paul,35
3,Tom,64

それらを食わせると下記のような出力が得られるプログラムを作成しました。
それがrpttxtです。

output.txt
{
    "id": 1,
    "name": "John",
    "age": 21
}
{
    "id": 2,
    "name": "Paul",
    "age": 35
}
{
    "id": 3,
    "name": "Tom",
    "age": 64
}

なんで作ったの?

世の中に似たツールはありそうな気はしましたが、求めている操作感のものがパッと見つからなかったので、車輪の再発明しました。
あと、「UNIXという考え方―その設計思想と哲学」という本を読んで素晴らしいなあと思ったので、それっぽいシンプルなインターフェースのコマンドラインツールにしました。

(例)コマンドラインでの実行

$ python3 rpttxt.py template.txt data.csv > output.txt

なにで作ったの?

python3で書いて、Windows用のバイナリはPyInstallerで作りました。
PyInstallerは楽チンでとてもいいツールです。
あと、Windowsだとコマンドライン立ち上げるのがダルいので一発で変換かけるバッチファイルもオマケで付けました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

(覚書)matplodlibで3D 散布図を作る

3D 散布図のイメージを作りたかったので、
google colaboratoryでmatplotlibを使い画像ファイル作りました。

すべての点にラベルつけてます。

#matplotlibで日本語を使えるようにする
!pip install japanize-matplotlib

from mpl_toolkits.mplot3d import Axes3D
from matplotlib import pyplot
from numpy.random import rand
from pylab import figure
from google.colab import files
import pandas as pd
import japanize_matplotlib

#散布図の各点のラベル名と3次元座標
#点が多い場合はcsvとか読みこんだ方がいいかも
df = pd.DataFrame({'ラード説': [20, 30, 20],
                   '鶏油説': [10, 15, 15],
                   '香味油説': [5, -10, 10],
                   'ウェイパー説': [40, 50, 25],
                   '味の素説': [15, -30, -15],
                   '中華鍋説': [0, 0, 50],
                   '鍋のあおり説': [0, 5, 30],
                   '硬めご飯説': [-20, 20, 40],
                   'タイ米説': [-15, -25, 45],
                   '卵かけご飯説': [-35, -15, 45],
                   '酒説': [10, -20, -30],
                   'マヨネーズ説':[-5, 20, -10],
                   'チャーシュー説': [40, 10, -15],
                   '水島流チャーハン説':[10, -50, -50]
                   })

#画像サイズと解像度
fig = figure(figsize=(10, 10), dpi=100)
ax = fig.add_subplot(111, projection='3d')

#各点を描画していく
for i in range(df.shape[1]):
 ax.scatter(df.iloc[0,i],df.iloc[1,i],df.iloc[2,i])
 ax.text(df.iloc[0,i],df.iloc[1,i],df.iloc[2,i],  '%s' % (df.columns[i]), size=15)

#軸ラベル
ax.set_xlabel('美味しい - 微妙')
ax.set_ylabel('素材の味 - 調味料の味')
ax.set_zlabel('しっとり - パラパラ')

#軸の長さ
ax.set_xlim(-55, 55)
ax.set_ylim(-55, 55)
ax.set_zlim(-55, 55)

#出力するpngファイル名
pyplot.savefig( '炒飯のコツ-3D-散布図.png' )
pyplot.show()
#pngファイルのDL
files.download('炒飯のコツ-3D-散布図.png')

炒飯のコツ-3D-散布図.png

※ちなみにこの図は私が書いてる炒飯のブログで使ったものです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python)スクレイピング内容をローカルPCに保存

プログラミング初心者がスクレイピングを学んでいます。とりあえずスクレイピングした内容をローカルファイルにダウンロードできるようになったので忘れないうちに備忘録としてメモ。

#ウェブサイトから記事とURLをスクレイピングして結果をローカルPCにダウンロードするためのサンプル

#必要なモジュールのインポート
from bs4 import BeautifulSoup
import requests
import pandas as pd

# データフレームを作成
columns = ["記事タイトル", "URL"]
df = pd.DataFrame(columns = columns)

# requestsでウェブサイトから内容を取得してBeautifulSoupで内容を取捨選択など加工
res = requests.get("https:~~スクレイピングしたいウェブサイトのURL~~")
soup = BeautifulSoup(res.content, 'html.parser') # BeautifulSoupの初期化
tags = soup.find_all("XXXXX", {"class": "YYYYYY"}) # XとYはウェブサイトに応じて変わる

# 記事名とURLをデータフレームに追加
for tag in tags:
 article = tag.a.string
 url = tag.a.get("href")
 se = pd.Series([article, url], columns)
 df = df.append(se, columns)

#「to_csv」を使ってコードが保存されているのと同じフォルダにcsvファイルを保存
df.to_csv("./news.csv")
print("終了")
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

gRPCでElixirとPython間通信をする

1.はじめに

Elixir~Python間を、gRPCを使って異言語間通信を試してみます。

今回の記事では、

  • gRPCサーバ:Elixir
  • gRPCクライアント:Python3

としています。

実行環境

ハード Raspberry Pi 3B+
OS Raspbian Buster, Ubuntu Server 20.04LTS
Python 3.7.3
Elixir 1.7.4 (compiled with Erlang/OTP 21)

※いづれもaptパッケージでインストールできるもの

2.Elixir側

最初に、Elixir側の準備をします。

ここでは、elixir-grpcのライブラリに含まれているサンプル「examples/helloworld」を使ってみます。

クライアントからnameに名前を代入してリクエストすると、サーバからは"Hello "という文字列が帰ってきます。

※詳しい手順は別記事にまとめてますので、こちらをご覧下さい。

コマンドライン
$ pwd
..../elixir/
#クローンします
$ git clone https://github.com/elixir-grpc/grpc.git
#サンプルディレクトリに移動して
$ cd ./grpc/examples/helloworld/
#依存関係の処理、コンパイルします。
$ mix do deps.get, compile

2.Python側

次はPython側でクライアント・サーバを作る手順を示します。

(1)grpcツールのインストール

$ sudo apt install python3-grpcio python3-grpc-tools -y

(2)インターフェースの生成

protoファイルを解析して、Python向けのインターフェースを生成するツールを準備します。

コマンドライン
$ pwd
..../elixir/grpc/examples/helloworld
#Pythonのスクリプトを保存するフォルダを作成
$ mkdir python
$ cd python
#elixirのほうで定義している、protoファイルをコピー
python $ cp ../priv/protos/helloworld.proto ./
#ツールのファイルを生成
python $ touch codegen.py
python $ chmod 755 codegen.py

Python向けのインターフェースを生成するツールのソースコードです。
今回はhelloworld.protoを対象にしてるので、コード中では__NAME="helloworld"としています。

codegen.py
#!/usr/bin/env /usr/bin/python3
# -*- coding: utf-8 -*-
"""
protoファイルのコンパイル

以下の2つのファイルが生成されます。
・*_pb2.py      : シリアライズのインターフェース
・*_pb2_grpc.py : gRPCのインターフェース
"""
from grpc.tools import protoc

#protoのファイル名(の名前部分)
__NAME="helloworld"

#Python向けのインターフェースを生成
protoc.main(
    (
        '',
        '-I.',
        '--python_out=.',
        '--grpc_python_out=.',
        ('{}.proto'.format(__NAME)),
    )
)

Python向けのインターフェースを生成します。

コマンドライン
python $ ./codegen.py
python $ ls
codegen.py  helloworld_pb2_grpc.py  helloworld_pb2.py  helloworld.proto

*_pb2.pyと、*_pb2_grpc.pyの二つのファイルが生成されました。

(3)Python側クライアントの作成

クライアントのスクリプトを作成

コマンドライン
#ファイルを生成
python $ touch client.py
python $ chmod 755 client.py
client.py
#!/usr/bin/env /usr/bin/python3
# -*- coding: utf-8 -*-
"""
gRPCクライアント
by myasu 2020 
"""

import sys
import grpc
# 先ほどprotoから生成したインターフェースをインポート
import helloworld_pb2
import helloworld_pb2_grpc


def grpc_client(request):
    """gRPCクライアントの通信処理
    Parameters
    ----------
    request : string
        gRPCサーバに送るメッセージ
    """
    # Elixir側のgRPCサーバのアドレスとポートを指定して接続
    with grpc.insecure_channel('localhost:50051') as channel:
        stub = helloworld_pb2_grpc.GreeterStub(channel)
        # Elixir側のgRPCサーバにrequestする
        response = stub.SayHello(helloworld_pb2.HelloRequest(name=request))

    # responseの内容を確認
    print(' Response:', response.message)


if __name__ == '__main__':
    """メイン処理
    """
    # 引数の読み込み
    args = sys.argv
    # 引数の長さをチェック
    if len(args) == 2:
        # 実行
        grpc_client(args[1])
    else:
        # エラー
        print('Arguments are too short')

Python→Elixir→Python間の通信テスト

最初にサーバ・Elixir側を起動します。

ターミナル1・ElixirのgRPCサーバ側
$ mix grpc.server
10:58:01.872 [warn]  cowlib should be >= 2.9.0, it's 2.8.1 now. See grpc's README for details
10:58:01.977 [info]  Running Helloworld.Endpoint with Cowboy using http://0.0.0.0:50051

(・・・ここから先はクライアントの要求があったときに表示・・・)
22:58:04.739 [info]  Handled by Helloworld.Greeter.Server.say_hello
22:58:04.744 [info]  Response :ok in 4ms
22:58:09.237 [info]  Handled by Helloworld.Greeter.Server.say_hello
22:58:09.237 [info]  Response :ok in 15μs
22:58:13.552 [info]  Handled by Helloworld.Greeter.Server.say_hello
22:58:13.552 [info]  Response :ok in 15μs
[Ctrl-\]で停止

次にクライアント・Python側を実行します。
スクリプトの引数には、任意のメッセージが指定できます。

ターミナル2・PythonのgRPCクライアント側
python $ ./client.py chika
 Response: Hello chika
python $ ./client.py you
 Response: Hello you
python $ ./client.py ruby
 Response: Hello ruby
python $ ./client.py CYaRon!
 Response: Hello CYaRon!

メッセージの先頭に、サーバ側が"Hello "を付けて返してきます。
このような感じで、異種言語間の通信が出来ました。

4.参考資料

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pythonで書くガチャ-基本的データ構造における実装-

内容

ガチャのデータ設計(基本的構造1基本的構造2)に対応した実装を行います。

こちらのソースコードを改良

Pythonで書くガチャ-データ設計-

設定情報一覧

gacha_lottery

id gacha_group item_type times rarity omake_times omake_rarity cost
A_normal_1 A 0 1 0 0 0 10
A_normal_11 A 0 10 0 1 3 100
B_fighter_2 B 0 2 0 0 0 30
A_omake_2_11 A 0 9 2 2 3 200
A_omake_fighter_6 A 2 5 0 1 3 100

ガチャを実行する際に使用するIDという位置づけになりますので、具体的に分かりやすい文字列のIDにします

gacha_items

id gacha_group weight item_id
1 A 3 5101
2 A 9 4201
3 A 9 4301
4 A 9 4301
5 A 20 3201
6 A 20 3301
7 A 20 3401
8 A 40 2201
9 A 40 2301
10 A 40 2401
11 B 15 4201
12 B 30 3201
13 B 55 2201

items

id rarity item_name item_type HP
5101 5 UR_勇者 1 1200
4201 4 SSR_戦士 2 1000
4301 4 SSR_魔法使い 3 800
4401 4 SSR_神官 4 800
3201 3 SR_戦士 2 600
3301 3 SR_魔法使い 3 500
3401 3 SR_神官 4 500
2201 2 R_戦士 2 400
2301 2 R_魔法使い 3 300
2401 2 R_神官 4 300
3199 3 SR_勇者 1 600

rarity

id rarity_name
5 UR
4 SSR
3 SR
2 R
1 N

改修の観点

機能要件

基本的構造2のデータ構造で実現できるガチャの仕組みとして、以下の要件を満たすようにします。

  • ガチャ対象アイテムをグループ(母集団)として複数設定
  • ガチャ実行時に属性の絞り込み(item_type)
  • ガチャ実行時に通常抽選のレアリティ以上絞り込み(rarity)
  • ガチャ実行時に通常抽選の回数指定(times)
  • ガチャ実行時におまけのレアリティ以上絞り込み(omake_rarity)
  • ガチャ実行時におまけの抽選回数指定(omake_times)

データ構造の変更点

前回(Pythonで書くガチャ-データ設計-)の実装におけるデータ構造と大きく異なる点としてガチャ情報アイテム情報が分離されていることがあります。そのため、情報の紐付けが必要となります。

実装

gacha.py
import random

def gacha(lots, times: int=1) -> list:
    return random.choices(tuple(lots), weights=lots.values(), k=times)

def get_rarity_name(rarity: int) -> str:
    rarity_names = {5: "UR", 4: "SSR", 3: "SR", 2: "R", 1: "N"}
    return rarity_names[rarity]

# ガチャのIDと設定の辞書
def get_gacha_lottery_info(gacha_lottery_id: str) -> dict:
    gacha_lottery = {
        "A_normal_1":  {"gacha_group": "A", "item_type": 0, "times": 1, "rarity": 0, "omake_times": 0, "omake_rarity": 0, "cost":10},
        "A_normal_11":  {"gacha_group": "A", "item_type": 0, "times": 10, "rarity": 0, "omake_times": 1, "omake_rarity": 3, "cost":100},
        "B_fighter_2":  {"gacha_group": "B", "item_type": 0, "times": 2, "rarity": 0, "omake_times": 0, "omake_rarity": 0, "cost":30},
        "A_omake_2_11":  {"gacha_group": "A", "item_type": 0, "times": 9, "rarity": 2, "omake_times": 2, "omake_rarity": 3, "cost":200},
        "A_omake_fighter_6":  {"gacha_group": "A", "item_type": 2, "times": 5, "rarity": 0, "omake_times": 1, "omake_rarity": 3, "cost":100}
    }
    return gacha_lottery[gacha_lottery_id]

def set_gacha(items: dict, gacha_items: dict):
    # ガチャの設定に必要なアイテム情報をガチャアイテム情報に含める
    dic_gacha_items = {}
    for gacha_item_id, info in gacha_items.items():
        info["item_info"] = items[info["item_id"]]
        dic_gacha_items[gacha_item_id] = info

    # 抽選対象リストを抽出
    def get_lots(lottery_info: dict):
        lots = {}
        omake_lots = {}
        for id, info in dic_gacha_items.items():
            if lottery_info["gacha_group"] != info["gacha_group"]:
                continue
            if lottery_info["item_type"] and lottery_info["item_type"] != info["item_info"]["item_type"]:
                continue

            if not(lottery_info["rarity"]) or lottery_info["rarity"] <= info["item_info"]["rarity"]:
                lots[id] = info["weight"]

            if lottery_info["omake_times"]:
                if not(lottery_info["omake_rarity"]) or lottery_info["omake_rarity"] <= info["item_info"]["rarity"]:
                    omake_lots[id] = info["weight"]

        return lots, omake_lots

    # ガチャ実行
    def exec(gacha_lottery_id: str) -> list:
        lottery_info = get_gacha_lottery_info(gacha_lottery_id)
        lots, omake_lots =get_lots(lottery_info)
        ids = gacha(lots, lottery_info["times"])
        if len(omake_lots) > 0:
            ids.extend(gacha(omake_lots, lottery_info["omake_times"]))
        return ids

    return exec

def main():
    # アイテム情報
    items = {
        5101: {"rarity": 5, "item_name": "UR_勇者", "item_type": 1, "hp": 1200},
        4201: {"rarity": 4, "item_name": "SSR_戦士", "item_type": 2, "hp": 1000},
        4301: {"rarity": 4, "item_name": "SSR_魔法使い", "item_type": 3, "hp": 800},
        4401: {"rarity": 4, "item_name": "SSR_神官", "item_type": 4, "hp": 800},
        3201: {"rarity": 3, "item_name": "SR_戦士", "item_type": 2, "hp": 600},
        3301: {"rarity": 3, "item_name": "SR_魔法使い", "item_type": 3, "hp": 500},
        3401: {"rarity": 3, "item_name": "SR_神官", "item_type": 4, "hp": 500},
        2201: {"rarity": 2, "item_name": "R_戦士", "item_type": 2, "hp": 400},
        2301: {"rarity": 2, "item_name": "R_魔法使い", "item_type": 3, "hp": 300},
        2401: {"rarity": 2, "item_name": "R_神官", "item_type": 4, "hp": 300},
        3199: {"rarity": 3, "item_name": "SR_勇者", "item_type": 1, "hp": 600}
    }

    # ガチャアイテム情報
    gacha_items = {
        1:  {"gacha_group": "A", "weight": 3, "item_id": 5101},
        2:  {"gacha_group": "A", "weight": 9, "item_id": 4201},
        3:  {"gacha_group": "A", "weight": 9, "item_id": 4301},
        4:  {"gacha_group": "A", "weight": 9, "item_id": 4401},
        5:  {"gacha_group": "A", "weight": 20, "item_id": 3201},
        6:  {"gacha_group": "A", "weight": 20, "item_id": 3301},
        7:  {"gacha_group": "A", "weight": 20, "item_id": 3401},
        8:  {"gacha_group": "A", "weight": 40, "item_id": 2201},
        9:  {"gacha_group": "A", "weight": 40, "item_id": 2301},
        10: {"gacha_group": "A", "weight": 40, "item_id": 2401},
        11: {"gacha_group": "B", "weight": 15, "item_id": 4201},
        12: {"gacha_group": "B", "weight": 30, "item_id": 3201},
        13: {"gacha_group": "B", "weight": 55, "item_id": 2201}
    }

    # 実施するガチャのタプル
    gacha_lottery_ids = (
        "A_normal_1","A_normal_11","B_fighter_2","A_omake_2_11","A_omake_fighter_6"
    )

    #アイテム等をセット
    func_gacha = set_gacha(items, gacha_items)

    # gacha_lottery_idの設定にてガチャを実行
    for gacha_lottery_id in gacha_lottery_ids:
        print("==%s==" % gacha_lottery_id)
        ids = func_gacha(gacha_lottery_id)
        for id in ids:
            item_info = items[gacha_items[id]["item_id"]]
            print("ID:%d, %s, %s" % (id, get_rarity_name(item_info["rarity"]), item_info["item_name"]))


if __name__ == '__main__':
    main()

実行結果

gacha_lotteryのID分ガチャを実行して、挙動を確認します

==A_normal_1==
ID:10, R, R_神官
==A_normal_11==
ID:8, R, R_戦士
ID:10, R, R_神官
ID:10, R, R_神官
ID:6, SR, SR_魔法使い
ID:5, SR, SR_戦士
ID:8, R, R_戦士
ID:5, SR, SR_戦士
ID:8, R, R_戦士
ID:10, R, R_神官
ID:5, SR, SR_戦士
ID:7, SR, SR_神官
==B_fighter_2==
ID:13, R, R_戦士
ID:13, R, R_戦士
==A_omake_2_11==
ID:7, SR, SR_神官
ID:10, R, R_神官
ID:10, R, R_神官
ID:6, SR, SR_魔法使い
ID:8, R, R_戦士
ID:7, SR, SR_神官
ID:9, R, R_魔法使い
ID:9, R, R_魔法使い
ID:6, SR, SR_魔法使い
ID:4, SSR, SSR_神官
ID:1, UR, UR_勇者
==A_omake_fighter_6==
ID:8, R, R_戦士
ID:5, SR, SR_戦士
ID:5, SR, SR_戦士
ID:8, R, R_戦士
ID:5, SR, SR_戦士
ID:2, SSR, SSR_戦士

考察

機能要件は、満たすことができまました。
実際の運用に耐えうる仕組みとしては、不十分な箇所がありますが、
まずは、この構成が基本形となります。
今後は、運用上必要な機能要件を満たすために、新しい情報の追加、情報の再配置といったデータ構造の変更を行い、それに合わせて実装を改修していくことになります。

今回の実装において、改めて技術的に難しいことは特段導入していません。
端的に言えば、データ構造を再検討し実装を改修しただけです。
実際の開発現場に置いてサービス設計者には、企画者の原案(機能要件)を表現するために、文章の構成を見直しストーリーを書き直す脚本家のような能力が求められます。(大事なことなんですがイマイチ理解されていない現実があります

追記

情報量(マスタ)が多くなり、またユーザの状態も考慮したガチャを行うため、次回以降はDBを用いた実装を行います

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【python】PEP8に準拠したdocstringの書き方

PEP8規約を守らないと犬に吠えられる!?

pythonには,スタイルガイド,コーディング規約としてPEP8というものがあります.
チームでの場合は,PEP8規約を満たすのかコードをチェックすることにより,共通の書き方に揃い,より可読性が高くなると同時に開発がしやすくなります.
PEP8をチェックするのはgithubなどにpushするとき,またはpull requestを作るときが考えれられます.そういった段階でCIツールを利用して,テストをおこない,規約に反する箇所を知らせてくれます.
そういったツールの一つとして,reviewdogというものがあります.
ファイル名

PEP8規約に反する行動をとると,この犬に吠えられるのです..
ファイル名

私は,docstringに関するエラーH404,H405というもので犬に吠えられ続けて,頑張ってコードを直して犬に許してもらった話です.

エラーコードH4xxの内容

H4xx 系のエラーコードはdocstringに関するものになります.
docstringとは,関数やクラスメソッドを作成したときにその関数についての記述を行う場所です.
ちなみにこの良さそうに見えるdocstring, これだと犬に吠えられます!!(PEP8に完全には準拠していない)

def docstring_sample(hoge1: str):
    """ 
    ここがdocstringです.

    引数がある場合はここで説明したりします.(何個かテンプレートみたいなのがあるのでそれはそれで調べてみてください)
    今回はnumpyスタイルで

    Parameters
    ----------
        hoge1 : str
            ここに入力された文字列を出力する"""
    print(hoge1)

他にもいろいろなことに関するエラーコードがありこちらに素晴らしくまとまっています!

では,4xxの内容を見ていきましょう!

  • [H401] Docstrings should not start with a space.
  • [H403] Multi line docstrings should end on a new line.
  • [H404] Multi line docstrings should start without a leading new line.
  • [H405] Multi line docstrings should start with a one line summary followed

英語が苦手な僕は,H404とH405の理解に苦しみました...
では訳します(意訳です)

  • [H401] Docstringsの記述はスペースから始めてはいけません.
  • [H403] 複数行のdocstringを書く場合は,最後の行で改行してから閉じなさい.
  • [H404] 複数行のdocstringを書く場合は,最初に1行空けてから書きなさい.
  • [H405] 複数行のdocstringを書く場合は,1行その関数に関するまとめを書いてから書きはじめなさい.

ということです.

これを読んだときの僕は,
「なるほど,ということは先程のサンプルでは,最後に改行がない所,最初に一行空けてないところがだめだったんだ,書き直そう」
ということは,こんな感じか

def docstring_sample(hoge1: str):
    """ 

    引数hoge1を出力する関数.(最初まとめを書けって言われたから)
    ここがdocstringです.

    引数がある場合はここで説明したりします.(何個かテンプレートみたいなのがあるのでそれはそれで調べてみてください)
    今回はnumpyスタイルで

    Parameters
    ----------
        hoge1 : str
            ここに入力された文字列を出力する
    """
    print(hoge1)

ところが,これでは犬に吠えられます
エラーコードはH405です.
なぜだ..っというので数時間溶かしました.
(僕のところでは犬に吠えられる限りmasterにマージできないルールなのです...)

対処法

数時間していろいろやってみた結果,やっと犬に認められました!!
それは

H405はdocstringの一番最初に書く!

つまり,一行改行するまえにサマリを書く-> 一行改行するということです.

ようやく,PEP8規約に準拠したdocstringができました!

def docstring_sample(hoge1: str):
    """引数hoge1を出力する関数.

    ここがdocstringです.
    引数がある場合はここで説明したりします.(何個かテンプレートみたいなのがあるのでそれはそれで調べてみてください)
    今回はnumpyスタイルで

    Parameters
    ----------
        hoge1 : str
            ここに入力された文字列を出力する
    """
    print(hoge1)

ちなみに一行しかdocstringを書かない場合は文末の"""は改行しなくていいです.

def docstring_sample(hoge1:str):
    """引数hoge1を出力する関数"""
    print(hoge1)

まとめ

PEP8の中には,流石にこれは無視してよくね?っていうのが何個かあると思います.(1行の文字数制限とか)
だけど,そこで自分を律して律儀に書くと,それはそれで達成感ありますよ!
ぜひやってみてください!

下に参考文献を載せておきます!
numpyスタイルは僕も参考にしていつも使わさせていただいています!すごくわかりやすくてありがとうございます!

参考文献

https://blog.sideci.com/about-style-guide-of-python-and-linter-tool-pep8-pyflakes-flake8-haking-pyling-7fdbe163079d

[Python]可読性を上げるための、docstringの書き方を学ぶ(NumPyスタイル)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pythonとphpの違いまとめ(主要項目の対比表)

pythonとphpの違いまとめ(主要項目の対比表)

pythonとphpのクラスやメソッドなど主要項目の違いまとめ。

どっちがどっちかわからくなるの防止。


項目 Python PHP
関数 def 関数名(): function 関数名():
処理の終わり 改行 ;
変数 変数名 $変数名
クラス定義 class クラス名: class クラス名{}
コンストラクタ def __init__(self,): アクセス権 function __construct(){}
インスタンス クラス名() new クラス名()
プロパティ *1 インスタンス.プロパティ名 $プロパティ名
プロパティの呼び出し インスタンス名.プロパティ名 インスタンス->プロパティ名
インスタンス自身 self $this
自身のプロパティ呼び出し self.プロパティ名 $this->プロパティ名
メソッド def メソッド名(self,) アクセス権 function メソッド名()
メソッド呼び出し インスタンス.メソッド名() インスタンス->メソッド名()
継承 class クラス名(親クラス名): class クラス名 extends 親クラス名{}
ファイル読込み import モジュール名 require_once(' ')
クラス読込み from モジュール名 import クラス名 require_once(' ')
親クラスのメソッド呼び出し super().メソッド名() parent::メソッド名()
クラスメソッド @classmethod アクセス権 static function メソッド名(){}
クラスメソッドの呼び出し クラス名.メソッド名() クラス名::メソッド名()
クラスプロパティ なし(?) アクセス権 static $プロパティ名
クラスプロパティの呼び出し なし(?) クラス名::$プロパティ名
出力 print() echo/print
配列 [] array()
for文 for 変数 in 配列など: for($i=初期値: 条件式: ステップ){}
配列からひとつずつ抜き出す for 変数 in 配列: foreach($変数名 as 配列)
if文 if 条件式: if(条件式){}
else if elif elseif
switch文 なし switch(){case 条件: 処理; break;}
and and &&/and
or or パイプ2本/or
インクリメント演算子 なし ++
デクリメント演算子 なし --
整数型に変換 int() intval()
文字列型に変換 str() strval()
小数点に型変換 float() floatval()
3桁区切り '{:,d}'.format(数値) number_format()

*1:pythonではインスタンス変数と呼ぶ



表にして眺めてみると違いや、規則性がわかりやすい。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PythonとPHPの違いまとめ(主要項目の対比表)

PythonとPHPの違いまとめ(主要項目の対比表)

pythonとphpのクラスやメソッドなど主要項目の違いまとめ。

どっちがどっちかわからくなるの防止。


項目 Python PHP
関数 def 関数名(): function 関数名(){}
処理の終わり 改行 ;
コメントアウト # // or /* */
変数 変数名 $変数名
クラス定義 class クラス名: class クラス名{}
コンストラクタ def __init__(self): アクセス権 function __construct(){}
インスタンス クラス名() new クラス名()
プロパティ *1 インスタンス.プロパティ名 $プロパティ名
プロパティの呼び出し インスタンス名.プロパティ名 インスタンス->プロパティ名
インスタンス自身 self $this
自身のプロパティ呼び出し self.プロパティ名 $this->プロパティ名
メソッド def メソッド名(self) アクセス権 function メソッド名()
メソッド呼び出し インスタンス.メソッド名() インスタンス->メソッド名()
継承 class クラス名(親クラス名): class クラス名 extends 親クラス名{}
ファイル読込み import モジュール名 require_once(' ')
クラス読込み from モジュール名 import クラス名 require_once(' ')
親クラスのメソッド呼び出し super().メソッド名() parent::メソッド名()
クラスメソッド @classmethod アクセス権 static function メソッド名(){}
クラスメソッドの呼び出し クラス名.メソッド名() クラス名::メソッド名()
クラスプロパティ (メソッド定義と同列に)
プロパティ名
アクセス権 static $プロパティ名
クラスプロパティの呼び出し クラス名.プロパティ名 クラス名::$プロパティ名
出力 print() echo/print
配列 [] array()
キーあり配列 {キー名:値} array(キー名=>値)
キーあり配列呼び名 辞書型 連想配列
配列の要素数 len(配列) count(配列)
変数展開*2 f'{変数}' "${変数}"
for文 for 変数 in range(始値, 終値, ステップ)
※ 終値は含まない
for($変数名=初期値: 条件式: ステップ){}
配列からひとつずつ抜き出す for 変数 in 配列: foreach($変数名 as 配列)
if文 if 条件式: if(条件式){}
else if elif 条件式: elseif (条件式){}
switch文 なし switch(){case 条件: 処理; break;}
and and &&/and
or or パイプ2本/or
インクリメント演算子 なし ++
デクリメント演算子 なし --
整数型に変換 int() intval()
文字列型に変換 str() strval()
小数点に型変換 float() floatval()
3桁区切り '{:,d}'.format(数値)
f'{数値:,d}'
number_format()

*1. pythonではインスタンス変数と呼ぶ
*2. PHP:シングルクオテーションだと文字列として出力 
  python:f文字列の場合



表にして眺めてみると違いや、規則性がわかりやすい。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RayによるPython分散並列処理入門

Rayとは

RayはPythonにおける分散並列処理を高速かつシンプルに書けるフレームワークで, 既存のコードを並列化することも容易な設計となっています.
Rayを使うことでmultiprocessingなどに比べ簡単にプロセスレベルの並列処理を記述することができます.

本記事はRayチュートリアルの内容をもとにしており,
コードはPython 3.8.2, Ray 0.8.4での動作を確認しています.

インストール

ターミナルでpipなどからインストールできます.

$ pip install ray

使い方

基本的な用途としては覚える文法はray.init ray.remote ray.get の3つのみで, この記事では加えてray.wait ray.put も紹介します.

Rayによる並列化の基本

実行に3秒かかる関数 func が二度呼び出され全体の実行に6秒かかる以下のコードについて, func の実行を並列化することを考えましょう.

import time

def func(x):
    time.sleep(3)
    return x

begin_time = time.time() # 開始時刻を記録

res1, res2 = func(1), func(2) # funcを2度呼ぶ
print(res1, res2)  # 出力: 1 2

end_time = time.time() # 終了時刻を記録

print(end_time - begin_time) # 6秒ぐらい

Rayを使う場合には 必ず最初に ray.init で使用するリソース数の指定などを行いRayのプロセスを起動する必要があります.

import ray

# ray.init() のように明示的に指定しなかった場合自動的にリソース数が決定されます
ray.init(num_cpus=4)

# 時間計測をより正確にする都合上Rayの起動を少し待つ
time.sleep(1)

ある関数を並列で実行させたい場合, その関数をRayの扱える remote関数 にする必要があります.
といってもやり方は簡単で, その関数に@ray.remote とデコレーターをつけるだけです.
remote関数は(関数名).remote(引数) として呼び出すとRayの並列ワーカーに送られて実行されます.
.remote(引数) は終了を待たずに Object ID というものをreturnします.

@ray.remote
def func(x):
    time.sleep(3)
    return x

begin_time = time.time()
res1, res2 = func.remote(1), func.remote(2)
print(res1) # 出力例: ObjectID(45b9....) 

結果を取得したい場合には, remote関数から返ってきたObject IDをray.getに渡してあげればよいです.
ray.get はObject IDに対応する結果がすべて取得できるまでブロッキングします.

print(ray.get(res1), ray.get(res2)) # 出力: 1 2

# ray.getはリストを受けとることもできる
print(ray.get([res1, res2])) # 出力: [1, 2]

end_time = time.time()
print(end_time - begin_time) # 3秒ぐらい

以上のコードを1つのスクリプトとして実行すると3秒程度しかかかっておらず, func の実行が並列化されていることがわかります.
基本はこれだけです.

依存関係のある並列化

Rayはremote関数間に依存関係があっても, Object IDをそのまま受け渡すことで処理が可能です.
受け渡されたObject IDは実際に実行される際には通常のPythonオブジェクトに復元されて実行されます.
以下の例では, vec 内の4つの各要素に対してfunc1func2を順に適用しています. 1要素の処理には2秒かかります.
※これ以降の例では時間計測のためのコードを省略しています

@ray.remote
def func1(x):
    time.sleep(1)
    return x * 2

@ray.remote
def func2(x):
    time.sleep(1)
    return x + 1

vec = [1, 2, 3, 4]
results = []

for x in vec:
    res = func1.remote(x)  # resにはObjectIDが入っている
    res = func2.remote(res) # ObjectIDをそのまま次のremote関数に渡す
    results.append(res)

# resultsはObjectIDのリスト
print(ray.get(results)) # 出力: [3, 5, 7, 9]

Rayは依存関係を解析し, 依存先のない func1 を先に並列実行し,その後 func1 の処理の終わった要素について func2 を並列実行します.
逐次では8秒かかるこの処理は並列化により2秒程度で実行されます.

また, Rayはネストされた呼び出しにも対応しており, func2 を次のように書き換えても問題なく動作します.
ネスト呼び出しの条件は, 呼び出したい関数が事前に定義されていることだけです.

@ray.remote
def func2(x):
    x = func1.remote(x)  # ObjectIDが返される
    time.sleep(1)
    return ray.get(x) + 1 # ObjectIDと直接足し算は出来ないため, ray.getしてから計算する

print(ray.get([func2.remote(x) for x in vec])) # 出力: [3, 5, 7, 9]

私の環境での実測値は2秒より少し遅くなりましたが, 8秒よりは速く並列に実行できています.

Actor

remote関数は実行されたあとそのままreturnしてしまい状態を持つことができません.
状態をもつような処理を, Rayではクラスを@ray.remote で修飾することにより実現します.
@ray.remote で修飾されたクラスを Actor と呼びます.

例えば, 次のような一度のインクリメントにつき1秒かかるカウンターを考えましょう.
Actorのインスタンスを作る時も, 関数呼び出しのときと同様 .remote() を付けます.

@ray.remote
class Counter:
    def __init__(self, init_val, sleep=True):
        # カウンターをinit_valで初期化
        self.count = init_val
        self.sleep = sleep

    def increment(self):
        if self.sleep:
            time.sleep(1)
        self.count += 1
        return self.count

# 初期値0と100のカウンターを作る
counter1, counter2 = Counter.remote(0), Counter.remote(100)

それぞれのカウンターを3回ずつインクリメントしながら, 各段階での値をresultsに記録していきましょう.

results = []
for _ in range(3):
    results.append(counter1.increment.remote())
    results.append(counter2.increment.remote())

print(ray.get(results)) # 出力: [1, 101, 2, 102, 3, 103]

合計6回インクリメントがされていますが, カウンターごとに並列化されているので3秒しかかからずに値を取得することができます.

また, Actorの同一のインスタンスのメソッドを並列に呼び出したいときには, Actorのインスタンスを引数にとるremote関数を定義すればよいです.
例えば次のように, 1秒おきに increment を呼び出す incrementer という関数を0.5秒ずらして実行させてみましょう.
ここではincrement自体が一瞬で終わるようなCounterを用意していることに注意してください.

@ray.remote
def incrementer(counter, id, times):
    # 1秒おきにtimes回インクリメントを行う
    for _ in range(times):
        cnt = counter.increment.remote()
        print(f'id= {id} : count = {ray.get(cnt)}')
        time.sleep(1)

counter = Counter.remote(0, sleep=False) # 1回のインクリメントが一瞬で終わるカウンター

incrementer.remote(counter, id=1, times=5)
time.sleep(0.5)  # 開始を0.5秒ずらす
inc = incrementer.remote(counter, id=2, times=5)

ray.wait([inc]) # 次に説明する, 終了を待つ関数

実行すると, 次のように incrementercounter の値を0.5秒おきに交互に更新している様子がわかります.

(0.0秒後) id = 1 : count = 1
(0.5秒後) id = 2 : count = 2
(1.0秒後) id = 1 : count = 3
(1.5秒後) id = 2 : count = 4
(2.0秒後) ......

ray.wait

並列実行されているObject IDのリストを ray.get に渡すと, そのすべての実行が終了するまで値を取得できません.
ray.wait を使うと, 並列実行された関数のうち指定した数が終了するまで待機し, その時点で終了したIDとそうでないIDを別々にreturnしてくれます.

@ray.remote
def sleep(x):
    # x秒休んでxを返す関数
    time.sleep(x)
    return x

ids = [sleep.remote(3), sleep.remote(5), sleep.remote(2)]
finished_ids, running_ids = ray.wait(ids, num_returns=2, timeout=None)

print(ray.get(finished_ids)) # 出力(3秒経過時点): [3,2] 
print(ray.get(running_ids))  # 出力(5秒経過時点): [5]

ray.put

実は, remote 関数に渡されたオブジェクトは, そのつど暗黙裏にシリアライズされてRayの共有メモリ上にコピーされます.
そのため, 巨大なオブジェクトを remote の引数に複数回渡してしまうと余計にコピーするための時間がかかるほか, 共有メモリ上の領域を無駄に消費してしまいます.

このような場合には, ray.put を用いて事前に一度だけ明示的にコピーを行うことによりこの無駄を回避することができます.
ray.putremote と同様Object IDを返し, これをremote関数に渡してあげればよいです.
一度コピーされたオブジェクトは共有されているので, 並列実行するワーカーはどれもこのオブジェクトを参照することができます.

@ray.remote
def func4(obj, idx):
    time.sleep(1)
    return idx

# big_object はサイズの大きなobjectだとする
big_object = None

big_obj_id = ray.put(big_object)

# func.remote()が4回呼ばれるが, いま渡しているのはObjectIDのため再度big_objectのコピーは発生しない
results = [func4.remote(big_obj_id, i) for i in range(4)]

print(ray.get(results)) # 出力: [0, 1, 2, 3]

なお, Rayのray.getによるデシリアライズは pickle.load に比べて非常に高速であるようです.

おわりに

公式ドキュメントにはより詳細な使い方が載っています.
特にExamplesには分散環境でのパラメータサーバーや強化学習などの具体的な用例が載っていて参考になるでしょう.
またRayを基盤とした高レベルなフレームワークも用意されており, 強化学習向けのRLlibやハイパーパラメータチューニング向けのTuneなどがあります.
是非Rayを使って快適な並列処理ライフを手に入れましょう.

参考サイト

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Deep Learning Specialization (Coursera) 自習記録 (C2W1)

はじめに

Deep Learning Specialization の Course 2, Week 1 (C2W1) の内容です。

(C2W2L01) Train / Dev / Test sets

内容

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

doc2vecでredmineのチケット(csv)を学習させてみる

やったこと

問い合わせ履歴(redmineのチケット)を機械学習し
検索ワードに対して、類似する過去チケットをリコメンドするPoCを作ってみました。

背景

私はコールセンター的な業務をすることがあり、問い合わせの記録をredmineにチケットとして記録してます。

そんな中、最近流行りの機械学習を使った検索で、業務の効率化をできそうかを試してみたく、doc2vecでPoC作ってみました。

結論

結論としては、作りが雑すぎたためか、使い物にならない精度でした。
しかし、前処理とかもう少し頑張れば実運用でも使えるかもしれない可能性は感じました。

実装の流れ

大きく3段階あります。
① docker上のubuntuでjupyter-notebookを起動し、機械学習&チケットの検索の環境を用意
② doc2vecでチケット(csv)を機械学習させ、モデルを作成
③ 検索ワードを入力し、類似するチケットをリコメンド

環境

  • docker (windows10)
    • ubuntu:16.04
    • jupyter-notebook
      • python
      • doc2vec
  • redmineのチケット(csvファイル)

全体的に、すぐに使える!業務で実践できる!Pythonによる AI・機械学習・深層学習アプリのつくり方を参考にさせて頂いております。

作り方

※環境構築関連は、ありがちだけどPythonでチャットボット作ってみたでもう少し詳しく説明してます

1. dockerを起動し、Dockerfilebuildする

2. イメージをrunする

docker run -it -p 8888:8888 -v //c/Users/xxx/xxx:/xxx book-mlearn-image

xxxの部分にパスを入力して、ホストのディレクトリをマウントする

3. issues.csv(問い合わせ対応のcsv)をマウント済みのディレクトリに格納する

4. jupyter-notebookを起動する

jupyter notebook --no-browser --ip=0.0.0.0 --allow-root --NotebookApp.iopub_data_rate_limit=100000000

実行すると、http://127.0.0.1:8888/?token=xxxにアクセスしてね、と表示されますが、docker上で動いてるのでdocker-machine envで出てくるipアドレスに置き換えてアクセスしてください。

例:http://192.168.99.100:8888/id=xxx

5. 新規でpython3のファイルを作成する

6. 必要なものをインストールする

以下をコピペし実行します。時間かかります。

!curl -kL https://bootstrap.pypa.io/get-pip.py | python3

!apt-get update -y
!apt-get upgrade -y
# ?について、十分な空き容量がないとエラーになる場合がある。エラーメッセージでググったら解決法でる。

!apt-get -yV install swig-doc
!apt-get -yV install swig-examples
!apt-get -yV install swig2.0-doc
!apt-get -yV install swig2.0-
!apt-get -yV install swig2.0 
!apt-get -yV install swig

!apt-get install mecab libmecab-dev mecab-ipadic-utf8 -y

!git clone --depth 1 https://github.com/neologd/mecab-ipadic-neologd.git /tmp/work/
# ?のclone先をホストOSとの共有ディレクトリにしているとエラーとなる場合がある。
# そのためtmp/workに保存している

!mkdir /var/lib/mecab/dic/mecab-ipadic-neologd

!apt-get install file -y

!/tmp/work/bin/install-mecab-ipadic-neologd -n -p /var/lib/mecab/dic/mecab-ipadic-neologd -y
# ?はdockerのVM VirtualMachineのメモリ割り当てが1024MBの場合エラーが出ることがある。1.5GB(1538MB)くらいに増やすと私はエラー出なくなりました。

!pip install mecab-python3

import MeCab
import sys
import MeCab
m = MeCab.Tagger ("-d /var/lib/mecab/dic/mecab-ipadic-neologd")
# mecabの稼働確認
print(m.parse ("すもももももももものうち"))

# word2vecの準備
!pip install gensim

7. チケットを学習しモデルを作成

以下を実行します。

import pandas as pd
import zipfile
import os.path
import urllib.request as req
import MeCab
from gensim import models
from gensim.models.doc2vec import TaggedDocument

# ここの/xxx/はissues.csvのパスに修正してください
df = pd.read_csv("/xxx/issues.csv", encoding='cp932')

list =[] # リストへの格納方法が冗長な感じがしますがお許しください

#Mecabの初期化
mecab = MeCab.Tagger()
mecab.parse("")

for i in range(len(df["No"])):
    list.append(
        {"ticket":{
            "No":str(df["No"][i]), # No・・・チケットのナンバー(#)
            },
        "content":[
            {"title":str(df["題名"][i]),  # 題名・・・チケットのタイトル
            # csvの学習させたい項目を単純に連結させる
            # 担当者/内容/システム名/機能名/ページ名/説明・・・チケットの各項目名のサンプルです
            "detail":str(df["担当者"][i]) + str(df["内容"][i]) + str(df["システム名"][i]) + str(df["ページ名"][i]) + str(df["説明"][i])},
        ]},
    )

def ticket_list():
    for ticketlist in list:
        ticket = ticketlist["ticket"]
        for content in ticketlist["content"]:
            yield ticket, content

#引数のテキストを分かち書きして配列にする
def split_words(text):
    text = text.replace('\n','')
    text = text.replace('\r','')
    text = text.replace('\u3000','')
    node = mecab.parseToNode(text)
    wakati_words = []
    while node is not None:
        hinshi = node.feature.split(",")[0]
        if  hinshi in ["名詞"]:
            wakati_words.append(node.surface)
        elif hinshi in ["動詞", "形容詞"]:
            wakati_words.append(node.feature.split(",")[6])
        node = node.next
    return wakati_words

#リストをDoc2Vecが読めるTaggedDocument形式にし、配列に追加する
documents = []
#チケットリストをループで回す
for ticket, content in ticket_list():
    #文字列を取得
    words = content["detail"]
    #文字列を分かち書きに
    wakati_words = split_words(words)
    #TaggedDocumentの作成 文書=分かち書きにしたチケット詳細 タグ=No:題名
    document = TaggedDocument(
        wakati_words, [ticket["No"] + " : " + content["title"]])
    documents.append(document)

#TaggedDocumentの配列を使ってDoc2Vecの学習モデルを作成
model = models.Doc2Vec(
    documents, dm=1, vector_size=300, window=5, min_count=1)

#Doc2Vecの学習モデルを保存
model.save('redmine.model')

print("モデル作成完了")

8. 検索ワードを入れ、類似するチケットを表示させる

以下を実行します。

import urllib.request as req
import zipfile
import os.path 
import MeCab
from gensim import models

#Mecabの初期化
mecab = MeCab.Tagger()
mecab.parse("")

#保存したDoc2Vec学習モデルを読み込み
model = models.Doc2Vec.load('redmine.model')

#引数のテキストを分かち書きして配列にする
def split_words(text):
    node = mecab.parseToNode(text)
    wakati_words = []
    while node is not None:
        hinshi = node.feature.split(",")[0]
        if  hinshi in ["名詞"]:
            wakati_words.append(node.surface)
        elif hinshi in ["動詞", "形容詞"]:
            wakati_words.append(node.feature.split(",")[6])
        node = node.next
    return wakati_words

def similar(search_word):
    words = search_word
    wakati_words = split_words(words)
    vector = model.infer_vector(wakati_words)
    print("--- 「" + search_word + '」に類似するチケットは? ---')
    print(model.docvecs.most_similar([vector],topn=3))
    print("")

#
similar("ここに検索したい文章を入れる")

すると...

--- 「aaaしたいがbbbできない」に類似するチケットは? ---
[('1234 : cccしたいができない', 0.9499524831771851), ('2345 : dddする方法を教えて欲しい', 0.9499462246894836), ('3456 : eeeでエラーが発生した', 0.9499049782752991)]

と、類似するチケットが表示されます。

ここでいう、1234のような数字はチケットのNo、
cccしたいができないはチケットの題名、
0.9499524831771851はチケットの詳細と、similar("~")に入れた文章との類似度です。

類似度は1に近いほど似てるという意味です。
ここでは類似度が高い上位3件を表示させています。


以上です。

リコメンド精度が低い理由は、いろいろ考えられますが、一番の問題は前処理がテキトウすぎることかなと思ってます。
さすがに雑すぎでしたかね...。

ちなみに、redmineのチケットをcsv形式で出力して使っている理由は、DBにアクセスするコードを書くのが面倒だったためです。

また、問い合わせ履歴をいい感じに処理してくれる機械学習として、watson discoveryも少し触ってみたのですが、色々と覚えるのに時間がかかりそうだったためすぐ作れそうなdoc2vecで実装しました。

何か間違いやアドバイスあればコメントください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Visual Studio CodeでライブラリをインストールしたのにUnable to import

ライブラリをインストールしたのにUnable to import

Djangoでバージョンを合わせたのにvscodeにずっと指摘されたのでその原因と解消方法を書いておきます。

原因

実行環境とVSCodeの仮想環境が異なるからです。

例えば、A,BというPythonの仮想環境があったとしましょう。

Aの方にライブラリをインストールしたとしてもBの方をつかってVSCodeで動いていたらそりゃ指摘されますよねってことです.

解決策

  1. 左下のPythonのVersionが書いてある部分を押します。
    Sept19-Linting.png

  2. 複数環境が出てくるので実行してる環境に合わせてあげることで指摘は消えるはずです。お疲れ様でした。

参考

https://stackoverflow.com/questions/48270385/vs-code-error-when-importing-django-module

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Python】Excelと同じ切片0の近似式の算出方法【scikit-learn】メモ

概要

  • Excelには、切片固定で近似式を算出する方法がある
  • numpy.polyfitでは、切片固定ができない。
  • scikit-learnのLinerRegression,make_pipeline,PolyFeaturesで実現する

Excelの場合

  • 赤線部分の設定を行えば切片を固定できる(青プロットの近似式)
  • 今回は固定値を0とした。 qiita_20200518.JPG

Pythonの場合

pandas+scikit-learn
from sklearn.linear_model import Ridge,LinearRegression
from sklearn.preprocessing import PolynomialFeatures
from sklearn.pipeline import make_pipeline

# Excelと同じデータを作成
x=np.array([0,3,12.5,18.7,25,20,16,12.8,10.24,8.192])
y=np.array([0,15,46.6,60.3,74.3,59.44,47.552,46,36.8,29.44])

# DEGREE(次数)
degree = 3

# LinearRegression
# make_pipelineでPolynomialFeaturesとLineaRegressionをがっちゃんこ
model = make_pipeline(PolynomialFeatures(degree,include_bias=False),LinearRegression(fit_intercept=False))
model.fit(x.reshape(-1,1),y)
y_model=model.predict(x.reshape(-1,1))

#データフレームの確認
df = pd.DataFrame({'y_model.predict':y_model,},index=x)
df.sort_index().plot(kind ='line',figsize=(10.,5.))

# 係数
model.steps[1][1].coef_
  • よく似たグラフになりました。
    qiita_20200518-2.png

  • 係数一致(ほぼ)

array([ 5.06817229e+00, -1.71343566e-01,  3.49200227e-03])

まとめ

  • numpyのpolyfitではできなかったけど、scikit-learnでできてよかった。
  • Excelの代替手段としてPython使ってるときに困ったのでメモ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

チャネルファースト・チャネルラストの入れ替え

はじめに

Pytorchを前提に記述されたプログラムをKerasに書き換えている時に、画像データ配列の軸を入れ替える必要があったため、その方法について共有します。

Image Channel Order

Channels First : (N, C, H, W) ← PyTorch
Channels Last : (N, H, W, C) ← Keras

N : 画像の枚数
C : チャネル数(色など)
H : 画像のHeight
W : 画像のWidth

チャネルファースト(Channels First)

PyTorchなどにおいて、画像データ配列でモデルを学習する際は、Channels First 形式が一般的です。画像の次元の並び方が (Channel, Height, Width)となっています。Channel(Color)の次元が配列の先頭にあることが名前からわかります。

チャネルラスト(Channels Last)

Keras,PIL,OpenCVなどにおいて、画像データ配列を扱う場合は、Channels Last 形式が一般的です。画像の次元の並び方が (Height, Width, Channel)となっています。Channelの次元が配列の最後にあることが名前からわかります。

Channels First → Channels Last

仮の Channels First の画像配列データを作成します。

仮の画像配列データ
img = np.arange(100*64*64*3).reshape(-1,3,64,64)
img.shape 
(100, 3, 64, 64)

方法1 : np.transpose()

np.transpose()
%%timeit
img.transpose(0,2,3,1).shape
(100, 64, 64, 3)
791 ns ± 92.8 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

方法2 : np.swapaxes()

np.swapaxes()
%%timeit
np.swapaxes(img, 1, 3).shape
(100, 64, 64, 3)
1.54 µs ± 410 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

方法3 : np.moveaxes()

np.moveaxes()
%%timeit
np.moveaxes(img, 1, 3).shape
(100, 64, 64, 3)
9.29 µs ± 956 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

方法4 : np.rollaxes()

np.rollaxes()
%%timeit
np.rollaxes(img, 1, 4).shape
(100, 64, 64, 3)
2.89 µs ± 358 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

方法5 : np.einsum()

np.einsum()
%%timeit
np.einsum('ijkl->ilkj', img).shape
(100, 64, 64, 3)
1.77 µs ± 210 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

最後に

np.transpose() が最速でした。お好きなのを使用してください。

what-is-the-correct-way-to-change-image-channel-ordering-between-channels-first を参考にしました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Djangoのversion確認

Djangoのバージョン知りたいとき

python 実行してexitするのが面倒なため一行でやる方法

方法

terminal 上で以下のコマンドを打ち込む

python -m django --version

結果

3.0.6
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

python bit全探索

方法1

>>> from itertools import product
>>> n = 3
>>> [[i for i, j in zip(range(n), mask) if j] for mask in product((0, 1), repeat=n)]
[[], [2], [1], [1, 2], [0], [0, 2], [0, 1], [0, 1, 2]]

>>> n = 5
>>> [[i for i, j in zip(range(n), mask) if j] for mask in product((0, 1), repeat=n)]
[[], [4], [3], [3, 4], [2], [2, 4], [2, 3], [2, 3, 4], [1], [1, 4], [1, 3], [1, 3, 4], [1, 2], [1, 2, 4], [1, 2, 3], [1, 2, 3, 4], [0], [0, 4], [0, 3], [0, 3, 4], [0, 2], [0, 2, 4], [0, 2, 3], [0, 2, 3, 4], [0, 1], [0, 1, 4], [0, 1, 3], [0, 1, 3, 4], [0, 1, 2], [0, 1, 2, 4], [0, 1, 2, 3], [0, 1, 2, 3, 4]]

方法2

>>> n = 3
>>> [[i for i in range(bit) if bit & (1<<i)] for bit in range(1<<n)]
[[], [0], [1], [0, 1], [2], [0, 2], [1, 2], [0, 1, 2]]

>>> n = 5
>>> [[i for i in range(bit) if bit & (1<<i)] for bit in range(1<<n)]
[[], [0], [1], [0, 1], [2], [0, 2], [1, 2], [0, 1, 2], [3], [0, 3], [1, 3], [0, 1, 3], [2, 3], [0, 2, 3], [1, 2, 3], [0, 1, 2, 3], [4], [0, 4], [1, 4], [0, 1, 4], [2, 4], [0, 2, 4], [1, 2, 4], [0, 1, 2, 4], [3, 4], [0, 3, 4], [1, 3, 4], [0, 1, 3, 4], [2, 3, 4], [0, 2, 3, 4], [1, 2, 3, 4], [0, 1, 2, 3, 4]]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Python3] janomeとmarkovifyを使った文章の自動生成

はじめに

Pure Pythonで書かれた日本語形態素解析エンジンJanomeと、マルコフ連鎖ライブラリmarkovifyを使って、日本語の文章を学習して自動生成します。

基本的には
markovifyで日本語の文章を学習して、マルコフ連鎖により文章生成を行う
をもとにさせていただいています。

背景・目的

実はmarkovifyを使用した日本語文章の学習と自動生成は先例が1つならずあるのですが、それらは大抵MeCabを使用したもので、その導入の関係上Windows環境や一部の仮想環境では些か手間がかかります(Herokuとか)。

その点Janomeは導入がWindowsでも容易で、実際に自動生成への利用法を紹介した記事もやはりあるのですが、markovifyとJanomeを併用したものは(ニッチ過ぎて)見当たりませんでした。markovifyを使ったほうがお手軽に生成文の自然さを高められますので、できれば使用したいところです。

そこで今回、両者を併用して文章を生成できるようにしてみたため、メモ代わりに置いておきます。まぁ、併用したくなるような人は、自力で書き換え可能な気はするので、本当にメモ程度ですが……。

準備

  • Python (3.8.1)
  • Janome (0.3.10)
  • markovify (0.8.0)

janomeもmarkovifyもpip installで導入可能です。(環境によってはpip3)

コード

まずは全体。textGen部分は参考文献1を半ば以上流用しています。

janomeGen.py
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
from janome.tokenizer import Tokenizer
import MeCab
import markovify

def split(text):
    # 改行、スペース、問題を起こす文字の置換
    table = str.maketrans({
        '\n': '',
        '\r': '',
        '(': '(',
        ')': ')',
        '[': '[',
        ']': ']',
        '"':'”',
        "'":"’",
    })
    text = text.translate(table)
    t = Tokenizer()
    result = t.tokenize(text, wakati=True)
    # 1形態素ずつ見ていって、間に半角スペース、文末の場合は改行を挿入
    splitted_text = ""
    for i in range(len(result)):
        splitted_text += result[i]
        if result[i] != '。' and result[i] != '!' and result[i] != '?':
            splitted_text += ' '
        if result[i] == '。' or result[i] == '!' or result[i] == '?':
            splitted_text += '\n'
    return splitted_text

def textGen(file):
    f = open(file, 'r', encoding="utf-8")
    text = f.read()
    sentence = None
    while sentence == None: # 素材によっては空の文章が生成されることがあるので、その対策
        # テキストを処理できる形に分割
        splitted_text = split(text)

        # モデルの生成
        text_model = markovify.NewlineText(splitted_text, state_size=3)

        # モデルを基にして文章を生成
        sentence = text_model.make_sentence()   

    # 学習データの保存
    with open('learned_data.json', 'w') as f:
        f.write(text_model.to_json())

    # データを使いまわす場合
    """
    with open('learned_data.json') as f:
        text_model = markovify.NewlineText.from_json(f.read())
    """

    # 結合された一連の文字列として返す
    return ''.join(sentence.split())

以下、順番に見ていきます。

テキストの下処理

    table = str.maketrans({
        '\n': '',
        '\r': '',
        '(': '(',
        ')': ')',
        '[': '[',
        ']': ']',
        '"':'”',
        "'":"’",
    })
    text = text.translate(table)

markovifyが読み取れるよう、一部の文字を置換しておきます。改行とスペースはそれぞれ文章の区切りと単語の区切りを示すために使うので一旦削除(英文交じりの日本文などはうまく処理できなくなってしまいますが、今回は無視)。

また、markovifyの動作に悪影響を及ぼす'bad characters'も無害な全角文字に置換しておきます。(markovify v0.7.2からはmarkovify.Textのwell_formedパラメータで、bad charactersを含むセンテンスを無視するかどうかを指定できますが、丸ごと無視してしまうのはもったいないので事前に置換で済ませています)

テキストの分割

t = Tokenizer()
    result = t.tokenize(text, wakati=True)
    splitted_text = ""
    for i in range(len(result)):
        splitted_text += result[i]
        if result[i] != '。' and result[i] != '!' and result[i] != '?':
            splitted_text += ' '
        if result[i] == '。' or result[i] == '!' or result[i] == '?':
            splitted_text += '\n'

やっていること自体は参考記事1とほぼほぼ同じなのでそちらを参照していただいたほうが正確です。

JanomeのTokenizerでこのようにtokenizeすると、形態素で分けたリストとして返してくれます。
「私はリンゴを一つ食べる。」なら ['私', 'は', 'リンゴ', 'を', '一つ', '食べる', '。']という感じ。形態素の本体のみ欲しい人にはMeCabよりもお手軽で便利。

今回はmarkovifyが読めるように形態素を1個ずつ読んで間を半角スペースで分け、文末に来たら改行で区切ります(英文と形を揃える感じ)。参考記事では句点でのみ切っていましたが、今回は!と?でも切るようにしました。読点をどう区切るかは好みによりますが、ここでは1つの単語として分けました。英語同様の形にする場合、if文のところを

        if i+1 < len(result):
            if result[i] != '。' and result[i] != '!' and result[i] != '?' and result[i+1] != '、':
                splitted_text += ' '
            if result[i] == '。' or result[i] == '!' or result[i] == '?':
                splitted_text += '\n'
        else:
            if result[i] != '。' and result[i] != '!' and result[i] != '?':
                splitted_text += ' '
            if result[i] == '。' or result[i] == '!' or result[i] == '?':
                splitted_text += '\n'

とか何とか書き換えるとうまくいくはずです。たぶん。

文章の生成

def textGen(file):
    f = open(file, 'r', encoding="utf-8")
    text = f.read()
    sentence = None
    while sentence == None: # 素材によってはNoneが返ることがあるので、その対策
        # テキストを処理できる形に分割
        splitted_text = split(text)

        # モデルの生成
        text_model = markovify.NewlineText(splitted_text, state_size=3)

        # モデルを基にして文章を生成
        sentence = text_model.make_sentence()   

    # 学習データの保存
    with open('learned_data.json', 'w') as f:
        f.write(text_model.to_json())
    # 結合された一連の文字列として返す
    return ''.join(sentence.split())

今回は青空文庫ではないものからの生成のために書いていたので、そのための処理は省いて単純に読み込んでいます。markovifyの割と標準的な手順なので、それ以外はおおよそ参考文献1に準じています。

また、state_sizeや素材文の分量の関係で時々Noneが返ってしまうことがある(markovifyのIssue#96, Issue#22)ので、ここでは安易にNoneじゃないものを返すまで回しておきました。ある程度の文章量があれば無限ループにはならないと思います。

なお、make_sentenceのキーワード引数triesで試行回数を指定しておくことでもある程度対応可能です。(下のコード)

    # テキストを処理できる形に分割
    splitted_text = split(text)

    # モデルの生成
    text_model = markovify.NewlineText(splitted_text, state_size=3)

    # モデルを基にして文章を生成
    sentence = text_model.make_sentence(tries=100)   

生成結果

テスト用に、青空文庫の坊ちゃんからdelruby.exeを用いてルビを削除し、不要な部分を除いたものを基に生成してみました。

  • 一人不足ですがと考えてみると世の中はみんなこの生徒のようなものだ、虫の好かない奴が親切で、しかも上品だが、貧乏士族のけちん坊と来ちゃ仕方がないから、大きな声を出すもんだ。
  • 溌墨の具合も至極よろしい、試してご覧なさいと、おれよりも下等だが、日本人はみな口から先へ免職になったら、よさそうな下宿を教えてくれるかも知れないから、為替で十円あげる。
  • それも花の都の電車が通ってる所なら、野だは狼狽の気味で、はたで見ているときに来るかい」「そのマドンナさんが不たしかなのが、飛び起きると同時に忘れたようにうらなり君が眼に付く、途中をあるいていても眼がくらむ。
  • 野だは時々山嵐に話しかけるが、山嵐の云う通りにした。
  • 男はあっと小声に云ったが、やがて帰って来て言葉が出ないから、出すんだ。
  • おれが戸を開けて中に居るんだ」「僕の前任者がやられたんだ。
  • 歴史も教頭と同説だと云ってやった。
  • 門口へ立ったなり中学校を教えろと云ったらあなたがおうちを持って教場へ出たら、山嵐は君それを引き込めるのかと驚ろいた。
  • うらなり君に別れて、うちを出る時から、こんなに答えるんだろう。
  • そうしておいて喧嘩を吹き懸ける男だ。

おおよそ目的は達せられているようです。

あとがき

JanomeもMeCabも日本語形態素解析をしてくれるという意味では同様の機能を持つので、細かい書き換えのみで実装できました。Bot作成時などに活用できそうです。

参考

  1. markovifyで日本語の文章を学習して、マルコフ連鎖により文章生成を行う
  2. [Python] MeCab とマルコフ連鎖ライブラリ markovify を使い、文章を学習して自動生成する方法
  3. マルコフ連鎖による文章生成 - 知識のサラダボウル
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Windows10 Python 3.8.3でOpenCVを使うためのメモ。

Windows10 Python 3.8.3でOpenCVを使うためのメモ。

image.png

pip install opencv-python を打つ。

Installing collected packages: numpy, opencv-python
Successfully installed numpy-1.18.4 opencv-python-4.2.0.34

入ったみたいです。

あらかじめ書いておいたPCカメラから撮影するスクリプト(省略)を起動して確認します。

python C:\python\cvtest.py

image.png

はい、テレワークで子供たちのおもちゃになったデッサン人形君が撮影できました。
大丈夫みたいです。

以上。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Windows10でPython環境構築メモ

Windows10でpython環境構築メモ

テレワーク等、諸事情にてパソコンを買い替えたのでWindows10にPython開発環境を作ったメモ。
古いパソコンで環境構築した際にメモっていなかったので。

URL:https://www.python.org/downloads/

image.png

Looking for a specific release?で、Python 3.8.3を選択。

image.png

Windows x86-64 executable installer をダウンロードしてウィザードでインストール。
注)"Add Python 3.x to PATH"をチェックして環境変数に登録する。

image.png

”Disable path length limit ”とか言うで、クリックしてあげる。

image.png

インストールが完了したか確認する。

CMD起動して、pythonと打つ。

qiita.png

大丈夫そう。おや、時間が。。まいっか。
後で設定を変えましょう。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む