Python環境でScala風リスト操作をできるようにするパッチを書いた
abst
こういう書き方ができるようになるパッチ書いた
[1, 2, 3, 4].map(_ * 10).reduce(_ + _)
motivation
かねてよりpythonのmap
,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
調子に乗って、Scalaの ParSeq 的なこともできるようにした。
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を触ってみました。まあ、eclipseもaptana 2も触ってはいたわけですが。
3になって、デフォルトのカラーテーマが黒背景・白文字でとてもよろしいのですが、Pydevと共存させたときに、背景黄色、文字白。見えないよ。
この設定探すのにすごい時間がかかった。apntanaが悪いわけじゃないんだけど、配色関係の設定は、Preferenceのあちこちに分散しているので探しにくかった。
のでメモ。
Preference -> General -> Editors -> Text Editors -> Annotations -> Occurrence (Pydev )
1時間も探したよ。
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;
なんて楽勝(笑)ひどいなこれ