I've got a number of classes with fields that are meant to be case insensitive, and I'd like to put the instances of these classes into HashMaps and look them up by string case insensitive.
Instead of using toLowerCase every time I want to index an instance by its string, or look up an instance by its string, I've instead tried encapsulate this logic in a CaseInsensitiveString class:
/** Used to enable us to easily index objects by string, case insensitive
*
* Note: this class preservse the case of your string!
*/
class CaseInsensitiveString ( val _value : String ) {
override def hashCode = _value.toLowerCase.hashCode
override def equals(that : Any) = that match {
case other : CaseInsensitiveString => other._value.toLowerCase ==_value.toLowerCase
case other : String => other.toLowerCase == _value.toLowerCase
case _ => false
}
override def toString = _value
}
object CaseInsensitiveString {
implicit def CaseInsensitiveString2String(l : CaseInsensitiveString) 开发者_如何学编程: String = if ( l ==null ) null else l._value
implicit def StringToCaseInsensitiveString(s : String) : CaseInsensitiveString = new CaseInsensitiveString(s)
def apply( value : String ) = new CaseInsensitiveString(value)
def unapply( l : CaseInsensitiveString) = Some(l._value)
}
Can anyone suggest a cleaner or better approach?
One drawback I've come across is when using junit's assertEquals like this:
assertEquals("someString", instance.aCaseInsensitiveString)
It fails, saying it expected "someString" but got CaseInsensitiveString<"someString">.
If I reverse the order of the variables in the assertEquals, then it works, probably because its then calling the equals function on the class CaseInsensitiveString. I currently work around this by keeping the order the same (so the expected one is actually the expected one) but call .toString on the CaseInsensitiveString:
assertEquals("someString", instance.aCaseInsensitiveString.toString)
This works too:
assertEquals(CaseInsensitiveString("someString"), instance.aCaseInsensitiveString)
Is it possible for me to add an implicit equals to String to solve this?
Here is a cleaner way of implementing using the "Proxy" and "Ordered" traits:
// http://www.scala-lang.org/docu/files/api/scala/Proxy.html
// http://www.scala-lang.org/docu/files/api/scala/Ordered.html
case class CaseInsensitive(s: String) extends Proxy with Ordered[CaseInsensitive] {
val self: String = s.toLowerCase
def compare(other: CaseInsensitive) = self compareTo other.self
override def toString = s
def i = this // convenience implicit conversion
}
No help on the ("string" == CaseInsensitive("String")) issue.
You can implicitly convert like so:
implicit def sensitize(c: CaseInsensitive) = c.s
implicit def desensitize(s: String) = CaseInsensitive(s)
Which should allow easy comparisons:
assertEquals("Hello"i, "heLLo"i)
In Scala 2.8, you want to define an Ordering[String]
, and override the compare
method to do case-insensitive comparison. Then you can pass that around (or define an implicit val) to any function that needs to do comparison -- all of the standard collections accept an Ordering[T]
for their comparisons.
I ran into this issue today. This is how I chose to solve it:
First, I declared an object of the Ordering type to do the sorting:
import scala.math.Ordering.StringOrdering
object CaseInsensitiveStringOrdering extends StringOrdering {
override def compare(x: String, y: String) = {
String.CASE_INSENSITIVE_ORDER.compare(x,y)
}
}
Next when I created my TreeMap I used this object as follows:
val newMap = new TreeMap[String,AnyRef]()(CaseInsensitiveStringOrdering)
This was with Scala 2.11.8 BTW.
It seems to me that Java's String.equalsIgnoreCase is what you need to use in order to solve the equality problem. Since JUnit is expecting a String, make sure that you're class is derived from String, that way it will solve the problem. Furthermore, remember the symmetric property of equality, if a == b then b == a, the implication that this has for programing is that if you have two objects, obj1 and obj2, then obj1.equals(obj2) == obj2.equals(obj1)
Make sure you're code meets these constraints.
Here is an example of using Ordering (since 2.8)
val s = List( "a", "d", "F", "B", "e")
res0: List[String] = List(B, F, a, d, e)
object CaseInsensitiveOrdering extends scala.math.Ordering[String] {
def compare(a:String, b:String) = a.toLowerCase compare b.toLowerCase
}
defined object CaseInsensitiveOrdering
val orderField = CaseInsensitiveOrdering
orderField: CaseInsensitiveOrdering.type = CaseInsensitiveOrdering$@589643bb
s.sorted(orderField)
res1: List[String] = List(a, B, d, e, F)
精彩评论