user1662067
user1662067

Reputation: 19

Scala/Java : Compare two list/set and removed matched element from both list

I have two lists with two different types, but both types have the same field to identify/compare.

My Requirement: I want to compare two lists based on some fields of objects, once matched delete element from both list/set. For example:

    case class Type1(name:String, surname: String, address: Int)
    case class Type2(name:String, surname: String, address: Int, dummy: String)

So record will be matched if both lists have the same field data for both types.

My List:

    val type1List = List(Type1("name1","surname1", 1),
        Type1("name2","surname2", 2),
        Type1("name3","surname3", 3)
        )

    val type2List = List(Type2("name1","surname1", 1),
        Type2("name2","surname2", 2),
        Type2("name4","surname4", 4)
        )

Comparing type1List and type2List, removed the matched date from both lists. type1List should contain only:

    val type1List = List(
        Type1("name3","surname3", 3)
        )

type2List should contain only:

    val type2List = List(
        Type2("name4","surname4", 4)
        )

I tried it with looping/interation , but that seems too complex and performance hit. Thanks in advance.

Upvotes: 0

Views: 1703

Answers (3)

Here is a generic approach to the task.
The idea is to find the common elements in both lists according to some custom functions, and then remove both of them.

def removeCommon[A, B, K](as: List[A], bs: List[B])
                         (asKey: A => K)
                         (bsKey: B => K): (List[A], List[B]) = {
  def removeDuplicates[V](commonKeys: Set[K], map: Map[K, List[V]]): List[V] =
    map
      .iterator
      .collect {
        case (key, value) if (!commonKeys.contains(key)) =>
          value.head
      }.toList

  val asByKey = as.groupBy(asKey)
  val bsByKey = bs.groupBy(bsKey)

  val commonKeys = asByKey.keySet & bsByKey.keySet

  val uniqueAs = removeDuplicates(commonKeys, asByKey)
  val uniqueBs = removeDuplicates(commonKeys, bsByKey)

  (uniqueAs, uniqueBs)
}

Which you can use like following:

final case class Type1(name:String, surname: String, address: Int)
final case class Type2(name:String, surname: String, address: Int, dummy: String)

val type1List = List(
  Type1("name1","surname1", 1),
  Type1("name2","surname2", 2),
  Type1("name3","surname3", 3)
)

val type2List = List(
  Type2("name1","surname1", 1, "blah"),
  Type2("name2","surname2", 2, "blah"),
  Type2("name4","surname4", 4, "blah")
)

val (uniqueType1List, uniqueType2List) =
  removeCommon(type1List, type2List) { type1 =>
    (type1.name, type1.surname, type1.address)
  } { type2 =>
    (type2.name, type2.surname, type2.address)
  }

// uniqueType1List: List[Type1] = List(Type1("name3", "surname3", 3))
// uniqueType2List: List[Type2] = List(Type2("name4", "surname4", 4, "blah"))

Upvotes: 2

pme
pme

Reputation: 14803

If you can not adjust the 2 Types, here a pragmatic solution:

First find the 'equals'

  val equals: Seq[Type1] = type1List.filter {
    case Type1(n, sn, a) =>
     type2List.exists { case Type2(n2, sn2, a2, _) =>
        n == n2 && sn == sn2 && a == a2
      }
  }

Then filter both Lists:

  val filteredType1List = type1List.filterNot(t1 => equals.contains(t1))
  val filteredType2List = type2List.filterNot {
    case Type2(n2, sn2, a2, _) =>
      equals.exists { case Type1(n, sn, a)=>
         n == n2 && sn == sn2 && a == a2
    }
  }

Upvotes: 1

jrook
jrook

Reputation: 3519

Assuming you know what field you want to use to base your diff on, here is an outline of a solution.

First define a super class for both types:

abstract class Type(val name : String) {
  def canEqual(a: Any) = a.isInstanceOf[Type]

  override def equals(obj: Any): Boolean = obj match {
    case obj : Type => obj.canEqual(this) && this.name == obj.name
    case _ => false
  }
  override def hashCode(): Int = this.name.hashCode
}

Then define your types as sub types of the above class:

case class Type1(override val name: String, surname: String, address: Int) extends Type(name)
case class Type2(override val name: String, surname: String, address: Int, dummy: String) extends Type(name)

Now a simple

type1List.diff(type2List)

will result in:

List(Type1(name3,surname3,3))

and

type2List.diff(type1List)

will give:

List(Type2(name4,surname4,4,dum3))

Still, I would be careful with solutions like this. Because circumventing equals and hashcode opens up the code to all kinds of bugs. So it is better to make sure this is kept limited to the scope you are working in.

Upvotes: 1

Related Questions