我在学习scala。感谢Odersky和所有其他作者的出色工作,这是非常有前景的。
我接受了一个欧拉问题(http://projecteuler.net/)来得到一个更为简单的例子。而我正试图以功能的方式。所以这不是“请立即回答我,否则我的老板会杀了我”,但是“如果你有时间,请你帮助一位势在必行的语言程序员去实现功能性世界吗?”
问题:我想要一个扑克牌课。一个扑克之手是由一些卡片组成的,从0到5.我想建立一个卡片列表,这就是:我的Hand类将是不可变的,如果我想添加一张卡片,那么我创建了一个新的Hand对象。 所以我需要一个可以创建为“val”的Card集合,而不是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。答案 0 :(得分: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
函数调用每个新卡的创建,然后将结果传递给具有自己的需求的父构造函数。
(BTW,从字符串到卡的映射可以匿名完成,以这种方式: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)
答案 1 :(得分:4)
以下代码中的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(_)))
。换句话说:
{{1}}
答案 2 :(得分:3)
因为在这个例子中你只允许使用五张牌,所以我会在编译时使用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
只会返回Iterator [Any],但当您通过字段标识符(_1 .. _5)直接使用您的卡片时,您将获得ACard
。
答案 3 :(得分:1)
首先,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)
}
因此,如果卡片“存在”,它们只会被添加到缓冲区中。
答案 4 :(得分:1)
首先,我们需要在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")
。
答案 5 :(得分:0)
尝试使用varargs,重载+运算符,在构造函数中重复添加,并设置以消除重复的卡片。
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)
}
}