Skip to content

Commit 233937f

Browse files
erikvanoostenjulienrf
authored andcommitted
Documentation and tests for IteratorDecorator. (scala#60)
* Documentation and tests for Iterator.intersperse. * Added some missing scala-doc and unit tests for `IteratorDecorator`. Corrected order of arguments to assertEquals.
1 parent 76719f5 commit 233937f

File tree

2 files changed

+177
-23
lines changed

2 files changed

+177
-23
lines changed

src/main/scala/scala/collection/decorators/IteratorDecorator.scala

Lines changed: 90 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,39 @@ package decorators
33

44
import scala.annotation.tailrec
55

6+
/** Enriches Iterator with additional methods.
7+
*
8+
* @define mayNotTerminateInf
9+
* Note: may not terminate for infinite iterators.
10+
* @define consumesIterator
11+
* After calling this method, one should discard the iterator it was called
12+
* on. Using it is undefined and subject to change.
13+
* @define consumesAndProducesIterator
14+
* After calling this method, one should discard the iterator it was called
15+
* on, and use only the iterator that was returned. Using the old iterator
16+
* is undefined, subject to change, and may result in changes to the new
17+
* iterator as well.
18+
* @define pseudoCodeExample
19+
* The `===` operator in this pseudo code stands for 'is equivalent to';
20+
* both sides of the `===` give the same result.
21+
*/
622
class IteratorDecorator[A](val `this`: Iterator[A]) extends AnyVal {
723

24+
/**
25+
* Inserts a separator value between each element.
26+
*
27+
* {{{
28+
* Iterator(1, 2, 3).intersperse(0) === Iterator(1, 0, 2, 0, 3)
29+
* Iterator('a', 'b', 'c').intersperse(',') === Iterator('a', ',', 'b', ',', 'c')
30+
* Iterator('a').intersperse(',') === Iterator('a')
31+
* Iterator().intersperse(',') === Iterator()
32+
* }}}
33+
* $pseudoCodeExample
34+
*
35+
* @param sep the separator value.
36+
* @return The resulting iterator contains all elements from the source iterator, separated by the `sep` value.
37+
* @note Reuse: $consumesAndProducesIterator
38+
*/
839
def intersperse[B >: A](sep: B): Iterator[B] = new Iterator[B] {
940
var intersperseNext = false
1041
override def hasNext = intersperseNext || `this`.hasNext
@@ -15,6 +46,27 @@ class IteratorDecorator[A](val `this`: Iterator[A]) extends AnyVal {
1546
}
1647
}
1748

49+
/**
50+
* Inserts a start value at the start of the iterator, a separator value between each element, and
51+
* an end value at the end of the iterator.
52+
*
53+
* {{{
54+
* Iterator(1, 2, 3).intersperse(-1, 0, 99) === Iterator(-1, 1, 0, 2, 0, 3, 99)
55+
* Iterator('a', 'b', 'c').intersperse('[', ',', ']') === Iterator('[', 'a', ',', 'b', ',', 'c', ']')
56+
* Iterator('a').intersperse('[', ',', ']') === Iterator('[', 'a', ']')
57+
* Iterator().intersperse('[', ',', ']') === Iterator('[', ']')
58+
* }}}
59+
* $pseudoCodeExample
60+
*
61+
* @param start the starting value.
62+
* @param sep the separator value.
63+
* @param end the ending value.
64+
* @return The resulting iterator
65+
* begins with the `start` value and ends with the `end` value.
66+
* Inside, are all elements from the source iterator separated by
67+
* the `sep` value.
68+
* @note Reuse: $consumesAndProducesIterator
69+
*/
1870
def intersperse[B >: A](start: B, sep: B, end: B): Iterator[B] = new Iterator[B] {
1971
var started = false
2072
var finished = false
@@ -41,6 +93,27 @@ class IteratorDecorator[A](val `this`: Iterator[A]) extends AnyVal {
4193
}
4294
}
4395

96+
/**
97+
* Folds elements with combination function `op` until
98+
* all elements have been processed, or `op` returns `None`.
99+
* $mayNotTerminateInf
100+
*
101+
* {{{
102+
* def sumOp(acc: Int, e: Int): Option[Int] = if (e == 4) None else Some(acc + e)
103+
* Iterator.empty.foldSomeLeft(0)(sumOp) === 0
104+
* Iterator(1, 2, 3).foldSomeLeft(0)(sumOp) === 6
105+
* Iterator(1, 2, 3, 4, 5).foldSomeLeft(0)(sumOp) === 6
106+
* }}}
107+
* $pseudoCodeExample
108+
*
109+
* @param z the start value
110+
* @param op the binary operator
111+
* @tparam B the result type of the binary operator
112+
* @return the result of evaluating `op` on the previous result of `op` (or `z` for the first time) and
113+
* elements of the source iterator, stopping when all the elements have been
114+
* iterated or earlier when `op` returns `None`
115+
* @note Reuse: $consumesIterator
116+
*/
44117
def foldSomeLeft[B](z: B)(op: (B, A) => Option[B]): B = {
45118
var result: B = z
46119
while (`this`.hasNext) {
@@ -52,6 +125,10 @@ class IteratorDecorator[A](val `this`: Iterator[A]) extends AnyVal {
52125
result
53126
}
54127

128+
/**
129+
* $mayNotTerminateInf
130+
* @note Reuse: $consumesIterator
131+
*/
55132
def lazyFoldLeft[B](z: B)(op: (B, => A) => B): B = {
56133
var result = z
57134
var finished = false
@@ -66,6 +143,10 @@ class IteratorDecorator[A](val `this`: Iterator[A]) extends AnyVal {
66143
result
67144
}
68145

146+
/**
147+
* $mayNotTerminateInf
148+
* @note Reuse: $consumesIterator
149+
*/
69150
def lazyFoldRight[B](z: B)(op: A => Either[B, B => B]): B = {
70151

71152
def chainEval(x: B, fs: immutable.List[B => B]): B =
@@ -87,26 +168,20 @@ class IteratorDecorator[A](val `this`: Iterator[A]) extends AnyVal {
87168
}
88169

89170
/**
90-
* Constructs an iterator where consecutive elements are accumulated as
91-
* long as the output of f for each element doesn't change.
92-
* <pre>
93-
* Vector(1,2,2,3,3,3,2,2)
94-
* .iterator
95-
* .splitBy(identity)
96-
* .toList
97-
* </pre>
98-
* produces
99-
* <pre>
100-
* List(Seq(1),
101-
* Seq(2,2),
102-
* Seq(3,3,3),
103-
* Seq(2,2))
104-
* </pre>
171+
* Constructs an iterator in which each element is a the sequence of accumulated elements
172+
* from the source iterator that have the same key, where the key is calculated by `f`.
173+
*
174+
* {{{
175+
* Iterator(1,2,2,3,3,3,2,2).splitBy(identity) === Iterator(Seq(1), Seq(2,2), Seq(3,3,3), Seq(2,2))
176+
* Iterator((1,1), (1,2), (2, 3)).splitBy(_._1) === Iterator(Seq((1,1), (1,2)), Seq((2,3)))
177+
* }}}
178+
* $pseudoCodeExample
105179
*
106180
* @param f the function to compute a key for an element
107181
* @tparam K the type of the computed key
108182
* @return an iterator of sequences of the consecutive elements with the
109183
* same key in the original iterator
184+
* @note Reuse: $consumesIterator
110185
*/
111186
def splitBy[K](f: A => K): Iterator[immutable.Seq[A]] =
112187
new AbstractIterator[immutable.Seq[A]] {

src/test/scala/scala/collection/decorators/IteratorDecoratorTest.scala

Lines changed: 87 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,36 +6,115 @@ import org.junit.{Assert, Test}
66
import scala.util.Try
77

88
class IteratorDecoratorTest {
9+
@Test
10+
def intersperseShouldIntersperseASeparator(): Unit = {
11+
Assert.assertEquals(Seq(1, 0, 2, 0, 3), Iterator(1, 2, 3).intersperse(0).toSeq)
12+
Assert.assertEquals(Seq('a', ',', 'b', ',', 'c'), Iterator('a', 'b', 'c').intersperse(',').toSeq)
13+
Assert.assertEquals(Seq('a'), Iterator('a').intersperse(',').toSeq)
14+
Assert.assertEquals(Seq.empty, Iterator().intersperse(',').toSeq)
15+
}
16+
17+
@Test
18+
def intersperseShouldIntersperseASeparatorAndInsertStartAndEnd(): Unit = {
19+
Assert.assertEquals(Seq(-1, 1, 0, 2, 0, 3, 99), Iterator(1, 2, 3).intersperse(-1, 0, 99).toSeq)
20+
Assert.assertEquals(Seq('[', 'a', ',', 'b', ',', 'c', ']'),
21+
Iterator('a', 'b', 'c').intersperse('[', ',', ']').toSeq)
22+
Assert.assertEquals(Seq('[', 'a', ']'), Iterator('a').intersperse('[', ',', ']').toSeq)
23+
Assert.assertEquals(Seq('[', ']'), Iterator().intersperse('[', ',', ']').toSeq)
24+
}
25+
26+
@Test
27+
def foldSomeLeftShouldFold(): Unit = {
28+
def sumOp(acc: Int, e: Int): Option[Int] = if (e == 4) None else Some(acc + e)
29+
Assert.assertEquals(0, Iterator().foldSomeLeft(0)(sumOp))
30+
Assert.assertEquals(6, Iterator(1, 2, 3).foldSomeLeft(0)(sumOp))
31+
Assert.assertEquals(6, Iterator(1, 2, 3, 4, 5).foldSomeLeft(0)(sumOp))
32+
Assert.assertEquals(0, Iterator(4, 5).foldSomeLeft(0)(sumOp))
33+
}
34+
35+
@Test
36+
def lazyFoldLeftShouldFold(): Unit = {
37+
// Notice how sumOp doesn't evaluate `e` under some conditions.
38+
def sumOp(acc: Int, e: => Int): Int = if (acc >= 5) acc else acc + e
39+
Assert.assertEquals(0, Iterator().lazyFoldLeft(0)(sumOp))
40+
Assert.assertEquals(3, Iterator(1, 1, 1).lazyFoldLeft(0)(sumOp))
41+
Assert.assertEquals(6, Iterator(1, 2, 3, 4, 5).lazyFoldLeft(0)(sumOp))
42+
Assert.assertEquals(5, Iterator(1, 1, 1, 1, 1, 1, 1, 1).lazyFoldLeft(0)(sumOp))
43+
Assert.assertEquals(9, Iterator(4, 5).lazyFoldLeft(0)(sumOp))
44+
Assert.assertEquals(9, Iterator(4, 5, 1).lazyFoldLeft(0)(sumOp))
45+
Assert.assertEquals(10, Iterator(10, 20, 30).lazyFoldLeft(0)(sumOp))
46+
}
47+
48+
@Test
49+
def lazyFoldLeftShouldFoldWeirdEdgeCases(): Unit = {
50+
// `delayedSumOp` doesn't return `acc`, causing a delayed stop of the iteration.
51+
def delayedSumOp(acc: Int, e: => Int): Int = if (acc >= 5) 5 else acc + e
52+
Assert.assertEquals(0, Iterator().lazyFoldLeft(0)(delayedSumOp))
53+
Assert.assertEquals(3, Iterator(1, 1, 1).lazyFoldLeft(0)(delayedSumOp))
54+
Assert.assertEquals(9, Iterator(4, 5).lazyFoldLeft(0)(delayedSumOp))
55+
Assert.assertEquals(5, Iterator(4, 5, 1).lazyFoldLeft(0)(delayedSumOp))
56+
Assert.assertEquals(5, Iterator(6, 1).lazyFoldLeft(0)(delayedSumOp))
57+
58+
// `alwaysGrowingSumOp` returns a new value every time, causing no stop in the iteration.
59+
def alwaysGrowingSumOp(acc: Int, e: => Int): Int = if (acc >= 5) acc + 1 else acc + e
60+
Assert.assertEquals(0, Iterator().lazyFoldLeft(0)(alwaysGrowingSumOp))
61+
Assert.assertEquals(3, Iterator(1, 1, 1).lazyFoldLeft(0)(alwaysGrowingSumOp))
62+
Assert.assertEquals(9, Iterator(5, 10, 10, 10, 10).lazyFoldLeft(0)(alwaysGrowingSumOp))
63+
Assert.assertEquals(9, Iterator(4, 5).lazyFoldLeft(0)(alwaysGrowingSumOp))
64+
Assert.assertEquals(10, Iterator(4, 5, 20).lazyFoldLeft(0)(alwaysGrowingSumOp))
65+
}
66+
67+
@Test
68+
def lazyFoldRightShouldFold(): Unit = {
69+
def sumOp(acc: Int): Either[Int, Int => Int] = if (acc >= 5) Left(acc) else Right(acc + _)
70+
Assert.assertEquals(0, Iterator().lazyFoldRight(0)(sumOp))
71+
Assert.assertEquals(3, Iterator(1, 1, 1).lazyFoldRight(0)(sumOp))
72+
Assert.assertEquals(15, Iterator(1, 2, 3, 4, 5).lazyFoldRight(0)(sumOp))
73+
Assert.assertEquals(8, Iterator(1, 1, 1, 1, 1, 1, 1, 1).lazyFoldRight(0)(sumOp))
74+
Assert.assertEquals(5, Iterator(5, 4).lazyFoldRight(0)(sumOp))
75+
Assert.assertEquals(6, Iterator(1, 5, 4).lazyFoldRight(0)(sumOp))
76+
Assert.assertEquals(32, Iterator(32, 21, 10).lazyFoldRight(0)(sumOp))
77+
}
78+
979
@Test
1080
def splitByShouldHonorEmptyIterator(): Unit = {
1181
val groupedIterator = Iterator.empty.splitBy(identity)
1282
Assert.assertFalse(groupedIterator.hasNext)
13-
Assert.assertEquals(Try(groupedIterator.next).toString, Try(Iterator.empty.next()).toString)
83+
Assert.assertEquals(Try(Iterator.empty.next()).toString, Try(groupedIterator.next).toString)
1484
}
1585

1686
@Test
1787
def splitByShouldReturnIteratorOfSingleSeqWhenAllElHaveTheSameKey(): Unit = {
1888
val value = Vector("1", "1", "1")
1989
val groupedIterator = value.iterator.splitBy(identity)
2090
Assert.assertTrue(groupedIterator.hasNext)
21-
Assert.assertEquals(groupedIterator.next.toVector, value)
91+
Assert.assertEquals(value, groupedIterator.next.toVector)
2292
Assert.assertFalse(groupedIterator.hasNext)
23-
Assert.assertEquals(Try(groupedIterator.next).toString, Try(Iterator.empty.next()).toString)
93+
Assert.assertEquals(Try(Iterator.empty.next()).toString, Try(groupedIterator.next).toString)
2494
}
2595

2696
@Test
2797
def splitByShouldReturnIteratorOfSeqOfConsecutiveElementsWithTheSameKey(): Unit = {
2898
val value = Vector("1", "2", "2", "3", "3", "3", "2", "2")
2999
val groupedIterator = value.iterator.splitBy(identity)
30100
Assert.assertTrue(groupedIterator.hasNext)
31-
Assert.assertEquals(groupedIterator.next.toVector, Vector("1"))
101+
Assert.assertEquals(Vector("1"), groupedIterator.next.toVector)
32102
Assert.assertTrue(groupedIterator.hasNext)
33-
Assert.assertEquals(groupedIterator.next.toVector, Vector("2", "2"))
103+
Assert.assertEquals(Vector("2", "2"), groupedIterator.next.toVector)
34104
Assert.assertTrue(groupedIterator.hasNext)
35-
Assert.assertEquals(groupedIterator.next.toVector, Vector("3", "3", "3"))
105+
Assert.assertEquals(Vector("3", "3", "3"), groupedIterator.next.toVector)
36106
Assert.assertTrue(groupedIterator.hasNext)
37-
Assert.assertEquals(groupedIterator.next.toVector, Vector("2", "2"))
107+
Assert.assertEquals(Vector("2", "2"), groupedIterator.next.toVector)
38108
Assert.assertFalse(groupedIterator.hasNext)
39-
Assert.assertEquals(Try(groupedIterator.next).toString, Try(Iterator.empty.next()).toString)
109+
Assert.assertEquals(Try(Iterator.empty.next()).toString, Try(groupedIterator.next).toString)
110+
}
111+
112+
@Test
113+
def splitByShouldSplitByFunction(): Unit = {
114+
Assert.assertEquals(Seq(Seq((1,1), (1,2)), Seq((2,3))), Iterator((1,1), (1,2), (2,3)).splitBy(_._1).toSeq)
115+
Assert.assertEquals(
116+
Seq(Seq((1,1), (1,2)), Seq((2,3)), Seq((1,4))),
117+
Iterator((1,1), (1,2), (2,3), (1,4)).splitBy(_._1).toSeq
118+
)
40119
}
41120
}

0 commit comments

Comments
 (0)