Skip to content

Commit aa605f7

Browse files
authored
Merge pull request #4 from jeantil/iterator-group_until_changed
adds splitBy extension method on scala.collection.Iterator
2 parents 08562cf + c57a652 commit aa605f7

File tree

4 files changed

+171
-1
lines changed

4 files changed

+171
-1
lines changed

src/main/scala/scala/collection/decorators/IterableDecorator.scala

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,28 @@ class IterableDecorator[C, I <: IsIterable[C]](coll: C)(implicit val it: I) {
4848
def lazyFoldRight[B](z: B)(op: it.A => Either[B, B => B]): B =
4949
it(coll).iterator.lazyFoldRight(z)(op)
5050

51+
52+
/**
53+
* Constructs a collection where consecutive elements are accumulated as
54+
* long as the output of f for each element doesn't change.
55+
* <pre>
56+
* Vector(1,2,2,3,3,3,2,2)
57+
* .splitBy(identity)
58+
* </pre>
59+
* produces
60+
* <pre>
61+
* Vector(Vector(1),
62+
* Vector(2,2),
63+
* Vector(3,3,3),
64+
* Vector(2,2))
65+
* </pre>
66+
*
67+
* @param f the function to compute a key for an element
68+
* @tparam K the type of the computed key
69+
* @return a collection of collections of the consecutive elements with the
70+
* same key in the original collection
71+
*/
72+
def splitBy[K, CC1, CC2](f: it.A => K)(implicit bf: BuildFrom[C, it.A, CC1], bff: BuildFrom[C, CC1, CC2]): CC2 = {
73+
bff.fromSpecific(coll)(it(coll).iterator.splitBy(f).map(bf.fromSpecific(coll)))
74+
}
5175
}

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

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,4 +86,68 @@ class IteratorDecorator[A](val `this`: Iterator[A]) extends AnyVal {
8686
loop(immutable.List.empty)
8787
}
8888

89+
/**
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>
105+
*
106+
* @param f the function to compute a key for an element
107+
* @tparam K the type of the computed key
108+
* @return an iterator of sequences of the consecutive elements with the
109+
* same key in the original iterator
110+
*/
111+
def splitBy[K](f: A => K): Iterator[immutable.Seq[A]] =
112+
new AbstractIterator[immutable.Seq[A]] {
113+
private var hd: A = _
114+
private var hdKey: K = _
115+
private var hdDefined: Boolean = false
116+
117+
override def hasNext: Boolean = hdDefined || `this`.hasNext
118+
119+
override def next(): immutable.Seq[A] = {
120+
if (hasNext) {
121+
val seq = Vector.newBuilder[A]
122+
if (hdDefined) {
123+
seq += hd
124+
} else {
125+
val init = `this`.next()
126+
hd = init
127+
hdKey = f(init)
128+
hdDefined = true
129+
seq += init
130+
}
131+
var hadSameKey = true
132+
while (`this`.hasNext && hadSameKey) {
133+
val el = `this`.next()
134+
hdDefined = true
135+
val key = f(el)
136+
if (key == hdKey) {
137+
seq += el
138+
} else {
139+
hadSameKey = false
140+
hdKey = key
141+
hd = el
142+
}
143+
}
144+
if (hadSameKey) {
145+
hdDefined = false
146+
}
147+
seq.result()
148+
} else {
149+
Iterator.empty.next()
150+
}
151+
}
152+
}
89153
}

src/test/scala/scala/collection/decorators/IterableDecoratorTest.scala

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ package scala.collection
22
package decorators
33

44
import org.junit.{Assert, Test}
5-
import scala.collection.immutable.{LazyList, List, Range, Map}
5+
6+
import scala.collection.immutable.{LazyList, List, Map, Range}
67

78
class IterableDecoratorTest {
89

@@ -54,4 +55,44 @@ class IterableDecoratorTest {
5455
Assert.assertEquals(2, result2)
5556
}
5657

58+
@Test
59+
def splitByShouldHonorEmptyIterator(): Unit = {
60+
val split = Vector.empty[Int].splitBy(identity)
61+
Assert.assertEquals(Vector.empty, split)
62+
}
63+
64+
@Test
65+
def splitByShouldReturnSingleSeqWhenSingleElement(): Unit = {
66+
val value = Vector("1")
67+
val split = value.splitBy(identity)
68+
Assert.assertEquals(Vector(value), split)
69+
}
70+
71+
@Test
72+
def splitByShouldReturnSingleSeqWhenAllElHaveTheSameKey(): Unit = {
73+
val value = Vector("1", "1", "1")
74+
val split = value.splitBy(identity)
75+
Assert.assertEquals(Vector(value), split)
76+
}
77+
78+
@Test
79+
def splitByShouldReturnVectorOfVectorOrConsecutiveElementsWithTheSameKey(): Unit = {
80+
val value = Vector("1", "2", "2", "3", "3", "3", "2", "2")
81+
val split: Vector[Vector[String]] = value.splitBy(identity)
82+
Assert.assertEquals(Vector(Vector("1"), Vector("2", "2"), Vector("3", "3", "3"), Vector("2", "2")), split)
83+
}
84+
85+
@Test
86+
def splitByShouldReturnListOfListOfConsecutiveElementsWithTheSameKey(): Unit = {
87+
val value = List("1", "2", "2", "3", "3", "3", "2", "2")
88+
val split: List[List[String]] = value.splitBy(identity)
89+
Assert.assertEquals(List(List("1"), List("2", "2"), List("3", "3", "3"), List("2", "2")), split)
90+
}
91+
92+
@Test
93+
def splitByShouldReturnSetOfSetOfConsecutiveElementsWithTheSameKey(): Unit = {
94+
val value = Set("1", "2", "2", "3", "3", "3", "2", "2")
95+
val split: Set[Set[String]] = value.splitBy(identity)
96+
Assert.assertEquals(Set(Set("1"), Set("2"), Set("3")), split)
97+
}
5798
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package scala.collection
2+
package decorators
3+
4+
import org.junit.{Assert, Test}
5+
6+
import scala.util.Try
7+
8+
class IteratorDecoratorTest {
9+
@Test
10+
def splitByShouldHonorEmptyIterator(): Unit = {
11+
val groupedIterator = Iterator.empty.splitBy(identity)
12+
Assert.assertFalse(groupedIterator.hasNext)
13+
Assert.assertEquals(Try(groupedIterator.next).toString, Try(Iterator.empty.next()).toString)
14+
}
15+
16+
@Test
17+
def splitByShouldReturnIteratorOfSingleSeqWhenAllElHaveTheSameKey(): Unit = {
18+
val value = Vector("1", "1", "1")
19+
val groupedIterator = value.iterator.splitBy(identity)
20+
Assert.assertTrue(groupedIterator.hasNext)
21+
Assert.assertEquals(groupedIterator.next.toVector, value)
22+
Assert.assertFalse(groupedIterator.hasNext)
23+
Assert.assertEquals(Try(groupedIterator.next).toString, Try(Iterator.empty.next()).toString)
24+
}
25+
26+
@Test
27+
def splitByShouldReturnIteratorOfSeqOfConsecutiveElementsWithTheSameKey(): Unit = {
28+
val value = Vector("1", "2", "2", "3", "3", "3", "2", "2")
29+
val groupedIterator = value.iterator.splitBy(identity)
30+
Assert.assertTrue(groupedIterator.hasNext)
31+
Assert.assertEquals(groupedIterator.next.toVector, Vector("1"))
32+
Assert.assertTrue(groupedIterator.hasNext)
33+
Assert.assertEquals(groupedIterator.next.toVector, Vector("2", "2"))
34+
Assert.assertTrue(groupedIterator.hasNext)
35+
Assert.assertEquals(groupedIterator.next.toVector, Vector("3", "3", "3"))
36+
Assert.assertTrue(groupedIterator.hasNext)
37+
Assert.assertEquals(groupedIterator.next.toVector, Vector("2", "2"))
38+
Assert.assertFalse(groupedIterator.hasNext)
39+
Assert.assertEquals(Try(groupedIterator.next).toString, Try(Iterator.empty.next()).toString)
40+
}
41+
}

0 commit comments

Comments
 (0)