Пример функционального программирования в scala

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

Я взял проблему Эйлера ( http://projecteuler.net/), чтобы привести пример с минимальными затратами. И я пытаюсь пойти функциональным путем. Таким образом, это не "пожалуйста, ответьте мне немедленно, или мой босс убьет меня", а "пожалуйста, если у вас есть время, вы можете помочь программисту, работающему с языком, с настоятельным желанием отправиться в путешествие по функциональному миру?

Проблема: я хочу урок по покеру. Покерная комбинация состоит из нескольких карт, от 0 до 5. Я хотел бы составить список карт один и за все, то есть: мой класс рук будет неизменным, если я хочу добавить карту, то Я создаю новый объект Hand. Поэтому мне нужна коллекция Card, которую можно создать как "val", а не как var. Первый шаг: конструкторы, по одному на каждое количество карточек. Но коллекция Card обрабатывается в каждом конструкторе, поэтому я должен иметь ее как var!

Вот код, класс Card - это просто Suit и Value, передаваемый конструктору в виде строки ("5S" - это 5 пиков):

class Hand(mycards : List[Card]) {
  // this should be val, I guess
  private var cards : List[Card] = {
    if (mycards.length>5)
      throw new IllegalArgumentException(
        "Illegal number of cards: " + mycards.length);
    sortCards(mycards)
  }

  // full hand constructor
  def this(a : String, b : String, c : String, d : String, e : String) = {
      this(Nil)

      // assign cards
      val cardBuffer = new ListBuffer[Card]()
      if ( a!=null ) cardBuffer += new Card(a)
      if ( b!=null ) cardBuffer += new Card(b)
      if ( c!=null ) cardBuffer += new Card(c)
      if ( d!=null ) cardBuffer += new Card(d)
      if ( e!=null ) cardBuffer += new Card(e)
      cards = sortCards(cardBuffer.toList)
  }
  // hand with less then 5 cards
  def this(a : String, b : String, c : String, d : String) = this(a,b,c,d,null)
  def this(a : String, b : String, c : String) = this(a, b, c, null)
  def this(a : String, b : String) = this(a, b, null)
  def this(a : String) = this(a, null)
  def this() = this(Nil)

/* removed */
}

Вы знаете, как сделать это по-настоящему функциональным способом? Благодарю.

PS: если вы действительно хотите знать, это проблема 54.

6 ответов

Мой ответ не о функциональном аспекте scala, но ваш код можно написать в ближайшее время, используя scala sugar:

class Hand(val mycards: List[Card]) {
  require (mycards.size <= 5,"can't be more than five cards")
  def this(input: String*) = { 
    this(input.map(c => new Card(c)).toList)
  }
}

input: String* во вспомогательном конструкторе говорится, что вы можете иметь переменное количество аргументов (даже тысячи строк). Я получаю информацию и вызываю создание для каждой новой Карты с map функции, а затем передать результат родительскому конструктору, который имеет собственное требование. (Кстати, отображение строки в карту может быть сделано анонимно, следующим образом: this(input.map(new Card(_)).toList))

class Hand(val mycards: List[Card]) {...

Может быть

class Hand(cards: List[Card]) {
val mycards = cards 
...

Отныне, если вы попытаетесь создать более пяти карт в руке, вы получите java.lang.IllegalArgumentException:

scala> class Card(s: String) {println("Im a :"+s)}
defined class Card

scala> new Hand("one","two","three","four","five","six")
Im a :one
Im a :two
Im a :three
Im a :four
Im a :five
Im a :six
java.lang.IllegalArgumentException: requirement failed: can't be more than five card
    at scala.Predef$.require(Predef.scala:157)
    at Hand.<init>(<console>:9)

Ну, то var в приведенном ниже коде исходит от вас не инициализации cards от главного конструктора:

// this should be val, I guess
private var cards : List[Card] = {
  if (mycards.length>5)
    throw new IllegalArgumentException(
      "Illegal number of cards: " + mycards.length);
  sortCards(mycards)
}

Итак, что вам нужно сделать, это исправить вторичный конструктор:

// full hand constructor
def this(a : String, b : String, c : String, d : String, e : String) = {
    this(Nil)

    // assign cards
    val cardBuffer = new ListBuffer[Card]()
    if ( a!=null ) cardBuffer += new Card(a)
    if ( b!=null ) cardBuffer += new Card(b)
    if ( c!=null ) cardBuffer += new Card(c)
    if ( d!=null ) cardBuffer += new Card(d)
    if ( e!=null ) cardBuffer += new Card(e)
    cards = sortCards(cardBuffer.toList)
}

Проблема проста: вам нужен список карт, образованный ненулевыми строками. Если бы я был тобой, я бы просто избегал передачи нулей, но... В любом случае, лучший способ справиться с этим - преобразовать это в параметры. Преобразование просто: Option(a) вернусь Some(a) является a не является нулевым, и None если это. Если вы составите список этого, вы можете тогда flatten это удалить None и конвертировать Some(a) Вернуться в a, Другими словами:

def this(a : String, b : String, c : String, d : String, e : String) = 
    this(List(a, b, c, d, e).map(Option(_)).flatten.map(Card(_)))

Поскольку в этом примере вам разрешено использовать только пять карт, я проверю это во время компиляции с использованием Tuple5:

type HandType = (ACard, ACard, ACard, ACard, ACard)
case class Hand(h: HandType)

abstract class ACard {
  def exists: Boolean
}
case class Card(value: Int, color: Color) extends ACard {
  def exists = true
}
case object NoCard extends ACard {
  def exists = false
}

abstract class Color(val c: Int)
case object H extends Color(1)
case object C extends Color(2)
case object S extends Color(3)
case object D extends Color(4)
case object NoColor extends Color(0)

implicit def tuple2Card(t: (Int, Color)) = Card(t._1, t._2)

val h1 = Hand((Card(4, H), Card(6, S), Card(2, S), Card(8, D), NoCard))
val h2 = Hand((4 -> H, 6 -> S, 2 -> S, 8 -> D, NoCard))

println(h1)
println(h2)
h1.h.productIterator foreach { c => println(c.asInstanceOf[ACard].exists) }

Конечно, в другом примере, когда может быть неопределенное количество элементов, вы должны проверить их во время выполнения. productIterator возвращает только Итератор [Любой], но когда вы используете свои карты напрямую по идентификаторам полей (_1 .. _5), вы получите ACard,

Во-первых, нам нужно исправить ошибку компиляции в вашем cards определение поля.

Обратите внимание, что в Scala вам обычно не нужно объявлять поля. Основные параметры конструктора уже являются полями! Итак, это можно записать проще:

class Hand(cards : List[Card]) {
  if (cards.length>5)
      throw new IllegalArgumentException(
        "Illegal number of cards: " + mycards.length);

Теперь у нас есть проблема изменчивости. Если вы хотите программировать в функциональном стиле, все должно быть неизменным, поэтому "конструктор полной руки" вообще не функционален: он имеет 6 побочных операций, последняя из которых не компилируется.

В функциональной настройке объект нельзя изменить после завершения его конструктора, поэтому весь код после this(Nil) бесполезно. Вы уже сказали, что cards является Nil, что еще ты хочешь?! Итак, все вычисления должны выполняться до вызова главного конструктора. Мы хотели бы удалить this(Nil) сверху и добавь this(sortCards(cardBuffer.toList)) ко дну. К сожалению, Scala не позволяет этого. К счастью, он позволяет использовать больше параметров, чем java: во-первых, вы можете использовать вложенный блок следующим образом:

this({
  val cardBuffer = ... /* terrible imperativeness */
  sortCards(cardBuffer.toList)
})

во-вторых, вы можете использовать apply метод вместо конструктора:

object Hand {
  def apply(a : String, b : String, c : String, d : String, e : String) = {
    val cardBuffer = ... /* terrible imperativeness */
    new Hand(sortCards(cardBuffer.toList))
  }
}

Теперь давайте начнем избавляться от императива ListBuffer, Первым улучшением будет использование var типа List[Card], Если сделать изменчивость более локальной, это поможет удалить ее позже:

// assign cards
var cards = Nil
if ( e!=null ) cards = new Card(e) :: cards
if ( d!=null ) cards = new Card(d) :: cards
if ( c!=null ) cards = new Card(c) :: cards
if ( b!=null ) cards = new Card(b) :: cards
if ( a!=null ) cards = new Card(a) :: cards
sortCards(cards)

Хорошо, теперь мы можем видеть, что именно мы мутируем, и можем легко удалить эту изменчивость:

val fromE = if ( e!=null ) new Card(e) :: Nil else Nil
val fromD = if ( d!=null ) new Card(d) :: fromE else fromE
val fromC = if ( c!=null ) new Card(c) :: fromD else fromD
val fromB = if ( b!=null ) new Card(b) :: fromC else fromC
val fromA = if ( a!=null ) new Card(a) :: fromB else fromB
sortCards(fromA)

Теперь у нас довольно много дублирования кода. Давайте удалим это методом грубой силы (найдите длинный дублирующий кусок кода и извлеките функцию)!

def prependCard(x : String, cards : List[Card]) = 
  if ( x!=null ) new Card(x) :: cards else cards
val cards = prependCard(a, prependCard(b, 
              prependCard(c, prependCard(d, 
                prependCard(e, Nil)
              ))
            ))
sortCards(cards)

Следующее, очень важное, преобразование будет состоять в том, чтобы заменить пустые ссылки на значения Option введите или удалите концепцию пустой карты.

Обновить:

По запросу я добавляю пример использования apply метод. Обратите внимание, что он объявлен в object Handне class Hand, так что ему не нужен экземпляр класса (это похоже на статический метод в Java). Мы просто применяем объект к параметрам: val hand = Hand("5S", "5S", "5S", "5S", "5S"),

Во-первых, null это зло, использовать Option вместо. Во-вторых, Scala поддерживает параметры по умолчанию. Таким образом, вместо создания всех конструкторов, вы можете просто использовать один из них так:

def this(a: String = null, ..., e: String = null) = ...

или с Option, что безопаснее.

def this(a: Option[String] = None, ..., e: Option[String] = None) = {
   this(Nil)

   val cardBuffer = new ListBuffer[Card]()
   a foreach { cardBuffer += new Card(_) }
   b foreach { cardBuffer += new Card(_) }
   c foreach { cardBuffer += new Card(_) }
   d foreach { cardBuffer += new Card(_) }
   e foreach { cardBuffer += new Card(_) }
   cards = sortCards(cardBuffer.toList)
}

Таким образом, карты добавляются в буфер, только если они "существуют".

Попытайтесь использовать varargs, перегруженный + оператор, повторное добавление в конструкторе и Set, чтобы удалить дубликаты карт.

package poker

class Hand(private val cards:Set[Card] = Set.empty[Card]) {
  def + (card:Card) = {
    val hand = new Hand(cards + card)
    require(hand.length > length, "Card %s duplicated".format(card))
    require(hand.length <= Hand.maxLength, "Hand length > %d".format(Hand.maxLength))
    hand
  }
  def length = cards.size
  override def toString = cards.mkString("(", ",", ")")
}

object Hand {
  val maxLength = 5
  def apply(cards:Card*):Hand = cards.foldLeft(Hand())(_ + _)
  private def apply() = new Hand()
}

//-----------------------------------------------------------------------------------------------//


class Card private (override val toString:String) 

object Card {
  def apply(card:String) = {
    require(cardMap.contains(card), "Card %s does not exist".format(card))
    cardMap(card)
  }

  def cards = cardMap.values.toList

  private val cardMap = {
    val ranks = Range(2,9).inclusive.map { _.toString } ++ List("T", "J", "Q", "K", "A")
    val suits = List("c","d","h","s")
    (for(r <- ranks; s <- suits) yield (r + s -> new Card(r + s))).toMap
  } 
}

//-----------------------------------------------------------------------------------------------//

object Test extends App {
  Array("1f", "Ac").foreach { s =>
    try {
      printf("Created card %s successfully\n",Card(s))
    } catch {
      case e:Exception => printf("Input string %s - %s \n", s, e.getMessage)
    }
  }
  println

  for(i <- 0 to 6) {
    val cards = Card.cards.slice(0, i)
    makeHand(cards)
  }
  println

  val cards1 = List("Ac","Ad","Ac").map { Card(_) } 
  makeHand(cards1)
  println

  val hand1 = Hand(List("Ac","Ad").map { Card(_) }:_* )
  val card = Card("Ah")
  val hand2 = hand1 + card
  printf("%s + %s = %s\n", hand1, card, hand2)

  def makeHand(cards:List[Card]) =  
    try {
      val hand = Hand(cards: _*)
      printf("Created hand %s successfully\n",hand)
    } catch {
      case e:Exception => printf("Input %s - %s \n", cards, e.getMessage)
    }
}
Другие вопросы по тегам