Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Merge two Seq to create a Map

I have an object such as:

case class Person(name: String, number: Int)

And two Sequences of this object:

Seq(("abc", 1), ("def", 2))
Seq(("abc", 300), ("xyz", 400))

I want to merge these two sequences in a single Map whose key is the names and values this separate object:

case class CombineObject(firstNumber: Option[Int], secondNumber: Option[Int])

So that my final map would look like:

Map(
  "abc" -> CombineObject(Some(1), Some(300)),
  "def" -> CombineObject(Some(2), None)),
  "xyz" -> CombineObject(None,    Some(400))
)

All I can think here is to run 2 for loops over the sequence to create the map. Is there any better way to solve the problem?

like image 944
saurabh agarwal Avatar asked Dec 03 '22 18:12

saurabh agarwal


2 Answers

Turn each Seq into its own Map. After that it's pretty easy.

case class Person( name : String
                 , number : Int )

val s1 = Seq(Person("abc",1),Person("def",2))
val s2 = Seq(Person("abc",300),Person("xyz",400))

val m1 = s1.foldLeft(Map.empty[String,Int]){case (m,p) => m+(p.name->p.number)}
val m2 = s2.foldLeft(Map.empty[String,Int]){case (m,p) => m+(p.name->p.number)}

case class CombineObject( firstNumber  : Option[Int]
                        , secondNumber : Option[Int] )

val res = (m1.keySet ++ m2.keySet).foldLeft(Map.empty[String,CombineObject]){
  case (m,k) => m+(k -> CombineObject(m1.get(k),m2.get(k)))
}
//res: Map[String,CombineObject] = Map(abc -> CombineObject(Some(1),Some(300))
//                                   , def -> CombineObject(Some(2),None)
//                                   , xyz -> CombineObject(None,Some(400)))

This assumes that each Seq has no duplicate name entries. It's not obvious how that situation should be handled.

like image 170
jwvh Avatar answered Jan 12 '23 04:01

jwvh


Another proposal with a recursive function. First, it sorts both lists by key then does the processing.

case class Person(
     name: String,
     number: Int
)

case class CombineObject(
     firstNumber : Option[Int],
     secondNumber : Option[Int]
)

val left = List(Person("abc", 1), Person("def", 2))
val right = List(Person("abc", 300), Person("xyz", 400))

def merge(left: List[Person], right: List[Person]): Map[String, CombineObject] = {
   @tailrec
   def doMerge(left: List[Person], right: List[Person], acc: Map[String, CombineObject] = Map.empty): Map[String, CombineObject] = {
     (left, right) match {
       case(Person(name1, number1) :: xs, Person(name2, number2) :: ys) =>
         if(name1 == name2) {
           doMerge(xs, ys, acc + (name1 -> CombineObject(Some(number1), Some(number2))))
         } else {
           doMerge(xs, ys, acc + (name2 -> CombineObject(None, Some(number2))) + (name1 -> CombineObject(Some(number1), None)))
         }
       //if both lists are always same size, next two cases are not needed
       case (Nil, Person(name2, number2) :: ys) => 
          doMerge(Nil, ys, acc + (name2 -> CombineObject(None, Some(number2))) )
       case (Person(name1, name2) :: xs, Nil) => 
          doMerge(xs, Nil, acc + (name1 -> CombineObject(None, Some(name2))))
       case _ => acc
     }
   }
   doMerge(left.sortBy(_.name), right.sortBy(_.name))
}

merge(left, right) //Map(xyz -> (None,Some(400)), def -> (Some(2),None), abc -> (Some(1),Some(300)))

Looks kind of scary :)

like image 43
Krzysztof Atłasik Avatar answered Jan 12 '23 02:01

Krzysztof Atłasik