Агрегатная функция этого не делает (за исключением того, что это очень общая функция, и ее можно использовать для этого).Вы хотите groupBy
.Ближе к минимуму.Когда вы начинаете с Seq[(String, String)]
, и вы группируете, беря первый элемент в кортеже (который равен (String, String) => String)
, он возвращает Map[String, Seq[(String, String)]
).Затем вам нужно отбросить первый параметр в значениях Seq [String, String)].
Итак
list.groupBy(_._1).mapValues(_.map(_._2))
Там вы получите Map[String, Seq[(String, String)]
.Если вы хотите Seq
вместо Map
, позвоните toSeq
для результата.Я не думаю, что у вас есть гарантия заказа в полученном Seq, хотя
Aggregate - более сложная функция.
Сначала рассмотрим Reduce Left и ReduRight.Пусть as
будет непустой последовательностью as = Seq(a1, ... an)
элементов типа A
, а f: (A,A) => A
будет некоторым способом объединить два элемента типа A
в один.Я отмечу это как бинарный оператор @
, a1 @ a2
, а не f(a1, a2)
.as.reduceLeft(@)
будет вычислять (((a1 @ a2) @ a3)... @ an)
.reduceRight
поставит скобки в другую сторону, (a1 @ (a2 @... @ an))))
.Если @
оказывается ассоциативным, то не заботятся о скобках.Можно вычислить это как (a1 @... @ ap) @ (ap+1 @...@an)
(внутри 2 больших парантезов тоже будут парантезы, но об этом не будем беспокоиться).Тогда можно было бы выполнить две части параллельно, в то время как вложенный брекетинг в reduLeft или reduRight заставляет полностью выполнять последовательные вычисления.Но параллельные вычисления возможны только тогда, когда известно, что @
является ассоциативным, а метод reduLeft не может этого знать.
Тем не менее, может существовать метод reduce
, чей вызывающий будет отвечать за обеспечение того, чтобы операция была ассоциативной.Затем reduce
упорядочит вызовы так, как считает нужным, возможно, делая их параллельно.Действительно, такой метод есть.
Однако существует ограничение на различные методы сокращения.Элементы Seq могут быть объединены только в результат того же типа: @
должно быть (A,A) => A
.Но может возникнуть более общая проблема их объединения в B
.Каждый начинается со значения b
типа B
и объединяет его с каждым элементом последовательности.Оператор @
равен (B,A) => B
, а один вычисляет (((b @ a1) @ a2) ... @ an)
.foldLeft
делает это.foldRight
делает то же самое, но начинается с an
.Там операция @
не имеет шансов быть ассоциативной.Когда кто-то пишет b @ a1 @ a2
, это должно означать (b @ a1) @ a2
, так как (a1 @ a2)
будет напечатано неправильно.Так что foldLeft и foldRight должны быть последовательными.
Предположим, однако, что каждый A
можно превратить в B
, запишем его с !
, a!
типа B
.Кроме того, предположим, что существует операция +
(B,B) => B
и что @
таков, что b @ a
на самом деле b + a!
.Вместо того, чтобы комбинировать элементы с @, можно сначала преобразовать все из них в B с помощью !
, а затем объединить их с +
.Это было бы as.map(!).reduceLeft(+)
.И если +
является ассоциативным, то это можно сделать с помощью Reduce, а не быть последовательным: as.map (!). Reduce (+).Может быть гипотетический метод as.associativeFold (b,!, +).
Совокупность очень близка к этому.Однако может быть, что существует более эффективный способ реализации b@a
, чем b+a!
Например, если тип B
равен List[A]
, а b @ a является a :: b, тогда a!
будетa::Nil
, а b1 + b2
будет b2 ::: b1
.a :: b намного лучше, чем (a :: Nil) ::: b.Чтобы извлечь выгоду из ассоциативности, но все же использовать @
, сначала нужно разделить b + a1! + ... + an!
на (b + a1! + ap!) + (ap+1! + ..+ an!)
, а затем вернуться к использованию @
с (b @ a1 @ an) + (ap+1! @ @ an)
.Еще нужно!на ap + 1, потому что нужно начинать с некоторого b.И + все еще необходим, появляясь между парантезами.Для этого as.associativeFold(!, +)
можно изменить на as.optimizedAssociativeFold(b, !, @, +)
.
Вернуться к +
. +
является ассоциативным или, что эквивалентно, (B, +)
является полугруппой. На практике большинство полугрупп, используемых в программировании, тоже являются моноидами, т.е. они содержат нейтральный элемент z
(для ноль ) в B, так что для каждого b
, z + b
= b + z
= b
. В этом случае операция !
, которая имеет смысл, скорее всего, будет a! = z @ a
. Более того, поскольку z является нейтральным элементом b @ a1 ..@ an = (b + z) @ a1 @ an
, который равен b + (z + a1 @ an)
. Так что всегда можно начать агрегацию с z. Если вместо этого требуется b
, вы делаете b + result
в конце. Со всеми этими гипотезами мы можем сделать s.aggregate(z, @, +)
. Это то, что делает aggregate
. @
- это аргумент seqop
(применяется в последовательности z @ a1 @ a2 @ ap
), а +
равен combop
(применяется к уже частично объединенным результатам, как в (z + a1@...@ap) + (z + ap+1@...@an)
).
Подводя итог, as.aggregate(z)(seqop, combop)
вычисляет то же самое, что и as.foldLeft(z)( seqop)
, при условии, что
(B, combop, z)
является моноидом
seqop(b,a) = combop(b, seqop(z,a))
Агрегированная реализация может использовать ассоциативность combop для группировки вычислений по своему усмотрению (однако, не меняет местами элементы, + не должен быть коммутативным, ::: нет). Это может запустить их параллельно.
Наконец, решение начальной задачи с использованием aggregate
оставлено читателю в качестве упражнения. Подсказка: используйте foldLeft
, затем найдите z
и combo
, которые будут удовлетворять условиям, указанным выше.