すこしふしぎ.

VR/HI系院生による技術ブログ.まったりいきましょ.(友人ズとブログリレー中.さぼったら焼肉おごらなきゃいけない)

pythonのoperatorモジュールと関数オブジェクト

こんばんは,1000chです. Check iOで遊んでいると,「このコード,別の書き方できないかなぁ」と思うことが多々あります. そこで今回,operatorモジュールを利用してコードを奇麗にできないかと挑戦しました.

問題

今回扱う問題は以下.Alice in wonderlandのmultiplication tableという問題です.

(問題文部分引用)After reading "Alice's Adventures in Wonderland", our robots decided to create their own "Multiplication table". Stephan would lead this mission (yeah, that probably was a bad idea). He forgot how to do multiplication and "invented" a new method. A rather weird method if we may be so blunt.

In Stephan's multiplication we convert numbers at binary representation without trailing zeroes. Then the first number is written in a vertical way (up to down) and the second horizontally (left to right). With that we fill a table with various binary operations for each crossing -- AND, OR, XOR, so we end up with three tables. In each table we convert rows at decimal and summarize it, then summarize the results of three tables. Let's look at several examples.

f:id:ism1000ch:20140306010939p:plain

..スクショのっけて大丈夫かなぁ..

問題を簡単にまとめると,

  • 2数a,bがinputとして与えられる
  • a,bを二進数表記し,各バイトの組み合わせで上のような表を作る.評価はand,or,xorの3種類
  • 表から,新しい数字を生成する.
  • すべてを合計してreturn

簡単のような微妙に面倒なような...という印象です. 愚直にやれば簡単だけど,冗長になりそうです.

といた

どーやったら簡潔に書けるかな?と考えつつ,こんなかんじで解きました

import operator as op

def checkio(first, second):
    a = process(op.and_,first,second) # andの表
    o = process(op.or_ ,first,second) # orの表
    x = process(op.xor ,first,second) # xorの表
    return a+x+o

def process(f, a, b):
    res, digits = 0, bin(a)[2:] # '0b11..'の'0b'を除外
    max_val = 2 ** len(bin(b)[2:]) - 1 # 全ビット1の数.-1だとうまく動きませんでした(´・ω・`)
    for d in digits: # aの各ビット
        if eval(d):
            res += f(max_val,b)
        else:
            res += f(0,b)
    return res

ぶっちゃけ各表を別々に作ることなく,forでaの各バイトを回してa & ba | ba ^ bを出して足して,,,とかやればokなんですけどね. ただそれはちょっとつまらないなー,と思い,上記のような回答にしてみました.

今回は3つの表を作るわけですが,その作り方の手順はほとんど同じです. 変わるのは,各マスの真偽値を"どのようなルールで決定するか"というところのみです. そこで,「変更するルール」を引数として与え,各表の値を返してくれる関数を作るという方針で解いたのが上記のコードになります.

ポイントは,python関数をオブジェクトとして利用できるという点です. C++で言うところの関数ポインタ的な使い方ですね. 普段あまり関数オブジェクトを利用することが無いのですが,コレはなかなか奇麗に使えてるのではと思います. 出題意図に沿っている...かはわからないですが,コードの意図はかなり把握しやすいのではないでしょうか.

今回は「論理積(&)」「論理和(|)」「排他的論理和(^)」を評価関数として引数に与えればokです. いわゆるオペレータをどのように関数化すればよいのか..と考え公式リファレンスを見てみると,operatorモジュールにいろいろ便利そうなものが集まっておりました.

こんな感じで使えます.

In [1]: import operator as op

In [3]: 4 | 2 # orオペレータ(100 or 010 -> 110)
Out[3]: 6

In [4]: op.or_(4,2) # or_関数.orオペレータと同値
Out[4]: 6

In [5]: o = op.or_ # 変数にor_関数を格納

In [6]: o
Out[6]: <function operator.or_> # oはoperator.or_を指している

In [7]: o(4,2) # or_関数と同様の挙動
Out[7]: 6

コレを利用して先ほどのコードでは,

def checkio(first, second):
    a = process(op.and_,first,second) # andの表
    o = process(op.or_ ,first,second) # orの表
    x = process(op.xor ,first,second) # xorの表

というようにprocess関数の引数として,直接and,or,xor関数を与えることができました.

operatorモジュールの中には他にも各種オペレータと同様挙動を提供する関数が格納されていますので,子ネタとして知っているとイザって時に便利かもしれんです.

まとめ

  • pythonは関数もobject
  • operatorモジュールってなんかいろいろ使い道ありそう