-
Notifications
You must be signed in to change notification settings - Fork 18
add cycle
method to LazyList
#17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
49 changes: 49 additions & 0 deletions
49
src/main/scala/scala/collection/immutable/next/package.scala
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
/* | ||
* Scala (https://www.scala-lang.org) | ||
* | ||
* Copyright EPFL and Lightbend, Inc. | ||
* | ||
* Licensed under Apache License 2.0 | ||
* (http://www.apache.org/licenses/LICENSE-2.0). | ||
* | ||
* See the NOTICE file distributed with this work for | ||
* additional information regarding copyright ownership. | ||
*/ | ||
|
||
package scala.collection.immutable | ||
|
||
package object next { | ||
|
||
implicit class NextLazyListExtensions[T](private val ll: LazyList[T]) extends AnyVal { | ||
/** | ||
* When called on a finite `LazyList`, returns a circular structure | ||
* that endlessly repeats the elements in the input. | ||
* The result is a true cycle occupying only constant memory. | ||
* | ||
* Does not force the input list (not even its empty-or-not status). | ||
* | ||
* Safe to call on unbounded input, but in that case the result is not a cycle | ||
* (not even if the input was). | ||
* | ||
* Note that some `LazyList` methods preserve cyclicality and others do not. | ||
* So for example the `tail` of a cycle is still a cycle, but `map` and `filter` | ||
* on a cycle do not return cycles. | ||
*/ | ||
def cycle: LazyList[T] = | ||
// case 1: the input is already known to be empty | ||
// (the test can be changed to ll.knownIsEmpty when this code moves to stdlib) | ||
if (ll.knownSize == 0) LazyList.empty | ||
// we don't want to force the input's empty-or-not status until we must. | ||
// `LazyList.empty #:::` accomplishes that delay | ||
else LazyList.empty #::: { | ||
// case 2: the input is later discovered to be empty | ||
if (ll.isEmpty) LazyList.empty | ||
else { | ||
// case 3: non-empty | ||
lazy val result: LazyList[T] = ll #::: result | ||
result | ||
} | ||
} | ||
} | ||
|
||
} |
120 changes: 120 additions & 0 deletions
120
src/test/scala/scala/collection/immutable/TestLazyListExtensions.scala
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
/* | ||
* Scala (https://www.scala-lang.org) | ||
* | ||
* Copyright EPFL and Lightbend, Inc. | ||
* | ||
* Licensed under Apache License 2.0 | ||
* (http://www.apache.org/licenses/LICENSE-2.0). | ||
* | ||
* See the NOTICE file distributed with this work for | ||
* additional information regarding copyright ownership. | ||
*/ | ||
|
||
package scala.collection.immutable | ||
|
||
import org.junit.Assert._ | ||
import org.junit.Test | ||
|
||
import next._ | ||
|
||
class TestLazyListExtensions { | ||
|
||
// This method will *not* terminate for non-cyclic infinite-sized collections. | ||
// (It's kind of nasty to have tests whose failure mode is to hang, but I don't | ||
// see an obvious alternative that doesn't involve copying code from LazyList. | ||
// Perhaps this could be improved at the time this all gets merged into stdlib.) | ||
def assertConstantMemory[T](xs: LazyList[T]): Unit = | ||
// `force` does cycle detection, so if this terminates, the collection is | ||
// either finite or a cycle | ||
xs.force | ||
|
||
@Test | ||
def cycleEmpty1(): Unit = { | ||
val xs = LazyList.empty // realized | ||
val cyc = xs.cycle | ||
assertTrue(cyc.isEmpty) | ||
assertTrue(cyc.size == 0) | ||
assertEquals(Nil, cyc.toList) | ||
} | ||
@Test | ||
def cycleEmpty2(): Unit = { | ||
val xs = LazyList.empty #::: LazyList.empty // not realized | ||
assertEquals(-1, xs.knownSize) // double-check it's not realized | ||
val cyc = xs.cycle | ||
assertTrue(cyc.isEmpty) | ||
assertTrue(cyc.size == 0) | ||
assertEquals(Nil, cyc.toList) | ||
} | ||
@Test | ||
def cycleNonEmpty(): Unit = { | ||
val xs = LazyList(1, 2, 3) | ||
val cyc = xs.cycle | ||
assertFalse(cyc.isEmpty) | ||
assertConstantMemory(cyc) | ||
assertEquals(LazyList(1, 2, 3, 1, 2, 3, 1, 2), cyc.take(8)) | ||
} | ||
@Test | ||
def cycleToString(): Unit = { | ||
assertEquals("LazyList()", | ||
LazyList.empty.cycle.toString) | ||
assertEquals("LazyList(<not computed>)", | ||
LazyList(1, 2, 3).cycle.toString) | ||
// note cycle detection here! | ||
assertEquals("LazyList(1, 2, 3, <cycle>)", | ||
LazyList(1, 2, 3).cycle.force.toString) | ||
} | ||
@Test | ||
def cycleRepeats(): Unit = { | ||
val xs = LazyList(1, 2, 3) | ||
val cyc = xs.cycle | ||
assertFalse(cyc.isEmpty) | ||
assertEquals(LazyList(1, 2, 3, 1, 2, 3, 1, 2), cyc.take(8)) | ||
} | ||
@Test | ||
def cycleConstantMemory1(): Unit = { | ||
val xs = LazyList(1, 2, 3) | ||
val cyc = xs.cycle | ||
assertTrue(cyc.tail eq cyc.tail.tail.tail.tail) | ||
assertTrue(cyc.tail.tail eq cyc.drop(4).tail) | ||
assertTrue(cyc.tail eq cyc.drop(3).tail) | ||
} | ||
@Test | ||
def cycleConstantMemory2(): Unit = { | ||
var counter = 0 | ||
def count(): Int = { counter += 1; counter } | ||
val xs = count() #:: count() #:: count() #:: LazyList.empty | ||
val cyc = xs.cycle | ||
assertEquals(0, counter) | ||
assertEquals(10, cyc.take(10).size) | ||
assertEquals(3, counter) | ||
} | ||
@Test | ||
def cycleConstantMemory3(): Unit = { | ||
val xs = LazyList(1, 2, 3) | ||
val cyc = xs.cycle | ||
assertConstantMemory(cyc) | ||
assertConstantMemory(cyc.tail) | ||
assertConstantMemory(cyc.tail.tail) | ||
assertConstantMemory(cyc.tail.tail.tail) | ||
assertConstantMemory(cyc.tail.tail.tail.tail) | ||
assertConstantMemory(cyc.drop(1)) | ||
assertConstantMemory(cyc.drop(10)) | ||
} | ||
@Test | ||
def cycleUnbounded(): Unit = { | ||
val xs = LazyList.from(1) | ||
val cyc = xs.cycle | ||
assertEquals(LazyList(1, 2, 3), cyc.take(3)) | ||
} | ||
@Test | ||
def cycleSecondCallIsSafeButNotIdempotent(): Unit = { | ||
val xs = LazyList(1, 2, 3) | ||
// this is safe to do | ||
val twice = xs.cycle.cycle | ||
// and the contents are as expected | ||
assertEquals(LazyList(1, 2, 3, 1, 2, 3, 1, 2), twice.take(8)) | ||
// but the result is not a cycle. it might be nice if it were, but oh well. | ||
// testing the existing behavior. | ||
assertFalse(twice.tail eq twice.tail.tail.tail.tail) | ||
} | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.