Search code examples
scalacollectionsserializableflatmap

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?


Solution

  • 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
      }
    }