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

- Add valuesToIndex map for optimised index getting #22

Merged
merged 1 commit into from
Dec 2, 2015
Merged
Show file tree
Hide file tree
Changes from all 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
79 changes: 42 additions & 37 deletions enumeratum-core/src/main/scala/enumeratum/Enum.scala
Original file line number Diff line number Diff line change
Expand Up @@ -34,69 +34,74 @@ import scala.language.postfixOps
trait Enum[A <: EnumEntry] {

/**
* The sequence of values for your [[Enum]]. You will typically want
* to implement this in your extending class as a `val` so that `withName`
* and friends are as efficient as possible.
*
* Feel free to implement this however you'd like (including messing around with ordering, etc) if that
* fits your needs better.
* Map of [[A]] object names to [[A]]s
*/
def values: Seq[A]

lazy final val namesToValuesMap: Map[String, A] = values map (v => v.entryName -> v) toMap
/**
* Method that returns a Seq of [[A]] objects that the macro was able to find.
*
* You will want to use this in some way to implement your [[values]] method. In fact,
* if you aren't using this method...why are you even bothering with this lib?
* Map of [[A]] object names in lower case to [[A]]s for case-insensitive comparison
*/
protected def findValues: Seq[A] = macro EnumMacros.findValuesImpl[A]
lazy final val lowerCaseNamesToValuesMap: Map[String, A] = values map (v => v.entryName.toLowerCase -> v) toMap
/**
* Map of [[A]] to their index in the values sequence.
*
* A performance optimisation so that indexOf can be found in constant time.
*/
lazy final val valuesToIndex: Map[A, Int] = values.zipWithIndex.toMap

/**
* Map of [[A]] object names to [[A]]s
* The sequence of values for your [[Enum]]. You will typically want
* to implement this in your extending class as a `val` so that `withName`
* and friends are as efficient as possible.
*
* Feel free to implement this however you'd like (including messing around with ordering, etc) if that
* fits your needs better.
*/
lazy final val namesToValuesMap: Map[String, A] = values map (v => v.entryName -> v) toMap
def values: Seq[A]

/**
* Map of [[A]] object names in lower case to [[A]]s for case-insensitive comparison
*/
lazy final val lowerCaseNamesToValuesMap: Map[String, A] = values map (v => v.entryName.toLowerCase -> v) toMap
* Tries to get an [[A]] by the supplied name. The name corresponds to the .name
* of the case objects implementing [[A]]
*
* Like [[Enumeration]]'s `withName`, this method will throw if the name does not match any of the values'
* .entryName values.
*/
def withName(name: String): A =
withNameOption(name) getOrElse (throw new NoSuchElementException(s"$name is not a member of Enum $this"))

/**
* Optionally returns an [[A]] for a given name.
*/
def withNameOption(name: String): Option[A] = namesToValuesMap get name

/**
* Optionally returns an [[A]] for a given name, disregarding case
* Tries to get an [[A]] by the supplied name. The name corresponds to the .name
* of the case objects implementing [[A]], disregarding case
*
* Like [[Enumeration]]'s `withName`, this method will throw if the name does not match any of the values'
* .entryName values.
*/
def withNameInsensitiveOption(name: String): Option[A] = lowerCaseNamesToValuesMap get name.toLowerCase
def withNameInsensitive(name: String): A =
withNameInsensitiveOption(name) getOrElse (throw new NoSuchElementException(s"$name is not a member of Enum $this"))

/**
* Tries to get an [[A]] by the supplied name. The name corresponds to the .name
* of the case objects implementing [[A]]
*
* Like [[Enumeration]]'s `withName`, this method will throw if the name does not match any of the values'
* .entryName values.
* Optionally returns an [[A]] for a given name, disregarding case
*/
def withName(name: String): A =
withNameOption(name) getOrElse (throw new NoSuchElementException(s"$name is not a member of Enum $this"))
def withNameInsensitiveOption(name: String): Option[A] = lowerCaseNamesToValuesMap get name.toLowerCase

/**
* Tries to get an [[A]] by the supplied name. The name corresponds to the .name
* of the case objects implementing [[A]], disregarding case
* Returns the index number of the member passed in the values picked up by this enum
*
* Like [[Enumeration]]'s `withName`, this method will throw if the name does not match any of the values'
* .entryName values.
* @param member the member you want to check the index of
* @return the index of the first element of values that is equal (as determined by ==) to member, or -1, if none exists.
*/
def withNameInsensitive(name: String): A =
withNameInsensitiveOption(name) getOrElse (throw new NoSuchElementException(s"$name is not a member of Enum $this"))
def indexOf(member: A): Int = valuesToIndex.getOrElse(member, -1)

/**
* Returns the index number of the member passed in the values picked up by this enum
* Method that returns a Seq of [[A]] objects that the macro was able to find.
*
* @param member
* @return the index of the first element of values that is equal (as determined by ==) to member, or -1, if none exists.
* You will want to use this in some way to implement your [[values]] method. In fact,
* if you aren't using this method...why are you even bothering with this lib?
*/
def indexOf(member: A): Int = values.indexOf(member)
protected def findValues: Seq[A] = macro EnumMacros.findValuesImpl[A]

}
23 changes: 20 additions & 3 deletions enumeratum-core/src/test/scala/enumeratum/EnumSpec.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package enumeratum

import org.scalatest.{ Matchers, FunSpec }
import org.scalatest.OptionValues._
import org.scalatest.{FunSpec, Matchers}

class EnumSpec extends FunSpec with Matchers {

Expand Down Expand Up @@ -97,8 +97,8 @@ class EnumSpec extends FunSpec with Matchers {

describe("when a sealed trait is wrapped in another object") {

import Wrapper._
import Wrapper.SmartEnum._
import Wrapper._

describe("#values") {

Expand Down Expand Up @@ -173,7 +173,7 @@ class EnumSpec extends FunSpec with Matchers {

describe("indexOf") {

it("should return the proper index") {
it("should return the proper index for elements that exist in values") {
import DummyEnum._
DummyEnum.indexOf(Hello) shouldBe 0
DummyEnum.indexOf(GoodBye) shouldBe 1
Expand All @@ -189,6 +189,23 @@ class EnumSpec extends FunSpec with Matchers {
SmartEnum.indexOf(SmartEnum.Hi) shouldBe 2
}

it("should return -1 for elements that do not exist") {
// Does this even make sense given that we need to have sealed traits/classes ??
sealed trait Reactions extends EnumEntry
case object Reactions extends Enum[Reactions] {

case object Blah extends Reactions

case object Yay extends Reactions

case object Nay extends Reactions

val values = findValues
}
case object Woot extends Reactions
Reactions.indexOf(Woot) shouldBe -1
}

}

describe("findValues Vector") {
Expand Down