Python: проверка "объекта в списке" и переполнение "__cmp__"

Это мой первый раз при переполнении стека, поэтому я извиняюсь, если формат не совсем подходит для сайта. Я только недавно начал изучать программирование, с тех пор прошло почти 2 недели. Я изучаю python по http://openbookproject.net/thinkcs/python/english3e/index.html и до сих пор все было довольно хорошо, где я застрял на несколько часов. Я много гуглил, но не смог найти правильного решения своей проблемы, вот и я.

Я пытаюсь запустить OldMaidGame() без проблем, как описано в CH17. http://openbookproject.net/thinkcs/python/english3e/ch17.html - Большая часть кода также взята из предыдущей главы.

Я обнаружил, что не могу заставить работать Deck.remove, Hand.remove_matches или любые другие виды удаления. После некоторой отладки я обнаружил, что проблема возникает, когда программа проверяет, присутствует ли данная карта в колоде / руке / и т.д. Это никогда не может сделать совпадение. Затем, оглянувшись на главу (в главе 16), я обнаружил, что "если карта в колоде / руке / и т.д.: удалить (карта)" и т. Д. Ищет. cmp () объекта, чтобы определить, действительно ли карта существует в колоде / руке / и т.д. Это моя версия cmp после внесения дополнений для 'ace's в данный код из электронной книги.

def __cmp__(self, other):
    """ Compares cards, returns 1 if greater, -1 if lesser, 0 if equal """
    # check the suits
    if self.suit > other.suit: return 1
    if self.suit < other.suit: return -1
    # suits are the same... check ranks
    # check for aces first.
    if self.rank == 1 and other.rank == 1: return 0
    if self.rank == 1 and other.rank != 1: return 1
    if self.rank != 1 and other.rank == 1: return -1
    # check for non-aces.
    if self.rank > other.rank: return 1
    if self.rank < other.rank: return -1
    # ranks are the same... it's a tie
    return 0

Сам по себе cmp кажется хорошим, я мог бы воспользоваться некоторыми советами о том, как сделать его лучше (например, с проверками туза). Поэтому я понятия не имею, почему карта в проверках колоды / руки всегда возвращает false. Это была заданная функция удаления:

class Deck:
    ...
    def remove(self, card):
        if card in self.cards:
            self.cards.remove(card)
            return True
        else:
            return False

Отчаянно пытаясь заставить его работать, я придумал это:

class Deck:
    ...
    def remove(self, card):
        """ Removes the card from the deck, returns true if successful """
        for lol in self.cards:
            if lol.__cmp__(card) == 0:
                self.cards.remove(lol)
                return True
        return False

Казалось, работает нормально, пока я не перешел к другим нерабочим функциям удаления:

class OldMaidHand(Hand):
    def remove_matches(self):
        count = 0
        original_cards = self.cards[:]
        for card in original_cards:
            match = Card(3 - card.suit, card.rank)
            if match in self.cards:
                self.cards.remove(card)
                self.cards.remove(match)
                print("Hand {0}: {1} matches {2}".format(self.name, card, match))
                count = count + 1
        return count

Я снова внес некоторые коррективы:

class OldMaidHand(Hand):
    def remove_matches(self):
        count = 0
        original_cards = self.cards[:]
        for card in original_cards:
            match = Card(3 - card.suit, card.rank)
            for lol in self.cards:
                if lol.__cmp__(match) == 0:
                    self.cards.remove(card)
                    self.cards.remove(match)
                    print("Hand {0}: {1} matches {2}".format(self.name, card, match))
                    count = count + 1
        return count

Удаление работало нормально для карты, но оно выдало ошибку (x отсутствует в списке), когда я попытался удалить совпадение. Еще один наш или около того, я мог бы сделать эту работу тоже, но так как я уже чувствую, что я на неправильном пути, так как я не могу починить оригинальную "карту в колоде / руке / и т.д." и т. Д., Я пришел сюда в поисках некоторых ответов / советов.

Спасибо за чтение, и я очень ценю любую помощь, которую вы можете оказать:)

--------------------- РЕДАКТИРОВАТЬ 1 * >

Это мой текущий код: http://pastebin.com/g77Y4Tjr

--------------------- РЕДАКТИРОВАТЬ 2 * >

Я перепробовал каждый совет, который мне посоветовал, и до сих пор не могу найти карту с надписью "in".

>>> a = Card(0, 5)
>>> b = Card(0, 1)
>>> c = Card(3, 1)
>>> hand = Hand('Baris')
>>> hand.add(a)
>>> hand.add(b)
>>> hand.add(c)
>>> d = Card(3, 1)
>>> print(hand)
Hand Baris contains
5 of Clubs
 Ace of Clubs
  Ace of Spades
>>> d in hand.cards
False
>>> 

Я также попробовал, чтобы card.py @DSM успешно использовался, и там я тоже получаю ошибки, как в функции сортировки, которая говорит, что не может сравнивать два объекта карты.
Поэтому мне было интересно, может быть, это проблема с Python 3.2, или, возможно, синтаксис где-то изменился?

5 ответов

Решение

"Поэтому мне было интересно, может быть, это проблема с Python 3.2, или, возможно, синтаксис где-то изменился?"

О, вы используете Python 3.2? Это никогда не будет работать в Python 3: Python 3 не использует __cmp__ !

Смотрите модель данных (ищите __eq__ ) Также прочитайте, что нового в Python 3, для некоторых других вещей, которые слишком легко пропустить.

Извините, это на нас, программистов на Python; мы должны были поймать это намного раньше. Большинство из них, вероятно, посмотрели весь код, поняли, даже не задумываясь о том, что исходным кодом был явно код Python 2, и предположили, что это то, с чем мы работали. Функция cmp даже не существует в Python 3.2, но причина того, что она не взорвалась с помощью NameError, заключается в том, что __cmp__ никогда не называется.

Если я запускаю код в Python 3.2, я точно воспроизвожу вашу проблему:

>>> c = Card(0,2)
>>> str(c)
'2 of Clubs'
>>> c in [c]
True
>>> c in Deck().cards
False

В Python 3 вы реализуете все богатые cmps или __eq__ и один из них и использовать декоратор total_ordering.

from functools import total_ordering

@total_ordering
class Card(object):
    """Represents a standard playing card."""
    suit_names = ["Clubs", "Diamonds", "Hearts", "Spades"]
    rank_names = [None, "Ace", "2", "3", "4", "5", "6", "7", 
              "8", "9", "10", "Jack", "Queen", "King"]
    def __init__(self, suit=0, rank=2):
        self.suit = suit
        self.rank = rank
    def __str__(self):
        return '%s of %s' % (Card.rank_names[self.rank],
                             Card.suit_names[self.suit])
    def __repr__(self): return str(self)
    def __lt__(self, other):
        t1 = self.suit, self.rank
        t2 = other.suit, other.rank
        return t1 < t2
    def __eq__(self, other):
        t1 = self.suit, self.rank
        t2 = other.suit, other.rank
        return t1 == t2


>>> c = Card(2,3)
>>> c
3 of Hearts
>>> c in Deck().cards
True

Я также не могу воспроизвести ошибку. Он отлично работает для меня. Мое единственное предложение заключается в том, что вам, вероятно, не следует изменять список, пока вы выполняете итерации по нему (т.е. вызывать self.cards.remove в цикле по self.cards). Это не может объяснить, почему версии, использующие "in", не будут работать для вас.

Ваша функция cmp может быть написана более кратко (и IMHO более просто) как:

def __cmp__(self, other):
    """ Compares cards, returns 1 if greater, -1 if lesser, 0 if equal """
    return cmp((self.suit,  self.rank == 1,  self.rank),
              (other.suit, other.rank == 1, other.rank))

или если вы предпочитаете:

    return (cmp(self.suit, other.suit) or
            cmp(self.rank == 1, other.rank == 1) or
            cmp(self.rank, other.rank))

Похоже, у вас есть проблема с вашей переменной колоды. Либо функция удаления указывает на другой объект с пустой колодой, либо у вас есть проблема с пространством имен. Является ли функция удаления частью объекта колоды?

Я бы предложил добавить несколько строк для печати. Один сразу после его инициализации, чтобы увидеть, что у него внутри, а другой, как вы называете, удалить.

Я не могу воспроизвести проблему с невозможностью удалить карты через Deck.remove. Если я начну с card.py на сайте thinkpython и добавлю функцию удаления, которую вы там разместили, похоже, она будет работать:

>>> deck = Deck()
>>> str(deck).split('\n')
['Ace of Clubs', '2 of Clubs', '3 of Clubs', '4 of Clubs', '5 of Clubs', '6 of Clubs', '7 of Clubs', '8 of Clubs', '9 of Clubs', '10 of Clubs', 'Jack of Clubs', 'Queen of Clubs', 'King of Clubs', 'Ace of Diamonds', '2 of Diamonds', '3 of Diamonds', '4 of Diamonds', '5 of Diamonds', '6 of Diamonds', '7 of Diamonds', '8 of Diamonds', '9 of Diamonds', '10 of Diamonds', 'Jack of Diamonds', 'Queen of Diamonds', 'King of Diamonds', 'Ace of Hearts', '2 of Hearts', '3 of Hearts', '4 of Hearts', '5 of Hearts', '6 of Hearts', '7 of Hearts', '8 of Hearts', '9 of Hearts', '10 of Hearts', 'Jack of Hearts', 'Queen of Hearts', 'King of Hearts', 'Ace of Spades', '2 of Spades', '3 of Spades', '4 of Spades', '5 of Spades', '6 of Spades', '7 of Spades', '8 of Spades', '9 of Spades', '10 of Spades', 'Jack of Spades', 'Queen of Spades', 'King of Spades']
>>> len(deck.cards)
52
>>> c = Card(suit=0, rank=8)
>>> str(c)
'8 of Clubs'
>>> c in deck.cards
True
>>> deck.remove(c)
True
>>> len(deck.cards)
51
>>> c in deck.cards
False
>>> str(deck).split('\n')
['Ace of Clubs', '2 of Clubs', '3 of Clubs', '4 of Clubs', '5 of Clubs', '6 of Clubs', '7 of Clubs', '9 of Clubs', '10 of Clubs', 'Jack of Clubs', 'Queen of Clubs', 'King of Clubs', 'Ace of Diamonds', '2 of Diamonds', '3 of Diamonds', '4 of Diamonds', '5 of Diamonds', '6 of Diamonds', '7 of Diamonds', '8 of Diamonds', '9 of Diamonds', '10 of Diamonds', 'Jack of Diamonds', 'Queen of Diamonds', 'King of Diamonds', 'Ace of Hearts', '2 of Hearts', '3 of Hearts', '4 of Hearts', '5 of Hearts', '6 of Hearts', '7 of Hearts', '8 of Hearts', '9 of Hearts', '10 of Hearts', 'Jack of Hearts', 'Queen of Hearts', 'King of Hearts', 'Ace of Spades', '2 of Spades', '3 of Spades', '4 of Spades', '5 of Spades', '6 of Spades', '7 of Spades', '8 of Spades', '9 of Spades', '10 of Spades', 'Jack of Spades', 'Queen of Spades', 'King of Spades']

Кажется, работает, если я заменю __cmp__ по твоему тоже

>>> deck = Deck()
>>> c = Card(suit=0,rank=1)
>>> c in deck.cards
True
>>> deck.remove(c)
True
>>> len(deck.cards)
51

Так что-то должно быть по-другому. Не могли бы вы выкинуть весь свой код - и некоторый код, демонстрирующий ошибку - куда-нибудь (pastebin, gist и т. Д.)?


(FWIW, мой туз за короля cmp выглядит так:

def __cmp__(self, other):
    def aceshigh(r): return 14 if r==1 else r
    t1 = self.suit, aceshigh(self.rank)
    t2 = other.suit, aceshigh(other.rank)
    return cmp(t1, t2)

Упражнение: уберите магические числа.)

Ваша функция сравнения должна работать, как указывали другие люди, и ей нужно больше подробностей, чтобы выяснить, что там происходит. Что касается предложений:

  1. Палка туза в конце рангов, используйте 0-12 для отображения рангов. Это кажется естественным подходом для меня.

  2. Воспользуйтесь преимуществами стандартной библиотеки:

    А. Использование random.shuffle чтобы перетасовать.

    Б. Использование cmp обрабатывать сравнения.

    C. collections.defaultdict делает для уборщика remove_matches метод на мой взгляд.

  3. Предлагаемый __str__ метод действительно очень раздражает.

  4. Воплощать в жизнь __repr__,

Альтернативная реализация:

from collections import defaultdict
import random

class Card(object):
    suits = ["Clubs", "Diamonds", "Hearts", "Spades"]
    ranks = ["2", "3", "4", "5", "6", "7", "8", "9", "10", "Jack", "Queen", "King", "Ace"]

    def __init__(self, suit=0, rank=0):
        self.suit = suit
        self.rank = rank

    def __str__(self):
        return self.ranks[self.rank] + " of " + self.suits[self.suit]

    def __repr__(self):
        return self.__str__()

    def __cmp__(self, other):
        return cmp((self.suit, self.rank), (other.suit, other.rank))

class Deck(object):
    def __init__(self):
        self.cards = []
        for suit in range(4):
            for rank in range(13):
                self.cards.append(Card(suit=suit, rank=rank))

    def shuffle(self):
        random.shuffle(self.cards)

    def remove(self, card):
        if card in self.cards:
            self.cards.remove(card)

    def pop(self):
        return self.cards.pop()

    def is_empty(self):
        if len(self.cards) is 0:
            return True
        return False

    def deal(self, hands, num_cards=999):
        num_hands = len(hands)
        for i in range(num_cards):
            if self.is_empty(): break   # break if out of cards
            card = self.pop()           # take the top card
            hand = hands[i % num_hands] # whose turn is next?
            hand.add(card)              # add the card to the hand

class Hand(Deck):
    def __init__(self, name=""):
       self.cards = []
       self.name = name

    def add(self,card):
        self.cards.append(card)

class OldMaidHand(Hand):
    def remove_matches(self):
        matches = defaultdict(list)
        for card in self.cards:
            matches[card.rank].append(card)
        for cards in matches.values():
            if len(cards) == 2:
                print("Hand {0}: {1} matches {2}".format(self.name, *cards))
                for card in cards:
                    self.remove(card)
Другие вопросы по тегам