Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Operating on Scala collections in generic way

I wrote the function to find longest common subsequence (LCS). For example, for two sequences of chars BANANA and ATANA it returns AANA. Implementation is naive inefficient adaptation of recursive algorithm, but it is irrelevant for purpose of this question.

def LCS[T](a: Seq[T], b: Seq[T]): Seq[T] = {
    if (a.isEmpty || b.isEmpty)
      Seq.empty
    else if (a.head == b.head)
      a.head +: LCS(a.tail, b.tail)
    else {
      val case1 = LCS(a.tail, b)
      val case2 = LCS(a, b.tail)
      if (case1.length > case2.length) case1 else case2
    }
}

I want to refactor this function in most generic way possible. Current implementation works for any types of input sequences, but always returns collection of type List[T]. I want to achieve following behaviour:

LCS(List('B','A','N','A','N','A'), List('A','T','A','N','A')) -> List('A','A','N','A')
LCS(Vector('B','A','N','A','N','A'), Vector('A','T','A','N','A')) -> Vector('A','A','N','A')

...and so on for all other Seqs...

It would be wonderful if LCS could also handle Strings and Arrays:

LCS("BANANA", "ATANA") -> "AANA"
LCS(Array('B','A','N','A','N','A'), Array('A','T','A','N','A')) -> Array('A','A','N','A')

I believe with help of Scala 2.8 generic collection library it's possible to achieve at least first requirement. Will be glad to see "heavy" machinery like higher-kinded polymorphism, type classes, CanBuildFrom and so on.

Thanks!

like image 449
Nikolay Artamonov Avatar asked Apr 20 '11 17:04

Nikolay Artamonov


2 Answers

To flush out my comment, here is what you'd do (no explanation given--for that, see the answer to this question).

def LCS[A,C](a: C, b: C)(
  implicit c2i: C => Iterable[A], cbf: collection.generic.CanBuildFrom[C,A,C]
): C = {
  val builder = cbf()
  def ListLCS(a: Iterable[A], b: Iterable[A]): List[A] = {
    if (a.isEmpty || b.isEmpty) Nil
    else if (a.head==b.head) a.head :: ListLCS(a.tail,b)
    else {
      val case1 = ListLCS(a.tail, b)
      val case2 = ListLCS(a, b.tail)
      if (case1.length > case2.length) case1 else case2
    }
  }
  builder ++= ListLCS( c2i(a), c2i(b) )
  builder.result()
}

It is possible to use a builder directly inside the inner function, but you'd have to rework the algorithm; as it is, you add items to the head of the list, whereas builders add to the end. So, in order to keep the same algorithm, we make a list as an intermediate.

like image 104
Rex Kerr Avatar answered Nov 15 '22 21:11

Rex Kerr


Replacing Seq.empty with a.companion.empty gave me a function with this behavior:

scala> LCS(Vector(1, 2, 1, 2, 3), Vector(1, 1, 3))
res3: Seq[Int] = Vector(1, 1, 3)

scala> LCS(List(1, 2, 1, 2, 3), List(1, 1, 3))
res4: Seq[Int] = List(1, 1, 3)

scala> LCS("BANANA", "ANA")                       
res5: Seq[Char] = Vector(A, N, A)

scala> LCS(Array(1, 2, 1, 2, 3), Array(1, 1, 3))
res6: Seq[Int] = ArrayBuffer(1, 1, 3)
like image 36
Rachel Shallit Avatar answered Nov 15 '22 22:11

Rachel Shallit