在Scala中的函数式编程示例
在Scala中的函数式编程示例
我正在学习Scala。由于Odersky和其他作者的伟大工作,它非常有前景。\n我选择了一个欧拉问题(http://projecteuler.net/)作为一个最小的例子。我正在尝试以函数式的方式解决问题。所以这不是一个“请立即回答我,否则我的老板会杀了我”的问题,而是一个“如果你有时间,你能帮助一个习惯于命令式语言的程序员进入函数式世界吗?”的问题?\n问题:我想要一个用于扑克牌的类。扑克牌由0到5张卡组成。我想一次性地构建卡牌列表,也就是说:我的Hand类将是不可变的,如果我想添加一张牌,那么我就会创建一个新的Hand对象。\n因此,我需要一个可以作为“val”创建的Card集合,而不是作为“var”创建。\n第一步:构造函数,每个卡牌数量一个。但是Card的集合在每个构造函数中被处理,所以我必须将其设置为“var”!\n下面是代码,Card类只是一个Suit和一个Value,以字符串形式传递给构造函数(“5S”表示黑桃5):\n
class Hand(mycards : List[Card]) { // 这个应该是val,我猜 private var cards : List[Card] = { if (mycards.length>5) throw new IllegalArgumentException( "Illegal number of cards: " + mycards.length); sortCards(mycards) } // 完整的手牌构造函数 def this(a : String, b : String, c : String, d : String, e : String) = { this(Nil) // 分配卡牌 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) } // 少于5张卡牌的手牌 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 */ }
\n你知道如何以真正的函数式方式实现吗?\n谢谢。\nPS:如果你真的想知道,这是第54个问题。
函数式编程在Scala中的一个例子
在这个例子中,你只被允许使用五张牌,我会在编译时使用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`。
使用元组是有趣的,即使-nom-nom解决方案更简洁。但是你的解决方案只提供了带有5个参数的完整构造函数,这可能是一个问题。这个例子要求计算手牌的等级,而我已经设法这样做:搜索一对牌的等级,如果有这样的等级,从手牌中提取这对牌,并在剩下的牌上迭代。为了创建子手牌,我需要用变量数量的NoCard调用完整的构造函数。也许这不是很难,但是一个可变参数构造函数似乎是一个完美的选择。
如果有人真的想要表示在编译时检查的有限长度列表,一个更好的选择是像`Option[(Card, Option[(Card, Option[(Card, Option[(Card, Option[Card])])])])`这样的东西
虽然上面的代码在没有一些类型级别的魔法的情况下看起来很丑陋,但是通过简单的类型别名,它仍然可以缩短为`OC[OC[OC[OC[OC[Unit]]]]]`。
在上面的代码中,问题出在没有从主构造函数初始化cards变量。解决方法是修复辅助构造函数,将传入的参数转换成选项(Option),然后使用flatten方法去除None,并将Some转换回原来的类型。
// 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) } def this(a : String, b : String, c : String, d : String, e : String) = this(List(a, b, c, d, e).map(Option(_)).flatten.map(Card(_)))
在这个例子中,问题的出现是因为需要在Hand类中限制手牌数量不能超过五张。解决方法是使用辅助构造函数和require函数来实现。
首先,辅助构造函数中的参数String*表示可以接受任意数量的字符串参数。然后使用map函数将每个字符串参数转换为一个Card对象,并将结果传递给父构造函数。父构造函数中的require函数用来检查手牌数量是否超过了五张。
在Hand类中,mycards属性的类型是List[Card],它等价于cards: List[Card],即将传递给构造函数的参数赋值给mycards属性。
如果尝试创建超过五张手牌,就会抛出java.lang.IllegalArgumentException异常,并显示错误消息"can't be more than five cards"。
通过这种方式,可以简洁地实现对手牌数量的限制。
以下是完整的代码示例:
class Card(s: String) { println("I'm a: " + s) } class Hand(val mycards: List[Card]) { require(mycards.size <= 5, "can't be more than five cards") def this(input: String*) = { this(input.map(new Card(_)).toList) } } val hand = new Hand("one", "two", "three", "four", "five", "six")
运行以上代码会输出以下结果:
I'm a: one I'm a: two I'm a: three I'm a: four I'm a: five I'm a: six Exception in thread "main" java.lang.IllegalArgumentException: requirement failed: can't be more than five cards at scala.Predef$.require(Predef.scala:224) at Hand.(Main.scala:6) at Main$.main(Main.scala:14) at Main.main(Main.scala)
可以看到,当手牌数量超过五张时,会抛出异常并显示错误消息"can't be more than five cards"。