Nor*_*dyk 17 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
Run Code Online (Sandbox Code Playgroud)
我一直在寻求实施一个隔离篮功能,对于给定的水果篮将返回单独的苹果和梨篮:
def segregateBasket(fruitBasket: Set[Fruit]): (Set[Apple], Set[Pear])
我尝试了几种方法,但它们似乎都没有完全适合这个法案.以下是我的尝试:
def segregateBasket1(fruitBasket: Set[Fruit]): (Set[Apple], Set[Pear]) = fruitBasket
.partition(_.isInstanceOf[Apple])
.asInstanceOf[(Set[Apple], Set[Pear])]
Run Code Online (Sandbox Code Playgroud)
这是我发现的最简洁的解决方案,但是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)
}
Run Code Online (Sandbox Code Playgroud)
解决了额外水果类型的问题(扩展非常简单),但仍然强烈依赖于风险类型转换'asInstanceOf',我宁愿避免.因此:
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)
}
Run Code Online (Sandbox Code Playgroud)
解决了显式转换的问题,但使用了可变集合,理想情况下我想坚持使用不可变集合和惯用代码.
我看过这里:Scala:根据类型过滤获得一些灵感,但也找不到更好的方法.
有没有人对如何在Scala中更好地实现此功能有任何建议?
Rex*_*err 12
"不可变"解决方案将使用您的可变解决方案,除非不显示集合.我不确定是否有充分的理由认为如果图书馆设计师这样做是可以的,但对你来说是诅咒.但是,如果你想坚持纯粹的不可变结构,这可能就像它得到的一样好:
def segregate4(basket: Set[Fruit]) = {
val apples = basket.collect{ case a: Apple => a }
val pears = basket.collect{ case p: Pear => p }
(apples, pears)
}
Run Code Online (Sandbox Code Playgroud)
nli*_*lim 12
val emptyBaskets: (List[Apple], List[Pear]) = (Nil, Nil)
def separate(fruits: List[Fruit]): (List[Apple], List[Pear]) = {
fruits.foldRight(emptyBaskets) { case (f, (as, ps)) =>
f match {
case a @ Apple(_, _) => (a :: as, ps)
case p @ Pear(_, _) => (as, p :: ps)
}
}
}
Run Code Online (Sandbox Code Playgroud)
Tra*_*own 10
使用Shapeless 2.0的LabelledGeneric类型类可以以非常干净和通用的方式完成此操作.首先,我们定义一个类型类,它将展示如何使用某些代数数据类型的元素将列表分区HList为每个构造函数的集合:
import shapeless._, record._
trait Partitioner[C <: Coproduct] extends DepFn1[List[C]] { type Out <: HList }
Run Code Online (Sandbox Code Playgroud)
然后是实例:
object Partitioner {
type Aux[C <: Coproduct, Out0 <: HList] = Partitioner[C] { type Out = Out0 }
implicit def cnilPartitioner: Aux[CNil, HNil] = new Partitioner[CNil] {
type Out = HNil
def apply(c: List[CNil]): Out = HNil
}
implicit def cpPartitioner[K, H, T <: Coproduct, OutT <: HList](implicit
cp: Aux[T, OutT]
): Aux[FieldType[K, H] :+: T, FieldType[K, List[H]] :: OutT] =
new Partitioner[FieldType[K, H] :+: T] {
type Out = FieldType[K, List[H]] :: OutT
def apply(c: List[FieldType[K, H] :+: T]): Out =
field[K](c.collect { case Inl(h) => (h: H) }) ::
cp(c.collect { case Inr(t) => t })
}
}
Run Code Online (Sandbox Code Playgroud)
然后partition方法本身:
implicit def partition[A, C <: Coproduct, Out <: HList](as: List[A])(implicit
gen: LabelledGeneric.Aux[A, C],
partitioner: Partitioner.Aux[C, Out]
) = partitioner(as.map(gen.to))
Run Code Online (Sandbox Code Playgroud)
现在我们可以写下面的内容:
val fruits: List[Fruit] = List(
Apple(1, 10),
Pear(2, "red"),
Pear(3, "green"),
Apple(4, 6),
Pear(5, "purple")
)
Run Code Online (Sandbox Code Playgroud)
然后:
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))
Run Code Online (Sandbox Code Playgroud)
我们还可以编写一个版本来返回列表的元组而不是使用record('symbol)语法 - 有关详细信息,请参阅我的博客文章.
从Scala 2.13, Sets (和大多数集合)开始提供一种partitionMap方法,该方法根据返回Right或的函数对元素进行分区Left。
通过对类型进行模式匹配,我们可以将PearsLeft[Pear]和Apples映射到Right[Apple]forpartitionMap以创建梨和苹果的元组:
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))
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1710 次 |
| 最近记录: |