ScalaでProject Euler(85)

Problem 54

まず手札を同じ数でまとめます。その際、ソートしておくとあとで便利です。枚数で比較して同じなら数で比較します。

5H 5C 6S 7S KD -> List((2, 5), (1, 13), (1, 7), (1, 6))

次に同じ数系の役になっていないか調べます。Scalaならパターンマッチで簡単に調べられます。

    def rank() :(eHand.Value,List[Int]) = collect match {
        case List((4,n),(1,m)) => (eHand.FourOfAKind, List(n, m))
        case List((3,n),(2,m)) => (eHand.FullHouse, List(n, m))
        case List((3,n),(1,m),(1,p)) => (eHand.ThreeOfAKind, List(n, m, p))
        ...

第2要素は同じ役だったときの判定用です。元からソートされているので簡単に判定できます。
ここで役にひっかからなかったら、ストレートかフラッシュかを判定します。
役は列挙型にします。

object eHand extends Enumeration {
    val HighCard,  OnePair, TwoPairs, ThreeOfAKind, Straight,
        Flush, FullHouse, FourOfAKind, StrightFlush, RoyalFlush = Value
}

型は、

eHand.Value

となります。

val a :eHand.Value = eHand.FourOfAKind

大小の比較もふつうにできます。先に書かれた方が小です。

def greater(a :List[Int], b :List[Int]) :Boolean = (a, b) match {
    case (Nil, Nil) => false
    case (_, Nil) => true
    case (Nil, _) => false
    case (h1 :: t1, h2 :: t2) if h1 == h2 => greater(t1, t2)
    case (h1 :: t1, h2 :: t2) => h1 > h2
}

object eHand extends Enumeration {
    val HighCard,  OnePair, TwoPairs, ThreeOfAKind, Straight,
        Flush, FullHouse, FourOfAKind, StrightFlush, RoyalFlush = Value
}

class cHand(s :String) {
    val hs :List[(Int,Char)] = init(s)
    
    def init(s :String) =
        s.split(' ').toList.map(x => (number(x(0)), x(1)))
    
    def number(c :Char) :Int = c match {
        case c if '2' <= c && c <= '9' => c.toString.toInt
        case 'T' => 10
        case 'J' => 11
        case 'Q' => 12
        case 'K' => 13
        case 'A' => 14
    }
    
    def wins(h :cHand) :Boolean = {
        val (eh1, a1) = rank
        val (eh2, a2) = h.rank
        if(eh1 != eh2) eh1 > eh2 else {
            greater(a1, a2)
        }
    }
    
    def rank() :(eHand.Value,List[Int]) = collect match {
        case List((4,n),(1,m)) => (eHand.FourOfAKind, List(n, m))
        case List((3,n),(2,m)) => (eHand.FullHouse, List(n, m))
        case List((3,n),(1,m),(1,p)) => (eHand.ThreeOfAKind, List(n, m, p))
        case List((2,n),(2,m),(1,p)) => (eHand.TwoPairs, List(n, m, p))
        case List((2,n),(1,m),(1,p),(1,q)) => (eHand.OnePair, List(n, m, p, q))
        case a =>   if(is_straight(a))
                        if(is_flush)
                            if(is_royal(a))
                                (eHand.RoyalFlush, Nil)
                            else
                                (eHand.StrightFlush, a.map(_._2))
                        else
                            (eHand.Straight, a.map(_._2))
                    else if(is_flush)
                        (eHand.Flush, a.map(_._2))
                    else
                        (eHand.HighCard, a.map(_._2))
    }
    
    def collect() :List[(Int,Int)] = {
        def greater(p :(Int,Int), q :(Int,Int)) :Boolean =
            if(p._1 != q._1)
                p._1 > q._1
            else
                p._2 > q._2
        
        val a = new Array[Int](13)
        for((n, s) <- hs) a(n-2) += 1
        a.toList.zip(List.range(2, 15)).filter(_._1 > 0).sortWith(greater)
    }
    
    def is_straight(a :List[(Int,Int)]) :Boolean = a match {
        case (_,n) :: (_,m) :: b if n == m + 1 => is_straight(a.tail)
        case (_,n) :: (_,m) :: b => false
        case _ => true
    }
    
    def is_flush() :Boolean =
        hs.tail.forall(x => x._2 == hs.head._2)
    
    def is_royal(a :List[(Int,Int)]) :Boolean =
        a.head._2 == 14
}

def line_to_hands(line :String) :(cHand,cHand) =
    (new cHand(line.take(14)), new cHand(line.drop(15)))

val s = io.Source.fromFile("poker.txt")
val hands = s.getLines.map(line_to_hands)
println (hands.count(x => x._1.wins(x._2)))