这是我的问题:
我尝试聚合对象列表:
val list = List(Foo(1), Foo(2), Bar(2), Bar(3), Baz(5), Baz(3))
在聚合之后,我希望此列表中的每个可聚合类型只有一个对象。在这个例子中,Foo和Bar应该是可聚合的,而Baz不是,所以结果应该是:
List(Foo(3), Bar(5), Baz(5), Baz(3))
我的想法是定义一个特性聚合,如下所示:
trait Aggregatable[T] {
def aggregate(agg: T): T
}
case class Foo(val x: Int) extends Aggregatable[Foo] {
def aggregate(agg: Foo) = {
val x = (0 /: List(this, agg))((old, elem) => (old + elem.x))
new Foo(x)
}
}
case class Bar(val x: Int) extends Aggregatable[Bar] {
def aggregate(agg: Bar) = {
val x = (0 /: List(this, agg))((old, elem) => (old + elem.x))
new Bar(x)
}
}
case class Baz(val x: Int)
嗯,我认为这是问题的明显部分......
在下一步中,我尝试聚合列表。首先,我将列表分为同类型列表:
val grouped = list.groupBy( _.getClass().toString() )
/* => grouped should be
* Map(
* class Foo ->
* List(Foo(1), Foo(2)),
* class Bar ->
* List(Bar(3), Bar(4)),
* class Baz ->
* List(Baz(5), Baz(3))
* )
*/
现在为了简单起见,我们现在假设我们想要找出第一个列表的前两个元素是否可聚合:
val firstList = grouped.toList.apply(0)._2 // List(Foo(1), Foo(2))
val a = firstList (0) // Foo(1)
val b = firstList (1) // Foo(2)
这是我实际问题的开始。要确定a和b是否可以聚合,必须有一种方法可以询问a和b是否从相同类型的Aggregatable [T]继承某些固定的T.
我问这个问题的方法是定义一个类型aggregatablePair:
type aggregatablePair = Pair[T, T] forSome { type T <: Aggregatable[T] }
从a和b中构建一对:
val pair = (a, b)
并汇总它们,如果它们是一个aggregatablePair:
pair match {
case aggPair: aggregatablePair => aggPair._1.aggregate(aggPair._2)
case _ => println("pair is not aggregatable")
}
但这不起作用......错误是:
type mismatch;
found: aggPair._2.type (with underlying type T forSome { type T <: Aggregatable[T] })
required: T where type T <: Aggregatable[T]
在我看来,这听起来像找到的类型匹配所需的类型...任何人都可以告诉我为什么它不? 什么是表达我想要的正确方法?
感谢您的帮助
答案 0 :(得分:1)
我找到了一个令人满意的&#34;令人满意的&#34;解决我的问题。一般的想法是将一个结果类型为List [Any]的方法aggregateOrCons添加到Aggregatable特征中,如果它们来自同一类型,则聚合两个对象或返回包含输入参数的列表。
trait Aggregatable[T] {
def aggregate(agg: T): T
def aggregateOrCons(agg: Any): List[Any] = {
agg match {
case t: T => List(this.aggregate(t))
case a => List(a, this)
}
}
}
我的输入参数现在正在排序,而不是按类分组,因为我只需要确保相同类型的对象出现在一行中。
val list = List(new Foo(1), new Baz(1), new Baz(2), new Bar(3), new Foo(2), new Bar(4))
val sorted = list.sortWith(
(a1, a2) => (a1.getClass().toString() compareTo a2.getClass().toString()) < 0
)
在下一步中,我定义了一个方法来聚合两个Any类型的对象。如果两个输入参数都是Aggregatable类型,我对它们应用aggregatableOrCons方法(如果两个参数相等,则会产生两个参数的聚合,如果不相同则产生包含参数的列表)。如果其中一个不是Aggregatable,则将返回包含输入参数的列表。
def aggregate(a: Any, b: Any): List[Any] = a match {
case agg1: Aggregatable[_] => b match {
case agg2: Aggregatable[_] => agg1.aggregateOrCons(agg2)
case b => List(b, agg1)
}
case a => List(b, a)
}
现在,能够折叠已排序的输入动画列表的唯一要求是中性元素。它应该与任何东西聚合,并且应该只返回输入参数。
object NeutralAggregatable extends Aggregatable[Any] {
def aggregate(agg: Any) = agg
}
现在我可以折叠已排序的列表
val neutral: Any = NeutralAggregatable
val aggregated = (List(neutral) /: sorted)((old, elem) =>
(aggregate(old.head, elem) ::: old.tail)
)
println(aggregated) // List(Foo(3), Baz(2), Baz(1), Bar(7))
答案 1 :(得分:0)
由于JVM的类型擦除,无法在运行时确定类型参数T
。但你不需要(而且你知道T
是什么,因为它也是地图中getClass
个键的类,但你不需要知道。)
由于您知道a
和b
属于同一类型(例如Foo
),如果a.instanceOf[Aggregatable]
(或a.instanceOf[Aggregatable[_]]
),则{{ 1}}也是。所以只需测试列表的第一项,如果为true,则累积所有元素。
P.S。我会使用具有b.instanceOf[Aggregatable]
类型的category theory collection library,而不是将其重新发布为Monoid
。 Aggregatable
知道如何积累自己。请参阅Applicative programming with effects的第7页。
从概念上讲,您正在执行以下functional programming步骤。
Monoid
来测试清晰度。我看到你用elem.getClass
完成了#1。