2015年10月6日火曜日

Pythonは黒魔術であってはならない・・・だが

まぁもちろん魔術というのは揶揄した言い方なのですが今回コードゴルフでなるほろーと思ったこととか。

Pythonとは

Pythonはリーダビリティ(可読性)特化言語である。
その為、普通に打ち込みを行っている分にはみなが均一に読みやすいコードになり改修も楽で自身が忘れたコードでも読み返して見やすいそういう作りの言語です。

だが・・・黒魔術はなくならない。

ここで言う黒魔術はリーダビリティを失った何が書いてあるのかぱっと見ではわからないコードの事を指します。
黒魔術の主成分は主にワンライナーとコードゴルフです。
これに特化する事で新たな発見や効率化を生み出しますが基本的には何が書いてあるか解り難い魔術言語となります。
特にRubyは上級黒魔術と言っていいでしょう(何あれ?ホントぱっとみなんだか意味不明)。

と、どうでもいい前置きでしたが、つまりはPythonでも言語方針に反しリーダビリティを投げ捨て、さらには処理速度すら投げ捨ててでも暗黒面に身を染めようじゃないか!というお話。



私は初めからPython 3x系で独学したためほとんどPython 2x系との違いなんて気にしたことがありませんでした。
知ってた違いは
・2系ではxrangeの方が処理が早く、3系ではデフォルトでrangeは2のxrengeの処理で行われる。
・関数等が【 ()括弧 】が必須で2系では【print "aa"】とできたが3系では【print("aa")】でなければエラー
・2系のraw_input()は3系ではinput()になった
程度です。なのでまさか2系に「input()」があるとは・・・閑話休題。


コードテクニック

では闇Python黒魔術を始めましょう。
と言っても「or・and」あたりは胡散臭い処理ですがそれ以外はわりと普通のコードテクだったりします。
また、主にPython 2x系の話となります。

1.使いまわす関数を減らす

Pythonでは関数を変数に代入する事でその変数を関数として使う事が出来ます。
【i=raw_input】と定義すれば今後【i()】だけで呼び出せるのです。

たとえばraw_input()を2回以上使う場合4byte違いがでます。
p=raw_input;a=p();b=p()
a=raw_input();b=raw_input()

rangeの場合は2回では同byteですがメインコードが薄くなります。
r=range;a=r(6);b=r(9)
a=range(6);b=range(9)

intでは2回程度では2byte損です。
int=i;a=i("10");b=i("10")
a=int("10");b=int("10")

2.input()とraw_input()の違いを知る

これは完全にPython 2x系の話になります
input()とraw_input()の違いはinput()は式をそのまま評価します。
【input()】は【eval(raw_input()) 】とするのと同じです。
入力される式が正しくないとエラーが複数発生したり扱いが少し難しいですが入力が文字か数値かだけの場合【input()】で数値として値を受け取る事が可能になります。

今回の様に一度目のみ数値の入力、二回目以降は文字列等の場合かつその数値は数値として受け取りたい場合に非常に有益です。

一度目の入力を数値の5、二度目の入力が文字列”ABC”であれば
使い分けた場合23byte、通常のままの場合32byte、一文字定義で26byteとなり、使い分けた場合が最短になります。

a=input();b=raw_input()
a=int(raw_input());b=raw_input()
p=raw_input;int(i());b=i()

3.暗黙型変換と評価で格納値を変える

暗黙の型変換はifなどの評価において0をfalse、0以外の数値(not 0)がtrueになります。
また、空文字「""」はfalse、文字に値がある場合trueとなります。
それと同時に、リスト番号での評価式はfalseが0となり、trueが1になります。


・数値や文字列がboolで処理される

if 0:print"表示" #表示されない
if 1:print"表示" #表示される
if "":print"表示" #表示されない
if "a":print"表示" #表示される


・評価が値数値で処理される

print ("A","B")[true] #Bが表示される
print ("A","B")[false] #Aが表示される


4.orとandの遅延評価でifを減らす

主に行数を減らすワンライナー向け

orは「どちらか条件ならば」であり処理は左から行われる。
つまり、左が条件に達した時点て確定する。
逆に左がfalseで場合右が値として確定する。

True or "a"
上では左がTrueで確定しTrueとなり、後ろが評価されない。

False or "a"
上では左がfalseとなり"a"が値として確定する。


andはorの逆で「両方が条件ならば」であり同じく左から処理される。
つまり、左が条件に達せない場合後続は処理されない。

True and "a"
上では左がTrueの次の評価に入り"a"が値として確定する。

False and "a"
上では左がFalseであり、その後ろは評価されない。


ifの代わりに使う場合はこのようになる
条件 and True時 or False時


5.リストや配列追加にappend等を使わない

a=[];a.append("ABC")
ではなく

a=[];a+=["abc"]
と記述すれば5byte短くなる

6.処理系以外はセミコロンで並べる

行にif・for・whileが無いなら【 ; 】セミコロンで処理を並べる事が出来る。
だが、リスト内包表現は代入式の為セミコロンで並べる事が出来る。


7.複数の同じ内容の初期化

a=0;b=0;c=0 #これよりも
a,b,c=0,0,0 #これよりも
 a=b=c=0  #こう
 a=b=c="" #文字列ならこう

 a,b,c='1','2','3' #文字限定でよりも
a,b,c='123'      #こう

8.exec('処理'*数値)でループの代替をする

ループ速度と言うか繰り返し実行速度が有益か怪しいが普通にループ処理を書くより短くなる。
また、文字列として処理が入る為一行で書くことが可能になる。

a=0
for x in[None]*10:print(a);a+=1

a=0;exec('print(a);a+=1'*10)

この様に書ける。forがなくなる事でaの初期化をセミコロンで処理を後続に並べる事も出来る。
【 '処理' 】を連続で投げている事になる。
execはきた文字列を処理として展開実行する。
これにより疑似ループが完成するがスコープ周りなど注意が必要。

9.カウンタ無ループfor x in[0]*数値

ル―プカウンタが不要なら

for x in[0]*数値

とする。
inの後に【 [ 】を隣接することで1byte短くなるし、0リスト生成は処理も速い。
ただし[None]*nの方が最速。







後で追記する

【lambda】無名関数さえあれば短くなるしワンライナーもry
【三項演算子】でif・elseを一行にさらに入れ子も
【リスト内包表現】再リスト化・抜出し。for系なのに代入式なので「;(セミコロン)」でつなげる事も可能
【リスト内包表現が1つの場合両端の[]を消しても良い?】


最後に

それ、Pythonじゃなくていいよね?
処理速度ならC++、コード圧縮したい(とにかく短く書きたい)ならRubyで書きませんか?

やっぱり邪道なのです。
折角このPythonという言語自体がリーダビリティをが良いのこれを失わせるなんてとんでもない。
・・・だが(以下ループ)

以上です。

0 件のコメント:

コメントを投稿