Scala:使用模式匹配在5张牌扑克牌中检测直线

时间:2011-08-08 16:18:48

标签: scala pattern-matching idiomatic

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

我正在Scala中编写一个小型扑克模拟器来帮助我学习该语言,并且我已经创建了一个 Hand 类,其中包含5个有序。每个都有 Rank Suit ,两者都定义为 Enumerations Hand 类具有评估手牌等级的方法,其中一个检查手牌是否包含直线(我们暂时可以忽略直线冲洗)。我知道有一些很好的算法可以确定一个Straight,但是我想知道我是否可以用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 定义的一般意见是什么?这应该是私人的还是以不同的方式完成?

8 个答案:

答案 0 :(得分:3)

您不能将信息传递给提取器,并且您不能使用另一个值中返回的值的信息,除了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
}

但是,当然,你真正想要的只是if中的SeqExtractor陈述。所以,不要太喜欢解决方案,因为你可能会错过更简单的做事方式。

答案 1 :(得分:2)

您可以执行以下操作:

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

正确处理aces需要做一些工作。

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

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

比较中的% 13处理aces为1级和14级。

答案 2 :(得分:1)

如下:

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

答案 3 :(得分:1)

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

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

我现在忽略了套房,我也忽略了在2下的ace的可能性。

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 type system'这样的更多东西感兴趣

答案 4 :(得分:0)

这样的事情怎么样?

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使用不正确,所以你的第二个尝试不起作用。)

答案 5 :(得分:0)

如果您正在撰写扑克计划,那么您已经在检查是否属于独一无二的。当一只手没有n种(n> 1)并且最小面额和最大值之间的差异恰好为4时,它是直的。

答案 6 :(得分:0)

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

我的卡类看起来像这样:

  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值,具体取决于它是否为直线。实际返回值(Ints列表)用于确定手的强度,第一个表示手牌类型从0(无对)到9(同花顺),其他是其他任何牌的等级计算其价值的手。对于直道,我们只担心排名最高的牌。

另外,请注意,您可以使用Ace作为低音,“滚轮”或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
    }
  }

答案 7 :(得分:0)

这是一个完整的惯用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)}
}