Реализация строкового класса, который делает сравнения без учета регистра в Scala

У меня есть несколько классов с полями, которые должны быть нечувствительными к регистру, и я хотел бы поместить экземпляры этих классов в HashMaps и искать их по строке без учета регистра.

Вместо того чтобы использовать toLowerCase каждый раз, когда я хочу индексировать экземпляр по его строке или искать экземпляр по его строке, я вместо этого попытался инкапсулировать эту логику в классе CaseInsensitiveString:

/** 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)
}

Кто-нибудь может предложить более чистый или лучший подход?

Один недостаток, с которым я столкнулся, заключается в использовании assertEquals в junit, например:

assertEquals("someString", instance.aCaseInsensitiveString)

Это терпит неудачу, говоря, что ожидал "someString", но получил CaseInsensitiveString<"someString">.

Если я переверну порядок переменных в assertEquals, то это сработает, вероятно, потому, что тогда он вызывает функцию equals для класса CaseInsensitiveString. В настоящее время я работаю над этим, сохраняя порядок таким же (так что ожидаемый является фактически ожидаемым), но вызываю.toString для CaseInsensitiveString:

assertEquals("someString", instance.aCaseInsensitiveString.toString)

Это тоже работает:

assertEquals(CaseInsensitiveString("someString"), instance.aCaseInsensitiveString)

Могу ли я добавить неявное равенство в String, чтобы решить эту проблему?

5 ответов

Решение

Вот более чистый способ реализации с использованием черт "Proxy" и "Ordered":

// 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
}

Нет справки по ("string" == CaseInsensitive("String")) проблеме.

Вы можете неявно конвертировать так:

  implicit def sensitize(c: CaseInsensitive) = c.s
  implicit def desensitize(s: String) = CaseInsensitive(s)

Что должно позволить легко сравнивать:

  assertEquals("Hello"i, "heLLo"i)

В Scala 2.8 вы хотите определить Ordering[String]и переопределить compare метод сравнения без учета регистра. Затем вы можете передать это (или определить неявное значение val) любой функции, которая нуждается в сравнении - все стандартные коллекции принимают Ordering[T] для их сравнения.

Я столкнулся с этим вопросом сегодня. Вот как я решил это решить:

Сначала я объявил объект типа Ordering для сортировки:

import scala.math.Ordering.StringOrdering
object CaseInsensitiveStringOrdering extends StringOrdering {
  override def compare(x: String, y: String) = {
    String.CASE_INSENSITIVE_ORDER.compare(x,y)
  }
}

Затем, когда я создал свой TreeMap, я использовал этот объект следующим образом:

val newMap = new TreeMap[String,AnyRef]()(CaseInsensitiveStringOrdering)

Это было с Scala 2.11.8 Кстати.

Вот пример использования Ordering (начиная с версии 2.8)

val s = List( "a", "d", "F", "B", "e")

res0: List [String] = Список (B, F, a, d, e)

object CaseInsensitiveOrdering extends scala.math.Ordering[String] {
    def compare(a:String, b:String) = a.toLowerCase compare b.toLowerCase
}

определенный объект CaseInsensitiveOrdering

val orderField = CaseInsensitiveOrdering

orderField: CaseInsensitiveOrdering.type = CaseInsensitiveOrdering $ @ 589643bb

s.sorted(orderField)

res1: List [String] = Список (a, B, d, e, F)

Мне кажется, что Java String.equalsIgnoreCase - это то, что вам нужно использовать для решения проблемы равенства. Поскольку JUnit ожидает String, убедитесь, что ваш класс является производным от String, и это решит проблему. Кроме того, помните симметричное свойство равенства, если a == b, то b == a, это означает, что если вы имеете два объекта, obj1 и obj2, то obj1.equals(obj2) == obj2. равна (obj1)

Убедитесь, что ваш код соответствует этим ограничениям.

Другие вопросы по тегам