Notes on codes, projects and everything

Learning a new language for more than 2 months (feat. Exercism)

Poker

This is one of more hardcore exercise, and I don’t think my solution is good. My original plan was to represent score with a large number, however that became over large so I used a vector to represent scores instead.

/// Given a list of poker hands, return a list of those hands which win.
///
/// Note the type signature: this function should return _the same_ reference to
/// the winning hand(s) as were passed in, not reconstructed strings which happen to be equal.
use std::cmp::Ordering;

#[derive(Debug, Eq)]
struct Hand {
    spade: Vec<usize>,
    heart: Vec<usize>,
    club: Vec<usize>,
    diamond: Vec<usize>,
}

impl Hand {
    fn new(hand: &str) -> Hand {
        hand.split(" ").fold(
            Hand {
                spade: vec![0; 14],
                heart: vec![0; 14],
                club: vec![0; 14],
                diamond: vec![0; 14],
            },
            |current, incoming| {
                let index = Self::get_index(
                    &incoming
                        .chars()
                        .take(incoming.len() - 1)
                        .collect::<String>(),
                );

                match incoming.chars().last().unwrap() {
                    'S' => Hand {
                        spade: Self::suit_increment(&current.spade, index),
                        heart: current.heart,
                        club: current.club,
                        diamond: current.diamond,
                    },
                    'H' => Hand {
                        heart: Self::suit_increment(&current.heart, index),
                        spade: current.spade,
                        club: current.club,
                        diamond: current.diamond,
                    },
                    'C' => Hand {
                        club: Self::suit_increment(&current.club, index),
                        spade: current.spade,
                        heart: current.heart,
                        diamond: current.diamond,
                    },
                    _ => Hand {
                        diamond: Self::suit_increment(&current.diamond, index),
                        spade: current.spade,
                        heart: current.heart,
                        club: current.club,
                    },
                }
            },
        )
    }

    fn suit_increment(original: &Vec<usize>, index: usize) -> Vec<usize> {
        original
            .clone()
            .iter()
            .enumerate()
            .map(|(idx, &x)| {
                if (index == 0 && idx == 13) || index == idx {
                    x + 1
                } else {
                    x
                }
            })
            .collect::<Vec<usize>>()
    }

    fn flatten(&self) -> Vec<usize> {
        vec![0; 14]
            .iter()
            .enumerate()
            .map(|(idx, _)| {
                &self.spade[idx] + &self.heart[idx] + &self.club[idx] + &self.diamond[idx]
            })
            .collect()
    }

    //
    // Score vector
    // [type] [# of threes/fours] [# of pair/largest] [#High A] [#B] .. [#2] [#Low A]
    // only exception is straight flush A1234, then set [#High A] to 0
    //
    fn score(&self) -> Vec<usize> {
        let flattened = self.flatten();

        (0..17)
            .into_iter()
            .map(|x| {
                if x == 0 {
                    self.type_bit()
                } else if x == 1 {
                    if self.has_threes() && self.has_pairs(1) {
                        flattened.iter().rposition(|&y| y == 3).unwrap()
                    } else if self.is_four_of_a_kind() {
                        flattened.iter().rposition(|&y| y == 4).unwrap()
                    } else {
                        0
                    }
                } else if x == 2 {
                    if self.has_threes() && self.has_pairs(1) {
                        flattened.iter().rposition(|&y| y == 2).unwrap()
                    } else if self.is_four_of_a_kind() {
                        flattened.iter().rposition(|&y| y == 1).unwrap()
                    } else {
                        0
                    }
                } else if x == 3 && flattened.iter().take(5).all(|&x| x > 0) {
                    0
                } else {
                    flattened[flattened.len() + 2 - x]
                }
            })
            .collect()
    }

    fn type_bit(&self) -> usize {
        if self.is_flush() && self.is_straight() {
            8
        } else if self.is_four_of_a_kind() {
            7
        } else if self.has_threes() && self.has_pairs(1) {
            6
        } else if self.is_flush() {
            5
        } else if self.is_straight() {
            4
        } else if self.has_threes() {
            3
        } else if self.has_pairs(2) {
            2
        } else if self.has_pairs(1) {
            1
        } else {
            0
        }
    }

    fn has_threes(&self) -> bool {
        self.has_repeat(3) > 0
    }

    fn has_pairs(&self, count: usize) -> bool {
        self.has_repeat(2) == count
    }

    fn has_repeat(&self, repeat: usize) -> usize {
        self.flatten()
            .iter()
            .take(13)
            .filter(|&x| *x == repeat)
            .count()
    }

    fn is_straight(&self) -> bool {
        let flattened = self.flatten();

        (0..flattened.len() - 4).any(|x| flattened.iter().skip(x).take(5).all(|&x| x > 0))
    }

    fn is_flush(&self) -> bool {
        [&self.spade, &self.heart, &self.club, &self.diamond]
            .iter()
            .any(|&x| x.into_iter().sum::<usize>() == 5)
    }

    fn is_four_of_a_kind(&self) -> bool {
        self.has_repeat(4) > 0
    }

    fn get_index(value: &str) -> usize {
        let cards = [
            "A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K",
        ];

        cards.iter().position(|&x| x == value).unwrap()
    }
}

impl PartialEq for Hand {
    fn eq(&self, other: &Hand) -> bool {
        self.score() == other.score()
    }
}

impl PartialOrd for Hand {
    fn partial_cmp(&self, other: &Hand) -> Option<Ordering> {
        self.score().partial_cmp(&other.score())
    }
}

impl Ord for Hand {
    fn cmp(&self, other: &Hand) -> Ordering {
        self.score().as_slice().cmp(other.score().as_slice())
    }
}

pub fn winning_hands<'a>(hands: &[&'a str]) -> Option<Vec<&'a str>> {
    let shands = hands.iter().map(|x| Hand::new(x)).collect::<Vec<Hand>>();

    if hands.len() == 1 {
        Some(hands.to_vec())
    } else {
        let scores = shands
            .iter()
            .map(|x| x.score())
            .collect::<Vec<Vec<usize>>>();
        let max = &scores.iter().max().unwrap();

        Some(
            scores
                .iter()
                .enumerate()
                .filter(|(_, x)| x == max)
                .map(|(idx, _)| hands[idx])
                .collect::<Vec<&str>>(),
        )
    }
}

As shown in the score method, I am using a vector with 17 dimensions to represent the score of a given hand. In a way it is something like a number, just a not so efficient version when it comes to comparing. I don’t quite like the complexity of the puzzle (too complex for the purpose of learning a language) so I didn’t spent too much time polishing the solution.

leave your comment

name is required

email is required

have a blog?

This blog uses scripts to assist and automate comment moderation, and the author of this blog post does not hold responsibility in the content of posted comments. Please note that activities such as flaming, ungrounded accusations as well as spamming will not be entertained.

Pings

Click to change color scheme