Пример функционального программирования в scala - PullRequest
7 голосов
/ 10 августа 2011

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

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

Проблема: мне нужен класс для покерных рук. Покерная комбинация состоит из нескольких карт, от 0 до 5. Я хотел бы составить список карт один и за все, то есть: мой класс Hand будет неизменным, если я хочу добавить карточку, я создаю новый объект 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 ]

6 голосов
/ 10 августа 2011

Мой ответ не о функциональном аспекте 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)
4 голосов
/ 11 августа 2011

Итак, 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(_)))
3 голосов
/ 10 августа 2011

Поскольку в этом примере вам разрешено использовать только пять карт, я проверю это во время компиляции с использованием 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.

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

Во-первых, нам нужно исправить ошибку компиляции в определении 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").

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

Во-первых, 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)
}

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

0 голосов
/ 30 ноября 2016

Попытка использовать 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)
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...