これは、現在Think Pythonとして知られている本の古いバージョンです。 1938>
How to Think Like a Computer Scientist |
Chapter15
15.1 構成
これまでに、いくつかの構成の例を見てきました。最初の例の1つは、式の一部としてメソッド呼び出しを使用するものでした。 このパターンを見て、リストとオブジェクトについて学んだので、オブジェクトのリストを作成できることを知っても驚かないでしょう。 この章と次の章では、Card オブジェクトを例として、これらの組み合わせのいくつかの例を見ていきます。
15.2 カードオブジェクト
もしあなたが一般的なトランプに慣れていないなら、今がデッキを手に入れる良い機会でしょう。 スートとは、スペード、ハート、ダイアモンド、クラブ(ブリッジでは降順)のことである。 ランクは、エース、2、3、4、5、6、7、8、9、10、ジャック、クイーン、キングです。 1938>
もしトランプを表す新しいオブジェクトを定義したい場合、ランクとスーツという属性が必要なのは明らかでしょう。 しかし、その属性がどのような型であるべきかは、それほど明らかではありません。 1つの可能性は、スーツには “Spade”、ランクには “Queen “といった単語を含む文字列を使用することである。 この実装の問題点の1つは、カードを比較してどちらが高いランクやスーツを持っているかを確認するのが容易でないことである。
代替案はランクとスーツをエンコードするために整数を使うことである。 コンピュータ科学者が言う「符号化」とは、「数字の列と表現したい項目との対応関係を定義すること」である。 例えば
スペード | -> | 3 | |||
ハート | -> | 2 | |||
ダイヤ | – | – > | |||
– | – | – | 1 | ||
Clubs | -> | 0 |
このマッピングの特徴は、スーツの順番で整数へのマッピングが行われていることである。 従って、整数を比較することで、スーツの比較ができる。 数字のランクはそれぞれ対応する整数に対応し、フェースカードは対応する整数に対応します。
ジャック | -> | 11 |
クイーン | -> | 12 |
キング | -となるわけです。> | 13 |
これらのマッピングに数学表記を使用している理由は、それらがPythonプログラムの一部ではないことです。 それらはプログラム設計の一部ですが、コードに明示的に現れることはありません。
class Card:
def __init__(self, suit=0, rank=2):
self.suit = suit
self.rank = rank
いつものように、各属性のオプションパラメータを受け取る初期化メソッドを提供します。
Cardを作成するには、Cardコンストラクタにsuitとrankを指定して呼び出します。
threeOfClubs = Card(3, 1)
次のセクションでは、今作成したカードがどれか確認することにします。
15.3 クラス属性と__str__メソッド
Cardオブジェクトを人が読みやすいように印刷するには、整数コードを単語にマッピングする必要があります。 それを行う自然な方法は、文字列のリストを使うことです。
class Card:
suitList =
rankList =
#init method omitted
def __str__(self):
return (self.Str__(self)):
suitList =
rankList =
クラス定義の最初のほうでクラス属性にこれらのリストを割り当てる。rankList + ” of ” +
self.suitList)
クラス属性はどのメソッドの外側にも定義され、クラス内のどのメソッドからもアクセスできる。
rankListの最初の要素に “narf “があるのは、リストの0番目の要素のプレースキープとして機能するためで、これは決して使われてはいけません。 有効なランクは1〜13だけです。 この無駄な項目は全く必要ないのです。 通常通り0から始めることもできますが、2を2、3を3というように符号化する方が混乱が少ないのです。
これまでのメソッドで、カードを作成し印刷することができます。
>>card1 = Card(1, 11)
>>print card1
Jack of Diamonds
suitListなどのクラス属性はすべての Cardobjects で共用されています。 この利点は、任意の Cardobject を使用してクラス属性にアクセスできることです:
>>card2 = Card(1, 3)
>>print card2
3 of Diamonds>>print card2.Card2.Card(1)
3 >>>>カードオブジェクトのクラス属性suitList
Diamonds
欠点は、クラスの属性を変更すると、そのクラスのすべてのインスタンスに影響することです。 たとえば、”Jack of Diamonds” は本当は “Jack of Swirly Whales” と呼ばれるべきであると判断した場合、次のようになります。suitList = “Swirly Whales”
>>print card1
Jack of Swirly Whales
問題は、すべてのダイヤモンドがSwirly Whalesになってしまったということです。
>> print card2
3 of Swirly Whales
通常、クラスの属性を変更することは良いアイデアではありません。
15.4 カードの比較
原始型には、値を比較して、ある値が他の値より大きいか小さいか等しいかを判断する条件演算子(<, >, ==, etc.)が用意されています。 ユーザ定義型では、__cmp__というメソッドを提供することで、組み込み演算子の動作をオーバーライドすることができます。 慣習により、__cmp__ は self と other という 2 つのパラメーターを持ち、最初のオブジェクトが大きい場合は 1、2 番目のオブジェクトが大きい場合は -1、互いに等しい場合は 0 を返します。 例えば、整数や浮動小数点数は完全順序型です。 ある集合は順不同で、ある要素が他の要素より大きいと言う意味のある方法はありません。 例えば、果物は順不同であり、リンゴとオレンジを比較できない。
トランプの集合は部分順であり、これはカードを比較できるときとできないときがあることを意味している。 例えば、あなたはクラブの3がクラブの2より高く、ダイアモンドの3がクラブの3より高いことを知っています。 しかし、クラブの3とダイヤの2、どちらが良いでしょうか? 1938>
カードを比較するためには、ランクとスートのどちらを重視するかを決めなければなりません。 正直なところ、その選択は恣意的です。 新しい山札は、クラブがすべて揃い、次にダイヤがすべて揃うというように分類されるので、選択の便宜上、スーツの方が重要であるとします。
これが決まれば、__cmp__が書ける:
def __cmp__(self, other):
# check the suits
if self.suit > other.suit: return 1
if self.suit < other.suit: return -1
# suit is the same… check ranks
if self.suit > other.suit: return -1
# rank is same.right.rank > other.rank: return 1
if self.rank < other.rank: return -1
# rank is the same… it’s tie
return 0
この順番だとAceはDeuces(2s)より下に表示されますね。
練習として、__cmp__を修正して、AcesがKingsより上位になるようにします。
15.5 Decks
カードを表すオブジェクトができたので、次の論理ステップはDeckを表すクラスを定義することです。 もちろん、デッキはカードで構成されるので、各デッキオブジェクトは属性としてカードのリストを含むことになります。 初期化メソッドは属性cardsを作成し、52枚のカードの標準セットを生成する:
class Deck:
def __init__(self):
self.cards =
for suit in range(4):
for rank in range(1, 14):
self.cards.append(Card(suit, rank))
デッキを生成する最も簡単な方法は、入れ子ループを使用することです。 外側ループは0から3までのsuiteを列挙し、内側ループは1から13までのrankを列挙します。 外側ループは4回、内側ループは13回反復するので、本体の実行回数は合計52回(13回×4回)である。 各反復は現在のスーツとランクを持つCardの新しいインスタンスを作成し、そのカードをcardsリストに追加する。
appendメソッドはリストで動作するが、もちろんタプルでは動作しない。
15.6 Printing the deck
通常、新しいタイプのオブジェクトを定義すると、オブジェクトの内容をプリントするメソッドが必要になる。Deckを表示するには、リストを走査してそれぞれのCardを表示します:
class Deck:
…
def printDeck(self):
for card in self.cards:
print card
ここで、そしてこれから、省略記号(…)は…Deckを表示するためのものです。…) は、クラスの他のメソッドを省略したことを示します。
printDeck の代わりとして、Deck クラスに __str__ メソッドを記述することができます。 str__の利点は、より柔軟であることです。 オブジェクトの内容をただ印刷するのではなく、プログラムの他の部分が印刷前に操作したり、後で使用するために保存できるような文字列表現を生成します。
Deck の文字列表現を返す __str__ のバージョンを以下に示します。少し派手さを加えるために、これはカードをカスケード状に配置し、各カードは前のカードより1スペース多くインデントされます:
class Deck:
…
def __str__(self):
s = “”
for i in range(len(self.cards)):
s = s + “”*i + str(self.cards) + “\n”
return s
この例ではいくつかの特徴を示しています。
次に、文字列の乗算演算子を使用して、各カードを最後のカードより1つ多くスペースでインデントしています。 1938>
第三に、カードを印刷するためにprintコマンドを使う代わりに、str関数を使用しています。 strの引数にオブジェクトを渡すことは、そのオブジェクトに対して__str__メソッドを呼び出すことと等しい。
最後に、変数sを累算器として使用している。 ループのたびに新しい文字列が生成され、stoの古い値と連結されて新しい値が得られる。 ループが終了すると、sは以下のようなDeckの完全な文字列表現となる。
>> deck = Deck()
>> print deck
Ace of Clubs
2 of Clubs
3 of Clubs
4 of Clubs
5 of Clubs
6 of Clubs>>>>>>Print deck
Ace of Clubs
3 of Clubs
3 of Clubs
5 of Clubs
6 of クラブ
クラブ7
クラブ8
クラブ9
クラブ10
クラブのジャック
クラブのクイーン
クラブのキング
ダイヤのエース
といった具合である。
15.7 デッキをシャッフルする
もしデッキが完全にシャッフルされていれば、どのカードもデッキのどこにでも等しく現れ、デッキのどの場所にも等しくどのカードがあることになります。 aとbの2つの整数引数で、randrangeはa <= x < bの範囲内の乱数整数を選ぶ。上界は厳密にbより小さいので、この第2引数にリストの長さを使うことができ、正当なインデックスを得ることが保証される。例えば、この式は山札の中のランダムなカードのインデックスを選択する:
random.randrange(0, len(self.cards))
山をシャッフルする簡単な方法は、カードを走査して各カードとランダムに選んだカードを交換することである。 カードが自分自身と入れ替わる可能性もあるが、それはそれでかまわない。
class Deck:
…
def shuffle(self):
import random
nCards = len(self.cards)
for i in range(nCards):
j = random.randrange(i, nCards)
self.cards, self.Card (self.Card):
range(j)は、ランダムに選ばれたカードと入れ替わる。
self.cards, self.cards
デッキに52枚のカードがあると仮定するのではなく、リストの実際の長さを取得し、それをnCardsに格納します。
デッキの各カードに対して、まだシャッフルされていないカードの中からランダムにカードを選択します。 そして、現在のカード(i)と選んだカード(j)を入れ替える。
self.cards, self.cards = self.cards, self.cards
練習として、この行のコードをシーケンス割り当てを使わずに書き換えてみてください
15.8 カードの削除と配付
Deck クラスで有用なもう一つのメソッドは removeCard で、カードを引数として受け取り、それを削除し、そのカードがデッキにあった場合は True、それ以外は False を返します:
class Deck:
.self.cards
…
def removeCard(self, card):
if card in self.cards:
self.cards.remove(card)
return True
else:
return False
in演算子は、最初のオペランドがthesecondにあれば真を返しますが、リストかタプルでなければなりません。 最初のオペランドがオブジェクトの場合、Python はオブジェクトの __cmp__ メソッドを使用して、リスト内のアイテムとの均等性を決定します。 カードクラスの __cmp__ は深い等式をチェックするので、removeCard メソッドは深い等式をチェックします。
カードを配るために、一番上のカードを取り除いて返したい。
class Deck:
…
def popCard(self):
return self.cards.pop()
実際には pop はリストの最後のカードを削除するので、デッキの底から配るという効果はないです。
もう1つ必要と思われる操作はブール関数isEmptyで、これはデッキにカードがない場合に真を返す:
class Deck:
…
def isEmpty(self):
return (len(self.)):
.card) == 0)
15.9 Glossary
encode ある値のセットを別の値のセットで表現し、それらの間のマッピングを構築すること。 クラス属性 クラス定義の内部で、どのメソッドの外部でも定義される変数。 クラス属性はクラスのどのメソッドからでもアクセスでき、クラスのすべてのインスタンスで共有されます。 アキュムレータ ループ内で、一連の値を文字列に連結したり、実行中の合計に追加するなどして蓄積するために使用する変数。
これは、現在 Think Python として知られている本の古いバージョンです。
How to Think Like a Computer Scientist |