Scala:使用模式匹配检测 5 张牌扑克牌中的顺子

发布于 2024-11-28 15:10:40 字数 1222 浏览 0 评论 0原文

对于那些不知道什么是 5 张牌扑克顺子的人:http://en.wikipedia .org/wiki/List_of_poker_hands#Straight

我正在用 Scala 编写一个小型扑克模拟器来帮助我学习该语言,并且我创建了一个手牌类中有 5 张有序的卡片。每张卡牌都有一个等级花色,两者都定义为枚举Hand 类具有评估手牌等级的方法,其中之一检查手牌是否包含顺子(我们暂时可以忽略顺子)。我知道有一些很好的算法可以用来确定顺子,但我想看看是否可以使用 Scala 的模式匹配来设计一些东西,所以我想出了以下方法:

def isStraight() = {
  def matchesStraight(ranks: List[Rank.Value]): Boolean = ranks match {
    case head :: Nil  => true
    case head :: tail if (Rank(head.id + 1) == tail.head) => matchesStraight(tail)
    case _ => false
  }

  matchesStraight(cards.map(_.rank).toList)
}

效果很好并且具有相当的可读性,但我想知道是否有有什么方法可以摆脱这个if。我会想象类似以下的东西,尽管我无法让它发挥作用:

private def isStraight() = {
  def matchesStraight(ranks: List[Rank.Value]): Boolean = ranks match {
    case head :: Nil  => true
    case head :: next(head.id + 1) :: tail => matchesStraight(next :: tail)
    case _ => false
  }

  matchesStraight(cards.map(_.rank).toList)
}

有什么想法吗?另外,作为一个附带问题,对内部 matchesStraight 定义的普遍看法是什么?这应该是私人的还是以不同的方式完成?

For those who don't know what a 5-card Poker Straight is: http://en.wikipedia.org/wiki/List_of_poker_hands#Straight

I'm writing a small Poker simulator in Scala to help me learn the language, and I've created a Hand class with 5 ordered Cards in it. Each Card has a Rank and Suit, both defined as Enumerations. The Hand class has methods to evaluate the hand rank, and one of them checks whether the hand contains a Straight (we can ignore Straight Flushes for the moment). I know there are a few nice algorithms for determining a Straight, but I wanted to see whether I could design something with Scala's pattern matching, so I came up with the following:

def isStraight() = {
  def matchesStraight(ranks: List[Rank.Value]): Boolean = ranks match {
    case head :: Nil  => true
    case head :: tail if (Rank(head.id + 1) == tail.head) => matchesStraight(tail)
    case _ => false
  }

  matchesStraight(cards.map(_.rank).toList)
}

That works fine and is fairly readable, but I was wondering if there is any way to get rid of that if. I'd imagine something like the following, though I can't get it to work:

private def isStraight() = {
  def matchesStraight(ranks: List[Rank.Value]): Boolean = ranks match {
    case head :: Nil  => true
    case head :: next(head.id + 1) :: tail => matchesStraight(next :: tail)
    case _ => false
  }

  matchesStraight(cards.map(_.rank).toList)
}

Any ideas? Also, as a side question, what is the general opinion on the inner matchesStraight definition? Should this rather be private or perhaps done in a different way?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

情深已缘浅 2024-12-05 15:10:40

您不能将信息传递给提取器,也不能使用一个值在另一个值中返回的信息,除了 if 语句 - 该语句涵盖了所有这些情况。

您可以做的是创建自己的提取器来测试这些东西,但如果没有任何重用,它不会给您带来太多好处。

例如:

class SeqExtractor[A, B](f: A => B) {
  def unapplySeq(s: Seq[A]): Option[Seq[A]] =
    if (s map f sliding 2 forall { case Seq(a, b) => a == b  } ) Some(s)
    else None
}

val Straight = new SeqExtractor((_: Card).rank)

那么您可以这样使用它:

listOfCards match {
    case Straight(cards) => true
    case _ => false
}

但是,当然,您真正想要的只是 SeqExtractor 中的 if 语句。因此,不要太迷恋解决方案,因为您可能会错过更简单的做事方法。

You can't pass information to an extractor, and you can't use information from one value returned in another, except on the if statement -- which is there to cover all these cases.

What you can do is create your own extractors to test these things, but it won't gain you much if there isn't any reuse.

For example:

class SeqExtractor[A, B](f: A => B) {
  def unapplySeq(s: Seq[A]): Option[Seq[A]] =
    if (s map f sliding 2 forall { case Seq(a, b) => a == b  } ) Some(s)
    else None
}

val Straight = new SeqExtractor((_: Card).rank)

Then you can use it like this:

listOfCards match {
    case Straight(cards) => true
    case _ => false
}

But, of course, all that you really want is that if statement in SeqExtractor. So, don't get too much in love with a solution, as you may miss simpler ways of doing stuff.

栖竹 2024-12-05 15:10:40

你可以这样做:

val ids = ranks.map(_.id)
ids.max - ids.min == 4 && ids.distinct.length == 5

不过,正确处理 A 需要做一些工作。

更新:这是一个更好的解决方案:

(ids zip ids.tail).forall{case (p,q) => q%13==(p+1)%13}

比较中的 % 13 处理 ace 的等级 1 和等级 14。

You could do something like:

val ids = ranks.map(_.id)
ids.max - ids.min == 4 && ids.distinct.length == 5

Handling aces correctly requires a bit of work, though.

Update: Here's a much better solution:

(ids zip ids.tail).forall{case (p,q) => q%13==(p+1)%13}

The % 13 in the comparison handles aces being both rank 1 and rank 14.

春庭雪 2024-12-05 15:10:40

怎么样:

def isStraight(cards:List[Card]) = (cards zip cards.tail) forall { case (c1,c2) =>; c1.rank+1 == c2.rank}

val cards = List(Card(1),Card(2),Card(3),Card(4))

scala> isStraight(cards)
res2: Boolean = true

How about something like:

def isStraight(cards:List[Card]) = (cards zip cards.tail) forall { case (c1,c2) => c1.rank+1 == c2.rank}

val cards = List(Card(1),Card(2),Card(3),Card(4))

scala> isStraight(cards)
res2: Boolean = true
锦上情书 2024-12-05 15:10:40

这是一种完全不同的方法,但它确实使用模式匹配。它在 match 子句中产生警告,这似乎表明它不应该工作。但它实际上产生了正确的结果:

Straight !!! 34567
Straight !!! 34567
Sorry no straight this time

我暂时忽略了套件,也忽略了 2 下的 A 的可能性。

abstract class Rank {
    def value : Int
}
case class Next[A <: Rank](a : A) extends Rank {
    def value = a.value + 1
}
case class Two() extends Rank {
    def value = 2
}

class Hand(a : Rank, b : Rank, c : Rank, d : Rank, e : Rank) {
    val cards = List(a, b, c, d, e).sortWith(_.value < _.value)
}

object Hand{
    def unapply(h : Hand) : Option[(Rank, Rank, Rank, Rank, Rank)] = Some((h.cards(0), h.cards(1), h.cards(2), h.cards(3), h.cards(4)))
}

object Poker {

    val two = Two()
    val three = Next(two)
    val four = Next(three)
    val five = Next(four)
    val six = Next(five)
    val seven = Next(six)
    val eight = Next(seven)
    val nine = Next(eight)
    val ten = Next(nine)
    val jack = Next(ten)
    val queen = Next(jack)
    val king = Next(queen)
    val ace = Next(king)

    def main(args : Array[String]) {
        val simpleStraight = new Hand(three, four, five, six, seven)
        val unsortedStraight = new Hand(four, seven, three, six, five)
        val notStraight = new Hand (two, two, five, five, ace)

        printIfStraight(simpleStraight)
        printIfStraight(unsortedStraight)
        printIfStraight(notStraight)
    }

    def printIfStraight[A](h : Hand) {

        h match  {
            case  Hand(a: A , b : Next[A], c : Next[Next[A]], d : Next[Next[Next[A]]], e : Next[Next[Next[Next[A]]]]) => println("Straight !!! " + a.value + b.value + c.value + d.value + e.value)
            case Hand(a,b,c,d,e)  => println("Sorry no straight this time")
        }
    }
}

如果您对像这样的更多内容感兴趣,请谷歌“教堂数字 scala 类型系统”

This is a completely different approache, but it does use pattern matching. It produces warnings in the match clause which seem to indicate that it shouldn't work. But it actually produces the correct results:

Straight !!! 34567
Straight !!! 34567
Sorry no straight this time

I ignored the Suites for now and I also ignored the possibility of an ace under a 2.

abstract class Rank {
    def value : Int
}
case class Next[A <: Rank](a : A) extends Rank {
    def value = a.value + 1
}
case class Two() extends Rank {
    def value = 2
}

class Hand(a : Rank, b : Rank, c : Rank, d : Rank, e : Rank) {
    val cards = List(a, b, c, d, e).sortWith(_.value < _.value)
}

object Hand{
    def unapply(h : Hand) : Option[(Rank, Rank, Rank, Rank, Rank)] = Some((h.cards(0), h.cards(1), h.cards(2), h.cards(3), h.cards(4)))
}

object Poker {

    val two = Two()
    val three = Next(two)
    val four = Next(three)
    val five = Next(four)
    val six = Next(five)
    val seven = Next(six)
    val eight = Next(seven)
    val nine = Next(eight)
    val ten = Next(nine)
    val jack = Next(ten)
    val queen = Next(jack)
    val king = Next(queen)
    val ace = Next(king)

    def main(args : Array[String]) {
        val simpleStraight = new Hand(three, four, five, six, seven)
        val unsortedStraight = new Hand(four, seven, three, six, five)
        val notStraight = new Hand (two, two, five, five, ace)

        printIfStraight(simpleStraight)
        printIfStraight(unsortedStraight)
        printIfStraight(notStraight)
    }

    def printIfStraight[A](h : Hand) {

        h match  {
            case  Hand(a: A , b : Next[A], c : Next[Next[A]], d : Next[Next[Next[A]]], e : Next[Next[Next[Next[A]]]]) => println("Straight !!! " + a.value + b.value + c.value + d.value + e.value)
            case Hand(a,b,c,d,e)  => println("Sorry no straight this time")
        }
    }
}

If you are interested in more stuff like this google 'church numerals scala type system'

秋叶绚丽 2024-12-05 15:10:40

像这样的事情怎么样?

def isStraight = {
  cards.map(_.rank).toList match {
    case first :: second :: third :: fourth :: fifth :: Nil if 
      first.id == second.id - 1 &&
      second.id == third.id - 1 &&
      third.id == fourth.id - 1 &&
      fourth.id == fifth.id - 1 => true
    case _ => false
  }
}

您仍然坚持使用 if (实际上更大),但没有递归或自定义提取器(我相信您错误地使用 next ,所以是为什么你的第二次尝试不起作用)。

How about something like this?

def isStraight = {
  cards.map(_.rank).toList match {
    case first :: second :: third :: fourth :: fifth :: Nil if 
      first.id == second.id - 1 &&
      second.id == third.id - 1 &&
      third.id == fourth.id - 1 &&
      fourth.id == fifth.id - 1 => true
    case _ => false
  }
}

You're still stuck with the if (which is in fact larger) but there's no recursion or custom extractors (which I believe you're using incorrectly with next and so is why your second attempt doesn't work).

时光与爱终年不遇 2024-12-05 15:10:40

如果您正在编写扑克程序,那么您已经在检查 n-of-a-kind 了。当一手牌没有 n 种(n > 1)并且最小面值和最大面值之间的差恰好是 4 时,它是顺子。

If you're writing a poker program, you are already check for n-of-a-kind. A hand is a straight when it has no n-of-a-kinds (n > 1) and the different between the minimum denomination and the maximum is exactly four.

别闹i 2024-12-05 15:10:40

几天前,我在为 Project Euler 问题 54 做类似的事情。像你一样,我有 Rank 和 Suit 作为枚举。

我的 Card 类如下所示:

  case class Card(rank: Rank.Value, suit: Suit.Value) extends Ordered[Card] {
    def compare(that: Card) = that.rank compare this.rank
  }

注意,我给了它 Ordered 特征,以便我们稍后可以轻松比较卡片。另外,在解析手牌时,我使用 sorted 从高到低对它们进行排序,这使得评估值变得更加容易。

这是我的 straight 测试,它根据是否是顺子返回一个 Option 值。实际返回值(整数列表)用于确定手牌的强度,第一个代表从 0(无对子)到 9(同花)的手牌类型,其他是牌中任何其他牌的等级。计入其价值的手牌。对于顺子,我们只担心排名最高的牌。

另请注意,您可以用 A 为低位、“轮盘”或 A2345 形成顺子。

  case class Hand(cards: Array[Card]) {
    ...
    def straight: Option[List[Int]] = {

      if( cards.sliding(2).forall { case Array(x, y) => (y compare x) == 1 } )
        Some(5 :: cards(0).rank.id :: 0 :: 0 :: 0 :: 0 :: Nil)

      else if ( cards.map(_.rank.id).toList == List(12, 3, 2, 1, 0) )
        Some(5 :: cards(1).rank.id :: 0 :: 0 :: 0 :: 0 :: Nil) 

      else None
    }
  }

I was doing something like this a few days ago, for Project Euler problem 54. Like you, I had Rank and Suit as enumerations.

My Card class looks like this:

  case class Card(rank: Rank.Value, suit: Suit.Value) extends Ordered[Card] {
    def compare(that: Card) = that.rank compare this.rank
  }

Note I gave it the Ordered trait so that we can easily compare cards later. Also, when parsing the hands, I sorted them from high to low using sorted, which makes assessing values much easier.

Here is my straight test which returns an Option value depending on whether it's a straight or not. The actual return value (a list of Ints) is used to determine the strength of the hand, the first representing the hand type from 0 (no pair) to 9 (straight flush), and the others being the ranks of any other cards in the hand that count towards its value. For straights, we're only worried about the highest ranking card.

Also, note that you can make a straight with Ace as low, the "wheel", or A2345.

  case class Hand(cards: Array[Card]) {
    ...
    def straight: Option[List[Int]] = {

      if( cards.sliding(2).forall { case Array(x, y) => (y compare x) == 1 } )
        Some(5 :: cards(0).rank.id :: 0 :: 0 :: 0 :: 0 :: Nil)

      else if ( cards.map(_.rank.id).toList == List(12, 3, 2, 1, 0) )
        Some(5 :: cards(1).rank.id :: 0 :: 0 :: 0 :: 0 :: Nil) 

      else None
    }
  }
缱倦旧时光 2024-12-05 15:10:40

这是一个完整的惯用 Scala 手牌分类器,适用于所有手牌(处理 5 高顺子):

case class Card(rank: Int, suit: Int) { override def toString = s"${"23456789TJQKA" rank}${"♣♠♦♥" suit}" }

object HandType extends Enumeration {
  val HighCard, OnePair, TwoPair, ThreeOfAKind, Straight, Flush, FullHouse, FourOfAKind, StraightFlush = Value
}

case class Hand(hand: Set[Card]) {
  val (handType, sorted) = {
    def rankMatches(card: Card) = hand count (_.rank == card.rank)
    val groups = hand groupBy rankMatches mapValues {_.toList.sorted}

    val isFlush = (hand groupBy {_.suit}).size == 1
    val isWheel = "A2345" forall {r => hand exists (_.rank == Card.ranks.indexOf(r))}   // A,2,3,4,5 straight
    val isStraight = groups.size == 1 && (hand.max.rank - hand.min.rank) == 4 || isWheel
    val (isThreeOfAKind, isOnePair) = (groups contains 3, groups contains 2)

    val handType = if (isStraight && isFlush)     HandType.StraightFlush
      else if (groups contains 4)                 HandType.FourOfAKind
      else if (isThreeOfAKind && isOnePair)       HandType.FullHouse
      else if (isFlush)                           HandType.Flush
      else if (isStraight)                        HandType.Straight
      else if (isThreeOfAKind)                    HandType.ThreeOfAKind
      else if (isOnePair && groups(2).size == 4)  HandType.TwoPair
      else if (isOnePair)                         HandType.OnePair
      else                                        HandType.HighCard

    val kickers = ((1 until 5) flatMap groups.get).flatten.reverse
    require(hand.size == 5 && kickers.size == 5)
    (handType, if (isWheel) (kickers takeRight 4) :+ kickers.head else kickers)
  }
}

object Hand {
  import scala.math.Ordering.Implicits._
  implicit val rankOrdering = Ordering by {hand: Hand => (hand.handType, hand.sorted)}
}

Here is a complete idiomatic Scala hand classifier for all hands (handles 5-high straights):

case class Card(rank: Int, suit: Int) { override def toString = s"${"23456789TJQKA" rank}${"♣♠♦♥" suit}" }

object HandType extends Enumeration {
  val HighCard, OnePair, TwoPair, ThreeOfAKind, Straight, Flush, FullHouse, FourOfAKind, StraightFlush = Value
}

case class Hand(hand: Set[Card]) {
  val (handType, sorted) = {
    def rankMatches(card: Card) = hand count (_.rank == card.rank)
    val groups = hand groupBy rankMatches mapValues {_.toList.sorted}

    val isFlush = (hand groupBy {_.suit}).size == 1
    val isWheel = "A2345" forall {r => hand exists (_.rank == Card.ranks.indexOf(r))}   // A,2,3,4,5 straight
    val isStraight = groups.size == 1 && (hand.max.rank - hand.min.rank) == 4 || isWheel
    val (isThreeOfAKind, isOnePair) = (groups contains 3, groups contains 2)

    val handType = if (isStraight && isFlush)     HandType.StraightFlush
      else if (groups contains 4)                 HandType.FourOfAKind
      else if (isThreeOfAKind && isOnePair)       HandType.FullHouse
      else if (isFlush)                           HandType.Flush
      else if (isStraight)                        HandType.Straight
      else if (isThreeOfAKind)                    HandType.ThreeOfAKind
      else if (isOnePair && groups(2).size == 4)  HandType.TwoPair
      else if (isOnePair)                         HandType.OnePair
      else                                        HandType.HighCard

    val kickers = ((1 until 5) flatMap groups.get).flatten.reverse
    require(hand.size == 5 && kickers.size == 5)
    (handType, if (isWheel) (kickers takeRight 4) :+ kickers.head else kickers)
  }
}

object Hand {
  import scala.math.Ordering.Implicits._
  implicit val rankOrdering = Ordering by {hand: Hand => (hand.handType, hand.sorted)}
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文