I have the following condition
if (map.contains(clazz)) {
// .....
}
Where map is defined as Map[Clazz,String]
And Clazz is defined as
case class Clazz(key: String, field1: String, field2: String)
However, the key field alone identifies the object, so the comparison of field1 and field2 is redundant. How to optimize the contains statement?
Simple and straight forward way instead of overriding equals and hashCode
Redefine your Clazz like below.
case class Clazz(key: String)(field1: String, field2: String)
This way equals and hashCode methods will be generated with only key in consideration and field1, field2 will be ignored for the equality check.
That means key uniquely determines the clazz instance which is what you want.
Now you can do contains check which will only use key for internally for equality check.
Scala REPL
scala> case class Clazz(key: String)(field1: String, field2: String)
defined class Clazz
scala> val map = Map(Clazz("foo")("apple", "ball") -> "foo", Clazz("bar")("cat", "bat") -> "bar")
map: Map[Clazz, String] = Map(Clazz(foo) -> "foo", Clazz(bar) -> "bar")
scala> map contains Clazz("foo")("moo", "zoo")
res2: Boolean = true
scala> map contains Clazz("bar")("moo", "zoo")
res3: Boolean = true
scala> map contains Clazz("boo")("moo", "zoo")
res4: Boolean = false
Other way is to just override equals and hashCode
case class Clazz(key: String, field1: String, field2: String) {
override def equals(otherClazz: Any) = that match {
case otherClazz: Clazz => otherClazz.key.equals(key)
case _ => false
}
override def hashCode = key.hashCode
}
Third way is the least recommended way
Just maintain a Map[String, Clazz] key to Clazz map.
Now you can check contains like below
val keyMap = map.map { case (clazz, _) => clazz.key -> clazz}
keyMap.contains(clazz.key)
When match is successful then you can get the value using the code.
map.get(keyMap(clazz.key)) //this will give Option[String]
You can re-define equals and hashCode:
case class Clazz(key: String, field1: String, field2: String) {
override def equals(that: Any) = that match {
case that: Clazz => that.key.equals(key)
case _ => false
}
override def hashCode = key.hashCode
}
Related
I have the following 3 case classes:
case class Profile(name: String,
age: Int,
bankInfoData: BankInfoData,
userUpdatedFields: Option[UserUpdatedFields])
case class BankInfoData(accountNumber: Int,
bankAddress: String,
bankNumber: Int,
contactPerson: String,
phoneNumber: Int,
accountType: AccountType)
case class UserUpdatedFields(contactPerson: String,
phoneNumber: Int,
accountType: AccountType)
this is just enums, but i added anyway:
sealed trait AccountType extends EnumEntry
object AccountType extends Enum[AccountType] {
val values: IndexedSeq[AccountType] = findValues
case object Personal extends AccountType
case object Business extends AccountType
}
my task is - i need to write a funcc Profile and compare UserUpdatedFields(all of the fields) with SOME of the fields in BankInfoData...this func is to find which fields where updated.
so I wrote this func:
def findDiff(profile: Profile): Seq[String] = {
var listOfFieldsThatChanged: List[String] = List.empty
if (profile.bankInfoData.contactPerson != profile.userUpdatedFields.get.contactPerson){
listOfFieldsThatChanged = listOfFieldsThatChanged :+ "contactPerson"
}
if (profile.bankInfoData.phoneNumber != profile.userUpdatedFields.get.phoneNumber) {
listOfFieldsThatChanged = listOfFieldsThatChanged :+ "phoneNumber"
}
if (profile.bankInfoData.accountType != profile.userUpdatedFields.get.accountType) {
listOfFieldsThatChanged = listOfFieldsThatChanged :+ "accountType"
}
listOfFieldsThatChanged
}
val profile =
Profile(
"nir",
34,
BankInfoData(1, "somewhere", 2, "john", 123, AccountType.Personal),
Some(UserUpdatedFields("lee", 321, AccountType.Personal))
)
findDiff(profile)
it works, but wanted something cleaner..any suggestions?
Each case class extends Product interface so we could use it to convert case classes into sets of (field, value) elements. Then we can use set operations to find the difference. For example,
def findDiff(profile: Profile): Seq[String] = {
val userUpdatedFields = profile.userUpdatedFields.get
val bankInfoData = profile.bankInfoData
val updatedFieldsMap = userUpdatedFields.productElementNames.zip(userUpdatedFields.productIterator).toMap
val bankInfoDataMap = bankInfoData.productElementNames.zip(bankInfoData.productIterator).toMap
val bankInfoDataSubsetMap = bankInfoDataMap.view.filterKeys(userUpdatedFieldsMap.keys.toList.contains)
(bankInfoDataSubsetMap.toSet diff updatedFieldsMap.toSet).toList.map { case (field, value) => field }
}
Now findDiff(profile) should output List(phoneNumber, contactPerson). Note we are using productElementNames from Scala 2.13 to get the filed names which we then zip with corresponding values
userUpdatedFields.productElementNames.zip(userUpdatedFields.productIterator)
Also we rely on filterKeys and diff.
A simple improvement would be to introduce a trait
trait Fields {
val contactPerson: String
val phoneNumber: Int
val accountType: AccountType
def findDiff(that: Fields): Seq[String] = Seq(
Some(contactPerson).filter(_ != that.contactPerson).map(_ => "contactPerson"),
Some(phoneNumber).filter(_ != that.phoneNumber).map(_ => "phoneNumber"),
Some(accountType).filter(_ != that.accountType).map(_ => "accountType")
).flatten
}
case class BankInfoData(accountNumber: Int,
bankAddress: String,
bankNumber: Int,
contactPerson: String,
phoneNumber: Int,
accountType: String) extends Fields
case class UserUpdatedFields(contactPerson: String,
phoneNumber: Int,
accountType: AccountType) extends Fields
so it was possible to call
BankInfoData(...). findDiff(UserUpdatedFields(...))
If you want to further-improve and avoid naming all the fields multiple times, for example shapeless could be used to do it compile time. Not exactly the same but something like this to get started. Or use reflection to do it runtime like this answer.
That would be a very easy task to achieve if it would be an easy way to convert case class to map. Unfortunately, case classes don't offer that functionality out-of-box yet in Scala 2.12 (as Mario have mentioned it will be easy to achieve in Scala 2.13).
There's a library called shapeless, that offers some generic programming utilities. For example, we could write an extension function toMap using Record and ToMap from shapeless:
object Mappable {
implicit class RichCaseClass[X](val x: X) extends AnyVal {
import shapeless._
import ops.record._
def toMap[L <: HList](
implicit gen: LabelledGeneric.Aux[X, L],
toMap: ToMap[L]
): Map[String, Any] =
toMap(gen.to(x)).map{
case (k: Symbol, v) => k.name -> v
}
}
}
Then we could use it for findDiff:
def findDiff(profile: Profile): Seq[String] = {
import Mappable._
profile match {
case Profile(_, _, bankInfo, Some(userUpdatedFields)) =>
val bankInfoMap = bankInfo.toMap
userUpdatedFields.toMap.toList.flatMap{
case (k, v) if bankInfoMap.get(k).exists(_ != v) => Some(k)
case _ => None
}
case _ => Seq()
}
}
I'm pretty new to Scala and I'm wondering if it is possible to create a type dynamically in some way. In practice what I want to achieve is something like this:
trait BaseAB
case class A(value: String) extends BaseAB
case class B(value: String) extends BaseAB
def build(name: String, m: String): BaseAB = {
type t = name match {
case "A" => A
base "B" => B
}
new t(m)
}
You can just create new instances in you case clauses, like
case "A" => A(m)
case "B" => B(m)
or you can create partially applied function representing constructor and then provide value
def build(name: String, m: String): BaseAB = {
val construct = name match {
case "A" => A.apply _
case "B" => B.apply _
}
construct(m)
}
> build("A", "boo")
res25: BaseAB = A("boo")
Your code works almost as-is, but it's not because there is some sort of "type-valued runtime-defined variables". Instead, it works because there are companion objects called A and B that have methods apply(s: String): A and apply(s: String): B, and also both conform to type String => BaseAB:
trait BaseAB
case class A(value: String) extends BaseAB
case class B(value: String) extends BaseAB
def build(name: String, m: String): BaseAB = {
val t = name match {
case "A" => A
case "B" => B
}
t(m)
}
In this code snippet, the type of t is inferred to be String => BaseAB (possibly with some additional marker traits like Serializable).
If you are sure that there are only "A" and "B", you can also write it as
(if (name == "A") A else B)(m)
it works for the same reason.
I want to make a string representation of case class
case class Person(s:Student)
case class Student(name:String,value:String){
def toMyString(flag:Boolean):String= if(flag)s"${name} =${value}" else s"Hello${name}+${value}"
}
Person(Student("abc","def")).productIterator
I want to call toMyString from productIterator.
My actual use case has many elements in case class .
Just ask each iterator element if it is the target type.
Person(Student("abc","def")).productIterator.collect{
case x:Student => x.toMyString(true)
}
// res0: Iterator[String] = non-empty iterator (String: "abc=def")
There are 2 solutions for generating the String for the element:
1.Create a trait for elements that need to implement toMyString and the elements that need to generate string extends from it, and implement it, like:
trait MyString {
def toMyString(flag: Boolean): String
}
case class Student(name: String, value: String) extends MyString{
def toMyString(flag: Boolean): String = if (flag) s"${name} =${value}" else s"Hello${name}+${value}"
}
val result: List[String] = Person(Student("abc","def")).productIterator.collect{
case x: MyString => x.toMyString(true)
}
2. Use the reflection for this by get toMyString method by method name, this is a tricky way and not type safe, should think more about this, like:
val student = Student("abc", "def")
val res: Iterator[String] = Person(student).productIterator.map(i => {
val method = i.getClass.getDeclaredMethods.filter(i => i.getName.equals("toMyString")).headOption
method match {
case Some(m) =>
m.invoke(i, true.asInstanceOf[AnyRef])
case None =>
null
}
})
We need Iterator[Student] instead of Iterator[Any]
Person(Student("abc","def")).productIterator.asInstanceOf[Iterator[Student]]
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
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
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
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
It's perfectly acceptable to override them provided your implementations fulfill the contract. The only problem is that it needs to be updated if Person changes, but other solutions would as well.
Create a normal class (not case) and write equals/hashcode for them. IDE-s usually got snippets for that (because they're necessary in Java).
class Person(val firstName: String, val lastName: String) {
override def hashCode = ???
override def equals = ???
}
Alternatively, if we're in solving "XY problem", you should write your broader concerns/goals. Maybe, in reality, you should have some Map and case class-es with lower-cased names only, or a Long token inside a DB. Just guessing...
For case insensitive comparison of all members of Person consider a (lower cased) string of their values, as follows,
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
I wouldn't recommend changing the functionality of == for a case class, but you could create a new operator which has the meaning of 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 want 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"))
Another approach using string interpolations (see https://stackoverflow.com/a/28445089/471136):
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)
}
I want to build a Scala DSL to convert from a existing structure of Java POJOs to a structure equivalent to a Map.
However the incoming objects structure is very likely to contain a lot of null references, which will result in no value in the output map.
The performance is very important in this context so I need to avoid both reflection and throw/catch NPE.
I have considered already this topic which does not meet with my requirements.
I think the answer may lie in the usage of macros to generate some special type but I have no experience in the usage of scala macros.
More formally :
POJO classes provided by project : (there will be like 50 POJO, nested, so I want a solution which does not require to hand-write and maintain a class or trait for each of them)
case class Level1(
#BeanProperty var a: String,
#BeanProperty var b: Int)
case class Level2(
#BeanProperty var p: Level1,
#BeanProperty var b: Int)
expected behaviour :
println(convert(null)) // == Map()
println(convert(Level2(null, 3))) // == Map("l2.b" -> 3)
println(convert(Level2(Level1("a", 2), 3))) // == Map(l2.p.a -> a, l2.p.b -> 2, l2.b -> 3)
correct implementation but I want an easier DSL for writing the mappings
implicit def toOptionBuilder[T](f: => T) = new {
def ? : Option[T] = Option(f)
}
def convert(l2: Level2): Map[String, _] = l2? match {
case None => Map()
case Some(o2) => convert(o2.p, "l2.p.") + ("l2.b" -> o2.b)
}
def convert(l1: Level1, prefix: String = ""): Map[String, _] = l1? match {
case None => Map()
case Some(o1) => Map(
prefix + "a" -> o1.a,
prefix + "b" -> o1.b)
}
Here is how I want to write with a DSL :
def convertDsl(l2:Level2)={
Map(
"l2.b" -> l2?.b,
"l2.p.a" -> l2?.l1?.a,
"l2.p.b" -> l2?.l1?.b
)
}
Note that it is perfectly fine for me to specify that the property is optional with '?'.
What I want is to generate statically using a macro a method l2.?l1 or l2?.l1 which returns Option[Level1] (so type checking is done correctly in my DSL).
I couldn't refine it down to precisely the syntax you gave above, but generally, something like this might work:
sealed trait FieldSpec
sealed trait ValueFieldSpec[+T] extends FieldSpec
{
def value: Option[T]
}
case class IntFieldSpec(value: Option[Int]) extends ValueFieldSpec[Int]
case class StringFieldSpec(value: Option[String]) extends ValueFieldSpec[String]
case class Level1FieldSpec(input: Option[Level1]) extends FieldSpec
{
def a: ValueFieldSpec[_] = StringFieldSpec(input.map(_.a))
def b: ValueFieldSpec[_] = IntFieldSpec(input.map(_.b))
}
case class Level2FieldSpec(input: Option[Level2]) extends FieldSpec
{
def b: ValueFieldSpec[_] = IntFieldSpec(input.map(_.b))
def l1 = Level1FieldSpec(input.map(_.p))
}
case class SpecArrowAssoc(str: String)
{
def ->(value: ValueFieldSpec[_]) = (str, value)
}
implicit def str2SpecArrowAssoc(str: String) = SpecArrowAssoc(str)
implicit def Level2ToFieldSpec(input: Option[Level2]) = Level2FieldSpec(input)
def map(fields: (String, ValueFieldSpec[_])*): Map[String, _] =
Map[String, Any]((for {
field <- fields
value <- field._2.value
} yield (field._1, value)):_*)
def convertDsl(implicit l2: Level2): Map[String, _] =
{
map(
"l2.b" -> l2.?.b,
"l2.p.a" -> l2.?.l1.a,
"l2.p.b" -> l2.?.l1.b
)
}
Then we get:
scala> val myL2 = Level2(Level1("a", 2), 3)
myL2: Level2 = Level2(Level1(a,2),3)
scala> convertDsl(myL2)
res0: scala.collection.immutable.Map[String,Any] = Map(l2.b -> 3, l2.p.a -> a, l2.p.b -> 2)
Note that the DSL uses '.?' rather than just '?' as the only way I could see around Scala's trouble with semicolon inference and postfix operators (see, eg., #0__ 's answer to scala syntactic suger question).
Also, the strings you can provide are arbitrary (no checking or parsing of them is done), and this simplistic 'FieldSpec' hierarchy will assume that all your POJOs use 'a' for String fields and 'b' for Int fields etc.
With a bit of time and effort I'm sure this could be improved on.