I've got a list of objects List[Object]
which are all instantiated from the same class. This class has a field which must be unique Object.property
. What is the cleanest way to iterate the list of objects and remove all objects(but the first) with the same property?
相关问题
- Unusual use of the new keyword
- how to split a list into a given number of sub-lis
- Removing duplicate dataframes in a list
- C#: How do i get 2 lists into one 2-tuple list in
- Get Runtime Type picked by implicit evidence
相关文章
- List可以存储接口类型的数据吗?
-
C# List
.FindAll 时 空指针异常 - Gatling拓展插件开发,check(bodyString.saveAs("key"))怎么实现
- RDF libraries for Scala [closed]
- What is the complexity of bisect algorithm?
- Why is my Dispatching on Actors scaled down in Akk
- Given a list and a bitmask, how do I return the va
- How do you run cucumber with Scala 2.11 and sbt 0.
Starting
Scala 2.13
, most collections are now provided with adistinctBy
method which returns all elements of the sequence ignoring the duplicates after applying a given transforming function:For instance:
Explanation: The groupBy method accepts a function that converts an element to a key for grouping.
_.property
is just shorthand forelem: Object => elem.property
(the compiler generates a unique name, something likex$1
). So now we have a mapMap[Property, List[Object]]
. AMap[K,V]
extendsTraversable[(K,V)]
. So it can be traversed like a list, but elements are a tuple. This is similar to Java'sMap#entrySet()
. The map method creates a new collection by iterating each element and applying a function to it. In this case the function is_._2.head
which is shorthand forelem: (Property, List[Object]) => elem._2.head
._2
is just a method of Tuple that returns the second element. The second element is List[Object] andhead
returns the first elementTo get the result to be a type you want:
To explain briefly,
map
actually expects two arguments, a function and an object that is used to construct the result. In the first code snippet you don't see the second value because it is marked as implicit and so provided by the compiler from a list of predefined values in scope. The result is usually obtained from the mapped container. This is usually a good thing. map on List will return List, map on Array will return Array etc. In this case however, we want to express the container we want as result. This is where the breakOut method is used. It constructs a builder (the thing that builds results) by only looking at the desired result type. It is a generic method and the compiler infers its generic types because we explicitly typed l2 to beList[Object]
or, to preserve order (assumingObject#property
is of typeProperty
):foldRight
is a method that accepts an initial result and a function that accepts an element and returns an updated result. The method iterates each element, updating the result according to applying the function to each element and returning the final result. We go from right to left (rather than left to right withfoldLeft
) because we are prepending toobjects
- this is O(1), but appending is O(N). Also observe the good styling here, we are using a pattern match to extract the elements.In this case, the initial result is a pair (tuple) of an empty list and a set. The list is the result we're interested in and the set is used to keep track of what properties we already encountered. In each iteration we check if the set
props
already contains the property (in Scala,obj(x)
is translated toobj.apply(x)
. InSet
, the methodapply
isdef apply(a: A): Boolean
. That is, accepts an element and returns true / false if it exists or not). If the property exists (already encountered), the result is returned as-is. Otherwise the result is updated to contain the object (o :: objects
) and the property is recorded (props + o.property
)Update: @andreypopp wanted a generic method:
to use:
Also note that this is pretty efficient as we are using a builder. If you have really large lists, you may want to use a mutable HashSet instead of a regular set and benchmark the performance.
I found a way to make it work with groupBy, with one intermediary step:
Use it like this:
Similar to IttayD's first solution, but it filters the original collection based on the set of unique values. If my expectations are correct, this does three traversals: one for
groupBy
, one formap
and one forfilter
. It maintains the ordering of the original collection, but does not necessarily take the first value for each property. For example, it could have returnedList(bluePrius, redLeon)
instead.Of course, IttayD's solution is still faster since it does only one traversal.
My solution also has the disadvantage that, if the collection has
Car
s that are actually the same, both will be in the output list. This could be fixed by removingfilter
and returninguniqueValues
directly, with typeFrom[T]
. However, it seems likeCanBuildFrom[Map[P, From[T]], T, From[T]]
does not exist... suggestions are welcome!A lot of good answers above. However,
distinctBy
is already in Scala, but in a not-so-obvious place. Perhaps you can use it likeOne more solution
I don't know which version of Scala you are using, but 2.8.2 definitely has
Edit (fixing the down votes)