Ответ 1
1) Что означает p
?
groupBy
принимает функцию, которая отображает элементы в ключ типа K
. При вызове в некоторой коллекции Coll
он возвращает Map[K, Coll]
, который содержит сопоставления из ключей K
ко всем элементам, которые сопоставлены с одним и тем же ключом.
Итак, в вашем случае str.groupBy(_.toChar)
выводит отображение карты из ключа K
(который является символом) в строку со всеми элементами (символами) c
такими, что k == c.toChar
.
Вы получите следующее:
Map(e -> "e", h -> "h", l -> "ll", o -> "o")
A Map
- это итерабельность пар ключей и значений. В этом случае каждая пара является символом и строкой элементов. Вызов операции Map
в Map
включает отображение этих пар - p
- это пара, где p._1
- символ, а p._2
- связанная строка (на которую вы можете вызвать length
, as вы сделали это выше).
2) Как это сделать идиоматически
Выше описано, как это сделать идиоматически - используя groupBy
и Map
. В качестве альтернативы вы можете использовать неизменяемую карту и рекурсию по длине строки для вычисления частот или неизменяемой карты и foldLeft
.
3) Характеристика характеристик
Лучше всего > оценить. Вот пара микрочипов для сильно повторяющейся строки (~ 3GHz iMac, JDK7, Scala 2.10.0 в ночное время):
object Imperative extends testing.Benchmark {
val str = "abc" * 750000
def run() {
var counts = new scala.collection.mutable.HashMap[Char,Int]
var i = 0
val until = str.length
while (i < until) {
var c = str(i)
if (counts.contains(c))
counts.put(c, counts(c) + 1)
else
counts.put(c, 1)
i += 1
}
//println(f)
}
}
object Combinators extends testing.Benchmark {
val str = "abc" * 750000
def run() {
val f = str.groupBy(_.toChar).map(p => (p._1, p._2.length))
}
}
object Fold extends testing.Benchmark {
val str = "abc" * 750000
def run() {
val f = str.foldLeft(Map[Char, Int]() withDefaultValue 0){(h, c) => h.updated(c, h(c)+1)}
}
}
Результаты:
-
Императив:
$ 103 57 53 58 53 53 53 53 53 53
-
Комбинаторы:
$ 72 51 63 56 53 52 52 54 53 53
-
Сложить:
$ 163 62 71 62 57 57 57 58 57 57
Обратите внимание, что изменение императивной версии для использования withDefaultValue
:
var counts = new scala.collection.mutable.HashMap[Char,Int].withDefaultValue(0)
var i = 0
val until = str.length
while (i < until) {
var c = str(i)
counts.put(c, counts(c) + 1)
i += 1
}
по-видимому, ужасно медленный из-за пересылки каждого вызова put
:
-
withDefaultValue
:$ 133 87 109 106 101 100 101 100 101 101
Заключение: бокс и распаковка символов в этом случае достаточно высока, так что различия в производительности между этими подходами трудно наблюдать.
EDIT:
Обновление. Вместо тэга Benchmark
вы можете использовать встроенный бенчмаркинг ScalaMeter.