Best case insensitive comparison for Scala case class

I have a case class that represents Person.

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

I need to do a face comparison based on first name and last name in a case insensitive case, for example:

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

or in Seq

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

The easiest way is to override the equals and hashCode methods inside the Person case class, but like rewriting equals and hashCode when the class is unhappy, what would be the best way to do this in a clean way.

Can someone recommend an idiomatic way to solve this case sensitivity case?

Thank you, Suriyanto

+5
source share
7 answers

I would not compromise the original equals , hashCode and therefore == for the case class. IMHO the most idiomatic solution here from the point of view of functional programming is to use a class like:

 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 

Thus, if you need to provide a different equality value for Person in this context, you can simply implement your version of Equal[Person] without touching anything. For example: at a given point in your code, two instances of Person are equal if they have the same 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 
+2
source

You can do some “normalization” when building Person:

 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 } 

It's up to you to decide if this is better than redefining peers and hashCode

+1
source

The easiest way is to override the equals and hashCode methods inside the Person case class, but how to overwrite equals and hashCode in case the class is unsatisfied

It is perfectly acceptable to override them if your implementations fulfill the contract . The only problem is that it needs to be updated if Person changes, but other solutions will also be.

+1
source

Create a normal class (not case ) and write equals / hashcode for them. Usually there are snippets for IDE files for them (because they are needed in Java).

 class Person(val firstName: String, val lastName: String) { override def hashCode = ??? override def equals = ??? } 

Alternatively, if we solve the "XY problem", you should write your broader problems / goals. Perhaps, in fact, you should have some Map and case class -es with only names with a lower window or Long token inside the database. Just guessing ...

0
source

For a case-insensitive comparison of all Person members, consider the row (lower circled) of its values, as shown below:

 case class Person(firstName: String, lastName: String) { def ==(that: Person) = { val thisStr = this.productIterator.mkString.toLowerCase val thatStr = that.productIterator.mkString.toLowerCase thisStr == thatStr } } 

Hence

 Person("john","Doe") == Person("john","doe") true Person("john","Doe") == Person("john","smith") false 
0
source

I would not recommend changing the == functionality for the case class, but you could create a new statement that has a case-insensitive comparison:

 case class Person(firstName: String, lastName: String) { def toUpper = this.copy(firstName = firstName.toUpperCase, lastName = lastName.toUpperCase) def =~=(that: Person) = this.toUpper == that.toUpper } Person("john", "smith") =~= Person("JoHn", "SmiTH") //true Seq( Person("JoHn", "SmiTH"), Person("Jack", "Jones") ).exists(_ =~= Person("john", "smith")) //true 

If you need something more specific than .exists :

 implicit class PersonSeq(s: Seq[Person]) { def containsInsensitive(p: Person) = s.exists(_ =~= p) } Seq( Person("JoHn", "SmiTH"), Person("Jack", "Jones") ).containsInsensitive(Person("john", "smith")) 
0
source

Another approach using string interpolations (see fooobar.com/questions/354400 / ... ):

 implicit class StringInterpolations(sc: StringContext) { def ci = new { def unapply(other: String) = sc.parts.mkString.equalsIgnoreCase(other) } } "Hello" match { case ci"Bye" => println("bad!") case ci"HELLO" => println("sweet!") case _ => println("fail!") } Person("John", "Doe") match { case Person(ci"john", ci"Doe") => case _ => assert(false) } 
0
source

Source: https://habr.com/ru/post/1232233/


All Articles