La génération d'une fréquence de la carte pour une chaîne en Scala
Disons que j'ai une chaîne de caractères, "bonjour", et je veux générer un personnage de la fréquence de la carte:
Map[Char,Int] = Map(h -> 1, e -> 1, o -> 1, l -> 2)
Je pourrais le faire de manière itérative:
val str = "hello"
var counts = new scala.collection.mutable.HashMap[Char,Int]
for (i <- str) {
if (counts.contains(i))
counts.put(i, counts(i) + 1)
else
counts.put(i, 1)
}
Par déconner dans le REPL, j'ai trouvé que je peux faire quelque chose d'un peu plus concis et de ne pas utiliser une mutable collection:
> str.groupBy(_.toChar).map{ p => (p._1, p._2.length)}
scala.collection.immutable.Map[Char,Int] = Map(h -> 1, e -> 1, o -> 1, l -> 2)
Mais je ne connais pas les caractéristiques de performance de groupBy (), ni ce qui se passe dans le bloc transmis à la carte (comme quoi, exactement, p).
Comment dois-je faire idiomatique à l'aide de la fonctionnelle de paradigmes dans la Scala?
Pour le fond, je suis juste venue à la Scala pour la première fois de Ruby. En Ruby, je voudrais utiliser inject
mais je ne suis pas sûr de ce que le parallèle moyen de le faire à la Scala est:
counts = str.each_byte.inject(Hash.new(0)){ |h, c| h[c] += 1; h}
Vous devez vous connecter pour publier un commentaire.
1) Qu'est -
p
veux dire?groupBy
prend une fonction qui associe à un des éléments clés de typeK
. Lorsqu'elle est appelée sur un certain prélèvementColl
, il renvoie uneMap[K, Coll]
qui contient les mappages de touchesK
à tous les éléments qui mappé sur la même touche.Donc, dans votre cas,
str.groupBy(_.toChar)
donne une carte de la cartographie à partir d'une clék
(qui est un caractère) à une chaîne avec tous les éléments (personnages)c
tels quek == c.toChar
.Vous obtenez ceci:
Un
Map
est un objet iterable de paires de clés et de valeurs. Dans ce cas, chaque paire est un personnage et une chaîne d'éléments. L'appel de lamap
opération sur unMap
implique de cartographie sur ces paires,p
est une paire dontp._1
est un personnage, etp._2
est la chaîne associée (sur lequel vous pouvez appelerlength
, comme vous l'avez fait ci-dessus).2) Comment faire idiomatique
Ci-dessus est de savoir comment le faire idiomatique - à l'aide de
groupBy
etmap
. Alternativement, vous pouvez utiliser un immuable la carte et la récursivité sur la longueur de la chaîne pour calculer les fréquences, ou immuable carte et unefoldLeft
.3) des caractéristiques de Performance
Mieux pour référence pour voir les différences.
Voici un couple de microbenchmark pour un très répétitif chaîne (~3GHz iMac, JDK7, Scala 2.10.0 tous les soirs):
Résultats:
Impératif:
$ 103 57 53 58 53 53 53 53 53 53
Combinators:
$ 72 51 63 56 53 52 52 54 53 53
Fois:
$ 163 62 71 62 57 57 57 58 57 57
Notez que la modification de l'impératif de la version à utiliser
withDefaultValue
:apparemment, est terriblement lent en raison de la redirection de chaque
put
appel:withDefaultValue
:$ 133 87 109 106 101 100 101 100 101 101
Conclusion: le boxing et unboxing de caractères dans ce cas est suffisante, de sorte que les différences de performance entre ces approches sont difficiles à observer.
EDIT:
Mise à jour: Vous pouvez utiliser ScalaMeter inline benchmarking en place de la
Benchmark
trait.L'extension de Axel de réponse.
Votre
groupBy
solution est déjà fonctionnelle. Il y a juste un tout petit minuscule correction à ce qui pourrait le rendre plus propre:La Scala alternative à
inject
estfoldLeft
,foldRight
,reduce
,reduceOption
selon la façon dont vous l'utiliser. La façon dont vous avez utiliséinject
en Ruby n'est pas fonctionnel, étant donné que votre solution est basée sur la mutationh
et dans le monde fonctionnelle de la mutabilité est un "no-no". Voici comment vous pouvez faire la solution à proximité de votreinject
mais dans le style fonctionnel en Scala:Évidemment
groupBy
est beaucoup mieux.groupBy(identity).mapValues(_.size)
parce qu'une Chaîne est déjà traitée comme une séquence de caractères; il n'y a pas besoin de convertir avectoChar
Votre exemple sur ruby peut être presque directement traduit à la Scala à l'aide de
foldLeft
et immuableMap
.Ici est l'une des solutions possibles:
En fait, si vous êtes ok avec local de la mutabilité, vous pouvez faire quelque chose comme ceci:
Expression
hash(_) += 1
sera délactosé àc => hash(c) = hash(c) + 1
puis àc => hash.update(c, hash.apply(c) + 1)
Cette solution devrait être plus efficace que fonctionnelle, car il ne créez pas d'intermédiaire collections. Aussi parce que la méthode retourne immuable
collection.Map[Char, Int]
, le résultat sera traitée comme immuable (tant que personne ne sera à effectuer dangereux de passer sur elle).hash.toMap
De départ
Scala 2.13
, nous pouvons utiliser la groupMapReduce méthode qui est (comme son nom l'indique) un équivalent degroupBy
suivie parmapValues
et réduire étape:Ce:
group
s caractères (groupe partie de groupeMapReduce)map
s chaque regroupés de la valeur de l'occurrence de 1 (carte de la partie de groupe deCarteRéduire)reduce
s valeurs au sein d'un groupe de valeurs (_ + _
) en faisant la somme (réduire une partie de groupMapRéduire).C'est une version équivalente réalisée en un seul passage par la séquence de caractères de: