Nous allons discuter d'une erreur dans la question:
Donc, si 99% des développeurs Java écrire du code plus efficace que 99% de
Scala développeurs, c'est un énorme obstacle à la croix pour plus de Scala
l'adoption. Est-il un moyen de sortir de ce piège?
C'est présumée, avec absolument aucune preuve de la sauvegarder. Si la valeur est false, la question est discutable.
Est-il prouvé le contraire? Eh bien, nous allons examiner la question elle-même, il ne faut pas prouver quoi que ce soit, mais montre les choses ne sont pas claires.
Totalement non-Scala idiomatiques, non fonctionnel, non concis, mais c'est
très efficace. Il parcourt la liste qu'une seule fois!
Des quatre revendications dans la première phrase, les trois premiers sont le vrai, et le quatrième, comme indiqué par utilisateur inconnu, est faux! Et pourquoi c'est faux? Parce que, contrairement à ce que la deuxième phrase unis, il parcourt la liste plus d'une fois.
Le code appelle les méthodes suivantes sur elle:
res.append(max)
res.append(x)
et
res.toList
Considérons tout d'abord append
.
append
prend un vararg paramètre. Cela signifie que max
et x
sont d'abord encapsulé dans une séquence d'un certain type (un WrappedArray
, en fait), puis est passé en paramètre. Une meilleure méthode aurait été +=
.
Ok, append
des appels ++=
, qui délègue à +=
. Mais, d'abord, il appelle ensureSize
, ce qui est la seconde erreur (+=
des appels que trop -- ++=
seulement optimise que pour plusieurs éléments). Parce qu'un Array
a une taille fixe de la collection, ce qui signifie que, à chaque redimensionnement, l'ensemble de l' Array
doit être copié!
Considérons donc cette. Lorsque vous redimensionnez, Java première efface la mémoire en stockant 0 dans chaque élément, puis de la Scala de copies de chaque élément du tableau précédent sur le nouveau tableau. Depuis taille double à chaque fois, ce qui se passe log(n) fois, avec le nombre d'éléments copiés augmente chaque fois qu'il se passe.
Prenons par exemple n = 16. Il le fait à quatre reprises, la copie 1, 2, 4 et 8 éléments respectivement. Depuis Java a pour effacer chacun de ces tableaux, et chaque élément doit être lu et écrit, chaque élément copié représente 4 traversals d'un élément. L'ajout de tout ce que nous avons (n - 1) * 4, ou, en gros, 4 traversals de la liste complète. Si vous comptez lire et d'écrire comme un seul passage, que les gens sont souvent, à tort, de faire, alors c'est encore trois traversals.
On peut améliorer cette situation par l'initialisation de l' ArrayBuffer
avec une taille initiale égale à la liste de lecture, moins un, puisque nous allons être jeter un seul élément. Pour obtenir cette taille, nous avons besoin de la traversée de la liste une fois, même si.
Maintenant, nous allons examiner toList
. Pour le dire simplement, il traverse l'ensemble de la liste pour créer une nouvelle liste.
Donc, nous avons 1 traversée de l'algorithme, 3 ou 4 traversals pour redimensionner, et 1 autre de la traversée pour toList
. C'est 4 ou 5 traversals.
L'algorithme original est un peu difficile à analyser, car take
, drop
et :::
parcourir un nombre variable d'éléments. Ajouter tous ensemble, cependant, il ne l'équivalent de 3 traversals. Si splitAt
a été utilisé, il serait réduit à 2 traversals. Avec plus de 2 traversals pour obtenir le maximum, on obtient 5 traversals -- le même numéro que le non-fonctionnels, non-concise de l'algorithme!
Donc, nous allons envisager des améliorations.
Sur l'impératif de l'algorithme, si l'on utilise ListBuffer
et +=
, toutes les méthodes sont constante de temps, ce qui le réduit à une seule traversée.
Sur le fonctionnel de l'algorithme, il pourrait être réécrit comme suit:
val max = xs.max
val (before, _ :: after) = xs span (max !=)
before ::: after
Qui le réduit à un pire cas de trois traversals. Bien sûr, il existe d'autres alternatives présenté, sur la base de la récursivité ou se coucher, que de le résoudre en une seule traversée.
Et, le plus intéressant de tous, de tous ces algorithmes sont O(n)
, et le seul qui presque engagées (accidentellement) dans le pire de la complexité a été l'impératif d'un (à cause de la matrice de copie). D'autre part, le cache des caractéristiques de l'impératif, on pourrait bien le rendre plus rapide, car les données sont contigus en mémoire. Qui, cependant, est indépendants de la grande-Oh ou fonctionnelle vs impératif, et c'est juste une question de structures de données qui ont été choisis.
Donc, si nous avons fait aller à la difficulté de l'analyse comparative, l'analyse des résultats, compte tenu de la performance des méthodes, et à examiner les possibilités d'optimisation, alors on peut trouver plus rapidement des façons de le faire d'une manière impérative que de manière fonctionnelle.
Mais tout cet effort est très différent de dire la moyenne programmeur Java code sera plus rapide que la moyenne de la Scala programmeur code -- si la question est un exemple, qui est tout simplement faux. Et même sans tenir compte de la question, nous l'avons vu aucune preuve que la prémisse fondamentale de la question qui est vrai.
MODIFIER
Tout d'abord, permettez-moi de reformuler mon point de vue, car il semble que je n'étais pas clair. Mon point est que le code de la moyenne Java programmeur écrit peut sembler plus efficace, mais ne l'est pas. Ou, dans l'autre sens, Java traditionnelle de style n'est pas le gain de performance -- seulement travailler dur n', que ce soit en Java ou Scala.
Ensuite, j'ai un indice de référence et les résultats, y compris presque toutes les solutions proposées. Deux points intéressants à ce sujet:
En fonction de la taille de la liste, la création d'objets peuvent avoir un impact plus important que de multiples traversals de la liste. L'original du code fonctionnel par Adrian prend avantage du fait que les listes sont persistantes des structures de données en ne copiant pas les éléments à droite de l'élément maximal à tous. Si un Vector
a été utilisé au lieu de cela, les deux côtés gauche et droit serait essentiellement le même, ce qui pourrait conduire à de meilleures performances.
Même si l'utilisateur inconnu et paradigmatique similaires récursive des solutions, paradigmatique est beaucoup plus rapide. La raison en est qu'il évite de pattern matching. La correspondance de motif peut être vraiment lent.
L'indice de référence code est ici, et les résultats sont ici.