我有一个对象列表List[Object]
,它们都是从同一个类中实例化的。此类的字段必须是唯一的Object.property
。迭代对象列表并删除具有相同属性的所有对象(但第一个)的最简洁方法是什么?
答案 0 :(得分:121)
list.groupBy(_.property).map(_._2.head)
说明:groupBy方法接受将元素转换为键以进行分组的函数。 _.property
只是elem: Object => elem.property
的简写(编译器生成一个唯一的名称,如x$1
)。现在我们有了一张地图Map[Property, List[Object]]
。 Map[K,V]
扩展Traversable[(K,V)]
。所以它可以像列表一样遍历,但元素是一个元组。这类似于Java的Map#entrySet()
。 map方法通过迭代每个元素并向其应用函数来创建新集合。在这种情况下,函数是_._2.head
,它是elem: (Property, List[Object]) => elem._2.head
的简写。 _2
只是一个返回第二个元素的元组方法。第二个元素是List [Object],head
返回第一个元素
要使结果成为您想要的类型:
import collection.breakOut
val l2: List[Object] = list.groupBy(_.property).map(_._2.head)(breakOut)
简单解释一下,map
实际上需要两个参数,一个函数和一个用于构造结果的对象。在第一个代码片段中,您没有看到第二个值,因为它被标记为隐式,因此由编译器从范围中的预定义值列表中提供。结果通常从映射容器中获取。这通常是件好事。 List上的map将返回List,Array上的map将返回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)
。在Set
中,方法apply
为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)
使用:
scala> list.distinctBy(_.property)
res7: List[Obj] = List(Obj(1), Obj(2), Obj(3))
另请注意,这非常有效,因为我们正在使用构建器。如果你有非常大的列表,你可能想要使用可变的HashSet而不是常规的集合并对性能进行基准测试。
答案 1 :(得分:14)
这是一个有点偷偷摸摸但快速的解决方案,可以保留顺序:
list.filterNot{ var set = Set[Property]()
obj => val b = set(obj.property); set += obj.property; b}
虽然它在内部使用var,但我认为它比foldLeft-solution更容易理解和阅读。
答案 2 :(得分:8)
从Scala 2.13
开始,现在大多数集合都提供了distinctBy
方法,该方法在应用给定的转换函数后将返回序列中的所有元素而忽略重复项:
list.distinctBy(_.property)
例如:
List(("a", 2), ("b", 2), ("a", 5)).distinctBy(_._1) // List((a,2), (b,2))
List(("a", 2.7), ("b", 2.1), ("a", 5.4)).distinctBy(_._2.floor) // List((a,2.7), (a,5.4))
答案 3 :(得分:6)
还有一个解决方案
@tailrec
def collectUnique(l: List[Object], s: Set[Property], u: List[Object]): List[Object] = l match {
case Nil => u.reverse
case (h :: t) =>
if (s(h.property)) collectUnique(t, s, u) else collectUnique(t, s + h.prop, h :: u)
}
答案 4 :(得分:4)
使用保留顺序:
def distinctBy[L, E](list: List[L])(f: L => E): List[L] =
list.foldLeft((Vector.empty[L], Set.empty[E])) {
case ((acc, set), item) =>
val key = f(item)
if (set.contains(key)) (acc, set)
else (acc :+ item, set + key)
}._1.toList
distinctBy(list)(_.property)
答案 5 :(得分:2)
我找到了一种方法,可以使用groupBy,只需一个中间步骤:
def distinctBy[T, P, From[X] <: TraversableLike[X, From[X]]](collection: From[T])(property: T => P): From[T] = {
val uniqueValues: Set[T] = collection.groupBy(property).map(_._2.head)(breakOut)
collection.filter(uniqueValues)
}
像这样使用:
scala> distinctBy(List(redVolvo, bluePrius, redLeon))(_.color)
res0: List[Car] = List(redVolvo, bluePrius)
与IttayD的第一个解决方案类似,但它根据一组唯一值过滤原始集合。如果我的期望是正确的,那么会进行三次遍历:一次针对groupBy
,一次针对map
,一次针对filter
。它维护原始集合的顺序,但不一定取每个属性的第一个值。例如,它可能已经返回List(bluePrius, redLeon)
。
当然,IttayD的解决方案仍然更快,因为它只进行一次遍历。
我的解决方案还有一个缺点,即如果集合的Car
实际上是相同的,那么它们都将在输出列表中。这可以通过删除filter
并直接返回uniqueValues
来修复,类型为From[T]
。但是,好像CanBuildFrom[Map[P, From[T]], T, From[T]]
似乎不存在......欢迎提出建议!
答案 6 :(得分:2)
上面有很多好的答案。但是,selectQuery = dbConnection.prepareStatement("SELECT MAX(COLUMN_NAME) FROM TABLE_NAME");
resultSet = selectQuery.executeQuery();
if (resultSet.next()) {
System.out.println(resultSet.getString(1));
}
已经在Scala中,但在一个不太明显的地方。也许你可以像
distinctBy
答案 7 :(得分:0)
具有从记录到键的集合和功能,这会产生按键区分的记录列表。尚不清楚groupBy是否将订单保留在原始集合中。它甚至可能取决于集合的类型。我猜测head
或last
会始终产生最早的元素。
collection.groupBy(keyFunction).values.map(_.head)
Scala何时获得nubBy
?它已经在Haskell呆了几十年了。
答案 8 :(得分:0)
如果要删除重复项并 保留列表的顺序 ,则可以尝试以下两种方法:
val tmpUniqueList = scala.collection.mutable.Set[String]()
val myUniqueObjects = for(o <- myObjects if tmpUniqueList.add(o.property)) yield o