★
緑色さんの多目的ブログ
みろりえいちぴー(旧)
引っ越し先 みろりHP: https://www.mrrhp.com
★
2019.02.27 Wednesday
| カテゴリ:- |
スポンサーサイト
| スポンサードリンク | - | - |
2018.12.09 Sunday
GitLab CI でシンプルに自動デプロイする
●
ぼくはただ、 push したとき勝手にサーバに上げてくれさえすればいいんだ……。
だのにネット上には、他のソフトやら何やらと併用したり何かと高度な記事ばかりで参った。ここに超シンプルなデプロイのみを行う GitLab CI を使った遊びをノートしておく。目指すのは以下のような流れ。
デプロイってのはなんか、作ったプログラムを公開するというような意味合いの言葉らしいぞ。
●
1. デプロイ先のディレクトリの準備
こういうふうにサーバにディレクトリを用意しておくぜ。もちろん書き込み権限は付与しておかないといかん。サーバをいじるのには Cyberduck アプリを使っている。
2. リポジトリの token をコピー
GitLab のリポジトリの Setting > CI/CD > Runner の 'Use the following registration token during setup' ってとこにある。
3. GitLab CI Runner をインストール
サーバのほうで
# 32bit なのか 64bit なのかチェック
$ uname -a
# X86_64 って表示されたから 64bit らしいぞ! その場合このバイナリを DL。
$ sudo wget -O /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-amd64
# gitlab runner の実行権限を与えます。
$ sudo chmod +x /usr/local/bin/gitlab-runner
# runner を実行するユーザを作成します。
$ sudo useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bash
# 作ったユーザを実行者として runner をインストールします。
$ sudo /usr/local/bin/gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner
# 起動しておきます。
$ sudo /usr/local/bin/gitlab-runner start
# 起動確認です。 active(running) とか出ればOK。
$ sudo /usr/local/bin/gitlab-runner status
4. Runner を作ってリポジトリに登録
$ sudo /usr/local/bin/gitlab-runner register
と打つと色々連続で聞かれるから、以下のように入力していく。
聞かれること
答え方
Please enter the gitlab-ci coordinator URL
https://gitlab.com/
Please enter the gitlab-ci token for this runner
上でコピーした token
Please enter the gitlab-ci description for this runner
for ci-cd-test, shell runner
Please enter the gitlab-ci tags for this runner
ci-cd-test,shell-runner
Please enter the executor
shell
5. Runner 登録を GitLab で確認
↓ 最初のうちは not connected 状態なのだけれど……
↓ ちょっと待つと緑丸がついて使えるようになる。
6. .gitlab-ci.yml
.gitlab-ci.yml をリポジトリの .git のある階層へ置く。
stages:
- でぷろい
でぷろいじょぶ:
stage: でぷろい
tags:
- ci-cd-test
- shell-runner
script:
- pwd
- origin=$(pwd)
- cd /var/www/ci-cd-test
- ¥cp -rf "${origin}/." .
7. 動作確認
↓ master ブランチに push したあと GitLab の CI/CD > Jobs で script に書いたものが実行されているのがわかる。
↓ サーバにもちゃんとアップされているぞ。
●
実はいま、みろりHPをリニューアルしようかと考えている。今回の話はその一環だ。ウェブサイトのソースを push しただけで更新できるなら、ラクだなーと思って手を出してみた。だけどこれぼくには複雑怪奇で、労力に見合ったかどうかはちと微妙なとこか。結局直接ソースをアップしても同じことだからなあ。
まあ本来、この CI という機能はプログラムのテスト等を自動化するのがメインらしい。だからこそ、こういうシンプルなデプロイだけを行う記事が見つからなかったということでもある。
2018.10.06 Saturday
iOS アプリ作ってみた Shibafu
●
●
こんなアプリだ。
VIDEO
もともとTODO管理をプレーンテキストでしている。
プレーンテキストを一番信用しているし、常に開いているSublimeTextエディタからサクサク編集できるから。けれどスマホからいじれないのが問題でな。外出時にタスクを思いついたときはデフォルトアプリのreminderとかを使うわけだけれど、それを帰宅後にいちいちテキストに写すのはあまりにもかったるい。
そういうわけでDropbox上のプレーンテキストをPC、スマホ両方から参照し編集できるアプリを作ったってわけ。完全に自分用でapp storeでの配布もしてない。appleの登録料高いからな。
●
配布できないため書くこともべつにないのだけれど、まあiOSアプリの開発感想を書く。
「アプリを作る」という点においてはやはり楽しめた。実用品だとなおさらだね。
完成後の使用感も良い。いくつか改善点は見つかったけれど。「ぐいっと」やらないと更新されない仕様だと、間違ってスマホ側から上書きしちゃう可能性あるとか。
継続タスクというくくりを作ったのは正解だ。毎日やりたいタスクは完了してログに残しはしてもタスク表からは消えない仕様。これは使いやすい。
あとは日時の決まっているタスクにもくくりを作ったほうが使いやすいかと思った。タスク表での背景色が違うだけでもだいぶよくなりそう。
けれどそういうのを詰め込みすぎて機能豊富なアプリにしてしまうのは下策だ。あくまでプレーンテキストが主体なのだ。プレーンテキスト操作の補助のみが、これに求められている。
タスクログの緑四角をタップしたときその日の完了タスクを閲覧する機能は未実装。
xcodeについて
くそ。
メモリ食い過ぎ。
公式にプラグインを入れられない。プラグインを入れるにはアンサインをしてセキュリティを下げないといけない。
そんな仕様で誰がこんなん使うの? と言いたいがアプリ作るにはxcode使うしかないんだよ。
エラー文がわけわかんない。大雑把で、ひとつのエラー文に対してありうる問題のバリエーションが多すぎ。
Playgroundは便利だと思ったけれど原因不明のフリーズが多すぎ。そしてメモリ食い過ぎ。
こういうのって、なんだ、IDEっていうの? 自分が担当していると自覚している部分(つまりプログラム部分)以外のところで理解不能なエラーが出まくるのがストレスフル。
swiftについて
とてもくそ。
公式ドキュメント が読みづらい。PHP は高望みとしてもPython くらいにはして。
バージョンごとに仕様が変わりすぎ。おかげでネットのサンプルが役に立たない。
4.* ごとにエラーが出るってどういうことだよ。
Pythonみたいなシーケンスのスライスがないのはクソ。
Optional型は面白いと思った。いやぼくは型の自由なPython出なので今のとこただ面倒なだけだが。
行末にセミコロンが要らないのは評価する。
ようはPythonよりいいとこが何も見つからない。
それを踏まえたxcode,swiftとの付き合い方だが……。まあおそらくxcodeとかswiftってのは滅多にアップデートしちゃいけないものなんじゃないか。それで自分のバージョンに合った情報だけを収集していくものなのだろう。というわけでiOSの話を書くときは冒頭みたいに毎度バージョンを書いておく。
●
配布不可とは言ったけれど
GitLab から落として自分のMacでビルドすれば使用は可能。その際のやりかたはREADMEに書いてある。
●
デベロッパ未登録で、ストアでの配布をせず、個人用に楽しんでいるわけだ。だが永久に使えるわけではなさそう。
一週間ほど使っていたら No Longer Available と出た。またぱそこからiPhoneへビルドし直せば使えるんだが、うんざりさせられる仕様だ。調べてみたらストアで購入したアプリでさえも、配布元の公開が終わってしまうと勝手に No Longer Available になるとか? たしかにアプリを作るのは楽しめたけれど、スマホという自由度のない世界には窮屈さを感じたよ。
2018.07.17 Tuesday
Sublime Text3 3.1.1でビルドができなくなった問題
●
Sublime ちゃんのアップデートが走ったら、Python のビルドができなくなった。
env: bash: No such file or directory って出ちまう。
こんな感じ。ターミナルでは実行できてるけれど、Sublime では実行されてない。なんか Sublime の実行結果に表示されている path が妙に少ないように感じたのでそのへんから探ってみたのだけれど、以下のような感じにして解決した。
基本設定から開ける Preferences.sublime-settings の build_env の項に bash コマンドのパスを追加。
"build_env":
{
"PATH": "/bin/:/usr/local/bin/"
},
俺の場合は /bin/ に bash があるのでそれを追加した。
Python.sublime-build のほうは(変わらず)こんな感じ。
{
"cmd": ["python -u \"$file\""],
"file_regex": "^[ ]*File \"(...*?)\", line ([0-9]*)",
"selector": "source.python",
"shell":true,
// ずっとコレ書いてたけれど、ターミナルの python コマンドで
// 目当てのバージョンが実行できるようになってればいらないっぽい。
// "osx":
// {
// "path": "~/.pyenv/shims",
// },
}
2018.07.01 Sunday
Python 雪子さん問題
●
例によってプログラミング問題です。今回は「
直線上の位置0にいる雪子さんが、nの距離にある自宅に帰ろうとしています。雪子さんは酔っ払ってるので前・後ろにランダムで歩きます。ただしn*2歩あるくと疲れ果てて倒れます。雪子さんが自宅に帰れるパターンの総数を求めよ。 」先日重複順列を作ろうとしていたのはこのためだったのだ。
●
これが重複順列作成の方針。こういう考え方でいってみる。初めての gif 作成。
上の図ではカーソルが全パターン最深部まで進んじゃってるけれど、本問題では全パターン網羅する必要ない。カーソルが引き返す条件を、「カーソルが2nに達したとき」「+の合計がnに達したとき」「カーソルがnに達したのに+の合計が0未満」としてみる。条件を先日の重複順列関数に足すと……。
def foo(n):
# 家に辿り着いたルート数。
answer = 0
# 雪子現在地。
yukiko = 0
# 雪子現在歩数。
cursor = -1
# 雪子体力限界。
max_cursor = n * 2 - 1
# 歩行履歴リスト。
lis = [0 for i in range(n * 2)]
# 問題の都合で登場する数値。
big_num = pow(10, 9) + 7
# 重複順列関数に雪子条件を足したもの。
def bar(yukiko, cursor, lis):
nonlocal answer
for i in [1,-1]:
# 1歩移動。
cursor += 1
lis[cursor] = i
yukiko += i
# 帰宅。記録してから、時間よ戻れ。
if yukiko == n:
answer += 1
cursor -= 1
yukiko -= i
# 限界の半分まできたのにまだ現在地0を超えてなかったら無理。時間よ戻れ。
# 体力限界。時間よ戻れ。
elif (cursor-1 == n and yukiko <= 0) or (cursor == max_cursor):
cursor -= 1
yukiko -= i
# 次の1歩へ。
else:
yukiko, cursor, lis = bar(yukiko, cursor, lis)
# for が終わったら時間よ戻れ。
yukiko -= lis[cursor]
cursor -= 1
return yukiko, cursor, lis
bar(yukiko, cursor, lis)
# 問題の都合。10^9+7の余りを返却します。
return answer % big_num
できた! とても大変であった。
●
テストケースを作って、時間を測ってみる。
# 計測開始!
import time
start = time.time()
# スタート。
import yukiko_node
# テスト。入力値、答えの順番。
test_cases = [
[1, 1],
[2, 3],
[3, 4],
[4, 19],
[5, 26],
[6, 144],
[7, 197],
[8, 1171],
[9, 1597],
[10, 9878],
]
for test_case in test_cases:
answer = yukiko_node.foo(test_case[0])
if answer != test_case[1]:
print(f'はずれー 出題:{test_case} 今出た答え:{answer}')
exit()
margin = time.time() - start
print(f'結果: {margin}秒')
# 結果
結果: 0.46582603454589844秒
ええやん。ただ入力が11を超えると段々1秒を超え、2秒を超え……きつくなっていくけれど。まあやり遂げたぜ!
2018.06.24 Sunday
Python 重複順列を自分で作りたい
●
半日もかけてしまった……今日がんばって作ったものを公開するぜ。
今回作りたいのは重複順列を生成するプログラムだ。
[True, False] 2
を与えると……
[True, True]
[True, False]
[False, True]
[False, False]
これを生成してくれるやつね。
ぶっちゃけこれをやってくれるライブラリはあるんだけどな。
# これでイイ。
import itertools
for x in itertools.product([True, False], repeat=2):
print(x)
でもまあコレを見本として、自分で作ってみよか。
●
こんな感じの考え方で。
def my_product(a, n):
# nぶんのリストを用意します。 [None, None, None] ここにTrue Falseを詰めていきます。
lis = [None for i in range(n)]
cursor = -1
cursor_max = len(lis) - 1
def foo(lis, cursor):
for i in a:
# 1階層もぐって1個詰める。
cursor += 1
lis[cursor] = i
# 最深部だったら出力して1階層戻る。
if cursor == cursor_max:
print(lis)
cursor -= 1
# 次の階層へ。
else:
lis, cursor = foo(lis, cursor)
# forが終わるとき、1階層戻る。
cursor -= 1
return lis, cursor
foo(lis, cursor)
my_product([True, False], 2)
できた。
●
すげえ苦労した。ガチ半日。再帰関数を作るのがめちゃむず。普通プログラムのエラーを理解するときは上から一行ずつ追っていくわけだが、再起はそうはいかないから……。最近じゃいちばん書けてテンション上がった python だったぜ。冒頭で itertools.product を挙げているけれど、実はそれと同等のものは作れていない。今回の関数は生成した配列を print するだけで yield してくれないのだ。
って、何の動機もなく突然こんなの書きだすわけない。これはとあるプログラミング問題のために作ったのだよ。実はそっちのほうで重複順列を使いたかったんだけれど、 yield されたらむしろ困る感じだったのだ。だから自作が必要だったわけ。そっちもまた後日載せよう。
2018.02.19 Monday
Brett Slatkin『Effective Python』その3 クラスと継承
●
前回に続いて、クラスの章で楽しめたところをノートしていく。
●
記録管理に辞書じゃなくてヘルパークラスを使う
もちろん簡単なやつなら辞書でいい。クラスを使うタイミングとしては「辞書が入れ子になっちゃった」、「タプルが3以上の要素数をもっちゃった」など。
これは自作ゲーム『一石二鳥』のとき何を参考にするでもなく自然にできてたからよさげ。
フック
list.sort(key=lambda...)
みたいに関数を渡して振る舞いをカスタマイズする仕組みをフックというそうだ。
そもそもsortに関数を渡せることを知らんかった……。
__call__を定義するとオブジェクトが関数みたいに呼び出せる
すげー! じゃあstrクラスを改造すれば'ただの文字列'()
なんてことができるってことかよw
と思ってすこしいじってみたんだが、どうやら文字列などの組み込み型は改造ができんようになってるようで……。このイタズラは完遂できなかった。
クラスメソッドをコンストラクタとして使う
@classmethod
def foo(cls):
return cls()
こういう……ことだとは思うんだけどこれだけじゃ意味不明だよな。
子クラスで親クラスの__init__を呼ぶとき
super().__init__()
とする。親クラスのメソッドは子クラスでも息づくんだから、こんなの必要ないじゃんと思ったが。そっか、子クラスにinitを書いてしまったら親クラスのinitは実行されなくなっちゃうのか。
private属性の使い方
self.__foo
こういう命名の変数はprivate属性になってクラス内でしかアクセスできなくなる。子クラスからもダメ。ただし_Klass__foo
っていう呼び出し方でコールすることが可能。
private変数は、コントロール外のサブクラスによる名前衝突を避けるために使いましょう。
●
クラスは理解したからこの章はまあオッケーだった。が、次章がどうもするりと入ってこねえ……。
2018.02.12 Monday
Brett Slatkin『Effective Python』その2 関数
●
前回 に続いて、関数のパートで楽しめたとこをノートしていく。
●
関数返り値にNoneを設定するのはヤメて、例外を飛ばせ。
try:
return a/b
except ZeroDivisionError as e:
raise ValueError('えらぁだよ') from e
てかこうやってエラーの種類を変えられるの知らなかったわ。
Pythonはクロージャをサポートしてる。
クロージャってのは、定義されたスコープの変数を参照する関数のこと。ああ、関数の中から外の変数を使えるもんね。アレのことか。
タプルの比較規則。
(0, b) (1, a)
の場合、まず[0]の0と1、そのつぎに[1]のbとaの比較になる。
nonlocal宣言でローカル変数の参照渡しみたいなことができる。
def foo():
a = 'fooのa'
def bar():
nonlocal a
a += 'なぁんつって'
bar()
print(a) # fooのaなぁんつって
foo()
こういうことか。aがグローバル変数だった場合 nonlocal じゃなくて global になるところが注意。
リストを作成して返す関数は、もしかしたらジェネレータ関数にできるんじゃない?
やり方は lis.append(x)
していたところを yield x
にするだけ。
これはかなりいいのでは? プログラミング問題とかでよくでかいリストを返す関数を作ってたけど、あれは全部エコなジェネレータにできるってことか。
可変長位置引数で見た目をすっきりさせよう。
def foo(message, *args):
こういうやつね!
foo('メッセージ', 1, 2, 3, 4)
って呼び出せば、1以降がリストになって args に代入される。……ん? いや別に、最初からリストで渡せばよくね?
ちなみにすでにリストになってるなら
foo('メッセージ', *list)
って渡せる。いや、最初からリストでよくね???
可変長位置引数を使うとき注意しないといけないことがあって、たとえば上の foo を
foo(message, message2, *args)
に定義変更しても呼出でエラーは起こらない。これは発見困難バグを生むのでやばい。さらに、これにジェネレータを渡すと勝手にタプルにするからメモリ爆発することがある。いやメンドくせーよ! 最初からリストで渡せばいいだろ!
キーワード引数、デフォルト引数は良い。
def foo(message, option1=1, option2=2):
見るからに使い勝手いい。
デフォルト引数には罠がある。
def foo(message, when=datetime.now()):
こうするとwhenのデフォルト値は定義時の時間で固定されちゃう。fooを呼び出すたびにその時間を取得してほしいなら、デフォルト引数Noneを使う。
def foo(message, when=None):
when = datetime.now() if when is None else when
こんな罠もあるよ♪
def foo(x, dic={}):
dic[x] = 1
return dic
print(foo('a')) # {a:1}
print(foo('b')) # {a:1, b:1} クソワロタww
定義時にひとつのディクショナリが定義されちゃってて、以降デフォルト引数にはそのディクショナリオブジェクトが使われ続けるので、こーゆーことになる。これも上と同じで None を使う。
キーワード専用引数。
これはすげぃ。
def foo(a, b, *, option1=False, option2=False):
こうすると、option1以降の引数はキーワードでしか指定できなくなる。完全にオプション専用の引数ができるってことじゃん。
Python2ではこの構文がないのだけど、同じ動作を作ることはできる。
def foo(a, b, **kwargs):
option1 = kwargs.pop('option1', False)
option2 = kwargs.pop('option2', False)
if kwargs:
raise TypeError('Unexpected **kwargs')
●
今回のフェイバリットは最後のキーワード専用引数かな。すげえ便利そう。
2018.02.03 Saturday
Python 迷路を解くプログラム
●
例によってプログラミング問題です。問題を見た瞬間おっもしろそー! と思った。
# これを…… -> こうする!
# ######## #+########
# ## ### # #+## ### #
# ## # #++++## #
#### ## ####+ ##
# ## ##### -> # ##+#####
# # # #++++#+++#
# #### # # #+####+#+#
# # # #++++++#+#
## ## ## # ## ## ##+#
######## # ########+#
●
みどりんの考えたのがこういうロジック。
●
メインロジック。いちばん上の solve_maze がトップレベル。
import os, itertools, copy, enum
def solve_maze(maze):
"""迷路を解きます。"""
# 最初の奴隷を用意します。
first_walker = Walker()
# 通路座標を奴隷の歩行可能座標として登録します。
first_walker.walkable_points = maze.pathways
# こいつをスタート地点に立たせます。
first_walker.start_point = maze.start_point
first_walker.step_to(maze.start_point)
# 分かれ道のたびに、奴隷を増殖させて袋小路まで進ませます。
# そのたびにwalkersリストに追加されるので、最終的に全通りのルートがwalkersに登録されることになります。
walkers = [first_walker]
for walker in walkers:
# 奴隷ひとりにつき再帰関数ひとめぐり。
walker_life(walker, walkers)
# 勝者を決めます。
winner = compete_walkers(walkers, maze)
return winner
def walker_life(walker, walkers):
"""奴隷の歩みを処理する再帰関数です。"""
# 今いるマスの東西南北に道があるか調べます(一度踏んだマスは通れない)。
next_points = walker.look_around_next_points()
# 進める場所がない場合、そいつは終了です。
if not next_points:
return
# 道がひとつだけある場合、次のマスへ移動させます。
if len(next_points) == 1:
walker.step_to(next_points[0])
# 道が2以上ある場合、奴隷を分裂させます。
else:
process_for_multiple_possibilities(walker, next_points, walkers)
walker_life(walker, walkers)
def process_for_multiple_possibilities(walker, next_points, walkers):
"""道が2以上ある場合の処理です。forが長くなるのヤだから分離しました。"""
# 進む前の奴隷をコピーしておきます。
walker_copies = [walker.create_myself() for i in range(len(next_points) - 1)]
# 現奴隷は次のマスへ移動します。
walker.step_to(next_points[0])
next_points.pop(0)
# コピーした奴隷たちを、それぞれ別の分かれ道へ進ませます。
# そして次の while のためwalkersリストに溜めておきます。
for walker_copy, next_point in zip(walker_copies, next_points):
walker_copy.step_to(next_point)
walkers.append(walker_copy)
def compete_walkers(walkers, maze):
"""最優秀ゴール者を決めます。関数が長くなるのヤだから分離しました。"""
# 奴隷の中から、ゴールした連中を取り出します。
goal_walkers = list(filter(lambda walker: walker.stand_point == maze.goal_point, walkers))
# インデックスとwalker.step_numのディクショナリ。{0:30, 1:25, 2:10}みたいになる。
dic = {i: goal_walker.step_num for i, goal_walker in enumerate(goal_walkers)}
# step_numでの昇順ソート。[(2,10), (1,25), (0,30)]みたいになる。
ranking = sorted(dic.items(), key=lambda value: value[1])
# ゴール者の中で、最短距離をいった者が最優秀ゴール者です。
return goal_walkers[ranking[0][0]]
迷路のマスを定義する。ぶっちゃけ今回のプログラムではあんまり必要ないクラスだけど enum っていうのを使ってみたくてさあ。
class MazeSigns(enum.Enum):
""" 迷路のマスをenumで定義してみます。
具体値の取り出し方は MazeSigns.WALL.value こう。"""
WALL = '#'
PATHWAY = ' '
FOOTPRINT = '+'
STAND_POINT = '*'
迷路データを簡単に扱えるようにするクラス。ちょっと色気づいて、最近オライリーで覚えた private 属性を使ってみた。
class Maze:
""" 迷路データを簡単に扱うためのクラス!
ほら、文字列のままだと座標指定でマスを取得するとかできないじゃん?
そういうのができるようになります。10x10だけに対応してるよ!"""
def __init__(self, original_maze):
self.original_maze = original_maze
# MazeSignsのディクショナリ。
self.sign_dic = {sign.value:sign for sign in MazeSigns}
# マップ文字列を二次元配列にしたものです。
self.map_list = self.__convert_map_to_list()
# 通路部分の座標のリストです。
self.pathways = self.__make_pathways()
# この迷路のスタート地点とゴール地点です。
_ = self.__get_entrances()
self.start_point = _[0]
self.goal_point = _[1]
def __convert_map_to_list(self):
"""(private)マップ文字列を二次元配列にして、中身を全部MazeSigns型に変換します。"""
lines = list(filter(lambda line: line != '', self.original_maze.split(os.linesep)))
lines.reverse()
# マネしちゃダメな堂々の二重内包表記。クソ読みづらい(笑)
return [[self.sign_dic[cell] for cell in list(line)] for line in lines]
def __make_pathways(self):
"""(private)歩行可能座標のリストを作ります。"""
# 通路の部分の座標を求めます。
pathways = []
for x, y in itertools.product(range(10), range(10)):
if self.map_list[y][x] == MazeSigns.PATHWAY:
pathways.append((x, y))
return pathways
def __get_entrances(self):
"""(private)迷路のスタート地点とゴール地点を求めます。"""
entrances = []
for coordinate in self.pathways:
if coordinate[0] in [0, 9] or coordinate[1] in [0, 9]:
entrances.append(coordinate)
return entrances
迷路を歩かせる奴隷を作成するクラス。
class Walker:
"""迷路を歩く奴隷を意味するクラス! 自分が歩いた座標を記録していくよ!"""
# インスタンスひとつひとつに振るIDです。
walker_id = 0
def __init__(self):
# ID。
self.walker_id = Walker.walker_id
# 初期座標。
self.start_point = None
# 現在地。
self.stand_point = None
# 歩行可能座標。
self.walkable_points = []
# 歩行履歴。
self.walk_history = []
# 歩行数。
self.step_num = 0
def __str__(self):
"""str(walker)したとき現在のコイツの状態を返します。"""
return os.linesep.join([
f'',
f'初期座標:{self.start_point}',
f'現在地:{self.stand_point}',
f'残歩行可能座標:{self.walkable_points}',
f'歩行履歴:{self.walk_history}',
f'歩行数:{self.step_num}',
])
def step_to(self, next_point):
"""引数の座標へ歩みます。"""
self.stand_point = next_point
self.walk_history.append(next_point)
self.walkable_points.remove(next_point)
self.step_num += 1
def look_around_next_points(self):
"""今のマスの東西南北に歩行可能マスがあるか調べます。"""
# 東西南北の座標です。
next_points = [
(self.stand_point[0], self.stand_point[1]+1),
(self.stand_point[0], self.stand_point[1]-1),
(self.stand_point[0]+1, self.stand_point[1]),
(self.stand_point[0]-1, self.stand_point[1]),
]
# 進める座標は、歩行履歴になくて歩行可能座標にあるもの。
next_points = filter(lambda point: point not in self.walk_history, next_points)
next_points = filter(lambda point: point in self.walkable_points, next_points)
return list(next_points)
def create_myself(self):
"""自分自身のコピーを作ります。"""
walker = copy.deepcopy(self)
walker.walker_id += 1
return walker
def draw_steps_on_map(self, maze):
"""自分が歩いた軌跡をマップに書きます。"""
map_list = maze.map_list
for coordinate in self.walk_history:
map_list[coordinate[1]][coordinate[0]] = (
MazeSigns.STAND_POINT if coordinate == self.stand_point else MazeSigns.FOOTPRINT)
# 文字列にします。マネしちゃダメな堂々の 3重 内包表記。クッソ読みづらい。
map_list.reverse()
return os.linesep.join([''.join(m) for m in [[maze_sign.value for maze_sign in line] for line in map_list]])
実行はこんな感じ。
#####################################################################
# ここまでプログラム。ここから実施。
#####################################################################
questions = [
'''
##########
##########
##########
###
### ####
### ####
### ####
### ####
####
##########
''',
'''
##########
### #
### ## # #
### ## # #
# ##
# # #### #
# # #
# ########
# ####
##### ####
''',
]
for i, question in enumerate(questions):
maze = Maze(question)
winner = solve_maze(maze)
print()
print(f'-*-*-*-* question{i} *-*-*-*-')
print(winner)
print(winner.draw_steps_on_map(maze))
結果はこう!
-*-*-*-* question0 *-*-*-*-
<Walker オブジェクト ID:2>
初期座標:(0, 1)
現在地:(9, 6)
残歩行可能座標:[(3, 2), (3, 3), (3, 4), (3, 5), (3, 6),
(4, 2), (4, 3), (4, 4), (4, 5), (4, 6)]
歩行履歴:[(0, 1), (1, 1), (2, 1), (3, 1), (4, 1), (5, 1),
(5, 2), (5, 3), (5, 4), (5, 5), (5, 6), (6, 6),
(7, 6), (8, 6), (9, 6)]
歩行数:15
##########
##########
##########
### ++++*
### +####
### +####
### +####
### +####
++++++####
##########
-*-*-*-* question1 *-*-*-*-
<Walker オブジェクト ID:2>
初期座標:(5, 0)
現在地:(9, 5)
残歩行可能座標:[(3, 6), (3, 7), (3, 8), (4, 8), (5, 8),
(6, 5), (6, 6), (6, 7), (6, 8), (7, 5),
(7, 8), (8, 6), (8, 7), (8, 8)]
歩行履歴:[(5, 0), (5, 1), (4, 1), (3, 1), (2, 1),
(1, 1), (1, 2), (1, 3), (1, 4), (1, 5),
(2, 5), (3, 5), (3, 4), (3, 3), (4, 3),
(5, 3), (6, 3), (7, 3), (8, 3), (8, 4),
(8, 5), (9, 5)]
歩行数:22
##########
### #
### ## # #
### ## # #
#+++## +*
#+#+####+#
#+#++++++#
#+########
#+++++####
#####+####
●
迷路を解くだけでなく、最短距離を求められてる。完璧! クラスを効果的に使えてるし、内包表記や高階関数を使いすぎなまでに使ってるし、再帰関数も使えてる。満足!
2018.01.28 Sunday
Brett Slatkin『Effective Python』その1 Python流思考
●
いえーい。最近はPythonの気分なので、その流れで読むぜ。面白かったとこをノートするいつものノリで。
●
PEP8をまもれ!
Python本はこればっかですね! 先日までSublime Text3にPEP8のチェッカーパッケージを入れていたんだけど、「ほかのひとのコードをネットからコピペしたときペケだらけになって非常に見づらい」「関数間を2行空ける」の2点がイヤでヤメちまった。まあ基本的なところは守っているぜ。
例として記載されていたなかで目新しかったのが下記ふたつ。
・len(lis) == 0
ではなく not lis
を使いましょう。
・import文は標準モジュール、サードパーティ、自前のモジュールの順番に記載する。
PEP8で気に入ってるやつ
コーディング規約ってのは、知ると目ウロコなものがたくさんある。とくに気に入ってるのが、「一行は79文字以下におさめましょう」ってやつ。これを守るとスクリプトがスッキリして見える。積極的に守っていきたい。
どうしても行が長くなることってあるじゃん? クラス名もメソッド名も長いから書こうとしたらどうしても幅とっちゃうんだよー、とか引数が多いから仕方ないじゃん、とか入れ子が多くなればなるほどインデントが増えるんだからどうしようもないじゃんとか。でもさ、そういうときコーディング規約はこう言ってくるんだよ。「そういうときは設計がおかしい。」 この発想は気に入った。そもそも名前は短いほうがいいし、引数はなるたけ少なくするのが基本だし、forとかifの入れ子は少なくするべきだ。
文字列型の bytes と str を知っておく。
文字列型には上記ふたつがあって、それぞれ変換は……
bytes_.decode('UTF8') # str化
str_.encode('UTF8') # bytes化
ほんでバイナリファイルの読み書きでは、open関数のオプションに rb とか wb を使うこと。まあそれはいいけどバイナリファイルなんてどんなときにいじるのか俺には想像がつかん。アレだろバイナリって、0と1だけの……。
ディクショナリ.get(キー, None)
キーがなければNoneを返す、というディクショナリのメソッド。うわこれ知らんかった、便利そうだ。
ヘルパー関数を使いましょう。
Pythonは複雑で読みづらい式を1行で書きやすい。そういうときは行を分けて読みやすくして、それをヘルパー関数にまとめましょう。最近高階関数を覚えて大はしゃぎでコードを難読化してる俺には耳が痛いお話です。
スライスを使おう!
lis[0:5]
と lis[:5]
は同じ。後者を使いましょう。うへえ、0書いてたわあ。
[::2]
みっつめにはstride……なん文字ごと、が指定できる。
スライスではstart,end,strideをいっしょに使うな。
lis[2:10:2]
はダメ。わかりづらいから。やるなら2行に分けること。
mapやfilterの代わりにリスト内包表記を使おう!
そうか……mapやfilterって、内包表記に置き換えることができるのか。これは目ウロコだ。
a = [0, 1, 2, 3, 4, 5]
# だよんをつける map版
print(list(map(lambda i: f'{i}だよん', a)))
# だよんをつける 内包表記
print([f'{i}だよん' for i in a])
# ↓どちらもこうなる
['0だよん', '1だよん', '2だよん', '3だよん', '4だよん', '5だよん']
# 奇数のみ filter版
print(list(filter(lambda i: i%2 == 1, a)))
# 奇数のみ 内包表記
print([i for i in a if i%2 == 1])
# どちらもこうなる
[1, 3, 5]
……ってことか。この例ではだけど、内包表記を使えば文字数は短くなるし、わざわざlist()を使わなくて済む。逆に結果をイテレータのままにしたい場合はmap、filterの使い所だろうね。
mapとかfilter、内包表記をさらっと書ける自分に成長を感じる! いやあプログラムはじめてから2年たってるもんなあ。
内包表記ではforを重ねることもできるぞ!
rows = [
[0, 1, 2],
[3, 4, 5],
]
# 二重forバージョン
_ = []
for row in rows:
for r in row:
_.append(r)
print(_)
# 内包表記バージョン
print([r for row in rows for r in row])
# どっちもこうなる
[0, 1, 2, 3, 4, 5]
ただし3つ以上になったらヤメとくこと。読みづらくなるから、ヘルパー関数とか使いましょう。
ところで二重の内包表記の書く順番ってちょっと間違えやすくない? 最初forを二重に内包表記って聞いて、こうかと思った。[r for r in row for row in rows]
作ろうとしてるリストがバカでかくなっちゃうときは、ジェネレータを使おう!
lis = [i for i in range(10000000000)]
こんなことしたら10000000000個要素が詰まったリストが生成されてメモリが爆発しちゃいそうじゃん? そんなときは gene = (i for i in range(10000000000))
こうする。中身の取り出しは next(gene)
これでいっこずつ取り出せる。lis[10]みたいに途中の要素を取り出すことができないのが難点かな? でもでっけえリストを生成しないってのはイイね。
連鎖ジェネレータはすげえ高速。
gene = (a for a in open('foo.txt', 'r', encoding='UTF8'))
gene2 = (a.strip() for g in gene)
enumerate()の使い所は……リストを回したいが添字も欲しいとき!
for i, value in enumerate(lis, 1):
print(i, value)
こういうこと。enumerateに第二引数を指定すれば、iがその数からスタートになる。別にlis[1]からスタートってわけじゃない。これ便利ね。
複数のイテレータを回したいならzip()を使うこと。
ああー、これは言われなくても知ってるよー。
# ただしイテレータの長さが違うと、片方が終わったところで終了しちゃう。
# それがイヤなときは itertools.zip_longest() を使うこと。
それはまったく知りませんでしたすみません。
for - else っていう構文がある。
へー! しらなかった!
# が、それは振る舞いが直感的じゃないから使うな。
えー!?
try, except, else, finally の使い分け。
try: 例外が起こるかもしれないコード
except: 例外が起こったときやること
else: tryが成功したときすること。ここをしっかり書いて、tryブロックを最小にしよう!
finally: file.close()など後始末を。「たとえelseでreturnが起こってもこのブロックは実行される。」へぇえー!!
2017.12.08 Friday
Python 素数
●
って、またpythonかよ。最近はホントpythonの波がきてる。
例によってプログラミング問題。今回は「
与えられた数nまでの素数を列挙する 」。最初に言ってしまうと今回のスクリプトは完全に俺の自力作ってわけじゃない。はじめに書いたものは以下のような仕組みだった。
# 1. [2〜n]のリストを作る。
[2, 3, 4, 5, 6, 7, 8, 9, 10, 11, ... n]
# 2. 下から順番に素数かどうかチェックする。2からその数までの数で割ってみて、割り切れなかったら素数である。
[2(素数だッ), 3(素数だッ), 4, 5, 6, 7, 8, 9, 10, 11, ... n]
# 3. 素数確定したら、リストからその数の倍数を全部削除していく。
[2(素数だッ), 3(素数だッ), , 5, , 7, , , 10, 11, ... n]
完成はしたのだけど、知り合いが言い出した。
「これ2〜nまで全部そのチェックしてんの?
nの平方根まででよくない? 」
マジで? nが100だったら10までチェックすればいいってこと? なんで?
これの理解に時間を使っちゃったんだが、知り合いの根気強い解説もありなんとか以下のような理解ができた。
2から平方根数までは上述の俺の発想で問題ない。
「平方根より上はチェック必要ないよ」と言われて俺が疑問なのは、「平方根より上の素数の倍数が残ってたらどうすんの?」ってことだ。でもそれが杞憂だったんだよ。
なぜかといえば、平方根より上の数の倍数で、かつnまでに収まる数を作るには平方根未満の数を相方に使わないといけない からだ。そして平方根未満の数の倍数については上述のチェックで終了している。
どっちも平方根を超えた数だったら、nより大きい数になっちゃう。その数は対象外だから問題ない。
よって、俺が心配していた「平方根より上の素数の倍数」はチェック済み、または対象外ということになる。なーんだ!
n=25としたときの具体例で復習してみる。
2 3 4 5
6 7 8 9 10
11 12 13 14 15
16 17 18 19 20
21 22 23 24 25
2から5までは上述の俺の発想で問題ない。
6から上の数の倍数で、かつ25までに収まる数を作るには5未満の数を相方に使わないといけない。そして2,3,4の倍数については上述のチェックで終了しているから問題ない。
どっちも平方根を超えた数、6以上だったら、25より大きい数になっちゃう。その数は対象外だから問題ない。
●
というわけで完成した回答がこれ。
# get_primes.py
from math import sqrt
def get_primes(n):
'''2からnまでの素数を求めます。'''
# boolスイッチ。素数じゃないもののスイッチをFalseにしていきます。
switches = [b > 1 for b in range(n+1)]
# 素数チェックはnの平方根まででOK。
for i in range(int(sqrt(n))+1):
# スイッチがFalseのものは、0,1,または下の倍数スイッチオフの対象になったものです。
if switches[i] is False:
continue
# 素数を残しつつ、その倍数のスイッチをFalseにしていきます。
j = i * 2
while j <= n:
switches[j] = False
j += i
# スイッチがTrueのものが素数です。
return [i for i in range(len(switches)) if switches[i] is True]
実行結果。素数のプログラムは速ければ速いほど良いというので時間も出してみた。
import time
# 計測開始。
start = time.time()
# 実行。
import get_primes
print(len(get_primes.get_primes(100000)))
# 計測結果。
my_time = time.time() - start
print(f'my_time : {my_time}')
#
# 9592 (2〜100000間の素数の数)
# my_time : 0.031523704528808594 (かかった秒数)
#
Copyright (C) 2007- Midoriiro All Rights Reserved.
+ 閲覧記事
+ 過去記事アーカイブ
+ 年月選択
+ カテゴリ
+ ブックマーク
+ 最近のコメント
+ アクセスカウンター
全体(since 2010.02.03.)
今日…
昨日…