技術をかじる猫

適当に気になった技術や言語、思ったこと考えた事など。

プログラマ脳を鍛える数学パズル 23

手元にコインがあり、ゲーム参加に 1 枚消費する。勝てば2枚得られる。
これで、24 回ゲームできる手順は何通りあるか?

原文では 24 回戦って、コインが手元にのこる手順と書いてあるくせに、24 回ゲームに参加する手順になってるところが少しもやっと…

from functools import lru_cache

START_COIN = 10
TURNS = 24

@lru_cache()
def current_game(game:int , coins: int) -> int:
    if game == 0:  # ここで coins > 0 を入れると結果が変わってくる。
        return 1
    if coins == 0:
        return 0
    return current_game(game - 1, coins + 1) + current_game(game - 1, coins - 1)


print(current_game(TURNS, START_COIN))

といってもそんなに複雑な事はなくて、終了条件だけ定めてやれば再起でさっくり片付く。
そして、何気に最近したのだけど、lru_cache なるデコレータが存在し、これを入れるだけでメモ化ができるという。

便利。

オンラインジャッジで遊んでみる

最近 paiza 弄って遊んでました。
裏はやっぱ Docker なのかなーとはいえ、複数の言語用コンテナ用意してメンテとか、K8S だろうか…

むしろ何がすごいなって、フロントエンドでちゃんとインデントとかサジェスチョンとか使えるところ。
言ってみればちょっと簡易な IDE ばりに補完とか走ってて、しかも言語別に用意してるはずなので、頭が下がる。

尚、Python 3 のバージョンは 3.6+ までは確認。
f 記法使えたので。

f'Hello {name}'

しかしこの手のコードはゴルフで C、そうでなくて、純粋に解くだけだと python とかスクリプト言語以外で解く気力がわかない(汗
IDE 無いと、型宣言言語とか面倒すぎがその理由。Java/C/C++ で解く人すげえなと…

B 問題で、仕様見落として何回か失敗したので、A 問題しこたま眺めてチェックして解いたら 1 時間もかかった(汗
問題にもよるんだろうけど、問題に対してかかりすぎ感…(平均よりは早いのだけどとはいえ1hはなぁ…

ゼロから作るDeepLerning 3章

書籍はこれ

3章の内容を超ざっくり説明すると、各種活性関数(ニューロンに使用される関数)の説明と、MNIST データセット の紹介/(を読み込んで処理Pythonデータ構造にするライブラリの)使い方。
よく訓練されたニューラルネットワークの数字文字の識字率(見て驚けー)。

まで。

学習までは踏み込んでない状況。

公式のリポジトリ見ればわかるので、大した意味はないのだけど、この章で実装した各種活性関数/出力関数。

import numpy as np
import matplotlib.pylab as plt


def step_func(x):
    return np.array(x > 0, dtype=np.int)


def sigmoid(x):
    return 1 / (1 + np.exp(-x))


def relu(x):
    return np.maximum(0, x)


def identity_function(x):
    return x


def softmax(a):
    exp_a = np.exp(a)
    sum_exp_a = np.sum(exp_a)
    return exp_a / sum_exp_a

プログラマ脳を鍛える数学パズル 22

絡まない糸電話、再起にしてようやっと理解できた

def string_phone(N: int) -> int:
    def pair(n: int) -> int:
        if n < 2:
            print(f'pait:{n} -> 1')
            return 1
        ans = 0
        for i in range(1, n, 2):
            ans += pair(i-1) * pair(n - i - 1)

        print(f'pait:{n} -> {ans}')
        return ans
    return pair(N)


print(string_phone(6))

速度が欲しければメモ化すれば良いと思う。
手続きが理解できないマン

ans = N-2人パターン数x0人パターン数 + N-4人パターン数x2人パターン数... + 0人パターン数xN-2人パターン数

みたいな計算になる。
仮に 6 を当てはめると

6人パターン数 = pair(0)*pair(4) + pair(2)*pair(2) + pair(4)*pair(0) = 1*2 + 1*1 + 2*1=5

そう考えれば解答例にしたがって、事前に 2 で割った方が返って理解が早かったかもわからん

関数型との融合の先に、デザパタと設計が根本的に変わるべきかも

ふと、最近自分のやってる設計をクラス図に落とせるかと言う事を考えて思った事をつらつら。

関数型と融合すること

関数型の概念として以下のようなものがある。

  • 関数(Javaでメソッドと思っていいかもしれない)を引数や返値に指定できる(関数が第一級オブジェクトである)

これは何を意味するかというと、内部で手続き的に変化していく状態(変数と言っていいかもしれない)を、変化していく引数と返値(引数を部分的に適用した関数かもしれない)に置き換えて、関数スタック構造を作っていくという概念に至る。
だから関数型言語では多くの場合で、変数を嫌うし、void 型みたいなものの存在を否定する。

これだけ聞くと慣れない人にはそれで何が作れるんだ?みたいに思うかもしれない。
というか私も始めそう思った。

やってみなければ分からないのだけど、やってみると意外な発見がある。
何より、「状態を排除する」というのが、どれだけ大事かわかる。

状態を排除するとは?

オブジェクト指向で、大抵バグの原因になるのはどういう状況だろう?

  • オブジェクトが想定した動きをしなかったとき

となるのだが、その時中で何が起きているか?と深堀りしよう。

  • オブジェクト自体の状態(クラス変数、依存するクラスの状態等)が変わっていて、呼び出し元の意図した状況での動作をしていない。

という事ではないだろうか?
もちろんオブジェクト指向の設計として、これはオブジェクトの状態設計が不十分だし、もう少し深堀すれば、その処理は本来そのクラスにあるべきではないとか、色々反論できる。

だが、ここで関数型と融合していたらどうだろう?
関数型の思考はともかく状態を排除する方向で進む。

すると何が起きえるのか?
状態(変数等の値)と処理(メソッド)の分離が進む

突き進むと、クラスが大きく二つに分離されていく。

  • データと、データを操作するメソッドが一つになったオブジェクト
  • クラス変数を持たず、前述のデータオブジェクトを引数に計算、応答する関数

(後者をあえて関数と書いたのは、Java 的に言うなら static なメソッドだけで完結すると言う事を言いたかったからだ(当たり前だが、意味ある単位でクラス分けは必要)。)

すると、データとその管理に付随するオブジェクトは、中身がすっからかん(処理が無いとは言わない、IOとかやるべきことはある)になり、処理関数は引数と応答というシンプルな構造になっていく。
必然的にテスタビリティが上がり、単純ゆえにバグを抑えられる。

私は、ソフトウェア設計には 二つの方法があるという結論に達した。 一つは、欠陥がないことが明らかなほど単純にする方法である。 もう一つは、明らかな欠陥がないほど複雑にする方法である。 C.A.R.Hoare

KISS 原則に従うのなら、ステートマシン図とかいろいろな手法で状態管理をさせるより、単純化できるこの様式が正義となる。

因みに、さらに関数原理主義に近づく場合、データクラスも setter は持たす、「新しい状態のクローンクラスを応答する」みたいにするのが正しい。
(もっともそこまでやると面倒なだけなので、必要が無い限りやるべきではない:YAGNI 原則参照)

抽象化アプローチの衝突

オブジェクト指向も、関数型指向も、目指すところが何かというと、抽象化である。
物理的なマシンスペック向上に伴い表現能力が拡大した今のハードウェアで、いつまでも小さな単位で物事を考えていては、完全に追いつかない。

オブジェクト指向は、データと関連する手続きを一つに梱包(Simula言語)し、名前を与えることでトップダウン的にそれを行おうとし、
関数型は数学的に、数学の数式に名前を与えるが如く、小さな関数に名前を与え、関数の組み合わせに名前を与えるというボトムアップ戦略をとった。

Scala 等の関数とオブジェクトのパラダイムがぶつかったのを皮切りに、Java も 8 で関数的アプローチを拾い始めた。
ここでトップダウンボトムアップが衝突を開始したわけだ。

既存のデザインパターンは、オブジェクト指向に沿って開発を行い、可用性を求めた時良く表れる構造を一般化、名前を付けたものである。

だが、これは現状ではオブジェクト指向が前提となっている。
そこに、「メソッドを生成して返す」とか、「メソッドを引数に処理を行う」といった関数的な状況を想定していない。

ファイルのI/O を例に考えてみる。

例えば try-release を行うために、Java は AutoClosable インターフェースを用意し、try 文の言語仕様を拡張した。多分中身はローンパターン。デザパタの一種だ。これは、try - finally までの間で動的に書き込む内容を生成→書き込みを行い、任意(っても finally)で処理を終了する仕組みだ。
では関数ならどうするか?と考えると、In に対して Out ありきで考えるので、「In 書き出すデータすべて Out ファイル」みたいな関数がありそれを使うだけ。状態を持ちまわらないのが前提なので、書き込む内容は事前にすべて準備し、その1関数内だけでOpen/Close も完結してるべきと考える。
(ここではあくまでそれぞれの文化的に善悪を言っているだけなので、現実的にメモリ消費量とかそうした話は考えない。後者は割と原理主義なので私もまずしない)

だがここで考えてみる。
リソース状態の管理としてオブジェクト指向にのっとり、オブジェクト化するのは、オブジェクト指向的に正しいが、try-finally 間だけというのはどういうことか?
もっと自由に動的に書き込む内容を変動させなければ、1スコープごときで閉じるリソース管理に何の意味があるのだろう?
リソースリークを避けるという点で、try-finally で制御しているのだが、そんなスポット的に使用するだけなら、関数型のやり方でもいいのではないだろうか?

上記は非常に極端な思想の違い部分だが、状態排除の為にこうした事を行っていくと、オブジェクト指向部分で求められるものと、関数型部分で求められる事の質が、根本的に変わるのではないだろうかと考えてる。つまり

  • オブジェクト指向
    1. ロジックの切り替え等、設計の動的拡張性の確保(ファクトリとか)
    2. データとその操作処理の梱包
  • 関数型指向:ロジック実装(個々の問題処理実装)

みたいな使い分けになっていかないだろうか…。

ではなぜこれでデザパタや既存設計手法の一部崩壊を?

こうなってくると、デザパタの適用箇所は実業務では少なくなり、必然的に新しいパターンが必要になる。
UML も然りで、関数型入り混じったパラダイムには表現力が足りない気がする(Java なら Function 型みたいな Generic 型で無理やり頑張ってる感があるが、図の表記としては冗長で、もう少しシンプルでないと利用者が敬遠する可能性もある)。

新しい表現方法を考えてみるのも一つの手かもしれない。

(尚、Excel方眼紙に手続きをダラダラ書くような設計は、手続き型以外では害悪でしかないと断じるので、もはやその発展を考えるべきではない)

何でUMLがSI業界に広まらないのか考えてみた

というかそもそもスタンスの違いがあるんじゃないかと思う。
昔ながらの SIer はもしかしなくても

  • プログラムにするべく全命令行を指定してやらないと
    • コーディングできないと思ってる。
    • 読んでもわからない=つまり設計じゃないと思ってる。

こんなところが多分あって、UML とかやってみるとわかるのだけど

  • クラスに単一責任の原則を適用し、メソッドの 役割、IN/OUT を明記すれば、中の実装は開発者が考えることである。

みたいな所があって、性悪説なジャパニーズ SIer は「それで作れるわけねーだろ!」ということで、広まらない説。
結構極端な物言いだけど、そんなにこの説ががずれてるわけではない気がする。

API 仕様書(役割/IN/OUT を明記)さえあればコーディングは十分できると思うのだけど…。
void なメソッドを量産する設計者が多い現状を見ると、その性悪説通りのプログラマも一定数いそうで怖い。

コードの不吉な匂い(変な名前を付ける遊び)

コードを見てると不吉な匂いが色々あります。
色々面白い名前つけれないか個人的に勝手に考えてみた。

いや別に真面目な話をしてるのではなくて、以下の図書3章に変な名前つけて笑おうぜってだけの話。

新装版 リファクタリング―既存のコードを安全に改善する― (OBJECT TECHNOLOGY SERIES)

新装版 リファクタリング―既存のコードを安全に改善する― (OBJECT TECHNOLOGY SERIES)

WETコード(重複したコード)

DRY コードの対極。
要するにコピペか何かで重複したものがあるコード。
WETコードは別に造語ではなく存在する言葉。

ロングストジャーニー(長すぎるメソッド)

スクロールがまるっと埋まるくらい長大なメソッド。
処理終了の道のり(ジャーニー)が長すぎる(ロングスト)から。
やることがい敗詰まっててテストコードが書けない状態。

感動の超大作(巨大クラス)

クラスの役割が多すぎてテスト数が爆発するような状態。
きちんと役割ごとにオブジェクトを切り分けよう。

ちゃんぽん(引数過多)

パラメータリストがアホみたいに長い問題。
引数6個から一気にバグが出るらしい

Googleの2億行のソースコードを解析した結果、関数に渡す引数の順番を間違える系のバグは、引数の個数が6個以上になったときに著しく増えるので - TWILAB

片頭痛(変更の偏り)

変更があったときに、一部分に変更が偏る問題。
大体このケースだと、特定一部がなんでも屋みたいにいろんな処理をしてて、バグ直しが集中してる。

大抵この値が高い

循環的複雑度の指標 - Qiita

村落(変更の分散)

たった一つ直すだけで、関連個所があれよあれよと大量に変更される状況。
メソッドの役割をきちんと定義せずに、しかも密結合しているとこんな状況がまま発生する。
綱渡りみたいにギリギリの依存で繋がってるので、修正時のバグを食いやすい。

横つながりが無駄に強い感じ…村かなと思ったけど、もっといい名前ないもんか

不倫メソッド(特性の横恋慕)

他のクラスインスタンスのしかも中身の変数に依存して何か処理をしている状況。
そもそも論メソッドのあるべきクラスを間違えてる可能性がある。

魚群(データの群れ)

アカウント名、パスワード、表示名、メールアドレスのような、関連して一緒に動きそうな情報が、クラスにまとまらずに動き回ってる状態。
データに対して処理するメソッドが各所に散らばるので、論理的なバグが出た時何処で問題が発生したのかわからなくなる。

基本データへの執着

変な名前思い浮かびませんでした…

「こんなにパラメータ少ないのにオブジェクトにまとめるとか面倒なんですけどー」とか言ってバラバラのデータを使ってるケース。
もしくはオブジェクト配列に突っ込んで渡すとかいう、引数増やすより酷い状況。

パラレル継承

これもネタにしづらかった。
関係ある A クラス B クラスがあって、A' クラスを A クラス継承で作ろうとしたら、なぜか B' extends B しないといけないという設計。
具象クラスにベットリ依存しすぎてポリモーフィズムが効いてないクチ。

ペテルギウス・ロマネコンティ(怠け者クラス)

名前は Re:ゼロ から。
リファクタか何かの結果、使用しなくなった処理。

怠惰ですねぇ~

疑わしき一般化

茶化す名前が思い浮かびませんでした。
「多分どっかで欲しくなるはずさー」という希望的観測で追加され、息をしていない潜在能力。

YAGNI 原則違反

サーセン途中で力尽きます。
いずれにせよ、バッドノウハウも覚えておけば何かと役に立ちます。

TDD アンチパターンの「嘘つき(テストしてるように見せかけて実は何もしてない)」とか「ローカルヒーロー(特定環境でだけ動くテスト)」「検閲者(テストしたい気持ちが行き過ぎて、private とかアクセス制限をブッチするテスト)」とか名前があった方が覚えやすいからね…。