Scala: обнаружение стрита в 5-карточной покерной руке с использованием сопоставления с шаблоном - PullRequest
2 голосов
/ 08 августа 2011

Для тех, кто не знает, что такое 5-карточный Poker Straight: http://en.wikipedia.org/wiki/List_of_poker_hands#Straight

Я пишу небольшой симулятор покера в Scala, чтобы помочь мне выучить язык, и я создал класс Hand с 5 заказанными картами . Каждая карта имеет Ранг и Костюм , оба определены как Перечисления . Класс 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)
}

Это прекрасно работает и довольно читабельно, но мне было интересно, есть ли способ избавиться от этого , если . Я мог бы представить что-то вроде следующего, хотя я не могу заставить его работать:

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

Есть идеи? Кроме того, в качестве дополнительного вопроса, каково общее мнение относительно внутреннего определения соответствует прямому ? Должно ли это быть частным или, возможно, сделано по-другому?

Ответы [ 8 ]

3 голосов
/ 08 августа 2011

Вы не можете передавать информацию в экстрактор, и вы не можете использовать информацию из одного значения, возвращенного в другом, за исключением оператора 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. Так что не слишком влюбляйтесь в решение, так как вы можете упустить более простые способы сделать что-либо.

2 голосов
/ 08 августа 2011

Вы можете сделать что-то вроде:

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

Для правильной обработки тузов требуется немного работы.

Обновление: Вот гораздо лучшее решение:

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

% 13 в сравнении обрабатывает тузы как ранга 1, так и ранга 14.

1 голос
/ 08 августа 2011

Это совершенно другой подход, но он использует сопоставление с образцом.Он выдает предупреждения в предложении о совпадении, которые, похоже, указывают на то, что он не должен работать.Но на самом деле он дает правильные результаты:

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

Я пока проигнорировал сюиты и также проигнорировал возможность туза ниже 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")
        }
    }
}

Если вы заинтересованы веще такие вещи, как эта Google 'церковная система счисления чисел типа' 1007 *

1 голос
/ 08 августа 2011

Как насчет чего-то вроде:

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
0 голосов
/ 06 марта 2013

Вот полный идиоматический классификатор рук 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)}
}
0 голосов
/ 09 августа 2011

Несколько дней назад я делал что-то подобное для задачи Project Euler 54. Как и у вас, у меня были ранги и масти в качестве перечислений.

Класс моей карты выглядит так:

  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 (стрит-флеш), а остальные - ранги любых других карт врука, которая рассчитывает на ее ценность.Что касается стритов, нас беспокоит только карта с наивысшим рейтингом.

Кроме того, обратите внимание, что вы можете сделать стрит с тузом ниже, "колесом" или 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
    }
  }
0 голосов
/ 09 августа 2011

Если вы пишете покерную программу, вы уже проверяете n-в своем роде.Рука - это стрит, когда у нее нет n-of-a-типов (n> 1), а разница между минимальным номиналом и максимумом равна ровно четырем.

0 голосов
/ 08 августа 2011

Как насчет этого?

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 и поэтому ваша вторая попытка не работает).

...