Python のジェネレータ (2) のつづき
1. ジェネレータを使う方法
前回と同じく、ジェネレータを使う例を考える。
Python のチュートリアル 5.5.7 レシピ に、moneyfmt() という関数が書かれている。とりあえず、これは横に置いておく。
「数字にコンマを振る」ために、ジェネレータを使うことをイメージする。
- 数をリストに見たて、末尾から 3 つずつ要素を返すジェネレータを作成。
- そのジェネレータからリストを生成し、
- 要素を逆順にして、
- 最後にコンマで要素をくっつける。
実装。
def gCommaStr(num):
result = ""
for i,e in enumerate(reversed(str(num))):
if not i == 0 and i % 3 == 0:
yield result
result = ""
result = e + result
yield result
print ",".join(list(reversed([x for x in gCommaStr(1234567890)])))
一応、コンマを打つ位置を可変にしておくと、
def gCommaStr2(num,n):
result = ""
for i,e in enumerate(reversed(str(num))):
if not i == 0 and i % n == 0:
yield result
result = ""
result = e + result
yield result
print ",".join(list(reversed([x for x in gCommaStr2(1234567890,3)])))
2. 再帰を使った方法
リスト操作なので、ついでに再帰の練習もしておこう。
数が小さいときは、
1 --> 1 12 --> 12 123 --> 123
そのまま返す。
コンマがつけられる最小の数値のリストは、
1234 --> 1,234
これが再帰をするときの基本的なコンポーネントになりそうだ。
続きを考えると、
12345 --> 12,345 123456 --> 123,456 1234567 --> 1,234,567 12345678 --> 12,345,678
ぼ~っとながめていると、何となく繰返しになっている構造が見えてくる。 (@_@;) 上記の最後のパターンを見ていたら、 「2,345」 と 「5,678」 というように分割して考えれば、先ほど考えた基本的なコンポーネントが結合した形と見ることができることに気がついた。 「5」 が重複しているが、これは再帰呼出しの後に削除すればいいような感じ。 「12345」 なら、 「12」 と 「2,345」 。 「123456」 なら、 「123」 と 「3,456」 というような部分に分割できると考える。
これで数値が大きくなっても、基本的なコンポーネントとそれ以外の部分に分けて考え、関数を再帰的に適用すればいいような気がしてきた。
さて、実装~ (+_+)
def recCommaStr(L):
if len(L) <= 3:
return L
elif len(L) == 4:
L.insert(1, ","); return L
else:
return recCommaStr(L[:-3])[:-1] + recCommaStr(L[-4:])
num = 1234567890
print "recCommaStr(L): " + "".join(recCommaStr(list(str(num))))
引数 L は、数値を文字列にした後、リストにしたものを渡すようにした。
何も知らずにこのコードを見たら、読む気が失せるけれど ^^; 、わかっていれば再帰的な実装はやり方を素直に実装しているように感じる。
これも同じくコンマが打たれる位置を指定できるように拡張すると、
def recCommaStr2(L,i):
if len(L) <= i:
return L
elif len(L) == i+1:
L.insert(1, ","); return L
else:
return recCommaStr2(L[:-i],i)[:-1] + recCommaStr2(L[-(i+1):],i)
num = 1234567890
print "recCommaStr2(L,i): " + "".join(recCommaStr2(list(str(num)),3))
引数 i がコンマの位置を現わす。
3. 割り算で分割する方法
あ~、そうだ。数値を 1000 で割っていけば、コンマの打ちたい分の数値だけ取得できるかぁ。
def divCommaStr(num):
result = str(num) if num < 1000 else ""
while num > 1000:
result = "," + str(num % 1000) + result
num /= 1000
if num < 1000:
result = str(num) + result
return result
print "divCommaStr(1234567890): " + divCommaStr(1234567890)
あれ?何かややこしい。 (+_+) こういうの考えるの苦手。脳みそ混乱してきた。 パタッ(o_ _)o~†
コンマの位置を可変にすると、
def divCommaStr2(num, i):
assert i > 0
divNum = 10 ** i
if num < divNum: return str(num)
result = ""
while num > divNum:
result = "," + str(num % divNum) + result
num /= divNum
if num < divNum:
result = str(num) + result
return result
print "divCommaStr2(1234567890, 3): " + divCommaStr2(1234567890, 3)
4. クラスを使った方法
クラスを使って実装してみる。コンマ付きの数字を表わす CommaStr クラスを作成。コンマを付けるのは、文字列としてオブジェクトの情報を出力するときに、末尾から数値を 3 つ置きに走査する時点でつけることにした。
class CommaStr:
def __init__(self, num, i):
self.i = i
self.nums = str(num)
def __str__(self):
result = ""
for i,e in enumerate(reversed(self.nums)):
if not i == 0 and i % self.i == 0:
result = e + "," + result
else:
result = e + result
return result
print "CommaStr(1234567890, 3):", CommaStr(1234567890, 3)
これだとクラスを使う意味があまりないなぁ。 ^^; グローバル変数と関数を組み合わせる方法と大差なし。でも、クラスで考えるというのは何となくイメージしやすい。
自分の好みとしては、同様にコンマ付きの数字全体を CommaStr クラスで表わし、各数字を Num クラスとする。 Num クラスには、インスタンス変数として数値とコンマをつけることができるフィールドを用意しておく。
実装。
class CommaStr2:
def __init__(self, num, i):
self.i = i
self._createNums(num)
def _createNums(self, num):
self.nums = []
for e in str(num):
self.nums.append(Num(e))
self.setComma(",")
def setComma(self, comma):
for i,e in enumerate(reversed(self.nums)):
if not i == 0 and i % self.i == 0:
e.comma = comma
def __str__(self):
return "".join(str(x) for x in self.nums)
class Num:
def __init__(self, num, comma=""):
self.num = num
self.comma = comma
def __str__(self):
return str(self.num) + self.comma
print "CommaStr2(1234567890, 3):", CommaStr2(1234567890, 3)
全体は長くて効率も悪いけれど、全体の構造の見通しがよく、各メソッドはシンプルでええわぁ~。 ^^
Python のジェネレータ (4) - 無限リスト につづく…
0コメント:
コメントを投稿