Skip to content

Conversation

@hrhino
Copy link
Contributor

@hrhino hrhino commented Aug 13, 2018

  • indexOf and lastIndexOf are calculable in constant time
  • sorted on a Range, if it uses the default Int ordering, is a no-op
  • sameElements can be calculated by comparing start/step/last, if the
    other collection is also a Range

cc @mkeskells

@scala-jenkins scala-jenkins added this to the 2.13.0-RC1 milestone Aug 13, 2018
if (contains(i)) (i - start) / step else -1

override def sorted[B >: Int](implicit ord: Ordering[B]): IndexedSeq[Int] =
if (ord eq Ordering.Int) this else super.sorted(ord)
Copy link
Contributor

@joshlemer joshlemer Aug 13, 2018

Choose a reason for hiding this comment

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

Is it worth also check if ord eq Ordering.Int.reverse to return this.reverse ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe so. Anyone else have an opinion?

The main reason I did this is that Ordering.Int is the vastly most common value that this method will be passed, and it's also the one for which the least work can be done. Passing Int.Reverse is probably far rarer.

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe Ordering.Int could also override val reverse: Ordering[Int] = ... so that it doesn't need to allocate a new instance each time

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is actually completely wrong for negative step size

case _ => super.indexOf(elem, from)
}

override final def lastIndexOf[@sp(Int) B >: Int](elem: B, end: Int = -1): Int =

Choose a reason for hiding this comment

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

the default for end should be length - 1

otherwise the result would always be -1 when not supplying an end index

as per SeqOps

https://github.com/scala/scala/blob/b78715242877fc623744f3d06d87e37d576945e9/src/library/scala/collection/Seq.scala

Copy link
Contributor Author

@hrhino hrhino Aug 13, 2018

Choose a reason for hiding this comment

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

oops, good catch.

  • that

package scala
package collection.immutable

import scala.{specialized => sp}

Choose a reason for hiding this comment

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

is it usual/necessary to rename the import here?

@hrhino

This comment has been minimized.

@mkeskells
Copy link
Contributor

maybe also def distinct as a noop unless step is 0

there are a bunch of iterator abstract methods as well that could be implemented. Not sure how often they are called though

@hrhino hrhino force-pushed the faster/range-indexof branch from 31efee1 to c2d407e Compare August 13, 2018 23:25
@hrhino
Copy link
Contributor Author

hrhino commented Aug 13, 2018

@mkeskells distinct is already overridden as def distinct = this. I'll look for more methods that could be optimised. I don't think we really need to worry about the RangeIterator too much.

@mkeskells

This comment has been minimized.

case 0 => other.isEmpty
case 1 => other.length == 1 && this.start == other.start
case n => other.length == n && (
(this.start == other.start)
Copy link
Contributor

Choose a reason for hiding this comment

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

what about reverse ordered Range?
Range.inclusive(1,10,1) == Range.inclusive(10,1,-1)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

no, they're unequal; sameElements is confusingly named in that it means "same elements and in the same order"

@mkeskells
Copy link
Contributor

mkeskells commented Aug 14, 2018

@szeiger (and a little off topic) do we really need 2 implementation of Range (inclusive and exclusive)
all of the behaviour could be handled by the companion apply and inclusive methods, and the implementation of Range could be simplified by removing all of the isInclusive checks, and then Range becomes final. As a bonus the JIT can be more aggressive as it doesn't need the deopt hooks

I dont think that the implementation classes, or isInclusive are part of the user API, so should be private[scala] at least. I presume this idea (if accepted) would require a deprecation cycle

@mkeskells
Copy link
Contributor

Some of these changes could be replicated/shared with NumericRange

@hrhino
Copy link
Contributor Author

hrhino commented Aug 14, 2018

@mkeskells yes I'd like to share them; I just wanted review on the Int-specialized version first

}
}

override final def indexOf[@sp(Int) B >: Int](elem: B, from: Int = 0): Int =
Copy link
Contributor Author

Choose a reason for hiding this comment

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

porting this to NumericRange is nontrivial, because B is unbounded above; here we know the element type is Int (and can special-case it accordingly) but NumericRange has no way of telling whether elem is a T or not, so the Integral arithmetic is unsafe.

@hrhino hrhino force-pushed the faster/range-indexof branch from 59ec32e to a8e9cba Compare August 14, 2018 13:41
@hrhino
Copy link
Contributor Author

hrhino commented Aug 14, 2018

(rebased to maybe unwedge scabot)

@hrhino
Copy link
Contributor Author

hrhino commented Aug 14, 2018

another method which may be profitably implemented here is copyToArray... IndexedSeq inherits IterableOnce's version, which goes (unsurprisingly) through an Iterator. Assuming fast apply should let us optimize it, but it turns out that the iterator version is faster for Vector (presumably because VectorIterator is smart) so an implementation in IndexedSeq would help only Range. (WrappedString needs an override as well but it should use getChars; I'll do that in a separate PR)

@SethTisue

This comment has been minimized.

@hrhino

This comment has been minimized.

@szeiger
Copy link
Contributor

szeiger commented Oct 10, 2018

@mkeskells Range.Inclusive and Range.Exclusive are part of the API. They are useful when you treat a range not just as a sequence. But the methods in Range shouldn't have to treat inclusive and exclusive ranges separately. Just make everything operate on an "inclusive end".

* @param offset the index where the sequence is searched.
* @return `true` if the sequence `that` is contained in this $coll at
* index `offset`, otherwise `false`.
* @throws NoSuchElementException if `offset` is greater than the size of this $coll
Copy link
Contributor

Choose a reason for hiding this comment

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

No, it should never throw an exception. See scala/bug#10931 and scala/bug#11199 for semantics in corner cases.

if (this.isEmpty && other.isEmpty) true
else {
(this.length : @annotation.switch) match {
case 0 => other.isEmpty
Copy link
Contributor

Choose a reason for hiding this comment

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

You already know that other.isEmpty is false at this point

@mkeskells
Copy link
Contributor

@szeiger I think that is what I was proposing - keeping the APIs but having one implementation

@lrytz
Copy link
Member

lrytz commented Nov 2, 2018

Ping @hrhino

@hrhino hrhino force-pushed the faster/range-indexof branch from a8e9cba to 6d52192 Compare November 6, 2018 13:59
@SethTisue SethTisue added the library:collections PRs involving changes to the standard collection library label Nov 8, 2018
@som-snytt
Copy link
Contributor

@hrhino temporarily taking over pinging duties. Curious if there were any other contenders for oldest mergeable PR.

@hrhino hrhino force-pushed the faster/range-indexof branch from 6d52192 to a966258 Compare December 22, 2018 03:10
case n => other.length == n && (
(this.start == other.start)
&& (this.step == other.step)
&& (this.last == other.last)
Copy link
Contributor

Choose a reason for hiding this comment

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

Comparing both length and last appears to be redundant

case _ => super.lastIndexOf(elem, end)
}

override def indexOfSlice[B >: Char](that: collection.Seq[B], from: Int = 0): Int =
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice catch. We should put import scala.Predef.{wrapString => _, _} into this file to avoid such accidents. ArrayOps does the same for the array conversions.

@sjrd sjrd mentioned this pull request Jan 30, 2019
51 tasks
@SethTisue
Copy link
Member

@hrhino interested in returning to this? time is running short for RC1

@SethTisue
Copy link
Member

the remaining review feedback was minor. I added two commits in response

if CI likes it, let's squash, rebase, and merge.

@SethTisue SethTisue self-assigned this Feb 26, 2019
…ations.

- `indexOf` and `lastIndexOf` are calculable in constant time
- `sameElements` can be calculated by comparing `start`/`step`/`last`, if the
  other collection is also a `Range`
- `contains` can't override `Seq`'s `contains`, but we can add a forwarder
  that will
I don't know why I thought it was on jl.String.
@SethTisue SethTisue force-pushed the faster/range-indexof branch from 0cf8c11 to ea470f7 Compare February 28, 2019 05:19
@SethTisue
Copy link
Member

squashed, rebased

@hrhino
Copy link
Contributor Author

hrhino commented Mar 9, 2019

@SethTisue yes, this has gotten older than I would have thought! What more do you need from me?

@SethTisue SethTisue merged commit 2c32023 into scala:2.13.x Mar 9, 2019
@SethTisue
Copy link
Member

nothing, we're good. thanks 👍

@som-snytt
Copy link
Contributor

My previous comment hinting that there is a competition for the title of oldest active PR was not intended to elicit protestations of false modesty. hrhino's offer of further help is a transparent ploy to keep it open a few more weeks and extend its standing. More experienced contributors know that the most effective tactic is to set the milestone a few point releases down the road; in a previous era, one had only to request comment from soc to guarantee that a PR remain unmerged for months or even years.

sjrd added a commit to sjrd/scala-js that referenced this pull request Mar 11, 2019
sjrd added a commit to sjrd/scala-js that referenced this pull request Mar 11, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

library:collections PRs involving changes to the standard collection library

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants