Python環境でScala風リスト操作をできるようにするパッチを書いた

abst

こういう書き方ができるようになるパッチ書いた

[1, 2, 3, 4].map(_ * 10).reduce(_ + _) 

motivation

かねてよりpythonmap,filter,reduceはうっとしくてかなわん、と思っていた。何より、 method chain にまったく向いていない。

例えば、Scalaでは以下の様なコード

val sum_evens = Seq(1, 2, 3, 4, 5).filter(_ % 2 == 0).map(_ * 10).fold(0)(_ + _)

これが、Pythonではこうなる

sum_evens = reduce(lambda x, y: x + y, map(lambda y: y * 10, filter(lambda x: x % 2 == 0, [1, 2, 3, 4, 5])))

つらい。

pythonic とか Scala 使えとかいうのは忘れる。unpythonicで結構なのでこれを何とかしたい。

patch for built-in types

Python は monkey patch のやりやすい言語だが、built-in typesに対しては普通にはできない。方法はあるはずと探しに探したところ、強制的にパッチする gist をみつけた。

コメントには以下のようにある。

 # found this from Armin R. on Twitter, what a beautiful gem ;)

Python界隈では大変有名な Armin Ronacher 先生作らしい(結局オリジナルは見つけられなかった)。

これだ!というわけで、ざっくりまとめてgithubに上げた。

https://github.com/kogecoo/prototype.py

ネーミングは同僚より。

install

pipに上げたら怒られそうな内容なので上げてない。 cloneしてinstallする必要がある。

$ git clone https://github.com/kogecoo/prototype.py.git
$ cd prototype.py
$ python setup.py install

usage

先ほどのコードはこのように書けるようになる。

from prototype import itr
itr.attach()

[1, 2, 3, 4, 5].filter(lambda x%2 == 0).map(lambda x: x*10).reduce(lambda x, y: x+y)

さらに、Scala-like な lambdaを書けるようにする(fn.py)https://github.com/kachayev/fn.py を使えば、そこはもうScala World...

from prototype import itr
from fn import _
itr.attach()

[1, 2, 3, 4].map(_ * 10).reduce(_ + _)  #  100

調子に乗って、ScalaParSeq 的なこともできるようにした。

from prototype import par
from operator import add

par.attach()

def mult10(x):
    return x * 10

if __name__=='__main__':
    [[1, 2], [3, 4]].par().flatmap(mult10).reduce(add)

残念なことにmultiprocessingの制約があってlambdaは使えない。fn.pyも無理。 Pathosというライブラリがこれを解決しそうだけど、pipがダメなので気長に待とう。

最後に

やればやるほどScalaで書けよと自分で突っ込みを入れたくなった。

defaultdictのdefaultdict

pythonの標準ライブラリにdefaultdictという便利な道具がある。
ところでたまにdefaultdictのdefaultdictをやりたくなるが、いつも忘れていつも同じこのstackoverflowの記事に流れ着く。
毎度毎度検索しているので、ちゃんとメモればきっと忘れないはず。

普通の辞書ならこう

>>> d = {}
>>> print d["foo"]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'foo'

で、defaultdictならこう

>>> from collections import defaultdict
>>> d = defaultdict(list)
[]
>>> d2 = defaultdict(int)
>>> print d2["bar"]
0

じゃあdefaultdictのdefaultdictなら、こう、と思ったら大間違い

>>> d = defaultdict(defaultdict(int))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: first argument must be callable

エラーが出てる。内容を読むと、 TypeError: first argument must be callable
とあるので、callableにしてしまおう。

>>> print d["foo"]
defaultdict(<type 'int'>, {})
>>> print d["foo"]["bar"]
0

なんかよくわからないけど動いた。これが答え。

さて、気持ち悪いから理由を知りたいという人は、前述のstackoverflowの記事を読むと良くて、きちんとなんでこれで動くのかにも触れている。

これをざっくりまとめると、defaultdict(some_type)はキーが無いときに、some_type()をcallする仕組みになってるから、という話。
馴染み深いint, float, str, unicode, dict, listなどの組み込み関数は、大抵型変換の時に使うが、これを引数なしメソッドとしてcallすると、零元相当(といっていいかは微妙だが。boolとかさ...)のものが得られる。

>>> int()
0
>>> list()
[]
>>> float()
0.0
>>> dict()
{}
>>> str()
''
>>> unicode()
u''
>>> bool()
False

ここで

defaultdict(defaultdict(int))

としてしまうと、デフォルト値を得るための内部の仕組みがdefaultdict(int)をメソッドとして呼ぼうとする。つまり、

defaultdict(int)()

こんな感じのコードが走ってエラーになる。
defaultdict(int)の空のインスタンスがデフォルト値になれば良いのだから、それを返す引数なし関数をdefaultdictに渡してやれば良い。つまり、以下のように書けば良い。

defaultdict(lambda: defaultdict(int))

ここまで書けば、きっと忘れない。

Aptana Studio 3とPydevのハイライティング

 IDEマンセー言っている割に、最近コード書くときはもっぱらvimサクラエディタです。こんばんわ。

 そんな僕も時代の流れに乗ってAptana Studio 3を触ってみました。まあ、eclipseaptana 2も触ってはいたわけですが。

 3になって、デフォルトのカラーテーマが黒背景・白文字でとてもよろしいのですが、Pydevと共存させたときに、背景黄色、文字白。見えないよ。
 この設定探すのにすごい時間がかかった。apntanaが悪いわけじゃないんだけど、配色関係の設定は、Preferenceのあちこちに分散しているので探しにくかった。


のでメモ。


Preference -> General -> Editors -> Text Editors -> Annotations -> Occurrence (Pydev )


1時間も探したよ。

DjangoでXMLRPCとかを使うとき

なんか403になるなー、と思ったら、CSRF対策をご丁寧にDjangoさんがやってくれちゃってます。
さすが、ジプシージャズの帝王は違うね。


●対策
突貫でやるなら@csrf_exemptデコレータでOFF。
詳しくは本家

本家いわく、あまりオススメの方法でないそうですが、
CSRFのことを念頭においていればいいので、この場合、ベストアンサー。






だと思うんだけど。

PythonでMethod Missing

 聞くところによると、RubyにはMethod Missingなる機能があるとか。
いわく、オブジェクトにメソッドがない場合に、どういう処理をするかを記述するとか。


こいつぁ、われらがPythonにも欲しい機能です。


上記の文面をまともに受け取る使い方でも良いのですが、たとえば、あるオブジェクトのメソッド全てについて、
同様な処理を付加するといったラッパーが簡単に書けちゃうわけです。


というわけで調べてみた。
そのものずばりは出てこなかったけれど、おおむねこんなかんじ。
Method missing(のようなもの)を使った簡易ラッパー。

import threading
import time

class Wrappee:
    def __init__( self, count ):
        self.count = count

    def print_something( self, text ):
        for i in range( self.count ):
            print( i, " something : ", text )
            time.sleep( 0.5 )

class Wrapper:
    def __init__( self, wrapper ):
        self.wrappee= wrapper

    # Emulate ruby like " missing method ".
    def __getattr__( self, name ):
        def _method_missing( *args ):
            return args

        print( "--", name, " is called --")
        ret = getattr( self.wrappee, name, _method_missing)
        return ret


wrappee = Wrappee( 10 )
wrapper = Wrapper( wrappee )
wrapper.print_something( " foo " )


ううん、これは便利だ。
ただ、ラップされるオブジェクトに指定の関数名がない場合にエラーが出ないのが玉に傷。
どこかのサイトで関数名一覧をディクショナリでもっておいて検査するという手法も見られたけど、もっと楽にいけないものだろうか。

Project Euler Problem 84

モノポリーで、どの升目に止まりやすいかという問題。
まじめに計算できそうな感じだけど、せっかくなのでサンプリングしてみる。
ただひたすら面倒なだけ。

import random
# monopoly simulator
cells = [ 'GO', 'A', 'CC', 'A', 'T', 
         'R', 'B', 'CH', 'B', 'B',
         'JAIL', 'C', 'U', 'C', 'C',
         'R', 'D', 'CC', 'D', 'D',
         'FP', 'E', 'CH', 'E', 'E',
         'R', 'F', 'F', 'U', 'F',
         'G2J', 'G', 'G', 'CC', 'G',
         'R', 'CH', 'H', 'T', 'H' ]
cccards = ['DC', 'DC', 'DC', 'DC', 'DC',
           'DC', 'DC', 'DC', 'DC', 'DC',
           'DC', 'DC', 'DC', 'DC', '0', '10']
ccards = ['DC', 'DC', 'DC', 'DC', 'DC',
               'DC', '0', '10', '11', '24', 
               '39', '5', 'G2NR', 'G2NR', 'G2NU',
               'GB3']
def pe84():
    ntrial = 500000
    shuffle_cards()
    visited = sampling( ntrial )
    print visited
    print get_solution( visited )
def get_solution( visited ):
    first = get_max_index( visited )
    visited[first] = 0
    second = get_max_index( visited )
    visited[second] = 0
    third = get_max_index( visited )
    return "".join( [encode_squareface( first ), encode_squareface( second ), encode_squareface( third ) ] )
def get_max_index( lis ):
    idx = 0
    max = 0
    for i in range( 0, len( lis ) ):
        if lis[i] >= max:
            idx = i
            max = lis[i]
    return idx
              
def sampling( ntrial ):
    visited = [ 0 for i in range(0, len(cells))]
    pos = 0
    tried = 0
    # should I count first GO as visited ?
    while tried < ntrial:
        nextpos = nextcell( pos)
        #print len( visited ), " ", nextpos
        visited[nextpos]+= 1
        pos = nextpos
        tried +=1
    return visited

def nextcell( current_position):
    global prev_doubles
    score1 = random.randint( 1, 4 )
    score2 = random.randint( 1, 4 )
    if score1 == score2:
        if prev_doubles == 2:
            prev_doubles = 0
            return 10 
        else:
            prev_doubles += 1
    else:
        prev_doubles = 0
    next_position = advance( score1+score2, current_position )
    #print score, next_position
    if cells[next_position] == 'CC':
        return ccjump( next_position )
    elif cells[next_position] == 'CH':
        return cjump( next_position )
    elif cells[next_position] == 'G2J':
        return 10
    else:
        return next_position
def ccjump( current_position ): 
    card = drawcc()
    if card == 'DC':
        return current_position
    else:
        return int( card )
def cjump( current_position ):
    card = drawc()
    if card == 'DC':
        return current_position
    elif card == 'G2NR':
        return g2nr( current_position ) 
    elif card == 'G2NU':
        return g2nu( current_position )
    elif card == 'GB3':
        return back( 3, current_position )
    else:
        return int( card )
def g2nr( current_position ):
    while( True ):
        current_position = advance( 1, current_position)
        if cells[current_position] == 'R':
            return current_position
def g2nu( current_position ): 
    while( True ):
        current_position = advance( 1, current_position )
        if cells[current_position]  == 'U':
            return current_position
def advance( nsteps, current_position ):
    while nsteps > 0:
        current_position+=1
        if current_position > len( cells ) - 1:
            current_position = 0
        nsteps-=1
    return current_position
def back( nsteps, current_position ):
    while nsteps > 0:
        current_position -= 1
        if current_position < 0:
            current_position = len( cells ) - 1
        nsteps -=1
    return current_position 
def shuffle_cards():
    global ccards
    global cccards
    random.shuffle( ccards ) 
    random.shuffle( cccards )
def drawc():
    global ccards
    drawed = ccards.pop( 0 )
    ccards.append( drawed )
    return drawed 
def drawcc():
    global cccards
    drawed = cccards.pop( 0 )
    cccards.append( drawed )
    return drawed
def encode_squareface( num ):
    print "n : ", num 
    if num < 10:
        return '0'+str(num )
    else:
        return str( num )

prev_doubles = 0
pe84()

なんだかとてもまじめに書いた。

スライスでちょっと考え込んだ

Pythonにて、

s = "123456789"

というようなリスト(この場合文字列だが)で、マイナスのインデックスを指定してスライスすると、末尾から数えてくれる。

print s[-4:-1]

とすれば"678"が取れる。


じゃあマイナスインデックスから、リスト末尾までのスライスはどうすればいいの?

print s[-4:0]

違う違う。

print s[-4:-0]

なんぞこれは。違う。



pythonのスライスの2つ目のargument(何ていうんだ)は"未満"の扱いなので、困る。
どうすればいいんだー。


と悩んで10分。

print s[-4:]


あ、そっか。


インデックス指定できないのは気持ち悪いけど、多分これしかなさそう。
末尾の次の要素、なんてないもんなぁ。これでいいのかpython

Project Euler 80

 久しぶりに。

 高校の時の参考書に載っていた。
今見ても、こんな面倒な手順を覚えられるかと思う。

おおむねこんな感じ。なんだか教科書に乗っている手順をそのまま書き下しているだけなので、すごい汚いなぁ。

def getSqrtDigitSum( n, howmanydigits):
	if howmanydigits <= 0:
		return 0;
	dq = []
	result = 0;
	carry = 0;
	carry2 = 0;
	temp = n;
	while temp != 0:
		dq.insert( 0, temp % 100 )
		temp /= 100

	for i in dq:
		if howmanydigits <= 0:
			return result;
		howmanydigits -= 1
		carry += i;
		carry2 *= 10;
		nearest = 0;
		while carry >= ( carry2 + nearest  + 1 ) * ( nearest + 1 ):
			nearest += 1
		carry -= ( carry2 + nearest ) * nearest;
		carry *= 100;
		result += nearest;

		carry2 += 2 * nearest;

	while howmanydigits > 0:
		howmanydigits -= 1
		carry2 *= 10;
		nearest = 0;
		while carry >= ( carry2 + nearest  + 1 ) * ( nearest + 1 ):
			nearest += 1
		carry -= ( carry2 + nearest ) * nearest;
		carry *= 100;
		result += nearest;
		carry2 += 2 * nearest;
	return result;

本当はルートの中に小数点が入っていても解けるし、たぶん何乗根出会っても解けそうだけど、
とりあえず解ければいいや的になっているのが残念。

どうにも、数学的事実を知っているかどうか勝負になってきていてあまり最近やる気がしない。

Pythonでリストから要素を取り出しつつ削除する場合

Pythonメモ


リストをループでまわす場合、要素を一つ一つ取り出すけど、削除しつつだとどうなるのだろう?


現在値の場合

counter = 0
for i in l:
	if i % 2 == 0:
		del(l[counter])
	counter+=1
print l


出力

>>> l
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, 43, 45, 47, 49, 51, 53, 55, 57, 59, 61, 63, 65, 67, 69, 71, 73, 75, 77, 79, 81, 83, 85, 87, 89, 91, 93, 95, 97, 99]

一方、まだ触っていないものだと、

for i in l:
    del(l[99-i])


出力

[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, 45, 46, 47, 48, 49]

相変わらずProject Euler24, 48

 もはやProject Euler日記になっている.いい加減抜けださないと生活に支障が出る(苦笑)

 ハッカソンでであった人に影響されて,Pythonを書きたくなったのでPythonで解きました.

問題24 0〜9の数字を辞書順に並べたときの1M番目

先頭が0である場合の数 9!
先頭が1である場合の数 9!
先頭が1で,次が0である場合の数8!
先頭が1で,次が0で,次が2である場合の数7!

として,1M番目を探す.%をつかってもっといい解き方がありそうだったけど,なんだか今日は頭が回らない.

#problem #24

digits=10
count = 1000000;
factorial=1;
sum = 0;
l = [];
numlist = range(digits);
result = [];
for j in range(digits,1,-1):    
    counter = 0;
    
    while count>sum:
        factorial = 1;
    
        for i in range(1, j):
            factorial*=i;
        counter+=1;
        sum+=factorial;
    
    if counter > 0:
        counter-=1;
        sum-=factorial;
    result.append(numlist[counter]);
    del numlist[counter];
result.append(numlist[0])
print result;

なんというか,僕はコードを書きながら考える人なので,慣れていない言語だと考えにくいと感じた.
でもC++で書いた後にPythonで書き直すとか無駄なのでやりませんが…

次.1^1+2^2+...+1000^1000
これまで大きい数字を扱うコードを沢山書いたので,それを使ってナイーブにやればいいんだけど,
それならPython

#Problem 48
num = 0;
for i in range(1, 1000, 1):
    num += (i**i);
print num%10000000000;

なんて楽勝(笑)ひどいなこれ