list.groupBy(_.property).map(_._2.head)
说明: groupBy 方法接受将元素转换为用于分组的键的函数。_.property
只是简写elem: Object => elem.property
(编译器生成一个唯一的名称,例如x$1
)。现在我们有了一张地图Map[Property, List[Object]]
. A Map[K,V]
延伸Traversable[(K,V)]
。所以它可以像列表一样被遍历,但是元素是一个元组。这和Java的类似Map#entrySet()
。 map 方法通过迭代每个元素并向其应用函数来创建新集合。在这种情况下,函数是_._2.head
这是简写elem: (Property, List[Object]) => elem._2.head
. _2
只是 Tuple 的一个方法,返回第二个元素。第二个元素是 List[Object] 和head
返回第一个元素
要使结果成为您想要的类型:
import collection.breakOut
val l2: List[Object] = list.groupBy(_.property).map(_._2.head)(breakOut)
简单解释一下,map
实际上需要两个参数,一个函数和一个用于构造结果的对象。在第一个代码片段中,您看不到第二个值,因为它被标记为隐式,因此由编译器从范围内的预定义值列表中提供。结果通常是从映射的容器中获取的。这通常是一件好事。 List 上的映射将返回 List,Array 上的映射将返回 Array 等。但是在这种情况下,我们希望将我们想要的容器表示为结果。这就是使用breakOut方法的地方。它仅通过查看所需的结果类型来构造构建器(构建结果的事物)。它是一个泛型方法,编译器会推断出它的泛型类型,因为我们显式地将 l2 键入为List[Object]
或者,为了保持秩序(假设Object#property
属于类型Property
):
list.foldRight((List[Object](), Set[Property]())) {
case (o, cum@(objects, props)) =>
if (props(o.property)) cum else (o :: objects, props + o.property))
}._1
foldRight
是一个接受初始结果的方法和一个接受元素并返回更新结果的函数。该方法迭代每个元素,根据将函数应用于每个元素来更新结果并返回最终结果。我们从右到左(而不是从左到右)foldLeft
)因为我们正在准备objects
- 这是 O(1),但追加是 O(N)。还要观察这里的良好样式,我们使用模式匹配来提取元素。
在这种情况下,初始结果是空列表和集合的一对(元组)。该列表是我们感兴趣的结果,该集合用于跟踪我们已经遇到的属性。在每次迭代中,我们检查集合是否props
已经包含该属性(在 Scala 中,obj(x)
被翻译成obj.apply(x)
. In Set
, 方法apply
is def apply(a: A): Boolean
。也就是说,接受一个元素并返回 true / false(如果存在或不存在)。如果该属性存在(已经遇到),则按原样返回结果。否则结果将被更新以包含对象 (o :: objects
)并且财产被记录(props + o.property
)
更新:@andreypopp 想要一个通用方法:
import scala.collection.IterableLike
import scala.collection.generic.CanBuildFrom
class RichCollection[A, Repr](xs: IterableLike[A, Repr]){
def distinctBy[B, That](f: A => B)(implicit cbf: CanBuildFrom[Repr, A, That]) = {
val builder = cbf(xs.repr)
val i = xs.iterator
var set = Set[B]()
while (i.hasNext) {
val o = i.next
val b = f(o)
if (!set(b)) {
set += b
builder += o
}
}
builder.result
}
}
implicit def toRich[A, Repr](xs: IterableLike[A, Repr]) = new RichCollection(xs)
to use:
scala> list.distinctBy(_.property)
res7: List[Obj] = List(Obj(1), Obj(2), Obj(3))
另请注意,这非常有效,因为我们使用的是构建器。如果您有非常大的列表,您可能需要使用可变的 HashSet 而不是常规集合并对性能进行基准测试。