-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
Add take/takeRight/drop/dropRight to Chain #4694
Changes from 4 commits
3b2c4bd
83fe74c
49625fe
f56cad6
4dddf8c
71e076b
fdcff72
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -256,6 +256,99 @@ sealed abstract class Chain[+A] extends ChainCompat[A] { | |||||||||||||||||||
result | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
/** | ||||||||||||||||||||
* take a certain amount of items from the front of the Chain | ||||||||||||||||||||
*/ | ||||||||||||||||||||
final def take(count: Long): Chain[A] = { | ||||||||||||||||||||
// invariant count >= 1 | ||||||||||||||||||||
@tailrec | ||||||||||||||||||||
def go(lhs: Chain[A], count: Long, arg: NonEmpty[A], rhs: Chain[A]): Chain[A] = | ||||||||||||||||||||
arg match { | ||||||||||||||||||||
case Wrap(seq) => | ||||||||||||||||||||
if (count == 1) { | ||||||||||||||||||||
lhs.append(seq.head) | ||||||||||||||||||||
} else { | ||||||||||||||||||||
// count > 1 | ||||||||||||||||||||
val taken = | ||||||||||||||||||||
if (count < Int.MaxValue) seq.take(count.toInt) | ||||||||||||||||||||
else seq.take(Int.MaxValue) | ||||||||||||||||||||
// we may have not taken all of count | ||||||||||||||||||||
val newCount = count - taken.length | ||||||||||||||||||||
val wrapped = Wrap(taken) | ||||||||||||||||||||
// this is more efficient than using concat | ||||||||||||||||||||
val newLhs = if (lhs.isEmpty) wrapped else Append(lhs, wrapped) | ||||||||||||||||||||
rhs match { | ||||||||||||||||||||
case rhsNE: NonEmpty[A] if newCount > 0L => | ||||||||||||||||||||
// we have to keep taking on the rhs | ||||||||||||||||||||
go(newLhs, newCount, rhsNE, Empty) | ||||||||||||||||||||
case _ => | ||||||||||||||||||||
newLhs | ||||||||||||||||||||
} | ||||||||||||||||||||
} | ||||||||||||||||||||
case Append(l, r) => | ||||||||||||||||||||
go(lhs, count, l, if (rhs.isEmpty) r else Append(r, rhs)) | ||||||||||||||||||||
case s @ Singleton(_) => | ||||||||||||||||||||
// due to the invariant count >= 1 | ||||||||||||||||||||
val newLhs = if (lhs.isEmpty) s else Append(lhs, s) | ||||||||||||||||||||
rhs match { | ||||||||||||||||||||
case rhsNE: NonEmpty[A] if count > 1L => | ||||||||||||||||||||
go(newLhs, count - 1L, rhsNE, Empty) | ||||||||||||||||||||
case _ => newLhs | ||||||||||||||||||||
} | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
this match { | ||||||||||||||||||||
case ne: NonEmpty[A] if count > 0L => | ||||||||||||||||||||
go(Empty, count, ne, Empty) | ||||||||||||||||||||
case _ => Empty | ||||||||||||||||||||
} | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
/** | ||||||||||||||||||||
* take a certain amount of items from the back of the Chain | ||||||||||||||||||||
*/ | ||||||||||||||||||||
final def takeRight(count: Long): Chain[A] = { | ||||||||||||||||||||
// invariant count >= 1 | ||||||||||||||||||||
@tailrec | ||||||||||||||||||||
def go(lhs: Chain[A], count: Long, arg: NonEmpty[A], rhs: Chain[A]): Chain[A] = | ||||||||||||||||||||
arg match { | ||||||||||||||||||||
case Wrap(seq) => | ||||||||||||||||||||
if (count == 1L) { | ||||||||||||||||||||
seq.last +: rhs | ||||||||||||||||||||
} else { | ||||||||||||||||||||
// count > 1 | ||||||||||||||||||||
val taken = | ||||||||||||||||||||
if (count < Int.MaxValue) seq.takeRight(count.toInt) | ||||||||||||||||||||
else seq.takeRight(Int.MaxValue) | ||||||||||||||||||||
// we may have not taken all of count | ||||||||||||||||||||
val newCount = count - taken.length | ||||||||||||||||||||
val wrapped = Wrap(taken) | ||||||||||||||||||||
val newRhs = if (rhs.isEmpty) wrapped else Append(wrapped, rhs) | ||||||||||||||||||||
lhs match { | ||||||||||||||||||||
case lhsNE: NonEmpty[A] if newCount > 0 => | ||||||||||||||||||||
go(Empty, newCount, lhsNE, newRhs) | ||||||||||||||||||||
case _ => newRhs | ||||||||||||||||||||
} | ||||||||||||||||||||
} | ||||||||||||||||||||
case Append(l, r) => | ||||||||||||||||||||
go(if (lhs.isEmpty) l else Append(lhs, l), count, r, rhs) | ||||||||||||||||||||
case s @ Singleton(_) => | ||||||||||||||||||||
// due to the invariant count >= 1 | ||||||||||||||||||||
val newRhs = if (rhs.isEmpty) s else Append(s, rhs) | ||||||||||||||||||||
lhs match { | ||||||||||||||||||||
case lhsNE: NonEmpty[A] if count > 1 => | ||||||||||||||||||||
go(Empty, count - 1, lhsNE, newRhs) | ||||||||||||||||||||
case _ => newRhs | ||||||||||||||||||||
} | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
this match { | ||||||||||||||||||||
case ne: NonEmpty[A] if count > 0L => | ||||||||||||||||||||
go(Empty, count, ne, Empty) | ||||||||||||||||||||
case _ => Empty | ||||||||||||||||||||
} | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
/** | ||||||||||||||||||||
* Drops longest prefix of elements that satisfy a predicate. | ||||||||||||||||||||
* | ||||||||||||||||||||
|
@@ -275,6 +368,101 @@ sealed abstract class Chain[+A] extends ChainCompat[A] { | |||||||||||||||||||
go(this) | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
/** | ||||||||||||||||||||
* Drop a certain amount of items from the front of the Chain | ||||||||||||||||||||
*/ | ||||||||||||||||||||
final def drop(count: Long): Chain[A] = { | ||||||||||||||||||||
// invariant count >= 1 | ||||||||||||||||||||
@tailrec | ||||||||||||||||||||
def go(count: Long, arg: NonEmpty[A], rhs: Chain[A]): Chain[A] = | ||||||||||||||||||||
arg match { | ||||||||||||||||||||
case Wrap(seq) => | ||||||||||||||||||||
val dropped = if (count < Int.MaxValue) seq.drop(count.toInt) else seq.drop(Int.MaxValue) | ||||||||||||||||||||
if (dropped.isEmpty) { | ||||||||||||||||||||
// we may have not dropped all of count | ||||||||||||||||||||
val newCount = count - seq.length | ||||||||||||||||||||
rhs match { | ||||||||||||||||||||
case rhsNE: NonEmpty[A] if newCount > 0 => | ||||||||||||||||||||
// we have to keep dropping on the rhs | ||||||||||||||||||||
go(newCount, rhsNE, Empty) | ||||||||||||||||||||
case _ => | ||||||||||||||||||||
// we know that count >= seq.length else we wouldn't be empty | ||||||||||||||||||||
// so in this case, it is exactly count == seq.length | ||||||||||||||||||||
rhs | ||||||||||||||||||||
} | ||||||||||||||||||||
} else { | ||||||||||||||||||||
// dropped is not empty | ||||||||||||||||||||
val wrapped = Wrap(dropped) | ||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess cats/core/src/main/scala/cats/data/Chain.scala Lines 937 to 945 in 765f781
So it looks like in order to meet the invariant criteria it should be something like val wrapped = if (dropped.size > 1) Wrap(dropped) else Singleton(dropped.head) The same concern is for However, perhaps it would be even better to add a general-purpose method to private def wrapOrSingleton[A](seq: Seq[A): NonEmpty[A] =
if (seq.size > 1) Wrap(seq) else Singleton(seq.head) // assume seq is never empty here Also, it looks like this note in the docs to
is not applicable anymore. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. UPD.: actually, Chain.fromSeq(Seq(1, 2)).drop(1) results to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. good catch. I fixed the comment and added the branch. I did use |
||||||||||||||||||||
// we must be done | ||||||||||||||||||||
if (rhs.isEmpty) wrapped else Append(wrapped, rhs) | ||||||||||||||||||||
} | ||||||||||||||||||||
case Append(l, r) => | ||||||||||||||||||||
go(count, l, if (rhs.isEmpty) r else Append(r, rhs)) | ||||||||||||||||||||
case Singleton(_) => | ||||||||||||||||||||
// due to the invariant count >= 1 | ||||||||||||||||||||
rhs match { | ||||||||||||||||||||
case rhsNE: NonEmpty[A] if count > 1L => | ||||||||||||||||||||
go(count - 1L, rhsNE, Empty) | ||||||||||||||||||||
case _ => | ||||||||||||||||||||
rhs | ||||||||||||||||||||
} | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
this match { | ||||||||||||||||||||
case ne: NonEmpty[A] if count > 0L => | ||||||||||||||||||||
go(count, ne, Empty) | ||||||||||||||||||||
case _ => this | ||||||||||||||||||||
} | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
/** | ||||||||||||||||||||
* Drop a certain amount of items from the back of the Chain | ||||||||||||||||||||
*/ | ||||||||||||||||||||
final def dropRight(count: Long): Chain[A] = { | ||||||||||||||||||||
// invariant count >= 1 | ||||||||||||||||||||
@tailrec | ||||||||||||||||||||
def go(lhs: Chain[A], count: Long, arg: NonEmpty[A]): Chain[A] = | ||||||||||||||||||||
arg match { | ||||||||||||||||||||
case Wrap(seq) => | ||||||||||||||||||||
val dropped = if (count < Int.MaxValue) seq.dropRight(count.toInt) else seq.dropRight(Int.MaxValue) | ||||||||||||||||||||
if (dropped.isEmpty) { | ||||||||||||||||||||
// we may have not dropped all of count | ||||||||||||||||||||
val newCount = count - seq.length | ||||||||||||||||||||
lhs match { | ||||||||||||||||||||
case lhsNE: NonEmpty[A] if newCount > 0L => | ||||||||||||||||||||
// we have to keep dropping on the lhs | ||||||||||||||||||||
go(Empty, newCount, lhsNE) | ||||||||||||||||||||
case _ => | ||||||||||||||||||||
// we know that count >= seq.length else we wouldn't be empty | ||||||||||||||||||||
// so in this case, it is exactly count == seq.length | ||||||||||||||||||||
lhs | ||||||||||||||||||||
} | ||||||||||||||||||||
} else { | ||||||||||||||||||||
// we must be done | ||||||||||||||||||||
// note: dropped.nonEmpty | ||||||||||||||||||||
val wrapped = Wrap(dropped) | ||||||||||||||||||||
if (lhs.isEmpty) wrapped else Append(lhs, wrapped) | ||||||||||||||||||||
} | ||||||||||||||||||||
case Append(l, r) => | ||||||||||||||||||||
go(if (lhs.isEmpty) l else Append(lhs, l), count, r) | ||||||||||||||||||||
case Singleton(_) => | ||||||||||||||||||||
// due to the invariant count >= 1 | ||||||||||||||||||||
lhs match { | ||||||||||||||||||||
case lhsNE: NonEmpty[A] if count > 1L => | ||||||||||||||||||||
go(Empty, count - 1L, lhsNE) | ||||||||||||||||||||
case _ => | ||||||||||||||||||||
lhs | ||||||||||||||||||||
} | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
this match { | ||||||||||||||||||||
case ne: NonEmpty[A] if count > 0L => | ||||||||||||||||||||
go(Empty, count, ne) | ||||||||||||||||||||
case _ => | ||||||||||||||||||||
this | ||||||||||||||||||||
} | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
/** | ||||||||||||||||||||
* Folds over the elements from right to left using the supplied initial value and function. | ||||||||||||||||||||
*/ | ||||||||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this was the bug. The rest of the changes are just optimizations to the code or scalacheck improvements.