考虑一个简单的案例类“卡片”,它有两个属性(“数字”和“颜色”),如下所示:
case class Card(number: Int, color: String)
考虑像这样的一系列牌:
val cards = Seq(
Card(5, "red"),
Card(7, "red"),
Card(3, "black"))
现在假设我想用Scala-idiomatic方式解决这些问题(功能导向?):
具体而言,我必须实现这些功能:
// Do the cards all have the same color?
def haveSameColor(cards: Seq[Card], ifEmpty: Boolean = true): Boolean = {
???
}
// Do the cards all have the same number?
def haveSameNumber(cards: Seq[Card], ifEmpty: Boolean = true): Boolean = {
???
}
// Are cards ordered ascendingly?
def areAscending(cards: Seq[Card], ifEmpty: Boolean = true): Boolean = {
???
}
有什么可能/最佳方法?循环,递归,折叠,缩小?
答案 0 :(得分:3)
forall
短暂退出,只要遇到第一次假
def haveSameColor(cards: Seq[Card], ifEmpty: Boolean = true) = {
if (cards.isEmpty) ifEmpty
else cards.forall(x => x.color.equals(cards.head.color))
}
// not much different approach
def haveSameNumber(cards: Seq[Card], ifEmpty: Boolean = true): Boolean = {
if (cards.isEmpty) ifEmpty
else cards.forall(x => x.number == cards.head.number)
}
def areAscending(cards: Seq[Card], ifEmpty: Boolean = true): Boolean = {
if (cards.isEmpty) ifEmpty
else cards.zip(cards.tail).forall{ case (prev, next) => prev.number <= next.number}
}
虽然,你说
是否清楚在空虚的情况下默认答案应该是什么?这不适合我。
正如你所看到的那样,功能上有很多重复 - 一个明显的标志就是有问题 - 我会使用没有ifEmpty参数的函数。
答案 1 :(得分:2)
尝试
case class Card(number: Int, color: String)
def haveSameColor(cards: Seq[Card]): Boolean = {
cards.groupBy{_.color}.size == 1
}
def haveSameNumber(cards: Seq[Card]): Boolean = {
cards.groupBy{_.number}.size == 1
}
def areAscending(cards: Seq[Card]): Boolean = {
cards.view.zip(cards.drop(1)).forall(c => c._1.number <= c._2.number)
}
等等
val cards = Seq(
Card(5, "red"),
Card(7, "red"),
Card(3, "black"))
我们有
haveSameColor(cards)
false
haveSameNumber(cards)
false
areAscending(cards)
false
此外,将这些方法嵌入到隐式类中,如下所示,
implicit class RichCardsChecking(val cards: Seq[Card]) extends AnyVal {
def haveSameColor(): Boolean = {
cards.groupBy{_.color}.size == 1
}
def haveSameNumber(): Boolean = {
cards.groupBy{_.number}.size == 1
}
def areAscending(): Boolean = {
cards.view.zip(cards.tail).forall(c => c._1.number <= c._2.number)
}
}
因此
cards.haveSameColor()
false
cards.haveSameNumber()
false
cards.areAscending()
false
答案 2 :(得分:1)
由于卡的数量可能不是那么大,我首先尝试尽可能多地使用现有功能(这通常会提供非常短的方法体):
如果空案件无关紧要:
def haveSameColor(cards: Seq[Card]) =
cards.tail.forall(cards.head.color == _.color)
如果您想以不同方式处理空案例,请考虑模式匹配:
def haveSameNumber(cards: Seq[Card], ifEmpty: Boolean = true) = {
cards match {
case Seq() =>
ifEmpty
case h +: t =>
t.forall(h.number == _.number)
}
}
由于牌数可能不高,我会从
开始def areAscending(cards: Seq[Card]) = {
cards == cards.sortBy(c => (c.color, c.number))
}
如果结果是热点,您可以稍后用优化版替换它。
一般来说,我首先要使用标准库提供的功能。
否则请考虑fold
/ reduce
或尾递归。
循环并不总是优雅,特别是如果你不得不突破它。
<强>更新强>
对于areAscending
,请考虑使用尾递归的此版本:
def areAscendingWithTailRecursion(cards: List[Card], ifEmpty: Boolean = true) = {
@tailrec
def areAscendingWithTailRecursion(c: Card, remainder: List[Card]): Boolean = {
remainder match {
case Nil =>
true
case h :: t =>
c.color <= h.color && c.number <= h.number &&
areAscendingWithTailRecursion(h, t)
}
}
cards match {
case Nil =>
ifEmpty
case List(_) =>
true
case c :: remainder =>
areAscendingWithTailRecursion(c, remainder)
}
但我怀疑这种努力是否值得获得性能。
至于你的用例
前两个函数按字面翻译为forall
(通用量词)。在性能方面可能难以击败。它只是对集合库的一次调用,在简洁性方面也很难被击败。
如果结果已知(无需处理其余部分),则所有函数都应该中断,因此我不会使用fold
/ reduce
。在那种情况下,我更喜欢尾递归。
答案 3 :(得分:1)
尝试使用takeWhile
,确保提前破解。
def haveSameColor(cards: Seq[Card]) =
cards.takeWhile(_.color == cards.head.color) == cards.size
def haveSameNumber(cards: Seq[Card]) =
cards.takeWhile(_.number == cards.head.number) == cards.size
对卡进行排序并检查它是否与当前cards
相同是O(n)。如果您需要性能,则需要编写更详细的代码 - 只需迭代cards
并查看两个元素是否按顺序排列(Scala 允许非本地回报)。
def areAscending(cards: Seq[Card])(implicit cardOrdering: Ordering[Card] = Ordering.fromLessThan(_.number < _.number)) =
cards.sorted == cards
这是REPL:
Welcome to Scala version 2.10.3 (Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_45). Type in expressions to have them evaluated. Type :help for more information.
scala> case class Card(number: Int, color: String) defined class Card
scala> val cards = Seq(
| Card(5, "red"),
| Card(7, "red"),
| Card(3, "black")
| ) cards: Seq[Card] = List(Card(5,red), Card(7,red), Card(3,black))
scala> def haveSameColor(cards: Seq[Card]) = cards.takeWhile(_.color
== cards.head.color) == cards.size haveSameColor: (cards: Seq[Card])Boolean
scala> def haveSameNumber(cards: Seq[Card]) = cards.takeWhile(_.number
== cards.head.number) == cards.size haveSameNumber: (cards: Seq[Card])Boolean
scala> def areAscending(cards: Seq[Card])(implicit cardOrdering: Ordering[Card] = Ordering.fromLessThan(_.number < _.number)) =
| cards.sorted == cards areAscending: (cards: Seq[Card])(implicit cardOrdering: Ordering[Card])Boolean
scala> haveSameColor(cards) res0: Boolean = false
scala> haveSameNumber(cards) res1: Boolean = false
scala> areAscending(cards) res2: Boolean = false
答案 4 :(得分:0)
有人在IRC上向我建议:
(自由改编)
def haveSameColor(cards: Seq[Card], ifEmpty: Boolean = true): Boolean =
if(cards.isEmpty)
ifEmpty
else
cards.map(_.color).toSet.size == 1
def haveSameNumber(cards: Seq[Card], ifEmpty: Boolean = true): Boolean =
if(cards.isEmpty)
ifEmpty
else
cards.map(_.number).toSet.size == 1
def areAscending(cards: Seq[Card], ifEmpty: Boolean = true): Boolean =
if(cards.isEmpty)
ifEmpty
else {
val numbers = cards.map(_.number)
numbers == numbers.sorted
}
虽然概念上清晰且易于阅读,但我担心这些解决方案在资源/计算方面都是过度的而且不容易。
另一种方法会很快失败 ,因为很明显并非所有元素都相同,或者序列没有提升。
另外,我希望可以某种方式使用折叠......但由于输出与输入(序列与布尔值)不同,我不知道它是如何工作的。