检查案例类实例集合的属性

时间:2014-03-24 10:00:00

标签: scala

考虑一个简单的案例类“卡片”,它有两个属性(“数字”和“颜色”),如下所示:

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 = {
  ???
}

有什么可能/最佳方法?循环,递归,折叠,缩小?

5 个答案:

答案 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 
  }

虽然概念上清晰且易于阅读,但我担心这些解决方案在资源/计算方面都是过度的而且不容易。

另一种方法会很快失败 ,因为很明显并非所有元素都相同,或者序列没有提升。

另外,我希望可以某种方式使用折叠......但由于输出与输入(序列与布尔值)不同,我不知道它是如何工作的。