Ответ 1
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()
. Метод карты создает новую коллекцию путем итерации каждого элемента и применения к нему функции. В этом случае функция _._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
фактически ожидает два аргумента: функцию и объект, которые используются для построения результата. В первом фрагменте кода вы не видите второе значение, потому что оно помечено как неявное и предоставлено компилятором из списка предопределенных значений в области. Результат обычно получается из сопоставленного контейнера. Обычно это хорошо. карта в списке вернет список, карта на массиве вернет Array и т.д. В этом случае мы хотим выразить контейнер, который мы хотим в качестве результата. Здесь используется метод breakOut. Он создает построитель (то, что создает результаты), только глядя на желаемый тип результата. Это общий метод, и компилятор передает свои общие типы, потому что мы явно набрали l2 как List[Object]
или, чтобы сохранить порядок (предполагая, что Object#property
имеет тип Property
):
list.foldRight((List[Object](), Set[Property]())) {
case (o, [email protected](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 вместо обычного набора и оценивать производительность.