Deden Bangkit Deden Bangkit - 1 month ago 8
Scala Question

Replacement some element with next or previous element in Scala List

After knowing some function in scala, I've tried to patch the element from list:

val aList: List[List[Int]] = List(List(7,2,8,5,2),List(8,7,3,3,3),List(7,1,4,8,8))


what I want to do is replacing 8 with right neighbour element if the position of 8 is head, otherwise with left neighbour if 8 is tail.

The updated aList should be:

List[List[Int]] = List(List(7,2,2,5,2),List(7,7,3,3,3),List(7,1,4,4,4))


I've tried the following code:

def f(xs: List[Int]) = xs match {
case x0 :: x1 :: x2 :: x3 :: x4 => List(x0,x1,x2,x3,x4)
case 8 :: x1 :: x2 :: x3 :: x4 => List(x1,x1,x2,x3,x4)
case x0 :: 8 :: x2 :: x3 :: x4 => List(x0,x0,x2,x3,x4)
case x0 :: x1 :: 8 :: x3 :: x4 => List(x0,x1,x1,x3,x4)
case x0 :: x1 :: x2 :: 8 :: x4 => List(x0,x1,x2,x2,x4)
case x0 :: x1 :: x2 :: x3 :: 8 => List(x0,x1,x2,x3,x3)
}

aList.flatMap(f)


The type is mismatch since the type is
Product with java.io.Serializable
but requred is
scala.collection.GenTraversableOnce


Could you please explain what is the difference and how it works?

Answer

The problem is just in the last match pattern:

case x0 :: x1 :: x2 :: x3 :: 8 => List(x0,x1,x2,x3,x3)

You put 8 in the position of the list tail, so it has to have the type List[Int] (or more generally GenTraversableOnce as the compiler tells you). If you have fixed length inner lists, you should change your patterns to have :: Nil in the end:

case 8 :: x1 :: x2 :: x3 :: x4 :: Nil => List(x1,x1,x2,x3,x4)
...
case x0 :: x1 :: x2 :: x3 :: 8 :: Nil => List(x0,x1,x2,x3,x3)

An alternative is

case List(8, x1, x2, x3, x4) => List(x1,x1,x2,x3,x4)
...
case List(x0, x1, x2, x3, 8) => List(x0,x1,x2,x3,x3)

Also, your first pattern means that the other ones won't be ever reached, it just leave the list as is.

If your inner lists are not necessarily fixed-size, you need a more generic solution. Clarify, please, if that's the case.

Also, if you want to map List[List[Int]] to List[List[Int]], you should use .map(f) instead of flatMap.

Edit

I noticed that in your example in the last sub-list you have two 8s replaced by the left 4. If you want to achieve this, you can make your function recursive and add a default case (for when all 8s are replaced).

def f(xs: List[Int]) = xs match {
  case 8  :: x1 :: x2 :: x3 :: x4 :: Nil => f(List(x1,x1,x2,x3,x4))
  case x0 :: 8  :: x2 :: x3 :: x4 :: Nil => f(List(x0,x0,x2,x3,x4))
  case x0 :: x1 :: 8  :: x3 :: x4 :: Nil => f(List(x0,x1,x1,x3,x4))
  case x0 :: x1 :: x2 :: 8  :: x4 :: Nil => f(List(x0,x1,x2,x2,x4))
  case x0 :: x1 :: x2 :: x3 :: 8  :: Nil => f(List(x0,x1,x2,x3,x3))
  case _ => xs
}

But even with these fixes this way f will cycle on a list with two 8s in the beginning and some other edge cases. So here is a more generic solution with pattern matching:

def f(xs: List[Int]): List[Int] = {
  // if there are only 8s, there's nothing we can do
  if (xs.filter(_ != 8).isEmpty) xs
  else xs match {
    // 8 is the head => replace it with the right (non-8) neighbour and run recursion
    case 8 :: x :: tail if x != 8 => x :: f(x :: tail)
    // 8 is in the middle => replace it with the left (non-8) neighbour and run recursion
    case x :: 8 :: tail if x != 8 => x :: f(x :: tail)
    // here tail either starts with 8, or is empty
    case 8 :: tail => f(8 :: f(tail))
    case x :: tail => x :: f(tail)
    case _ => xs
  }
}