Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python: 'object in list' checks and '__cmp__' overflow

this is my first time at stack overflow so I'm sorry if the format doesn't fit quite right with the site. I just recently started learning programming, almost 2 weeks have passed since. I'm learning python from http://openbookproject.net/thinkcs/python/english3e/index.html and everything had been quite nice until now, where I just got stuck for hours. I googled a lot but couldn't find a proper solution to my problem so here I am.

I'm trying to get the OldMaidGame() run without problems as explained on CH17. http://openbookproject.net/thinkcs/python/english3e/ch17.html - Most of the code also comes from the previous chapter.

What I've found out is I can't get the Deck.remove, Hand.remove_matches, or any other kind of remove function to work. After some debugging I found out that the problem occurs when the program checks if the given card is present in the deck/hand/etc. It can't ever make a match. Then after some looking back on the chapter, (in ch16), I found out that 'if card in deck/hand/etc: remove(card)' etc looks up the .cmp() of the object to determine if the card actually exists in the deck/hand/etc. This is my version of the cmp after doing the additions for 'ace's on the given code from the e-book.

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

The cmp itself seems fine afaik, ofc I could use some tips on how to make it better (like with ace checks). So I have no idea why the card in deck/hand checks always return false. This was the given remove function:

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

Desperately trying to get it to work, I came up with this:

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

Seemed to work fine, until I moved on to the other non-working remove functions:

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

I again made some adjustments:

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

The removing worked fine for the card, but it would give an error (x not in list) when I tried to remove match. Another our or so, I might've been able to make that work too, but since it already feels like I'm on the wrong road since I can't fix the original 'card in deck/hand/etc' etc, I came here looking for some answers/tips.

Thanks for reading and I greatly appreciate any help you can give :)

--------------------- EDIT 1 *>

This is my current code: http://pastebin.com/g77Y4Tjr

--------------------- EDIT 2 *>

I've tried every single cmp advised here, and I still can't get it to find a card with '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
>>> 

I've also tried the card.py @DSM has used successfully, and I get errors there too, like at the sort function it says it cant compare the two card objects.
So I was wondering, maybe it is a problem with Python 3.2, or maybe the syntax has changed somewhere?

like image 704
Ulquiomaru Avatar asked Jun 02 '11 13:06

Ulquiomaru


People also ask

How do you check if an object exists in a list Python?

We can use the in-built python List method, count(), to check if the passed element exists in the List. If the passed element exists in the List, the count() method will show the number of times it occurs in the entire list.

How do I check if all elements in an array are true in Python?

The all() function returns True if all items in an iterable are true, otherwise it returns False. If the iterable object is empty, the all() function also returns True.

How do you check if something is not in a list Python?

“not in” operator − This operator is used to check whether an element is not present in the passed list or not. Returns true if the element is not present in the list otherwise returns false.

How do I check if a string is not in a list Python?

Python Find String in List using count() We can also use count() function to get the number of occurrences of a string in the list. If its output is 0, then it means that string is not present in the list.


2 Answers

"So I was wondering, maybe it is a problem with Python 3.2, or maybe the syntax has changed somewhere?"

Oh, you're running Python 3.2? This'll never work in Python 3: python 3 doesn't use __cmp__!

See the data model (look for __eq__). Also read the what's new in Python 3 for some other things it's way too easy to miss.

Sorry, this is on us Python programmers here; we should have caught this far earlier. Most of probably looked at all the code, realized without even thinking about it that the source was obviously python 2 code, and assumed that's what we were working with. The cmp function doesn't even exist in Python 3.2, but the reason that it doesn't blow up with a NameError is because __cmp__ is never called.

If I run the code in Python 3.2, I reproduce your problem exactly:

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

In Python 3, you either implement all the rich cmps or __eq__ and one of them and use a total_ordering decorator.

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
like image 195
DSM Avatar answered Oct 08 '22 23:10

DSM


I also can't reproduce the error. It works fine for me. My only suggestion would be that you probably shouldn't be modifying a list while you're iterating over it (ie, calling self.cards.remove within a loop over self.cards). That can't explain why the versions using "in" wouldn't work for you.

Your cmp function can be written somewhat more succinctly (and IMHO more simply) as:

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))

or if you prefer:

    return (cmp(self.suit, other.suit) or
            cmp(self.rank == 1, other.rank == 1) or
            cmp(self.rank, other.rank))
like image 31
Cosmologicon Avatar answered Oct 08 '22 21:10

Cosmologicon