web-dev-qa-db-fra.com

Flux infinis en Scala

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.)

43
Luigi Plinge
Stream.from(1)

crée un flux à partir de 1 et incrémenté de 1. Tout est dans le API docs .

122
Kim Stebel

Une solution utilisant des itérateurs

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 Iterators définies sur son objet compagnon .

Éditer

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
30
ziggystar

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.

4
gilad hoch

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 )
1
Duckki

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.

0
cubic lettuce