@@ -176,6 +176,12 @@ import scala.language.implicitConversions
176
176
* loop(1, 1)
177
177
* }
178
178
* }}}
179
+ *
180
+ * Note that `mkString` forces evaluation of a `Stream`, but `addString` does
181
+ * not. In both cases, a `Stream` that is or ends in a cycle
182
+ * (e.g. `lazy val s: Stream[Int] = 0 #:: s`) will convert additional trips
183
+ * through the cycle to `...`. Additionally, `addString` will display an
184
+ * un-memoized tail as `?`.
179
185
*
180
186
* @tparam A the type of the elements contained in this stream.
181
187
*
@@ -253,12 +259,22 @@ self =>
253
259
* @note Often we use `Stream`s to represent an infinite set or series. If
254
260
* that's the case for your particular `Stream` then this function will never
255
261
* return and will probably crash the VM with an `OutOfMemory` exception.
262
+ * This function will not hang on a finite cycle, however.
256
263
*
257
264
* @return The fully realized `Stream`.
258
265
*/
259
266
def force : Stream [A ] = {
260
- var these = this
261
- while (! these.isEmpty) these = these.tail
267
+ // Use standard 2x 1x iterator trick for cycle detection ("those" is slow one)
268
+ var these, those = this
269
+ if (! these.isEmpty) these = these.tail
270
+ while (those ne these) {
271
+ if (these.isEmpty) return this
272
+ these = these.tail
273
+ if (these.isEmpty) return this
274
+ these = these.tail
275
+ if (these eq those) return this
276
+ those = those.tail
277
+ }
262
278
this
263
279
}
264
280
@@ -309,9 +325,24 @@ self =>
309
325
310
326
override def toStream : Stream [A ] = this
311
327
312
- override def hasDefiniteSize = {
313
- def loop (s : Stream [A ]): Boolean = s.isEmpty || s.tailDefined && loop(s.tail)
314
- loop(this )
328
+ override def hasDefiniteSize : Boolean = isEmpty || {
329
+ if (! tailDefined) false
330
+ else {
331
+ // Two-iterator trick (2x & 1x speed) for cycle detection.
332
+ var those = this
333
+ var these = tail
334
+ while (those ne these) {
335
+ if (these.isEmpty) return true
336
+ if (! these.tailDefined) return false
337
+ these = these.tail
338
+ if (these.isEmpty) return true
339
+ if (! these.tailDefined) return false
340
+ these = these.tail
341
+ if (those eq these) return false
342
+ those = those.tail
343
+ }
344
+ false // Cycle detected
345
+ }
315
346
}
316
347
317
348
/** Create a new stream which contains all elements of this stream followed by
@@ -690,7 +721,8 @@ self =>
690
721
* `end`. Inside, the string representations of defined elements (w.r.t.
691
722
* the method `toString()`) are separated by the string `sep`. The method will
692
723
* not force evaluation of undefined elements. A tail of such elements will be
693
- * represented by a `"?"` instead.
724
+ * represented by a `"?"` instead. A cyclic stream is represented by a `"..."`
725
+ * at the point where the cycle repeats.
694
726
*
695
727
* @param b The [[collection.mutable.StringBuilder ]] factory to which we need
696
728
* to add the string elements.
@@ -701,16 +733,81 @@ self =>
701
733
* resulting string.
702
734
*/
703
735
override def addString (b : StringBuilder , start : String , sep : String , end : String ): StringBuilder = {
704
- def loop (pre : String , these : Stream [A ]) {
705
- if (these.isEmpty) b append end
706
- else {
707
- b append pre append these.head
708
- if (these.tailDefined) loop(sep, these.tail)
709
- else b append sep append " ?" append end
736
+ b append start
737
+ if (! isEmpty) {
738
+ b append head
739
+ var cursor = this
740
+ var n = 1
741
+ if (cursor.tailDefined) { // If tailDefined, also !isEmpty
742
+ var scout = tail
743
+ if (scout.isEmpty) {
744
+ // Single element. Bail out early.
745
+ b append end
746
+ return b
747
+ }
748
+ if ((cursor ne scout) && scout.tailDefined) {
749
+ cursor = scout
750
+ scout = scout.tail
751
+ // Use 2x 1x iterator trick for cycle detection; slow iterator can add strings
752
+ while ((cursor ne scout) && scout.tailDefined) {
753
+ b append sep append cursor.head
754
+ n += 1
755
+ cursor = cursor.tail
756
+ scout = scout.tail
757
+ if (scout.tailDefined) scout = scout.tail
758
+ }
759
+ }
760
+ if (! scout.tailDefined) { // Not a cycle, scout hit an end
761
+ while (cursor ne scout) {
762
+ b append sep append cursor.head
763
+ n += 1
764
+ cursor = cursor.tail
765
+ }
766
+ }
767
+ else {
768
+ // Cycle.
769
+ // If we have a prefix of length P followed by a cycle of length C,
770
+ // the scout will be at position (P%C) in the cycle when the cursor
771
+ // enters it at P. They'll then collide when the scout advances another
772
+ // C - (P%C) ahead of the cursor.
773
+ // If we run the scout P farther, then it will be at the start of
774
+ // the cycle: (C - (P%C) + (P%C)) == C == 0. So if another runner
775
+ // starts at the beginning of the prefix, they'll collide exactly at
776
+ // the start of the loop.
777
+ var runner = this
778
+ var k = 0
779
+ while (runner ne scout) {
780
+ runner = runner.tail
781
+ scout = scout.tail
782
+ k += 1
783
+ }
784
+ // Now runner and scout are at the beginning of the cycle. Advance
785
+ // cursor, adding to string, until it hits; then we'll have covered
786
+ // everything once. If cursor is already at beginning, we'd better
787
+ // advance one first unless runner didn't go anywhere (in which case
788
+ // we've already looped once).
789
+ if ((cursor eq scout) && (k > 0 )) {
790
+ b append sep append cursor.head
791
+ n += 1
792
+ cursor = cursor.tail
793
+ }
794
+ while (cursor ne scout) {
795
+ b append sep append cursor.head
796
+ n += 1
797
+ cursor = cursor.tail
798
+ }
799
+ // Subtract prefix length from total length for cycle reporting.
800
+ // (Not currently used, but probably a good idea for the future.)
801
+ n -= k
802
+ }
803
+ }
804
+ if (! cursor.isEmpty) {
805
+ // Either undefined or cyclic; we can check with tailDefined
806
+ if (! cursor.tailDefined) b append sep append " ?"
807
+ else b append sep append " ..."
710
808
}
711
809
}
712
- b append start
713
- loop(" " , this )
810
+ b append end
714
811
b
715
812
}
716
813
0 commit comments