Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best way to perform case insensitive comparison for Scala case class

Tags:

scala

I have a case class that represent a Person.

case class Person(firstName: String, lastName: String)

I need to perform person comparison based on the first name and last name in case insensitive way, such as:

Person("John", "Doe") == Person("john", "Doe") // should return true

or in a Seq

Seq(Person("John", "Doe")).contains(Person("john", "Doe")

The simplest way is to override equals and hashCode methods inside Person case class, but as overwriting equals and hashCode in case class is frowned upon, what will be the best way to do that in a clean way.

Can somebody recommend an idiomatic way to solve this case sensitivity issue?

Thanks, Suriyanto

like image 565
suriyanto Avatar asked Sep 27 '22 15:09

suriyanto


2 Answers

I wouldn't compromise the original meaning of equals, hashCode and, consequently, == for a case class. IMHO the most idiomatic solution here from a functional programming point of view is to use a type class:

case class Person(firstName: String, lastName: String)

trait Equal[A] {
  def eq(a1: A, a2: A): Boolean
}

object Equal {
  def areEqual[A : Equal](a1: A, a2: A): Boolean = implicitly[Equal[A]].eq(a1, a2)

  implicit object PersonEqual extends Equal[Person] {
    override def eq(a1: Person, a2: Person): Boolean = a1.firstName.equalsIgnoreCase(a2.firstName) &&
      a1.lastName.equalsIgnoreCase(a2.lastName)
  }
}

In a REPL session:

scala> import Equal.areEqual
import Equal.areEqual

scala> val p1 = Person("John", "Doe")
p1: Person = Person(John,Doe)

scala> val p2 = p1.copy(firstName = "john")
p2: Person = Person(john,Doe)

scala> areEqual(p1, p2)
res0: Boolean = true

scala> val p3 = p1.copy(lastName = "Brown")
p3: Person = Person(John,Brown)

scala> areEqual(p1, p3)
res1: Boolean = false

This way if you need to provide a different equality meaning for Person in a given context you can just implement your version of Equal[Person] without touching anything else. E.g.: In a given point of your code two Person instances are equal if they have the same last name:

implicit object PersonLastnameEqual extends Equal[Person] {
  override def eq(a1: Person, a2: Person): Boolean = a1.lastName.equalsIgnoreCase(a2.lastName)
}

REPL session:

scala> val p1 = Person("John", "Doe")
p1: Person = Person(John,Doe)

scala> val p2 = p1.copy(firstName = "Mary")
p2: Person = Person(Mary,Doe)

scala> areEqual(p1, p2)
res0: Boolean = true
like image 187
lambdista Avatar answered Sep 30 '22 08:09

lambdista


You could perform some "normalization" at Person construction:

sealed trait Person {
  def firstName:String
  def lastName:String
}

object Person {
  def apply(firstName:String, lastName:String):Person = PersonNormalized(firstName.toLowerCase, lastName.toLowerCase)

  private case class PersonNormalized(firstName:String, lastName:String) extends Person
}

Its up to you to decide if it better than overriding equals and hashCode

like image 40
Nyavro Avatar answered Sep 30 '22 07:09

Nyavro