Пример функционального программирования в 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)
}
}