Implement PartialEq for enum variants with data

#[derive(Clone, Debug, PartialOrd, PartialEq, Eq, Ord)]
enum PokerHand {
    StraightFlush(u8, Vec<u8>),
    FourOfAKind(u8, Vec<u8>),
    FullHouse(u8, Vec<u8>),
    Flush(u8, Vec<u8>),
    Straight(u8, Vec<u8>),
    ThreeOfAKind(u8, Vec<u8>),
    TwoPair(u8, Vec<u8>),
    OnePair(u8, Vec<u8>),
    HighCard(u8, Vec<u8>),
}

This test fails.

#[test]
fn full_house_beats_a_flush() {
    let a = PokerHand::Flush(6, vec![8, 7, 6, 5, 3]);
    let b = PokerHand::FullHouse(7, vec![5, 4]);
    assert!(b > a);
}

Although I couldn't find any documentation for it, seems fields aren't used for derived comparison. I definitely don't want to implement PartialEq listing out each possible enum variant pair, as there are many. What are my options?

Here's the documentation.

If I understand correctly, you want the ordering to be the same as comparing two (u8, Vec<u8>)? I suggest a method on the enum to expose something like a (&u8, &[u8]) and then an implementation that uses that method.

1 Like

They are, it's just that the enum variant has more priority. If every variant will always have the same value for the first u8, it'd probably be better to make it not a field, but a function, which matches on the enum and gives out the result.

1 Like

It won't, as shown in my example.

But won't that too require a match on every possible enum variant?

In your example I see PokerHand::Flush(6, _) and PokerHand::FullHouse(7, _). Will they always be 6 and 7 correspondingly?

For those variants, yes, other variants will have different and unique values. This is basically a representation of a poker hand, when the two hands are equal, then the vector should be compared.

Well, then it will be modeled like this:

  • The type of hand is defined by the enum variant (not by the value inside the variant, but by the variant itself): the "greater" hands will be lower in the list of variants.
  • The contents of hand are defined by the value of the variant - here you might want to use something other then the Vec, to make the comparison order-agnostic (e.g. so that [5, 4] < [4, 6]) and accomodate for the fixed size of the hand. For now, I'll leave is as-is.

With these things considered, we get the following definition:

#[derive(Clone, Debug, PartialOrd, PartialEq, Eq, Ord)]
enum PokerHand {
    HighCard(Vec<u8>),
    OnePair(Vec<u8>),
    TwoPair(Vec<u8>),
    ThreeOfAKind(Vec<u8>),
    Straight(Vec<u8>),
    Flush(Vec<u8>),
    FullHouse(Vec<u8>),
    FourOfAKind(Vec<u8>),
    StraightFlush(Vec<u8>),
}

And the test (modified accordingly) now passes.

3 Likes

This is working, with some help from regex and lazy_static crates.

#[derive(Clone, Debug, PartialEq, Eq)]
enum PokerHand {
    StraightFlush(u8, Vec<u8>),
    FourOfAKind(u8, Vec<u8>),
    FullHouse(u8, Vec<u8>),
    Flush(u8, Vec<u8>),
    Straight(u8, Vec<u8>),
    ThreeOfAKind(u8, Vec<u8>),
    TwoPair(u8, Vec<u8>),
    OnePair(u8, Vec<u8>),
    HighCard(u8, Vec<u8>),
}

lazy_static! {
    static ref NUM_RE: Regex = Regex::new(r"\d").unwrap();
}

impl Ord for PokerHand {
    fn cmp(&self, other: &Self) -> Ordering {
        NUM_RE.find_iter(&format!("{:?}", self))
            .zip(NUM_RE.find_iter(&format!("{:?}", other)))
            .map(|(x, y)| (x.as_str(), y.as_str()))
            .fold(Ordering::Equal, |ord, (x, y)| {
                ord.then(x.cmp(y))
            })
        }
}

impl PartialOrd for PokerHand {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

You probably don't want to rely on the Debug formatter (even if we doesn't think that this is highly inefficient and throws away the information stored in the enum variant's choice, the Debug formatter is in general not machine-readable).

2 Likes

Do you mean I should implement Debug on my own?

I mean, is there any reason not to use the approach from my previous post? I don't see any yet.

1 Like

No, he means that you shouldn't try to parse the output of Debug.

1 Like

No, other than I didn't see it. It's seems simpler and less fragile, I'll try it and report back.

BTW, the comparison isn't order-agnostic, so[5, 4] > [4, 6]. In other words, there is no reason to keep comparing once an unequal value is found. My implementation above had a bug using fold, which I fixed locally using try-fold.

That's called lexicographical ordering, and it is exactly what #[derive(Ord)] does with fields and it's also what Vec does with elements, exactly because it's usually what you want.

1 Like