Scala中基于类型的集合分区
给定以下数据模型:Scala中基于类型的集合分区,scala,scala-collections,shapeless,Scala,Scala Collections,Shapeless,给定以下数据模型: sealed trait Fruit case class Apple(id: Int, sweetness: Int) extends Fruit case class Pear(id: Int, color: String) extends Fruit 我一直在寻求实现一个隔离篮功能,对于给定的水果篮,该功能将返回单独的苹果和梨篮: 花篮(果篮:一套[水果]:(一套[苹果]、一套[梨]) 我尝试过几种方法,但似乎没有一种完全符合要求。以下是我的尝试: def s
sealed trait Fruit
case class Apple(id: Int, sweetness: Int) extends Fruit
case class Pear(id: Int, color: String) extends Fruit
我一直在寻求实现一个隔离篮功能,对于给定的水果篮,该功能将返回单独的苹果和梨篮:
花篮(果篮:一套[水果]:(一套[苹果]、一套[梨])
我尝试过几种方法,但似乎没有一种完全符合要求。以下是我的尝试:
def segregateBasket1(fruitBasket: Set[Fruit]): (Set[Apple], Set[Pear]) = fruitBasket
.partition(_.isInstanceOf[Apple])
.asInstanceOf[(Set[Apple], Set[Pear])]
这是我找到的最简洁的解决方案,但它会受到通过asInstanceOf
进行显式类型转换的影响,如果我决定添加其他类型的水果,那么扩展它将是一件痛苦的事情。因此:
def segregateBasket2(fruitBasket: Set[Fruit]): (Set[Apple], Set[Pear]) = {
val mappedFruits = fruitBasket.groupBy(_.getClass)
val appleSet = mappedFruits.getOrElse(classOf[Apple], Set()).asInstanceOf[Set[Apple]]
val pearSet = mappedFruits.getOrElse(classOf[Pear], Set()).asInstanceOf[Set[Pear]]
(appleSet, pearSet)
}
def segregateBasket3(fruitBasket: Set[Fruit]): (Set[Apple], Set[Pear]) = {
val appleSet = collection.mutable.Set[Apple]()
val pearSet = collection.mutable.Set[Pear]()
fruitBasket.foreach {
case a: Apple => appleSet += a
case p: Pear => pearSet += p
}
(appleSet.toSet, pearSet.toSet)
}
解决了额外的水果类型的问题(扩展非常容易),但仍然强烈依赖于我宁愿避免的高风险类型转换“asInstanceOf”。因此:
def segregateBasket2(fruitBasket: Set[Fruit]): (Set[Apple], Set[Pear]) = {
val mappedFruits = fruitBasket.groupBy(_.getClass)
val appleSet = mappedFruits.getOrElse(classOf[Apple], Set()).asInstanceOf[Set[Apple]]
val pearSet = mappedFruits.getOrElse(classOf[Pear], Set()).asInstanceOf[Set[Pear]]
(appleSet, pearSet)
}
def segregateBasket3(fruitBasket: Set[Fruit]): (Set[Apple], Set[Pear]) = {
val appleSet = collection.mutable.Set[Apple]()
val pearSet = collection.mutable.Set[Pear]()
fruitBasket.foreach {
case a: Apple => appleSet += a
case p: Pear => pearSet += p
}
(appleSet.toSet, pearSet.toSet)
}
解决了显式强制转换的问题,但使用可变集合,理想情况下,我希望使用不可变集合和惯用代码
我在这里寻找了一些灵感,但也找不到更好的方法
有人对如何在Scala中更好地实现此功能有什么建议吗 你的例子让我有点困惑。每个“隔离”方法的返回类型都是
Tuple2
,但您希望能够自由添加更多类型的水果。您的方法将需要返回具有动态长度的内容(Iterable
/Seq
/etc),因为元组的长度需要在编译时确定
话虽如此,也许我过于简化了,但是仅仅使用groupBy
怎么样
val fruit = Set(Apple(1, 1), Pear(1, "Green"), Apple(2, 2), Pear(2, "Yellow"))
val grouped = fruit.groupBy(_.getClass)
然后对键/值执行任何操作:
grouped.keys.map(_.getSimpleName).mkString(", ") //Apple, Pear
grouped.values.map(_.size).mkString(", ") //2, 2
链接:一个“不可变”解决方案将使用您的可变解决方案,除非不向您显示集合。我不确定是否有充分的理由认为图书馆设计师这样做是可以的,但对你来说是一种诅咒。但是,如果您想坚持纯粹的不可变结构,这可能是最好的:
def segregate4(basket: Set[Fruit]) = {
val apples = basket.collect{ case a: Apple => a }
val pears = basket.collect{ case p: Pear => p }
(apples, pears)
}
可以使用Shapeless 2.0的LabelledGeneric
type类以一种非常干净和通用的方式来实现这一点。首先,我们定义一个类型类,该类将显示如何将具有某些代数数据类型元素的列表划分为每个构造函数的集合的HList
:
import shapeless._, record._
trait Partitioner[C <: Coproduct] extends DepFn1[List[C]] { type Out <: HList }
现在我们可以写以下内容:
val fruits: List[Fruit] = List(
Apple(1, 10),
Pear(2, "red"),
Pear(3, "green"),
Apple(4, 6),
Pear(5, "purple")
)
然后:
scala> val baskets = partition(fruits)
partitioned: shapeless.:: ...
scala> baskets('Apple)
res0: List[Apple] = List(Apple(1,10), Apple(4,6))
scala> baskets('Pear)
res1: List[Pear] = List(Pear(2,red), Pear(3,green), Pear(5,purple))
我们还可以编写一个返回列表元组的版本,而不是使用记录('symbol)
语法。有关详细信息,请参见。从Scala 2.13
,集合开始提供了一种方法,该方法基于返回Right
或Left
的函数对元素进行分区
通过对类型的模式匹配,我们可以将Pear
s映射到Left[Pear]
和Apple
s映射到Right[Apple]
以创建梨和苹果的元组:
val (pears, apples) =
Set(Apple(1, 10), Pear(2, "red"), Apple(4, 6)).partitionMap {
case pear: Pear => Left(pear)
case apple: Apple => Right(apple)
}
// pears: Set[Pear] = Set(Pear(2, "red"))
// apples: Set[Apple] = Set(Apple(1, 10), Apple(4, 6))
听起来“真正”的问题可能是关于一个n路分区[模式]。。(尽管类型除了用作鉴别器外,大多是次要的;没有涉及到带有[sub]类型的真正的丑八怪)即see(在这种情况下,分区可能位于类上;但对于通用的n向分区,静态类型在Scala中丢失,没有单独的强类型查找/关联)这是一个很好的解决问题的方法,比雷克斯的方法稍微复杂一点,但只需在篮下跑一圈就很好了。非常感谢您的帮助。可以删除额外的匹配项:fruits.foldRight(emptyBaskets){case(a@Apple(u,u),(as,ps))=>(a::as,ps)case(p@Pear(u,u),(as,ps))=>(as,p::ps)}/code>从可读性来看,这是我最喜欢的解决方案,对小型收藏来说非常有吸引力(通过多次收集进行迭代不会增加大量开销)。非常感谢Rex。多好:-)这将a[可能是5行,包括可变var
或ArrayBuffer
]减少为一行易读的代码这是什么魔法!我很想了解这一点(我写Scala是为了谋生几年,对Scala非常了解,但是这种类型的魔法使用Scalaz/无形状/代数类型/宏和那些:+:
(联合类型?)我一直对自己说,这是一种分心,我的日常工作不需要它(大多数时候我都不需要),但我的自我不让我休息,直到我完全理解它。(而且我可以理解CanBuildFrom
和类型类!你会认为我会休息一下)我从哪里学到的?
val (pears, apples) =
Set(Apple(1, 10), Pear(2, "red"), Apple(4, 6)).partitionMap {
case pear: Pear => Left(pear)
case apple: Apple => Right(apple)
}
// pears: Set[Pear] = Set(Pear(2, "red"))
// apples: Set[Apple] = Set(Apple(1, 10), Apple(4, 6))