2

In continuation of Scala learning curve

I have two lists of objects. I need to merge these lists into one list, while applying a logic with matching pares.

So, for example, here are the two lists:

case class test(int: Int, str: String)

val obj1 = test(1, "one")
val obj2 = test(2, "two")
val list1 = List(obj1, obj2)


val obj3 = test(2, "Another two")
val obj4 = test(4, "four")
val list2 = List(obj1, obj2)

What I need is:

List(test(1, "one old"), test(2, "Another two updated"), test(4, "four new"))

Of coarse, I can iterate though all elements in an old fashioned way, and do all the conversions there, but that is not the "Scala way" (I guess).

I tried approaching it with foldLeft, but got stuck. Here is what I have that is not working:

list1.foldLeft(list2) { (a:test, b:test) =>
    b.int match {
        case a.int => {
           //Apply logic and create new object
        }
    }
}

UPDATE For now I did it in two steps:

var tasks : Seq[ChecklistSchema.Task] = left.tasks.map((task:ChecklistSchema.Task) =>
            right.tasks.find(t => t.groupId == task.groupId) match {
                case Some(t: ChecklistSchema.Task) => t
                case _ => {
                    task.status match {
                        case TaskAndValueStatus.Active => task.copy(status = TaskAndValueStatus.Hidden)
                        case _ => task
                    }
                }
            }
        )

        tasks = tasks ++ right.tasks.filter((t:ChecklistSchema.Task) => !tasks.contains(t))

There is got to be a better approach!

Thanks,

Shurik Agulyansky
  • 2,607
  • 2
  • 34
  • 76

4 Answers4

2

*Assuming val list2 = List(obj3, obj4).

Here's my approach to this:

  1. Apply "old" to all list1 entries
  2. Create a map for list2 in order to efficiently check (in the next method) if a duplicated value came from list2. (breakOut here instructs the compiler to build it using the most appropriate factory. More at https://stackoverflow.com/a/7404582/4402547)
  3. applyLogic decides what to call a not-old test ("new" or "updated")
  4. Put them together, groupBy on the index, applyLogic, and sort (optional).

def merge(left: List[Test], right: List[Test]) = {
  val old = list1.map(t => Test(t.int, t.str+" old"))
  val l2Map = list2.map(t => (t.int -> t)) (breakOut): Map[Int, Test]

  def applyLogic(idx: Int, tests: List[Test]): Test = {
    tests.size match {
      case 1 => {
        val test = tests.head
        if(l2Map.contains(test.int)) Test(test.int, test.str + " new") else test
      }
      case 2 => {
        val updated = tests(1)
        Test(updated.int, updated.str+" updated")
      }
    }
  }

  (old ++ list2).groupBy(t => t.int).map(f => applyLogic(f._1, f._2)).toList.sortBy((t => t.int))
}


val left = List(Test(1, "one"), Test(2, "two"))
val right = List(Test(2, "Another two"), Test(4, "four"))
val result = List(Test(1, "one old"), Test(2, "Another two updated"), Test(4, "four new"))
assert(merge(left, right) == result)
Community
  • 1
  • 1
E-dou
  • 101
  • 3
1

I don't know if this solution is "Scala way" but it is using foldLeft.

  case class Test(a: Int, b: String) {
    def labeled(label: String) = copy(b = b + " " + label)
  }

  def merge(left: List[Test], right: List[Test]) = {
    val (list, updated) = left.foldLeft((List[Test](), Set[Int]())) { case ((acc, founded), value) =>
      right.find(_.a == value.a) match {
        case Some(newValue) => (newValue.labeled("updated") :: acc, founded + value.a)
        case None => (value.labeled("old") :: acc, founded)
      }
    }

    list.reverse ::: right.filterNot(test => updated(test.a)).map(_.labeled("new"))
  }

  val left = List(Test(1, "one"), Test(2, "two"))
  val right = List(Test(2, "Another two"), Test(4, "four"))
  val result = List(Test(1, "one old"), Test(2, "Another two updated"), Test(4, "four new"))
  assert(merge(left, right) == result)
grzesiekw
  • 477
  • 4
  • 8
0
(list1 ++ (list2.map(l => l.copy(str = l.str + " new")))).groupBy(_.int).map(
  l =>
    if (l._2.size >= 2) {
      test(l._2(0).int, "Another two updated")
    } else l._2(0)
)

map to update new value and use groupBy to update distinct value

chengpohi
  • 14,064
  • 1
  • 24
  • 42
0

I suggest using Set instead of List and override your case class equals function like the below code:

val obj1 = test(1, "one")
val obj2 = test(2, "two")
val set1 = Set(obj1, obj2)

val obj3 = test(2, "Another two")
val obj4 = test(4, "four")
val set2 = Set(obj3, obj4)

println((set2 ++ set1))

case class test(i: Int, str: String) {
    override def equals(obj: Any): Boolean = this.i == obj.asInstanceOf[test].i
}

Be aware the order of using sets in the combination is important and give different results.

output is like this: Set(test(2,Another two), test(4,four), test(1,one))

Also you can keep your list and do three extra conversion like this:

println((list2.toSet ++ list1.toSet).toList)
adramazany
  • 625
  • 9
  • 15