前書き
あるときふと思った。クソコードを書きたい。
この記事は、細々とやっているブログからの転載です。
コードと動作
コードは次の通り。素因数を表示するものだ。
一応Python3だが、print関数をprint文に置き換えればPython2でも動作する。
print((lambda f: (lambda n: f(f, [], n, 2)))(lambda f, r, n, i: r if n == 1 else (r.append(i) or f(f, r, n/i, i)) if n % i == 0 else f(f, r, n, i+1))(int(input())))
実行とその結果は次のようである。
>python -c "print((lambda f: (lambda n: f(f, [], n, 2)))(lambda f, r, n, i: r if n == 1 else (r.append(i) or f(f, r, n/i, i)) if n % i == 0 else f(f, r, n, i+1))(int(input())))"
100
[2, 2, 5, 5]
我ながら満足のクソっぷりである。
改善したい点を挙げるとしたら、リストを使いまわしているところだろうか。
ラムダ式を上手くジェネレータのように活用できないだろうか。
一応解説
解説というか、見やすく分解しただけだが。
print(
# 再帰可能にする工夫と、高階関数としての役割
(lambda func:
(lambda num:
func(func, [], num, 2)
)
)
# 素因数分解をつかさどる部分
(lambda func, result, num, i:
# 終了条件と返り値
result if num == 1
# i が num の約数のとき
else (result.append(i) or func(func, result, num/i, i)) if num % i == 0
# i が num の約数でないとき
else func(func, result, num, i+1)
)
# 実際に関数に与える引数
(int(input()))
)
ワンラインの拘りなしにこれを書くとこのようになる。
def higher_order(func):
return lambda num: func(func, [], num, 2)
@higher_order
def ret_factor(func, result, num, i):
if num == 1:
return result
if num % i == 0:
result.append(i)
return func(func, result, num/i, i)
else:
return func(func, result, num, i+1)
print(
ret_factor(int(input()))
)
そもそもラムダ式がわからん。デコレータも知らん。という方はこちら。
def ret_factor(func, result, num, i):
if num == 1:
return result
if num % i == 0:
result.append(i)
return func(func, result, num/i, i)
else:
return func(func, result, num, i+1)
print(
ret_factor(func=ret_factor, result=[], num=int(input()), i=2)
)
普通に書くなら
ラムダ式での再帰のために、第一引数に自身を取る冗長な構造にしている。
Pythonでのワンライナーが非常に参考になった。
普通に書くなら、次のようになる。
def ret_factor(result, num, i):
if num == 1:
return result
if num % i == 0:
result.append(i)
return ret_factor(result, num/i, i)
else:
return ret_factor(result, num, i+1)
また、リストに値を格納して最後に返すのは、大抵無駄だ。
このようなときには、ジェネレータを使うべきである。
def ret_factor(num, i):
if num == 1:
return
if num % i == 0:
yield i
yield from ret_factor(num/i, i)
else:
yield from ret_factor(num, i+1)
本当はこれをワンラインにしたいのだが...
いかんせん終了時の処理を上手く表現できないのだ。
その後の足掻き
print(
list(
# 再帰可能にする工夫と、高階関数としての役割
(lambda func:
(lambda num:
func(func, num, 2)
)
)
# 素因数分解をつかさどる部分
(lambda func, num, i:
# 終了条件
None if num == 1
# i が num の約数のとき
else ((yield i), (yield from func(func, num/i, i))) if num % i == 0
# i が num の約数でないとき
else (yield from func(func, num, i+1))
)
# 実際に関数に与える引数
(int(input()))
)
)
このようにしたら出来た。達成感より先に気持ち悪さがあるが。
これはつまり、次のように書けるということなのだ...
>>> def func(num):
... ((yield num), (yield from func(num-1))) if num > 0 else None
...
>>> list(func(10))
[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
なんとも恐ろしい。
さらに改造
例えば、次のように改造できる。
print(
(lambda max: [m for m in range(2, max) if
# 約数(1を除く)が1つだったら素数
len(
# 再帰可能にする工夫と、高階関数としての役割
(lambda func:
(lambda num:
func(func, [], num, 2)
)
)
# 素因数分解をつかさどる部分
(lambda func, result, num, i:
# 終了条件と返り値
result if num == 1
# i が num の約数のとき
else (result.append(i) or func(func, result, num/i, i)) if num % i == 0
# i が num の約数でないとき
else func(func, result, num, i+1)
)
# 実際に関数に与える引数
(m)
) == 1]
)
# 求める素数の上限
(int(input()))
)
改行と無駄な空白を潰すとこんな感じ。
print((lambda max: [m for m in range(2, max) if len((lambda func: (lambda num: func(func, [], num, 2)))(lambda func, result, num, i: result if num == 1 else (result.append(i) or func(func, result, num/i, i)) if num % i == 0 else func(func, result, num, i+1))(m)) == 1])(int(input())))
入力された数値未満の素数リストを出力するワンライナーだ。
しかし、1000ほどの大きな値を入れるとRecursionErrorで落ちる。
末尾再帰最適化で解消できるはずだが、まあそこまではしなくてよいだろう。
一応参考までに。Pythonのクロージャで末尾再帰最適化をする。
さすがにこれも込みでワンライナーにするのは骨だ。