ABC088D Grid Repainting

この記事で使うアルゴリズム

深さ優先探索

はじめに

カテゴリー競プロ初中級者100問では、Qiitaにて@e869120さんがレッドコーダーが教える、競プロ・AtCoder上達のガイドライン【中級編:目指せ水色コーダー!】としてまとめられている100問をPythonで解いています。

全問題の一覧はこちらです

問題

https://atcoder.jp/contests/abc088/tasks/abc088_d

この問題は幅優先探索を用いて解くことができます。

幅優先探索はキューによって実装できます。

Pythonの場合、キューはqueue.Queuecollections.dequeを利用すると良いでしょう。いずれも標準ライブラリです。

この記事ではqueue.Queueを用います。

この問題を解くまえに

この問題は幅優先探索の応用です。まずは、ABC007C 幅優先探索を先に解いても良いでしょう。

方針

  • ゴールにたどり着くまでに通ったマス以外の白マスは黒く塗りつぶすことができる。
    • つまり出来るだけ少ないマスを通ってゴールにたどり着けば良いので、最短経路問題に帰着される。
      • ここでは幅優先探索を用いて最短経路問題を解く

幅優先探索の実装

  • キューを作成する。キューの各要素は(y座標、z座標、スタート地点からの距離)として利用していく。
  • キューにスタート地点をenqueueする。
  • 幅優先探索を行う。具体的には、以下の処理をキューが空になるまで繰り返す。
    • dequeueする
    • 訪問済みの場合は無視する
    • 未訪問の場合
      • 訪問フラグを立てる
      • 移動可能かを確認し、enqueueする

ポイント

訪問済みかどうかの確認はenqueueする前には必須である。

この問題の場合、dequeueした後には必須ではないが確認するべきだと思われる。
(スタート地点からの距離が等しいマスが、同じマスを複数回enqueueする可能性があり、queueのサイズが肥大化するためTLEやMLEのリスクが増える)

コード

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import sys
import queue
input = sys.stdin.readline
sys.setrecursionlimit(int(1E+7))
dxdy = ((-1,0), (1,0), (0,-1), (0,1))
H, W = map(int,input().split())
maze = []
visited = [ [0]*W for _ in range(H)]
black = 0
for h in range(H):
s = input()
maze.append(s)
for c in s:
if c=='#':
black += 1
q = queue.Queue()
q.put((0,0,0))
cost = -1
while(not q.empty()):
y, x, d = q.get()
if x == W-1 and y == H-1:
cost = d
break
if visited[y][x] == 1:
continue
else:
visited[y][x] = 1
for dx, dy in dxdy:
if (0<= x+dx < W) and (0<= y+dy < H):
if visited[y+dy][x+dx] == 0 and maze[y+dy][x+dx]=='.':
q.put((y+dy,x+dx,d+1))
if cost==-1:
print (-1)
else:
print (H*W-cost-1-black)

記事情報

  • 投稿日:2020年4月17日
  • 最終更新日:2020年5月8日

ABC007C 幅優先探索

この記事で使うアルゴリズム

幅優先探索

はじめに

カテゴリー競プロ初中級者100問では、Qiitaにて@e869120さんがレッドコーダーが教える、競プロ・AtCoder上達のガイドライン【中級編:目指せ水色コーダー!】としてまとめられている100問をPythonで解いています。

全問題の一覧はこちらです

問題

https://atcoder.jp/contests/abc007/tasks/abc007_3

この問題は幅優先探索を用いて解くことができます。

幅優先探索はキューによって実装できます。

Pythonの場合、キューはqueue.Queuecollections.dequeを利用すると良いでしょう。いずれも標準ライブラリです。

この記事ではqueue.Queueを用います。

方針

  • キューを作成する。キューの各要素は(y座標、z座標、スタート地点からの距離)として利用していく。
  • キューにスタート地点をenqueueする。
  • 幅優先探索を行う。具体的には、以下の処理をキューが空になるまで繰り返す。
    • dequeueする
    • 訪問済みの場合は無視する
    • 未訪問の場合
      • 訪問フラグを立てる
      • 移動可能かを確認し、enqueueする

ポイント

訪問済みかどうかの確認はenqueueする前には必須である。

この問題の場合、dequeueした後には必須ではないが確認するべきだと思われる。
(スタート地点からの距離が等しいマスが、同じマスを複数回enqueueする可能性があり、queueのサイズが肥大化するためTLEやMLEのリスクが増える)

コード

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import sys
import queue
input = sys.stdin.readline
sys.setrecursionlimit(int(1E+7))
dxdy = ((-1,0), (1,0), (0,-1), (0,1)) # タプルやリストで持っておくと便利
R, C = map(int,input().split())
sy, sx = map(int,input().split())
gy, gx = map(int,input().split())
maze = []
visited = [ [0]*C for _ in range(R)]
for r in range(R):
s = input()
maze.append(s) # 二次元リストにせず、文字列のリストのままでOK
q = queue.Queue()
q.put((sy-1,sx-1,0)) # スタート地点をenqueue
while(not q.empty()):
y, x, d = q.get()
if x == gx-1 and y == gy-1: # ゴールにたどり着いたら終了
ans = d
break
if visited[y][x] == 1: # 訪問済みの場合は無視する
continue
else:
visited[y][x] = 1 # 訪問フラグを立てる
for dx, dy in dxdy:
if (0<= x+dx < C) and (0<= y+dy < R): # 範囲内に収まっているか
if visited[y+dy][x+dx] == 0 and maze[y+dy][x+dx]=='.': # 見訪問かつ通行可能か
q.put((y+dy,x+dx,d+1)) # 距離を+1してenqueue
print (ans)

記事情報

  • 投稿日:2020年4月16日
  • 最終更新日:2020年5月8日

ABC138D Ki

この記事で使うアルゴリズム

深さ優先探索

はじめに

カテゴリー競プロ初中級者100問では、Qiitaにて@e869120さんがレッドコーダーが教える、競プロ・AtCoder上達のガイドライン【中級編:目指せ水色コーダー!】としてまとめられている100問をPythonで解いています。

全問題の一覧はこちらです

問題

https://atcoder.jp/contests/abc138/tasks/abc138_d

この問題は深さ優先探索を用いて解くことができます。

深さ優先探索はスタックや再帰関数によって実装できます。今回は再帰関数を用います。

方針

  • 操作の際の根に当たるノードのカウンターVに先に値を加算する。
  • 深さ優先探索を行いながら、親ノードのカウンターの値を加算していく。
    • 深さ優先探索ではlinkを参照してノードを追加します。
      • この時、親ノードを追加して無限ループとならないように除外します。そのためにparentで親ノードを記憶しています。
  • 最後にカウンターVの値を文字列結合して出力する。

ポイント

入力行数が多いので高速化が必要です。

1
input = sys.stdin.readline

デフォルト値だと小さすぎるので、再帰処理の上限を引き上げる。

1
sys.setrecursionlimit(int(1E+7))

コード(全体)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import sys
input = sys.stdin.readline
sys.setrecursionlimit(int(1E+7))
def main():
N, Q = map(int, input().split())
link = [[] for _ in range(N+1)]
for n in range(N-1):
a, b = map(int, input().split())
link[a].append(b)
link[b].append(a)
V = [0] * (N+1)
for q in range(Q):
p, x = map(int, input().split())
V[p] += x # `操作`の際の根に当たるノードのカウンター`V`に先に値を加算する。

def dfs(i, parent, acc): # 深さ優先探索
V[i] += acc # 親ノードまでの累積値を加算
for j in link[i]:
if j != parent: # 親ノードを追加しない
dfs(j,i,V[i])
cur = 1
parent = 0
acc = 0
dfs(cur, parent , acc) # 親ノードを0としてスタート
V = [str(v) for v in V]
print (" ".join(V[1:]))

if __name__ == '__main__':
main()

記事情報

  • 投稿日:2020年4月15日
  • 最終更新日:2020年5月8日

ABC002D 派閥

この記事で使うアルゴリズム

全探索

はじめに

カテゴリー競プロ初中級者100問では、Qiitaにて@e869120さんがレッドコーダーが教える、競プロ・AtCoder上達のガイドライン【中級編:目指せ水色コーダー!】としてまとめられている100問をPythonで解いています。

全問題の一覧はこちらです

問題

https://atcoder.jp/contests/abc002/tasks/abc002_4

この問題は全探索を用いて解くことができます。

方針

  • 議員の集合を考えます。その集合内で考どの二人を選んでも二人が知り合いである場合、その集合は派閥にすることができます。
  • 議員の集合として考えられる組み合わせは2^N-1通りとなります。
    • 理由は、集合に所属する議員を1、そうでない議員を0として表現するとわかりやすいでしょう。

ポイント

議員の集合として考えられる組み合わせを列挙する方法として、ループを回して二進数に変換する方法が考えられます。

(例)N=5の場合

1
2
3
4
5
1 -> 00001
2 -> 00010
3 -> 00011
...
31 -> 11111

しかし、Pythonの場合は標準ライブラリitertools.combinationsを使うと楽に実装できます。

以下のように、組み合わせを簡単に列挙することができます。

1
2
for comb in combinations(range(3),2):
print (comb)
1
2
3
(0, 1)
(0, 2)
(1, 2)

コード

それでは実装にうつりましょう。

知り合いであるかどうかをlinkを使って表します。

1
2
3
4
5
6
7
from itertools import combinations
N, M = map(int, input().split())
link = [[0 for i in range(N)] for j in range(N)]
for m in range(M):
x, y = map(int, input().split())
link[x-1][y-1] = 1
link[y-1][x-1] = 1

続いて、議員の集合として考えられる組み合わせを列挙します。

最大の派閥に属する議員数を求めたいので、議員数が多くなるような集合から先に考えます。

1
2
3
ans = 1
for i in range(N,1,-1): # N, N-1, N-2, ... 2
for comb in combinations(range(N),i):

議員の集合に対して考えられるペアを列挙し、知り合いかどうかをチェックします。

一つでも知り合いでない組があれば、その集合は派閥にはなり得ません。

ここで、またcombinationsが有効です。

1
2
3
4
5
6
for c in combinations(comb, 2): # 議員の集合に対して考えられるペアを列挙
if (link[c[0]][c[1]] == 0): # 一つでも知り合いでない組があればNG
break
else: # 派閥が作れた段階でプログラムを終了
print (i)
exit()

最後にコード全体を載せておきます。

コード(全体)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from itertools import combinations
N, M = map(int, input().split())
link = [[0 for i in range(N)] for j in range(N)]
for m in range(M):
x, y = map(int, input().split())
link[x-1][y-1] = 1
link[y-1][x-1] = 1

for i in range(N,1,-1): # N, N-1, N-2, ... 2
for comb in combinations(range(N),i):
for c in combinations(comb, 2): # 議員の集合に対して考えられるペアを列挙
if (link[c[0]][c[1]] == 0): # 一つでも知り合いでない組があればNG
break
else: # 派閥が作れた段階でプログラムを終了
print (i)
exit()
print (1)

記事情報

  • 投稿日:2020年4月14日
  • 最終更新日:2020年5月8日

ABC162

はじめに

この記事では、AtCoder Beginer Contest 162のA~E問題を解説します。

このコンテストから、使用可能言語が変更となりました。
Pythonのバージョンは3.8.2に変更されました。

これでようやく、gcd(最大公約数)のインポートのストレス(Python競プロでやりがちなミス)から解放されました。

A - Lucky 7

https://atcoder.jp/contests/abc162/tasks/abc162_a

問題文に「3桁の整数Nが与えられます」とあるので、intとして扱いたくなりますが、
数字7が含まれているかを確認したいだけなので、文字列として扱うのが良いでしょう。

1
2
3
4
5
N = input()
if '7' in N:
print ('Yes')
else:
print ('No')

B - FizzBuzz Sum

https://atcoder.jp/contests/abc162/tasks/abc162_b

問題文がちょっとややこしい気がしますが、「N項目までに含まれる数の和を求めてください。」とあるので、要するに3の倍数でも5の倍数でもない数を足せば良いです。

気をつけるのはrangeの範囲くらいでしょうか。

1
2
3
4
5
6
N = int(input())
ans = 0
for n in range(1,N+1):
if (n%3 != 0 and n%5 != 0):
ans += n
print (ans)

C - Sum of gcd of Tuples (Easy)

https://atcoder.jp/contests/abc162/tasks/abc162_c

GCDの問題です。Pythonistaとしてはこのタイミングで、GCDを出してくることに運命を感じますね。

gcd(a,b,c) = gcd(gcd(a,b),c)

であるため、単純にループを回せば良いです。

ただし、Pythonの場合は計算が結構ギリギリなので、gcd(a,b)の部分を使いまわします。

他の方のコードを見ていると、この高速化をしないで通している方もいました。

1
2
3
4
5
6
7
8
9
from math import gcd
K = int(input())
ans = 0
for i in range(1,K+1):
for j in range(1,K+1):
gcd_ij = gcd(i, j)
for k in range(1,K+1):
ans += gcd(gcd_ij, k)
print (ans)

D - RGB Triplets

https://atcoder.jp/contests/abc162/tasks/abc162_d

まずは、一つ目の条件(全ての文字が異なる組)を数えます。

その後、二つ目の条件を満たす組を数えて引きます。

Python文字列中に指定した文字がいくつ含まれるかを数えたい場合はcountが便利です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
N = int(input())
S = input()
ans = S.count('R') * S.count('G') * S.count('B') # 一つ目の条件

for i in range(N):
for j in range(1,N): # 1から
if (i-j<0 or i+j>=N): # リストの範囲内かを確認
break
l = S[i-j]
m = S[i]
r = S[i+j]
if l!=m and m!=r and r!=l: # 二つ目の条件
ans -= 1
print (ans)

E - Sum of gcd of Tuples (Hard)

https://atcoder.jp/contests/abc162/tasks/abc162_e

ほとんど解説PDFの通りです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
MOD = int(1e+9 + 7) # 忘れずにintをつける
N, K = map(int, input().split())

cnt = [0] * (K + 1) # 後々のことを考え、サイズをK+1にする
 
for x in range(1, K + 1): # 先頭0は使わない
cnt[x] = pow(K // x, N, MOD) # 要素が全てXの倍数となるような数列の個数をXごとに求める

for x in range(K, 0, -1): # 大きい順に計算
for i in range(2, K // x + 1): # 2X, 3Xの個数を求めてひく
cnt[x] -= cnt[x * i]

ans = 0
for k in range(K+1):
ans += cnt[k] * k # gcdがkである組はA[k]個ある
ans = ans % MOD # ここでもMODをつける
print(ans)

ちょっとプリントデバッグを仕込んでみましょう。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
MOD = int(1e+9 + 7) # 忘れずにintをつける
N, K = map(int, input().split())

cnt = [0] * (K + 1) # 後々のことを考え、サイズをK+1にする
 
for x in range(1, K + 1): # 先頭0は使わない
cnt[x] = pow(K // x, N, MOD) # 要素が全てXの倍数となるような数列の個数をXごとに求める
print (cnt)

for x in range(K, 0, -1): # 大きい順に計算
for i in range(2, K // x + 1): # 2X, 3Xの個数を求めてひく
cnt[x] -= cnt[x * i]
print (cnt)

ans = 0
for k in range(K+1):
ans += cnt[k] * k # gcdがkである組はA[k]個ある
ans = ans % MOD # ここでもMODをつける
print(ans)

そして、プログラムを実行し3 2を入力して下さい。

1
2
3
4
3 2
[0, 8, 1]
[0, 7, 1]
9

[0, 8, 1]

  • 0: 先頭の要素はダミー
  • 8: 全てが1の倍数となる数列の数
  • 1: 全てが2の倍数となる数列の数

[0, 7, 1]

  • 0: 先頭の要素はダミー
  • 7: GCD=1となる数列の数
  • 1: GCD=2となる数列の数

となっていることが確認できます。

記事情報

  • 投稿日:2020年4月13日
  • 最終更新日:2020年4月13日

bisectの基本的な使い方と、自作方法

はじめに

標準ライブラリbisectで配列二分法(リストの二分探索)が実装されています。

今回はbisectの基本的な使い方を説明した後、自前で実装します。

bisectの基本

どこに挿入すれば良いか?

この節は、基本的な使い方を理解している方はスキップしていただいて問題ありません。

bisectは昇順ソート済みのリストに対してある値を挿入したい時、ソート状態を維持するにはどこに挿入すれば良いかを計算します。

1
2
3
4
5
import bisect
A = [2, 3, 5, 7, 11]
x = 4
ins = bisect.bisect(A, x)
print (ins)
1
2

bisectinsertとの相性が良いです。

1
2
A.insert(ins, x)
print (A)
1
[2, 3, 4, 5, 7, 11]

bisect_leftbisect_rightの違い

では、x = 5のように、すでにリスト内に同じ値が存在する場合はどうなるでしょうか。

1
2
3
A = [2, 3, 5, 7, 11]
x = 5
print (bisect.bisect(A, x))
1
4

リスト内に同じ値がある場合、その右側に挿入できるように位置を返します。

挿入点が左側となるような関数bisect_leftも存在します。

1
2
3
print (bisect.bisect(A, x))
print (bisect.bisect_left(A, x))
print (bisect.bisect_right(A, x))
1
2
3
4
3
4

挿入が目的であればどちらを使っても結果は変わりませんね。

bisect_leftbisect_rightの使い分け

これらを使い分けする意味がある状況もあります。例えば、ある区間に含まれる要素を数えあげるといったシーンです。

以下のリストの中に3以上7以下の要素はいくつあるでしょうか。

1
B = [2, 2, 2, 3, 3, 5, 5, 7, 7, 7, 7, 11, 11]

bisect_left, bisect_rightを適切に組み合わせることで計算できます。

1
2
3
L = 3
U = 7
print (bisect.bisect_right(B, U) - bisect.bisect_left(B, L))
1
8

区間内の要素の数え上げを、閉区間・開区間の概念でまとめると以下のようになります

  • [L, U]・・・L以上 U以下
    • bisect_right(U) - bisect_left(L)
  • [L, U)・・・L以上 U未満
    • bisect_left(U) - bisect_left(L)
  • (L, U]・・・Lより大きく U以下
    • bisect_right(U) - bisect_right(L)
  • (L, U)・・・Lより大きく 未満
    • bisect_left(U) - bisect_right(L)

方針

さて、 本題のbisectの実装に入りましょう!

リスト化できない関数を探索可能な二分探索を実装し、それを元にbisectと等価な機能を実装します。

リストを直接二分探索する実装としても良いのですが、上記の方法がより汎用的なアプローチに思えるためです。

流れ

  1. 二分探索の実装
    二分探索を行う関数meguru_bisectはこちらの実装学習する天然ニューラルネットを利用させていただきました。記事の中で説明している通り、実装が簡潔でバグが入り難いと思います。

  2. bisect_leftおよびbisect_rightの実装
    実は、リスト化できない関数を探索可能な二分探索が実装できてしまうと、ここはとても楽です。
    bisect_leftbisect_rightの違いは、等号(=)の有無だけで表現できます。

あるインデックスiが与えられた時、事前に用意している値xとリストai番目の値を比較するだけです。

  1. テスト
    最後に本家bisectと等しくなるかをunittestを使ってテストします。

コード

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import bisect
import unittest

def meguru_bisect(ng, ok, func):
while (abs(ok - ng) > 1):
mid = (ok + ng) // 2
if func(mid):
ok = mid
else:
ng = mid
return ok

def bisect_left(a, x):
def func(i):
return x <= a[i]
return meguru_bisect(-1, len(a), func)

def bisect_right(a, x):
def func(i):
return x < a[i]
return meguru_bisect(-1, len(a), func)

class TestBisect(unittest.TestCase):
def test_bisect(self):
test_patterns = [
([2,3,4,4,5,6], 6),
([], 6),
([3,3], 0),
([3,3], 6),
]
for a, x in test_patterns:
with self.subTest(a=a, x=x):
self.assertEqual(bisect_left(a, x), bisect.bisect_left(a, x))
with self.subTest(a=a, x=x):
self.assertEqual(bisect_right(a, x), bisect.bisect_right(a, x))


if __name__=='__main__':
a = [2,3,4,4,5,6]
x = 6
print (bisect_left(a, x))
print (bisect.bisect_left(a, x))
print (bisect_right(a, x))
print (bisect.bisect_right(a, x))

例題

bisectを用いる良い題材として、AtCoderの問題があります。ご興味がある方は、
ABC077C Snuke Festivalをご覧ください。

記事情報

  • 投稿日:2020年4月12日
  • 最終更新日:2020年4月29日

ABC023D 射撃王

この記事で使うアルゴリズム

二分探索

はじめに

カテゴリー競プロ初中級者100問では、Qiitaにて@e869120さんがレッドコーダーが教える、競プロ・AtCoder上達のガイドライン【中級編:目指せ水色コーダー!】としてまとめられている100問をPythonで解いています。

全問題の一覧はこちらです

問題

https://atcoder.jp/contests/abc023/tasks/abc023_d

この問題は二分探索を用いて解くことができますが、標準ライブラリbisectを使うことはできません。なぜならbisectはリストに対する二分探索であり、この問題ではリスト化できない関数に対する二分探索が必要になるためです。

方針

  • 「ある時点での高度」や「速度」に対して貪欲に風船を割っても最適な割り方とはならない。
  • しかし、ある得点が与えられた時「その得点以下となるように風船を割ることができるか」を判定することはできる。
    • 各風船をいつまでに割れば良いかを考え、全ての風船に対して実行可能なスケジューリングがあるかを確認すれば良い
  • 判定を元にして二分探索を行えば良い

ポイント

  • 特典の最大値に注意する(1e+14)

コード

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def is_ok(x):
ls = []
for i in range(N):
ls.append((x-H[i])//S[i]) # いつまでに割れば良いか
ls.sort() # 期限が近いものから先にわる
for n in range(N):
if (ls[n] < n):
return False # 時間ぎれ
return True

def meguru_bisect(ng, ok):
while (abs(ok - ng) > 1):
mid = (ok + ng) // 2
if is_ok(mid):
ok = mid
else:
ng = mid
return ok

N = int(input())
H = []
S = []
for i in range(N):
h, s = map(int,input().split())
H.append(h)
S.append(s)
print (meguru_bisect(0,int(1e+14)))

二分探索を行う関数meguru_bisectはこちらの実装学習する天然ニューラルネットを利用させていただきました。記事の中で説明している通り、実装が簡潔でバグが入り難いと思います。

関連記事

記事情報

  • 投稿日:2020年4月12日
  • 最終更新日:2020年5月8日

ABC077C Snuke Festival

この記事で使うアルゴリズム

二分探索

はじめに

カテゴリー競プロ初中級者100問では、Qiitaにて@e869120さんがレッドコーダーが教える、競プロ・AtCoder上達のガイドライン【中級編:目指せ水色コーダー!】としてまとめられている100問をPythonで解いています。

全問題の一覧はこちらです

問題

https://atcoder.jp/contests/abc077/tasks/arc084_a

方針

  • Bを固定しながら、Bより小さいAはいくつあるかBより大きいCはいくつあるかを考える
    • ソートしてから二分探索すると良い。
      • 計算量はソートがO(logN)でそれをBの数だけ行うO(N)ので、O(NlogN)となる。
      • 標準ライブラリbisectを使うと良い。

コード

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import bisect
N = int(input())
A = list(map(int, input().split()))
B = list(map(int, input().split()))
C = list(map(int, input().split()))
A.sort()
B.sort()
C.sort()
ans = 0
for b in B:
a = bisect.bisect_left(A, b) # 挿入点はどの同じ値よりも左
c = bisect.bisect_right(C, b) # 挿入点はどの同じ値よりも右
ans += a * (len(C)-c)
print (ans)

着想

上段(下段)を固定することはすぐに思い浮かぶが、中段を固定することは思い浮かび難いので慣れる必要がある。3層の構造になっている場合は中段の固定を考えた方が問題が簡単に解くことができる。

Bが増えるほど、Aの候補は増えCの候補が減るというような単調性を見つける。単調性があるときは二分探索で計算量を減らす。

関連記事

記事情報

  • 投稿日:2020年4月11日
  • 最終更新日:2020年6月19日

Pythonパッケージの自作とインストール手順

はじめに

この記事ではパッケージを自作し、それをインストールする手順について学びます。

モジュール・パッケージとは

公式ドキュメントから引用します。

モジュールは Python の定義や文が入ったファイルです。ファイル名はモジュール名に接尾語 .py がついたものになります。

パッケージ (package) は、Python のモジュール名前空間を “ドット付きモジュール名” を使って構造化する手段です。例えば、モジュール名 A.B は、 A というパッケージのサブモジュール B を表します。

つまり、モジュールは単一のPythonファイルであり、それをまとめたものがパッケージとなります。

まずは、パッケージを自作していきましょう。

パッケージの作成

今回のワークスペースディレクトリを作成します。

ここではsandboxと命名しましょう。

sandbox以下にパッケージを作成していきます。

先述の通り、パッケージはモジュールをまとめたものですので、ディレクトリとなります。

sandbox以下にパッケージ名のディレクトリを作成します。

ここではmy_packageと命名しましょう。

1
2
sandbox/
└── my_package

続いてmy_package以下に__init__.pyを作成します。

今回は最低限の構成とするため、空のテキストファイルとして作成します。

1
2
3
sandbox/
└── my_package
   └── __init__.py

続いて、モジュールを作成します。

ここではmy_module.pyと命名しましょう。

my_module.pyの内容

1
2
def my_function():
print ("called my_function")
1
2
3
4
sandbox/
└── my_package
  ├── __init__.py
  └── my_module.py

最後に作成したモジュールを呼び出すスクリプトを作成します。

ここではscript.pyと命名しましょう。

script.pyの内容

1
2
from my_package.my_module import my_function
my_function()
1
2
3
4
5
sandbox/
├── my_package
│   ├── __init__.py
│   └── my_module.py
└── script.py

さてスクリプトを実行してみましょう。

1
python3 script.py
1
called my_function

うまく行きました!

スクリプトを作成しなくても、下記のコマンドで確かめることもできます。

1
python3 -c 'from my_package.my_module import my_function'

pipでのインストール

さて、次にpipでのインストールに挑戦してみましょう。

なぜインストールするか

その前に、sandbox以外のディレクトリに移動し先ほどのコマンドを実行してみましょう。

1
python3 -c 'from my_package.my_module import my_function'

下記のようなエラーとなってしまうはずです。

1
2
3
Traceback (most recent call last):
File "<string>", line 1, in <module>
ModuleNotFoundError: No module named 'my_package'

パスが通っていないため、my_packageのインポートに失敗しているのです。

そこで、どこからでもインポートできるようにpipでのインストールを考えたくなるのです。

仮想環境の設定

pipコマンドでインストールをしてしまうので、今の環境を壊さないために仮想環境を用いましょう。

どのような方法でも良いですがここではvenvを使います。

sandboxディレクトリに移動し

1
python3 -m venv venv

としてください。venvディレクトリが作成されます。

以下のコマンドで仮想環境のアクティベイトができ

1
. /venv/bin/activate

以下のコマンドで仮想環境のディアクティベイトができます。

1
deactivate

手順

sandbox以下にsetup.pyを作成します。

そして以下のように記述します。

1
2
3
4
5
from setuptools import setup, find_packages
setup(
name='my_package',
packages=find_packages(),
)
1
2
3
4
5
6
sandbox/
├── my_package
│   ├── __init__.py
│   └── my_module.py
├── script.py
└── setup.py

これで準備は完了です。

忘れずに仮想環境をactivateした後、sandbox以下で

1
pip install -e .

とすればパッケージのインストールができます。

今度はディレクトリを変えても

1
python3 -c 'from my_package.my_module import my_function'

が正常に動作します。

記事情報

  • 投稿日:2020年4月11日
  • 最終更新日:2020年4月11日

Python test入門 その3

はじめに

前回の記事Python test入門 その2に引き続き、Pythonのテストについて、標準ライブラリであるunittestを使って学んでいきます。

今回も公式リファレンスのコードを引用しながら、解説をしていきます。

解説

例外のテスト

正しく例外が発生しているかをテストしたい場合は、assertRaisesを使う。

コンテキストマネージャーとして使う

1
2
3
4
5
6
7
8
9
10
11
12
import unittest

def some_func():
raise ValueError

class TestMethods(unittest.TestCase):
def test_assert_raises(self):
with self.assertRaises(ValueError):
some_func()

if __name__ == '__main__':
unittest.main()
1
OK

下記のように異なる例外が発生すると失敗する。

1
2
3
4
5
6
7
8
9
10
11
12
import unittest

def some_func():
raise ArithmeticError

class TestMethods(unittest.TestCase):
def test_assert_raises(self):
with self.assertRaises(ValueError):
some_func()

if __name__ == '__main__':
unittest.main()
1
FAILED (errors=1)

関数を渡す

以下のような記述も可能。

1
2
3
class TestMethods(unittest.TestCase):
def test_assert_raises(self):
self.assertRaises(ValueError, some_func)

類似するメソッド

例外、警告、およびログメッセージの発生をテストするメソッドは、今回紹介したassertRaisesを含めて6パターン用意されています。

1
2
3
4
5
assertRaises
assertRaisesRegex
assertWarns
assertWarnsRegex
assertLogs

その他のメソッド

その他には以下のようなメソッドがあります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
assertAlmostEqual
assertNotAlmostEqual
assertGreater
assertGreaterEqual
assertLess
assertLessEqual
assertRegex
assertNotRegex
assertCountEqual
assertMultiLineEqual
assertSequenceEqual
assertListEqual
assertTupleEqual
assertSetEqual
assertDictEqual

一部を紹介します。

assertAlmostEqual

ほぼ等しいかどうかをテストします。

1
2
3
class TestMethods(unittest.TestCase):
def test_assert_almost_equal(self):
self.assertAlmostEqual(5 + 2e-8, 5 + 1e-8)
1
OK

通常は小数第7位まで丸めた差分で評価するが、placesで指定も可能

1
2
3
class TestMethods(unittest.TestCase):
def test_assert_almost_equal(self):
self.assertAlmostEqual(5 + 2e-7, 5 + 1e-7, places=6)
1
OK

差分(delta)を指定することも可能

1
2
3
class TestMethods(unittest.TestCase):
def test_assert_almost_equal(self):
self.assertAlmostEqual(100, 102, delta=3)
1
OK

assertListEqual

リストが等しいかをテストします。

1
2
3
4
5
class TestMethods(unittest.TestCase):
def test_assert_list_equal(self):
a = [2, 3]
b = [2, 3]
self.assertListEqual(a, b)
1
OK

順序も等しい必要があります。

1
2
3
4
5
class TestMethods(unittest.TestCase):
def test_assert_list_equal(self):
a = [3, 2]
b = [2, 3]
self.assertListEqual(a, b)
1
2
3
4
5
6
7
8
9
10
11
12
13
AssertionError: Lists differ: [3, 2] != [2, 3]

First differing element 0:
3
2

- [3, 2]
+ [2, 3]

----------------------------------------------------------------------
Ran 1 test in 0.000s

FAILED (failures=1)

まとめ

これでunittestの解説を終わりにしたいと思います。

この記事でunittestの概要だけでも理解いただけたのであれば嬉しいです。

記事情報

  • 投稿日:2020年4月10日
  • 最終更新日:2020年4月10日