Skip to content
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

New circular Seq operations: rotateRight, rotateLeft, startAt #168

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions src/main/scala/scala/collection/decorators/SeqDecorator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,62 @@ class SeqDecorator[C, S <: IsSeq[C]](coll: C)(implicit val seq: S) {
*/
def replaced[B >: seq.A, That](elem: B, replacement: B)(implicit bf: BuildFrom[C, B, That]): That =
bf.fromSpecific(coll)(new collection.View.Map(seq(coll), (a: seq.A) => if (a == elem) replacement else a))

/**
* for improved readability, the integer index of an element in a Seq
*/
type Index = Int

/**
* the integer index of an element in a circular Seq, any value is valid
*/
type IndexO = Int

private def index(i: IndexO): Index =
java.lang.Math.floorMod(i, seq(coll).size)

/** Considers the sequence circular and rotates it right by `step` places.
*
* @param step the number of places to be rotated to the right
* @tparam B the element type of the returned collection
* @return a new collection consisting of all elements of this collection
* circularly rotated by `step` places to the right.
* @example {{{
* List(1, 2, 3, 4, 5).rotateRight(1) => List(5, 1, 2, 3, 4)
* }}}
*/
def rotateRight[B >: seq.A, That](step: Int)(implicit bf: BuildFrom[C, B, That]): That =
if (seq(coll).isEmpty)
bf.fromSpecific(coll)(collection.View.Empty)
else {
val j: Index = seq(coll).size - index(step)
bf.fromSpecific(coll)(new collection.View.Drop(seq(coll), j) ++ new collection.View.Take(seq(coll), j))
}

/** Considers the sequence circular and rotates it left by `step` places.
*
* @param step the number of places to be rotated to the left
* @tparam B the element type of the returned collection
* @return a new collection consisting of all elements of this collection
* circularly rotated by `step` places to the left.
* @example {{{
* List(1, 2, 3, 4, 5).rotateLeft(1) => List(2, 3, 4, 5, 1)
* }}}
*/
def rotateLeft[B >: seq.A, That](step: Int)(implicit bf: BuildFrom[C, B, That]): That =
rotateRight(-step)

/** Considers the sequence circular and rotates it to start with the element at `i` circular index.
*
* @param i the circular index of the element to be rotated at the start of the new collection
* @tparam B the element type of the returned collection
* @return a new collection consisting of all elements of this collection
* circularly rotated so to start with the element at circular index `i`.
* @example {{{
* List(1, 2, 3, 4, 5).startAt(2) => List(3, 4, 5, 1, 2)
* }}}
*/
def startAt[B >: seq.A, That](i: IndexO)(implicit bf: BuildFrom[C, B, That]): That =
rotateLeft(i)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure if this method should exists. When I have read its description, I though: but this is the same as rotateLeft. Once I have checked the implementation, I see it really just calls it.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@OndrejSpanel I can definitely see your point.

The reason why, at the end, I went for the proposed duplication is that startAt accepts an index, while rotateLeft accepts a number of steps. Even if the result is the same, depending on how you see the circular sequence both can feel more natural.

Just as an example, startAt plays nicely with reflectAt another method you can find in my more complete and documented RingSeq library, see https://scala-tessella.github.io/ring-seq/usage.html

}
29 changes: 29 additions & 0 deletions src/test/scala/scala/collection/decorators/SeqDecoratorTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,33 @@ class SeqDecoratorTest {
assertEquals(s.replaced(3, 4), Seq(1, 2, 4, 2, 1))
assertEquals(s.replaced(4, 4), s)
}

@Test def testRotatedRight(): Unit = {
val s = Seq(1, 2, 3, 2, 1)
val sRotated = Seq(1, 1, 2, 3, 2)
assertEquals(s.rotateRight(1), sRotated)
assertEquals(s.rotateRight(6), sRotated)
assertEquals(s.rotateRight(-4), sRotated)
val string = "RING"
assertEquals(string.rotateRight(1), "GRIN")
val empty = Vector.empty[Int]
assertEquals(empty.rotateRight(1), empty)
}

@Test def testRotatedLeft(): Unit = {
val s = Seq(1, 2, 3, 2, 1)
val sRotated = Seq(2, 3, 2, 1, 1)
assertEquals(s.rotateLeft(1), sRotated)
assertEquals(s.rotateLeft(6), sRotated)
assertEquals(s.rotateLeft(-4), sRotated)
}

@Test def testStartedAt(): Unit = {
val s = Seq(1, 2, 3, 2, 1)
val sRotated = Seq(2, 3, 2, 1, 1)
assertEquals(s.startAt(1), sRotated)
assertEquals(s.startAt(6), sRotated)
assertEquals(s.startAt(-4), sRotated)
}

}