Disons que j'ai une fonction, par exemple l'ancien favori
def factorial(n:Int) = (BigInt(1) /: (1 to n)) (_*_)
Maintenant, je veux trouver la plus grande valeur de n
pour laquelle factorial(n)
rentre dans un Long. je pourrais faire
(1 to 100) takeWhile (factorial(_) <= Long.MaxValue) last
Cela fonctionne, mais le 100 est un nombre arbitraire; ce que je veux vraiment sur le côté gauche est un flux infini qui continue de générer des nombres plus élevés jusqu'à ce que la condition takeWhile
soit remplie.
Je suis venu avec
val s = Stream.continually(1).zipWithIndex.map(p => p._1 + p._2)
mais y a-t-il une meilleure façon?
(Je suis également conscient que je pourrais obtenir une solution récursivement mais ce n'est pas ce que je recherche.)
Stream.from(1)
crée un flux à partir de 1 et incrémenté de 1. Tout est dans le API docs .
Vous pouvez également utiliser un Iterator
au lieu d'un Stream
. Stream
conserve les références de toutes les valeurs calculées. Donc, si vous prévoyez de visiter chaque valeur une seule fois, un itérateur est une approche plus efficace. L'inconvénient de l'itérateur est cependant sa mutabilité.
Il existe quelques méthodes pratiques pour créer des Iterator
s définies sur son objet compagnon .
Malheureusement, je ne connais aucun moyen court (pris en charge par la bibliothèque) de réaliser quelque chose comme
Stream.from(1) takeWhile (factorial(_) <= Long.MaxValue) last
L'approche que je prends pour avancer un Iterator
pour un certain nombre d'éléments est drop(n: Int)
ou dropWhile
:
Iterator.from(1).dropWhile( factorial(_) <= Long.MaxValue).next - 1
Le - 1
Fonctionne dans ce but particulier mais n'est pas une solution générale. Mais cela ne devrait pas poser de problème d'implémenter une méthode last
sur un Iterator
en utilisant pimp ma bibliothèque. Le problème est que le dernier élément d'un Iterator infini pourrait être problématique. Il devrait donc être implémenté comme une méthode comme lastWith
intégrant le takeWhile
.
Une solution de contournement laide peut être effectuée en utilisant sliding
, qui est implémenté pour Iterator
:
scala> Iterator.from(1).sliding(2).dropWhile(_.tail.head < 10).next.head
res12: Int = 9
comme l'a souligné @ziggystar, Streams
conserve la liste des valeurs précédemment calculées en mémoire, donc l'utilisation de Iterator
est une grande amélioration.
pour améliorer encore la réponse, je dirais que les "flux infinis" sont généralement calculés (ou peuvent être calculés) sur la base de valeurs précalculées. si tel est le cas (et c'est certainement le cas dans votre flux factoriel), je suggère d'utiliser Iterator.iterate
au lieu.
ressemblerait à peu près à ceci:
scala> val it = Iterator.iterate((1,BigInt(1))){case (i,f) => (i+1,f*(i+1))}
it: Iterator[(Int, scala.math.BigInt)] = non-empty iterator
alors, vous pourriez faire quelque chose comme:
scala> it.find(_._2 >= Long.MaxValue).map(_._1).get - 1
res0: Int = 22
ou utilisez la solution @ziggystar sliding
...
un autre exemple simple qui me vient à l'esprit serait le nombre de fibonacci:
scala> val it = Iterator.iterate((1,1)){case (a,b) => (b,a+b)}.map(_._1)
it: Iterator[Int] = non-empty iterator
dans ces cas, vous ne calculez pas votre nouvel élément à partir de zéro à chaque fois, mais faites plutôt un travail O(1)) pour chaque nouvel élément, ce qui améliorerait encore plus votre temps de fonctionnement.
La fonction "factorielle" d'origine n'est pas optimale, car les factorielles sont calculées à partir de zéro à chaque fois. L'implémentation la plus simple/immuable utilisant la mémorisation est comme ceci:
val f : Stream[BigInt] = 1 #:: (Stream.from(1) Zip f).map { case (x,y) => x * y }
Et maintenant, la réponse peut être calculée comme ceci:
println( "count: " + (f takeWhile (_<Long.MaxValue)).length )
La variante suivante ne teste pas le courant, mais l'entier suivant, pour trouver et retourner le dernier nombre valide:
Iterator.from(1).find(i => factorial(i+1) > Long.MaxValue).get
En utilisant .get
ici est acceptable, car find
sur une séquence infinie ne renverra jamais None
.